注:原創作品轉載請註明出處
《Linux內核分析》 MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一、計算機是如何工作的?——三個法寶
1. 存儲程序計算機工作模型,計算機系統最最基礎性的邏輯結構;
2. 函數調用堆棧,高級語言得以運行的基礎,只有機器語言和彙編語言的時候堆棧機制對於計算機來說並不那麼重要,但有了高級語言及函數,堆棧成爲了計算機的基礎功能;
enter // 進入函數
pushl %ebp
movl %esp,%ebp
leave // 函數執行結束,離開
movl %ebp,%esp
popl %ebp
3. 中斷,多道程序操作系統的基點,沒有中斷機制程序只能從頭一直運行結束纔有可能開始運行其他程序。
二、函數調用棧的機制
三、中斷機制
五、中斷分析
#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.
*/
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;//此時置my_need_sched爲1
}
time_count++;
#endif
return;
}
void my_schedule(void)
{
tPCB *next;
tPCB *prev;//定義即將要發生切換的任務
//錯誤處理
if(my_current_task == NULL||my_current_task->next == NULL)
{
return;
}
printk(KERN_NOTICE ">>>my_schedule<<< \n");
next = my_cueernt_task->next;
prev = my_current_task;
if(next.state==0)//兩個正在運行的進城之間進行切換
{
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
//切換進程,內嵌彙編代碼
asm volatile(
"pushl %%ebp\n\t" //save ebp
"movl %%esp,$0\n\t" //save esp,prev執行到此,將當前的esp保存到prev的thread的sp
"movl $2,%%esp" //將next的esp移入esp
"movl $1f,$1" //將prev的eip壓棧
"pushl %3\n\t"
"ret\n\t"
"1:\t"
:"=m"(prev->thread.sp),"=m"(prev->thread.ip)
:"m"(next->thread.sp),"m"(next->thread.ip)
}
else//next 是一個新進程,重來沒有執行過,所以初始的堆棧是空的
{
next.state=0
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
//切換進程,內嵌彙編代碼
asm volatile(
"pushl %%ebp\n\t" //save ebp
"movl %%esp,$0\n\t" //save esp,prev執行到此,將當前的esp保存到
"movl $2,%%esp" //將next的esp移入esp
"movl $2,%%ebp" //第一次執行,next的堆棧是空的
"movl $1f,$1" //將prev的eip壓棧
"pushl %3\n\t"
"ret\n\t"
:"=m"(prev->thread.sp),"=m"(prev->thread.ip)
:"m"(next->thread.sp),"m"(next->thread.ip)
}
}
所有進程第一次執行時
首先,它設置了兩個變量,當前進程爲prev,下一個進程爲next。然後,判斷下一個進程的狀態是否爲可運行(即是否爲0),由於除了0號進程以外,其它進程都是第一次執行,因此它們的狀態都爲不可運行,即爲-1,走else路徑。
我們看看else路徑,首先,它即將執行的1號進程狀態置爲運行狀態,然後用內聯彙編進行進程切換了。首先,它保存0號進程的ebp寄存器值到0號進程的棧裏,然後把esp寄存器值保存0號進程的thread.ip裏。接着,把esp寄存器值指向1號進程的esp值,即1號進程棧的棧頂,接着把ebp值也只想同樣的位置。我們知道,當一個函數剛開始執行時(即完成了push ebp;mov esp ,ebp),esp和ebp是指向同樣的位置的。接着,執行這句,mov $1f,%1,這是保存0號進程的eip的值,讓再度切換到0號進程時可以繼續執行,1f是個在if 分支裏面的地址,等我們分析到if分支時,我們再詳細說明,這裏要明白這是保存了0號進程的ip值就好。 再接着,把1號進程的入口值壓棧,再ret,彈出給eip,這樣1號進行就開始執行了,執行my_process函數。類似地,當1號進程去切換到2號進程時,過程也類似,直到所有進程都執行了一遍,從最後一個進程切換到0號進程時,情況不一樣了。
所有進程非第一次執行時
當最後一個進程切換到0號進程時,由於0號進程已經可以運行了,所以它走if分支。那我們來分析一下切換的內聯彙編代碼。首先,把當前進程的ebp值壓棧進行保存,然後把當前進程的esp值存入到對應的trhead.sp裏面。再接着,開始切換棧了,把下一個進程也就是1號進程的esp值賦給esp寄存器,也就是讓esp切換到0號進程的棧空間。再接着mov 1f,%1,即保存當前進程的eip值,而1f是這段內斂彙編最後一條指令(popl %ebp)的值,這樣當再次切換回來時,第一條執行的語句就是pop ebp。再接着,把下一個進程也就是0號進程的ip值壓棧,再ret,彈給EIP,這樣就切換到下一個進程了,也就是切換回0號進程了,然後0號進程第一句就是popl %ebp。以後,所有的進程切換都會執行if分支了,過程跟上面描述的類似。注意,這段代碼,可能有個難點就是 (movl $1f,%1)1f是最後一條指令的地址,這句就是保存eip的地址,這樣讓所有進程切換回來的時候,第一句就執行popl %ebp。