Linux内核分析:完成一个简单的时间片轮转多道程序内核代码
PS.賀邦 ? 原創作品轉載請注明出處 ?《Linux內核分析》MOOC課程 ? ?http://mooc.study.163.com/course/USTC-1000029000?
1.mykernel實驗指導(操作系統是如何工作的)
使用實驗樓虛擬機打開shell輸入下列代碼
- 1?cd?LinuxKernel/linux-3.9.4
- 2?qemu?-kernel?arch/x86/boot/bzImage
可以看到初始的內核運行情況如下:
?
內核不停的執行my_start_kernel(),每隔一段時間被my_timer_handler()中斷,然后執行一條打印語句:printk(KERN_NOTICE “\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n”);
后,又回到my_start_kernel()繼續執行。
打開mymain.c文件,可以看到其中只有如下這一個函數。它作為內核啟動的起始位置,從這個函數開始運行,并無限循環。
打開myinterrupt.c文件,里面也只有一個my_timer_handler(),它被Linux內核周期性調用,從而產生了一個周期性的中斷機制。
2.修改內核代碼,使之成為一個簡單的時間片輪轉多道程序內核,然后重新編譯運行。
從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也放在這里。?
然后執行make,重新編譯內核。效果如下:?
然后再次輸入:
- qemu -kernel arch/x86/boot/bzImage 命令
啟動內核。 可以發現跳轉和時間片都有了明顯的改變。
實驗結果與預期結果相符,實驗成功。
3.重點代碼理解
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?*/
?
4.分析進程的啟動和進程的切換機制。
?
首先,內核啟動__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號進程的切換。此后類似。
5.對“操作系統是如何工作的”理解。
操作系統的內核有一個起始位置,從這個起始位置開始執行。開始工作時,CPU分配給第一個進程,開始執行第一個進程,然后通過一定的調度算法,比如時間片輪轉,在一個時間片后,發生中斷,
第一個進程被阻塞,在完成保存現場后將CPU分配給下一個進程,執行下一個進程。這樣,操作系統就完成了基本的進程調度的功能。
轉載于:https://www.cnblogs.com/L1nke/p/5247152.html
總結
以上是生活随笔為你收集整理的Linux内核分析:完成一个简单的时间片轮转多道程序内核代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 7、redis之使用spring集成co
- 下一篇: [转载]玩转Asp.net MVC 的八