轉載:https://blog.csdn.net/FIELDOFFIER/article/details/44280717
《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000?”
實驗環境:c+Linux64位 (32位系統可能結果會不同)
依照學術誠信條款,我保證此回答為本人原創,所有回答中引用的外部材料已經做了出處標記。
源代碼以及運行環境搭建請參考mykernel,其中提供了一個簡單的Linux內核源代碼,本文主要分析其中的三個文件:
通過對這三個文件的分析來理解進程的切換原理。
注意,雖然在源代碼中建立了4個進程并且進行循環的切換,但是為了簡便分析時假定只有兩個進程,編號0和1。另,在實際的Linux系統中每個進程都會有兩個堆棧:用戶態一個、內核態一個,但是在這個模擬的內核中每個進程只分配了一個堆棧。
docker底層實現原理?在進程切換中最為重要的是運行棧的切換和eip(即程序計數器)的正確跳轉,mymain.c中的函數my_start_kernel是最開始執行的代碼,因此從這個函數開始進行分析。my_start_kernel函數首先建立起了4個進程并且進行了初始化,如分配棧等,注意在剛建立的時候只有0號進程的狀態是runuable,其余的都是unrunnable。還有就是PCB結構中的threap.sp,每個進程對應一個棧,所以在這里thread.sp指向對應PCB內的char stack[KERNEL_STACK_SIZE - 1],即用這個字符數組作為運行棧,因為棧是由高地址向低地址增長,所以指向stack[KERNEL_STACK_SIZE - 1]。
接下來重點分析這段代碼:
/* 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*/);
開始之前的棧的情況:
"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 */
centos6內核版本。
my_process()在執行時將會判斷是否需要進行進程切換,由于我們假設內核中只有0和1兩個進程,我們假定切換條件已經滿足,在此直接分析從0號進程切換到1號進程的情況。
由于當前1號進程的pid[1].state是 “-1”(unrunnable),所以將會執行my_schedule()函數的else分支的內容。
my_interrupt.c文件的my_schedule()函數的else分支的內容如下:
else{next->state = 0;my_current_task = next;printk(KERN_NOTICE "switch from %d process to %d process\n \>>>process %d running!!!<<<\n\n\n",prev->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));}
"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 */
shell多線程。首先,經過了一系列的函數調用,1號進程的棧已經發生了變化,示意圖如下:
接下來開始分析從1號進程切換回0號進程的的流程
由于0號進程之前運行過,所以其狀態是runnable,則調度過程中將進入my_schedule()方法的if分支執行,即執行如下代碼:
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 taskprintk(KERN_NOTICE "switch from %d process to %d process\n>>>process %d running!!!<<<\n\n",prev->pid,next->pid,next->pid);}
"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"
shell進程?
"ret\n\t" /* restore eip */
此時eip和esp都已經切換回了由進程0切換到進程1最后時刻的值,當eip繼續去指令時,得到的將是0號進程上一次被切換掉時的指令的下一條指令,如下:
/* 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" /* 進程0在這里被切換走 */"popl %%ebp\n\t" /* 接著應該執行這條指令 */: : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/);
"popl %%ebp\n\t"
在這之后,0號進程將順著執行流,完成對my_schedule()的調用,并且返回0號進程的my_process(),然后就是在進程0與進程1之間不斷的切換。
參考文獻:
https://github.com/mengning/mykernel/blob/master/mymain.c
Linux內核組成,https://github.com/ExiaHan/linuxKernelStudy/blob/master/secondWeekend/processSwitch.md
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态