呂鬆鴻+ 原創做品轉載請註明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000linux
存儲程序計算機工做模型,計算機系統最最基礎性的邏輯結構;shell
函數調用堆棧,高級語言得以運行的基礎,只有機器語言和彙編語言的時候堆棧機制對於計算機來講並不那麼重要,但有了高級語言及函數,堆棧成爲了計算機的基礎功能;markdown
enter 框架
pushl %ebp函數
movl %esp,%ebpui
leave this
movl %ebp,%espspa
popl %ebp操作系統
函數參數傳遞機制和局部變量存儲線程
中斷,多道程序操做系統的基點,沒有中斷機制程序只能從頭一直運行結束纔有可能開始運行其餘程序。
瞭解對站存在的目的和編譯器對堆棧使用的規則是理解操做系統一些關鍵性代碼的基礎。
堆棧相關寄存器:
esp:堆棧指針(stack pointer),指向系統棧最上面一個棧幀的棧頂
ebp: 基址指針(base pointer),指向系統棧最上面一個棧幀的底部
cs:eip:指令寄存器(extended instruction pointer),指向下一條等待執行的指令地址
pushl %ebp
和movl %esp,%ebp
,leave是這兩條指令的反操做。call指令的兩個做用:
- 將下一條指令的地址A保存在棧頂 - 設置eip指向被調用程序代碼開始處
執行call function
cs:eip原來的值指向call下一條指令,該值被保存到棧頂 cs:eip的值指向function的入口地址
進入function
pushl %ebp //意爲保存調用者的棧幀地址 movl %esp, %ebp //初始化function的棧幀地址 而後函數體中的常規操做
退出function
movl %ebp,%esp popl %ebp ret
(一)mykernel實驗
實驗步驟
(1)進入實驗樓,打開shell以後按照說明輸入
(2)查看源代碼
a.查看mymain.c
以前的#include...都是硬件初始化用到的語句,而截圖部分是是「操做系統」開始執行的入口
從代碼可見,每循環10 000次,打印一句話。
b.查看myinterrupt.c
每執行一次,都會執行一次時鐘中斷
(二)在mykernel基礎上構造一個簡單地操做系統內核
/* * linux/mykernel/mypcb.h * Kernel internal PCB types * Copyright (C) 2013 Mengning */ #define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE 1024*8 /* CPU-specific state of this task */ struct Thread { unsigned long ip;//保存eip unsigned long sp;//保存esp }; typedef struct PCB{ int pid; volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ char stack[KERNEL_STACK_SIZE]; /* CPU-specific state of this task */ struct Thread thread; unsigned long task_entry; struct PCB *next; }tPCB; void my_schedule(void);//調度器
/* * linux/mykernel/mymain.c * Kernel internal my_start_kernel * Copyright (C) 2013 Mengning */ #include <linux/types.h> #include <linux/string.h> #include <linux/ctype.h> #include <linux/tty.h> #include <linux/vmalloc.h> #include "mypcb.h" tPCB task[MAX_TASK_NUM]; tPCB * my_current_task = NULL; volatile int my_need_sched = 0;//用來判斷是否須要調度的標識 void my_process(void); void __init my_start_kernel(void) { int pid = 0; int i; /* Initialize process 0 (初始化0號進程)*/ task[pid].pid = pid; task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//定義0號進程的入口:myprocess task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid];//因爲0號進程初始化時只有這一個進程,因此next指向本身 /*fork more process (建立更多其餘的進程)*/ for(i=1;i<MAX_TASK_NUM;i++) { memcpy(&task[i],&task[0],sizeof(tPCB)); task[i].pid = i; task[i].state = -1; task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; task[i].next = task[i-1].next; task[i-1].next = &task[i]; } /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; asm volatile( //%0表示參數thread.ip,%1表示參數thread.sp。 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp 把參數thread.sp放到esp中*/ "pushl %1\n\t" /* push ebp 因爲當前棧是空的,esp與ebp指向相同,因此等價於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*/ ); } void my_process(void) { int i = 0; while(1) { i++; if(i%10000000 == 0) { printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); if(my_need_sched == 1) { my_need_sched = 0; my_schedule(); } printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); } } }
my_need_sched
判斷是否須要調度,一旦發現其值爲1,就調用my_schedule完成進程的調度。/* * linux/mykernel/myinterrupt.c * Kernel internal my_timer_handler * Copyright (C) 2013 Mengning */ #include <linux/types.h> #include <linux/string.h> #include <linux/ctype.h> #include <linux/tty.h> #include <linux/vmalloc.h> #include "mypcb.h" 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 if(time_count%1000 == 0 && my_need_sched != 1) { printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); my_need_sched = 1; } time_count ++ ; #endif return; } void my_schedule(void) { tPCB * next; tPCB * prev; if(my_current_task == NULL //task爲空,即發生錯誤時返回 || my_current_task->next == NULL) { return; } printk(KERN_NOTICE ">>>my_schedule<<<\n"); /* schedule */ next = my_current_task->next;//把當前進程的下一個進程賦給next prev = my_current_task;//當前進程爲prev if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ { /* switch to next process */ /*若是下一個進程的狀態是正在執行的話,就運用if語句中的代碼表示的方法來切換進程*/ asm volatile( "pushl %%ebp\n\t" /* save ebp 保存當前進程的ebp*/ "movl %%esp,%0\n\t" /* save esp 把當前進程的esp賦給%0(指的是thread.sp),即保存當前進程的esp*/ "movl %2,%%esp\n\t" /* restore esp 把%2(指下一個進程的sp)放入esp中*/ "movl $1f,%1\n\t" /* save eip $1f是接下來的標號「1:」的位置,把eip保存下來*/ "pushl %3\n\t" /*把下一個進程eip壓棧*/ "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; printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); } else//用於下一個進程爲未執行過的新進程時。首先將這個進程置爲運行時狀態,將這個進程做爲當前正在執行的進程。 { next->state = 0; my_current_task = 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" /* 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; }
1.操做系統「兩劍」:中斷上下文、進程上下文的切換.
2.操做系統的核心功能就是:進程調度和中斷機制,經過與硬件的配合實現多任務處理,再加上上層應用軟件的支持,最終變成能夠使用戶能夠很容易操做的計算機系統。
3.進程切換機制中包含esp的切換、堆棧的切換。從esp能夠找到進程的描述符;堆棧中ebp的切換,肯定了當前變量空間屬於哪一個進程。