PS.賀邦 原創做品轉載請註明出處 《Linux內核分析》MOOC課程 http://mooc.study.163.com/course/USTC-1000029000 linux
使用實驗樓虛擬機打開shell輸入下列代碼git
能夠看到初始的內核運行狀況以下:github
內核不停的執行my_start_kernel(),每隔一段時間被my_timer_handler()中斷,而後執行一條打印語句:printk(KERN_NOTICE 「\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n」);算法
後,又回到my_start_kernel()繼續執行。shell
打開mymain.c文件,能夠看到其中只有以下這一個函數。它做爲內核啓動的起始位置,從這個函數開始運行,並沒有限循環。數組
打開myinterrupt.c文件,裏面也只有一個my_timer_handler(),它被Linux內核週期性調用,從而產生了一個週期性的中斷機制。函數
從https://github.com/mengning/mykernel上下載mypcb.h;mymain.c;myinterrupt.c;
而後替換位於home/shiyanlou/LinuxKernel/linux-3.9.4/mykernel/中的mymain.c;myinterrupt.c;
將mypcb.h也放在這裏。 this
而後執行make,從新編譯內核。效果以下: spa
而後再次輸入:操作系統
啓動內核。 能夠發現跳轉和時間片都有了明顯的改變。
實驗結果與預期結果相符,實驗成功。
mymain.c
void__init my_start_kernel(void) {
Int pid = 0;
Int i;
/* Initialize process 0*/
task[pid].pid = pid;//task[0].pid=0;
ask[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
task[pid].task_entry = task[pid].thread.ip = (unsignedlong)my_process;//令0號進程的入口地址爲my_process();
task[pid].thread.sp = (unsignedlong)&task[pid].stack[KERNEL_STACK_SIZE-1];//0號進程的棧頂爲stack[]數組的最後一個元素
task[pid].next = &task[pid];//next指針指向本身/*fork more process */
for(i=1;i<MAX_TASK_NUM;i++)//根據0號進程,複製出幾個只是編號不一樣的進程
{
memcpy(&task[i],&task[0],sizeof(tPCB));//void *memcpy(void *dest, const void *src, size_t n);從源src所指的內存地址的起始位置開始拷貝n個字節到目標dest所指的內存地址的起始位置中。
task[i].pid = i; task[i].state = -1;//這些進程的狀態都設置爲未運行。
task[i].thread.sp = (unsignedlong)&task[i].stack[KERNEL_STACK_SIZE-1]; task[i].next = task[i-1].next;//新建立的進程的next指向0號進程的首地址
task[i-1].next = &task[i];//前一個進程的next指向最新建立的進程的首地址,從而成爲一個循環鏈表。
mypcb.h
#define MAX_TASK_NUM 4 //最大進程數,這裏設置爲了4個。
#define KERNEL_STACK_SIZE 1024*8 //每一個進程的內核棧的大小。
/* CPU-specific state of this task */
structThread { unsignedlongip;//用於保存進程的eip
unsignedlongsp;//用戶保存進程的esp};
typedefstructPCB{ intpid;//進程的id號
volatilelongstate; /* 進程的狀態:-1 unrunnable, 0 runnable, >0 stopped */
charstack[KERNEL_STACK_SIZE];//進程的棧,只有一個核心棧。/* CPU-specific state of this task */
structThread thread;//每一個進程只有一個線程。
unsignedlongtask_entry;//進程的起始入口地址。
myinterrupt.c
if(next->state == 0)/*若是下一個將要運行的進程已經處於運行狀態 -1 unrunnable, 0 runnable, >0 stopped */
{
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* 保存當前進程的ebp到本身的棧中。 save ebp */
"movl %%esp,%0\n\t" /* 保存當前進程的esp到本身的棧中。 save esp */
"movl %2,%%esp\n\t" /* 從next->thread.sp中彈出下一個進程的esp。與第二句相對應。 restore esp */
"movl $1f,%1\n\t" /* 將下一個進程的eip設置爲1f。$1f就是指標號1:的代碼在內存中存儲的地址 save eip */
"pushl %3\n\t" /* 將next->thread.ip壓入當前進程的棧中。*/
"ret\n\t" /* 從當前進程的棧中彈出剛剛壓入的next->thread.ip。完成進程切換。 restore eip */
"1:\t" /* 即$1f指向的位置。next process start here */
"popl %%ebp\n\t" /* 切換到的進程把ebp從棧中彈出至ebp寄存器。與第一句相對應。*/
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
my_current_task = next; //當前進程切換爲next
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); //打印切換信息
}
else//若是下一個將要運行的進程還從未運行過。
{
next->state = 0;//將其設置爲運行狀態。
my_current_task = next;////當前進程切換爲next
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->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" /* 將要被切換出去的進程的ip設置爲$1f。這樣等一下它被切換回來時(必定是運行狀態)確定會進入if判斷分支,能夠從if中的標號1處繼續執行。 save eip */
"pushl %3\n\t" /* 將next->thread.ip(由於它尚未被運行過,因此next->thread.ip如今仍處於初始狀態,即指向my_process(),壓入將要被切換出去的進程的堆棧。*/
"ret\n\t" /* 將剛剛壓入的next->thread.ip出棧至eip,完成進程切換。 restore eip */
首先,內核啓動__init my_start_kernel(void),建立了4個進程,分別是0,1,2,3號,設置0號爲運行態,其它3個進程爲未運行態。
0號進程的入口都被初始化爲 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;即指向my_process()。
0號進程的棧頂被初始化爲 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
以後的進程也都是根據0號進程複製獲得,因此它們的起始入口也是my_process(),初始棧頂也是指向了本身的stack[KERNEL_STACK_SIZE-1];
my_current_task = &task[pid];將當前進程設置爲0號進程。而後從0號進程開始運行。
「movl %1,%%esp\n\t」
將0號進程的棧頂放入esp寄存器。
「pushl %1\n\t」 /* push ebp */
當前esp指向了stack數組的末尾,這裏也是棧頂,由於棧是空的,因此esp==ebp。
「pushl %0\n\t」 /* push task[pid].thread.ip */
「ret\n\t」 /* pop task[pid].thread.ip to eip */
切換到0號進程的入口地址開始執行。
「popl %%ebp\n\t」
這句多餘,在ret以後,不會被執行。
:
: 「c」 (task[pid].thread.ip),」d」 (task[pid].thread.sp)
以後0號進程不斷執行my_process()。一段時間後,my_timer_handler()被內核調用,觸發中斷, my_need_sched = 1;將全局變量my_need_sched 設置爲了1。
此後,當0號進程執行到了if(my_need_sched == 1)時就會進入這個if條件分支中,執行 my_schedule();執行進程調度。
0號進程的next指針指向的是1號進程,因此在my_schedule()中的next指針指向了1號進程,prev指針指向了0號進程。
由於1號進程當前還未被運行過,因此會執行else條件分支:next->state = 0;//將1號進程設置爲運行狀態。
my_current_task = next;//當前進程切換爲1號進程printk(KERN_NOTICE 「>>>switch %d to %d<<<\n」,prev->pid,next->pid);//打印switch 0 to 1
「pushl %%ebp\n\t」 /* save ebp */
「movl %%esp,%0\n\t」 /* save esp */
將0號進程的ebp和esp都保存到0號進程的棧上。
「movl %2,%%esp\n\t」 /* restore esp */
「movl %2,%%ebp\n\t」 /* restore ebp */
將1號進程的存在1號進程結構體中next->thread.sp保存的esp的值存入esp寄存器和ebp寄存器,由於1號進程還未被運行過,因此esp仍指向了1號棧的stack[KERNEL_STACK_SIZE-1]。
「movl $1f, %1\n\t」 將0號進程的eip設置爲if。
「pushl %3\n\t」
「ret\n\t」
將1號進程的eip加入0號進程的棧中,而後經過ret指令,將這個eip從0號進程的棧中彈出,存入eip寄存器,完成從0號進程到1號進程的切換。此後相似。
操做系統的內核有一個起始位置,從這個起始位置開始執行。開始工做時,CPU分配給第一個進程,開始執行第一個進程,而後經過必定的調度算法,好比時間片輪轉,在一個時間片後,發生中斷,
第一個進程被阻塞,在完成保存現場後將CPU分配給下一個進程,執行下一個進程。這樣,操做系統就完成了基本的進程調度的功能。