深入分析計算機的函數調用與進程切換

作者:奮鬥的白楊(楊延生)
注:原創作品轉載請註明出處

《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. 中斷,多道程序操作系統的基點,沒有中斷機制程序只能從頭一直運行結束纔有可能開始運行其他程序。


二、函數調用棧的機制

重要是用到了一個函數堆棧的框架。

一般在call指令(即,調用函數)前,會先eip(eip現在通常指向現在執行指令的下一條指令)壓棧,便於當函數調用結束時,從該eip指向的位置繼續執行。如果函數調用有參數,一般也會在保存eip前,先將函數參數壓入棧中。

新的eip將指向調用函數的地址,後面便從始從調用的函數處執行代碼。

在進入到調用函數內部時,通過要先建立堆棧框架,一般就是先將之前的ebp壓棧,再將esp賦給ebp,這樣新的函數就有一個空的棧,一切新的局部變量都將在新的棧中保存。

將調用的函數執行完後,會先將esp退回到ebp處,同時將之前存儲的ebp的內容又賦給ebp,恢復到函數調用前的樣子。


三、中斷機制

中斷機制是多道程序設計的基礎,不然程序就會一直順序執行直到該程序結束。

中斷程序的核心就是對中斷的處理,需要處理進程之間的上下文切換。涉及到保護現場和恢復現場。

在進程要進行切換時,都需要將該進程的ebp壓入內核棧,同時esp保存在PCB中,eip等都要保存在PCB中。同時將新的進程PCB中的esp賦給esp,

四、實驗截圖與代碼





五、中斷分析


#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。

六、總結


存儲程序計算機在運行過程中一個核心的問題便是 保存與恢復,不論是函數調用還是進程切換,在進入新的函數或進程前,一定要對之前的狀態進行保存,以便以在回頭時繼續執行。該思想體現在計算機工作中的方方面面,可以舉一反三。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章