fork如何實現執行一次返回兩個值的?
註:沒有想到會得到這麼多的贊:),所以還是要說明下,我這裡的回答是基於我目前在看的Linux 0.11的源碼中fork的實現,之所以把這個實現過程寫下來,也是為了將這個同樣困擾自己的問題在自己讀過源碼後的思考結果寫出來,和大家分享。這裡說明下這個回答針對的是遠古的Linux 0.11的內核實現,不過這個實現過程還是有參考價值的,至少可以讓我們這些不了解內核的人可以對類似fork API這樣的不好理解的Linux系統調用有所深入的理解,從而方便大家可以更好的進行系統編程。很感謝部分知友對於較新內核實現的說明。
我目前還是以Linux 0.11源碼為入門,先了解下Linux內核的基本實現機制。如果有額外的時間和精力,再看看較新的內核。學海無涯,腳踏實地吧。
fork()函數是用戶態的API介面,最終會展開為一段包含int 0x80這個編程異常指令以及movl eax, res; return res的彙編代碼,從而觸發編程異常,從用戶態進入內核,執行system_call異常處理函數。
而system_call通過保存在eax寄存器中的系統調用號會去調用內核中真正實現fork功能的系統調用sys_fork。
sys_fork要做的工作首先是為新創建的子進程找到一個可用的pid,即last_pid。然後調用copy_process複製父進程的pcb數據到子進程的pcb。當然複製完成後要做一些修改,把子進程的父進程號設置為當前調用fork()API的父進程,同時將子進程tss結構體中的eax寄存器的值設置為0。並將子進程的狀態設置為就緒態,從而使得子進程後面可以得到CPU的調度。
再進行其他一些數據複製和處理以後,sys_fork函數返回到異常處理函數system_call,返回值為子進程的pid即last_pid。system_call檢查父進程是否需要重新調度。如果需要再次調度,則執行調度。否則system_call將系統調用sys_fork的返回值存入寄存器eax中,執行異常返回操作從內核返回到用戶態,接著從父進程int 0x80後的下一條指令開始執行,也就是執行movl eax, res; return res,這就實現了父進程從fork() API調用的返回,返回值就是保存在eax寄存器的last_pid。
如果system_call執行調度,父進程就會被換入到就緒隊列中,失去CPU的使用權。那麼可能是子進程也可能是其他進程繼而得到CPU的使用權,開始執行。總之,子進程總會有執行的時刻,而子進程的pcb中的cs,eip寄存器保存的是和父進程通過int 0x80編程異常進入內核處理函數時一樣的指令地址,即子進程也是從int 0x80後面的指令movl eax, res; return res開始執行,而子進程的eax的值被設置為0,從而子進程開始執行時的指令流程,和父進程從異常處理函數system_call返回到用戶空間後的執行流程是一樣的,將eax的值作為fork()API的返回值,從而完成子進程像是從fork()API返回的樣子,但實質只是其執行流程是從fork()API返回處開始執行的,且返回值是0。
而父進程不管是不經過調度先進入內核執行sys_fork系統調用,再從內核返回到fork()API,再從fork()API返回;還是經過調度被換出後進入等待隊列,到再次得到CPU開始執行,兩者的指令的執行流程都是一樣。
綜上,父進程和子進程都要執行,返回值也不一樣,所以fork()API表現為一次調用兩次返回,但實質任何函數調用都只會返回一次,父進程是返回,而子進程只是從父進程的返回指令處開始執行。
簡單理解是,fork會導致當前程序停下來,控制權移交給操作系統。操作系統根據調用fork的這個進程,複製出一個幾乎一模一樣的進程,然後在它們某個表示返回值的屬性那邊寫上不一樣的兩個值,最後恢復正常執行。
fork會複製出一模一樣的兩個進程, 包括他們當前執行的狀態:
這是父進程:
這是子進程:
接下來就是cpu調度這兩進程執行, 返回不同的pid.
之前看過說是:進程(應該是在task_struct)維護了一個表示其子進程的鏈表,fork的時候就返回其子進程的pid,對於父進程返回的就是子進程的pid,對於子進程由於沒有子進程,所以子進程返回0。具體沒研究過,所以 逃...
需要明確一點的是,所有函數的返回值是存儲在寄存器eax中的,當fork返回時,子進程會返回0是因為在初始化任務段時TSS,將eax值置為0。
父進程調用copy_process得到lastpid是子進程的pid,所以父進程將lastpid放入eax中,所以父進程中fork返回子進程pid
子進程切到cpu上執行時,載入上下文環境(載入TSS),寄存器eax的值被置為0,所以子進程中fork的返回值為0希望回答對你有幫助fork執行流程:
用戶態調用fork函數 -&> 觸發80中斷,切換到內核態執行系統函數(sys_fork)-&> 複製當前進程信息(堆棧寄存器內容等)生成子進程 -&> 設置當前進程,子進程的eax寄存器(兩個進程設置的eax值不同) -&> 當前進程返回(return值為eax中的內容) -&> fork執行完畢 -&> 操作系統切換進程到子進程 -&> 子進程獲取到返回值(eax)
注意( 當前進程返回)這一步也可能有進程切換,這個看操作系統具體實現
子進程創建完成後的狀態,執行位置處於sys_fork函數即將return的位置
並不是一次返回兩個返回值,而是從一個進程變成兩個進程,每個進程的返回值不一樣。
具體過程看其他人解釋就行,我想說的是這句話就是給新人理解帶來誤導,
並不是一次執行,返回兩次,而是fork()完就變成兩個進程了,各返回一次。
fork() 是 Unix-like 的操作系統的系統調用函數,可以複製出一個一模一樣的子進程,返回值是子進程的進程 id,也就是子進程 pid。
因此程序執行到
pid_t c_pid = fork();
這個語句時,先複製出一個一模一樣的進程,兩個進程中,父進程會返回子進程 pid,而那個被複制出來的子進程肯定沒有子進程啦,因此子進程會返回 0。
進程繼續按照代碼執行,就可以根據 c_pid 的值的不同,讓他們進入不同條件代碼,也還可以用exec 函數(同樣是系統調用)用其它的進程代碼覆蓋原有進程,從而實現創建一個新的進程。
推薦閱讀:
※YunOS 與 Android 有什麼關係?
※如何理解「In UNIX, everything is a file」?
※為什麼有些Linux發行版更新地那麼頻繁?
※Linux下有什麼工具可以分析出一個程序的運算時間分布嗎?
※Linux為什麼要衍生出那麼多的版本,統一一下產品線不好么?