這是我在學習Linux0.11內核時做的筆記,以作為以後複習使用。本文解釋為什麼fork()函數會調用一次,返回兩次。以及為什麼返回給父進程的是子進程的pid,而返回給子進程的是0。 大致過程 用戶程式調用fork()函數(標準庫)->中斷處理程式(system_call.s)->sys_fork( ...
這是我在學習Linux0.11內核時做的筆記,以作為以後複習使用。本文解釋為什麼fork()函數會調用一次,返回兩次。以及為什麼返回給父進程的是子進程的pid,而返回給子進程的是0。
大致過程
用戶程式調用fork()函數(標準庫)->中斷處理程式(system_call.s)->sys_fork()(system_call.s)->find_empty_process()(fork.c)->copy_process()(fork.c)
詳細過程
首先,由用戶程式來調用標準庫提供的API -- fork()函數以進入中斷處理程式(不懂的可以看之前的筆記)。
然後中斷處理程式會根據傳進來的系統調用號參數來得知要執行的系統調用函數,即sys_fork()。在進入中斷處理程式前,就會先壓入ss、esp、eflags、cs和eip寄存器(調用系統調用的必要操作,由硬體完成)。在中斷處理程式中也會先壓入當前進程的一些寄存器,這些寄存器都是在這裡剛好用來當成copy_process()函數的參數(不懂為什麼的可以先看最後的文章後面的解釋)。被中斷處理程式壓入的寄存器有如下:
push ds ;// 保存原段寄存器值。
push es
push fs
push edx ;// ebx,ecx,edx 中放著系統調用相應的C 語言函數的調用參數。
push ecx ;// push %ebx,%ecx,%edx as parameters
push ebx ;// to the system call
然後再進行一些操作之後,就會使用如下語句來調用系統調用:
call [_sys_call_table+eax*4] ;// eax就是保存著系統調用號
接下來就是進入sys_fork(),這個是由彙編語言編寫於system_call.s文件中。該函數第一步就是尋找能存放新進程信息的進程表空位和空閑PID值,通過如下語句:
call _find_empty_process ;// 調用find_empty_process()(kernel/fork.c,135)。
在sys_fork()函數中會壓入當前一些寄存器用來當成copy_process()函數的參數,語句如下:
push gs
push esi
push edi
push ebp
push eax ;// 這裡的eax就是剛剛在find_empty_process()函數中返回的進程表空位的索引(不懂的可以看文章後面的解釋)
接下來就是調用copy_process()函數了,傳入該函數的參數非常多,按照順序來如下:
int nr, long ebp, long edi, long esi, long gs, long none,long ebx, long ecx, long edx,long fs, long es, long ds,long eip, long cs, long eflags, long esp, long ss
函數中,先申請一頁空間來存放進程的信息,而這個空間地址到時候由進程表來索引查找。先設置子進程的狀態為不可中斷等待狀態,以防被錯誤調度。在該空間中填入進程的信息,這些信息基本都是來自之前父進程通過壓棧得來。其中子進程需要在該空間中記下自己的pid、父進程的pid、設置時間片、各種寄存器(幾乎所有都跟父進程的一致,除了內核棧地址esp0、堆棧段選擇符ss0以及eax,該eax設置為0,這就是子進程返回值為0的原因)、分配新的ldt給子進程。由於Linux0.11內核已經有寫時複製機制,所以接下來子進程只複製父進程的頁表項,這樣就可以做到父子進程共用同樣的進程空間了。子進程當然也要對父進程打開的各種文件描述符的打開次數增1。子進程要在GDT 中設置新任務的TSS 和LDT 描述符項。這時還要對子進程的狀態再做一次設置,設置為就緒狀態,等待調度。函數的最後就是返回子進程的pid。
解釋
·函數的參數是怎麼來的?其實就是取棧中的參數。用戶在調用函數的時候,傳入的參數是被壓入棧中的,而且是從右往左。
如:int func(int a, int b); //b會先被壓入棧,然後再是a。函數在取實參的時候就是從棧頂開始取,即從a開始。
·函數的返回值都是被保存在寄存器eax中的。
如:return a; //寄存器eax就保存著a的值。
講到這裡,基本講完了,現在就可以來解決開頭的兩個問題了。
問題
·為什麼fork()函數會調用一次,返回兩次?
首先,我們回憶一下,子進程的寄存器幾乎與父進程的一樣,也就是說其cs、eip也和父進程的一樣。而父進程在進入中斷處理程式的時候壓入了cs、eip,這兩個的組合就是指向fork()函數中從中斷處理程式返回之後的下一條語句。父進程在調用完系統調用返回之後是去執行cs:eip指向的語句,而子進程也是如此。該函數執行到最後是要返回的,所以兩個進程各返回一次。
·為什麼返回給父進程的是子進程的pid,而返回給子進程的是0?
父子進程的返回值不同,就是因為他們的eax存放的值不同。在copy_process()函數最後的return last_pid;是父進程執行的,也就是子進程的pid被保存在了父進程的eax中;而我剛剛前面提到,子進程的eax被設置為0。這就是不同返回值的原因。
歡迎各位發現錯誤後指出,本人一定及時改正並致以最誠懇的感謝!