linux内核计算代码时间,完成一个简单的时间片轮转多道程序内核代码
《Linux 內核分析》實驗二:How Does a Operating System Work?
1.函數調用堆棧和中斷
在上一節實驗中我們已經明白了,存儲程序計算機的運行原理,就是通過不斷的取指執行存儲在堆棧上 CPU 指令,而這些二進制指令一一對應于匯編函數指令。因此,對于操作系統,我們引入相應的函數調用堆棧機制。
我們都知道 CPU 的運算速度是極其快的,很多時候例如輸入輸出過程,如果沒有系統中斷,CPU 就只能等待外設輸入輸出完成后再工作,這樣會造成了極大的資源浪費。因此,現代操作系統都引入了中斷機制,CPU 會根據當前進程的執行情況,交替執行多個程序,提高運行效率。
在本次實驗基于一個小小的時間片輪轉多道程序 mykernel,分析操作系統的函數調用堆棧和中斷的實現過程。
2.內核代碼分析
mykernel 程序有三個源文件,分別是 mypcb.h,mymain.c 和 myinterrupt.c,mypcb.h 頭文件,定義了一些結構和函數。mymain.c 定義了多個進程啟動和運行的函數。myinerrupt.c 定義了時間片和中斷處理的函數。
2.1>我們先來看看 mypcb.h 頭文件:
#define MAX_TASK_NUM 10 // max num of task in system
#define KERNEL_STACK_SIZE 1024*8
#define PRIORITY_MAX 30 //priority range from 0 to 30
首先,定義了最大任務數量、內核棧大小和任務的優先級序列。
/* CPU-specific state of this task */
struct Thread {
unsigned long ip;//point to cpu run address
unsigned long sp;//point to the thread stack's top address
//todo add other attrubte of system thread
};
然后,定義 Thread 結構體,ip 和 sp 分別表示執行指針和棧指針,用于執行和保護現場。
//PCB Struct
typedef struct PCB{
int pid; // pcb id
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
char stack[KERNEL_STACK_SIZE];// each pcb stack size is 1024*8
/* CPU-specific state of this task */
struct Thread thread;
unsigned long task_entry;//the task execute entry memory address
struct PCB *next;//pcb is a circular linked list
unsigned long priority;// task priority
//todo add other attrubte of process control block
}tPCB;
再然后,定義了進程管理結構體,包括:1、pid 進程號;2、state 進程狀態;3、棧空間;4、thread 變量;5、任務入口地址;6、下一個 PCB 指針地址;7、任務的優先級。
//void my_schedule(int pid);
void my_schedule(void);
最后,定義了 my_schedule 任務調度函數。
2.2>再來分析 mymain.c 文件:
tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;
首先,定義三個全局變量,一個是 PCB 數組,一個是指向當前任務的指針,my_need_sched 被來判斷是否調用中斷處理函數。
void __init my_start_kernel(void)
{
int pid = 0;
/* Initialize process 0*/
task[pid].pid = pid;
task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
// set task 0 execute entry address to my_process
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].next = &task[pid];
/*fork more process */
for(pid=1;pid
{
memcpy(&task[pid],&task[0],sizeof(tPCB));
task[pid].pid = pid;
task[pid].state = -1;
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].priority=get_rand(PRIORITY_MAX);//each time all tasks get a random priority
}
task[MAX_TASK_NUM-1].next=&task[0];
printk(KERN_NOTICE "\n\n\n\n\n\n system begin :>>>process 0 running!!!<<
/* start process 0 by task[0] */
pid = 0;
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
"pushl %1\n\t" /* push ebp */
"pushl %0\n\t" /* push task[pid].thread.ip */
"ret\n\t" /* pop task[pid].thread.ip to eip */
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
}
my_start_kernel 函數非常重要,首先初始化任務 0,設置為可運行狀態,設置任務運行的 ip,設置任務運行 sp 空間,設置下一個 PCB 指向自己。然后,設置余下的任務并隨機分配優先權(我們設置最大任務數為 10)。最后,通過內聯匯編把 sp 賦給 esp,ebp 進棧,把 ip 保存到棧中,這樣如果任務切換的話就可以保證下一個進程結束后回到現場繼續執行。
void my_process(void)
{
int i = 0;
while(1)
{
i++;
if(i%10000000 == 0)
{
if(my_need_sched == 1)
{
my_need_sched = 0;
sand_priority();
my_schedule();
}
}
}
}//end of my_process
my_process 函數很簡單,就是建立一個循環不斷運行進程,輸出進程信息,同時,如果 my_need_sched = 1,就開始中斷并切換進程。
2.3>最后分析 myinterrupt.c 文件:
extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;
/*
* Called by timer interrupt.
* it runs in the name of current running process,
* so it use kernel stack of current running process
*/
void my_timer_handler(void)
{
#if 1
// make sure need schedule after system circle 2000 times.
if(time_count%2000 == 0 && my_need_sched != 1)
{
my_need_sched = 1;
//time_count=0;
}
time_count++ ;
#endif
return;
}
my_timer_handler 函數也非常簡單,通過判斷 time_count 時間片變量的閾值,更改 my_need_sched 值實現了中斷調用。
void my_schedule(void)
{
tPCB * next;
tPCB * prev;
// if there no task running or only a task ,it shouldn't need schedule
if(my_current_task == NULL || my_current_task->next == NULL)
{
printk(KERN_NOTICE " time out!!!,but no more than 2 task,need not schedule\n");
return;
}
/* schedule */
next = get_next();
prev = my_current_task;
printk(KERN_NOTICE " the next task is %d priority is %u\n",next->pid,next->priority);
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
{//save current scene
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
"1:\t" /* next process start here */
"popl %%ebp\n\t"
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
my_current_task = next;//switch to the next task
printk(KERN_NOTICE "switch from %d process to %d process\n >>>process %d running!!!<<pid,next->pid,next->pid);
}
else
{
next->state = 0;
my_current_task = next;
printk(KERN_NOTICE "switch from %d process to %d process\n >>>process %d running!!!<<pid,next->pid,next->pid);
/* switch to new process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl %2,%%ebp\n\t" /* restore ebp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
return;
}//end of my_schedule
my_schedule 函數是這個內核的重點,首先初始化 next 和 prev 兩個 PCB 結構體,然后判斷如果任務 state 狀態是可運行時,說明這個任務正在執行,保存 ebp 和 esp,并切換到下一個任務 ip 執行;如果任務 state 狀態是不可運行時,說明這個任務沒執行過,保存當前任務,開始執行新任務。
3.總結:
通過分析實驗代碼,我們學習了一個簡單的時間片輪轉多道操作系統內核,了解了操作系統的中斷上下文和進程上下文切換。每個任務被分配一定的時間片執行,如果在時間片結束后任務仍在執行,CPU 將會剝奪它的執行權并分配給其他任務;如果任務在時間片結束前完成,CPU 則會立即進行切換,調度程序就是在維護一個就緒進程隊列,當進程用完屬于它的時間片后,在隊列中就會按照優先級重新排序,這是最簡單、最公平也最高效的一種方式。
總結
以上是生活随笔為你收集整理的linux内核计算代码时间,完成一个简单的时间片轮转多道程序内核代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gui怎么读?
- 下一篇: linux 系统时间是在哪里记录的,Li