天天看點

Linux fork()實作

fork()系統調用通過複制一個現有程序來建立一個全新的程序(程序的另外一個名字叫做任務)。

程序被存放在一個叫做任務隊列的雙向循環連結清單中。

連結清單中的每一項都是類型為task_struct稱為程序描述符的結構。(它包含一個具體程序的所有資訊)

程序描述符的存放:

核心通過一個唯一的程序辨別值或PID來辨別每個程序。//最大值預設為32768,short int短整型的最大值,它就是系統中允許同時存在的程序的最大數目

程序狀态

(1)TASK_RUNNING(運作)

(2)TASK_INTERRUPTIBLE(可中斷)

(3)TASK_UNINTERRUPTIBLE(不可中斷)

(4)TASK_ZOMBIE(僵死)

(5)TASK_STOPPED(停止)

fork():

父子程序差別

子程序和父程序的差別僅僅在于PID,PPID,某些資源統計量(挂起的信号,沒有必要被繼承)

Linux采用了’寫時複制技術COW加快建立程序(Copy-On-Write):

linux的fork()使用寫時拷貝頁實作。

寫時拷貝:一種可以推遲甚至免除拷貝資料的技術

使用寫時拷貝,核心并不複制整個程序位址空間,而是讓父子程序共享同一個拷貝。隻有需要寫入的時候,資料才會被複制,進而各個程序擁有各自的拷貝。

資源的複制隻有在需要寫入的時候才進行,在這之前隻是以隻讀方式共享,本質是将位址空間上的頁的拷貝推遲到實際發生寫入的時候

fork()的實際開銷:指派父程序的頁表(頁表是一種特殊的資料結構,放在系統空間的頁表區,存放邏輯頁與實體頁幀的對應關系)以及給子程序建立唯一的程序描述符。

(一般情況下,程序建立後都會馬上運作一個可執行的檔案。)

實作:

linux通過clone()系統調用實作fork()。

fork(),vfork(),和_clone()庫函數都根據各自需要的參數标志去調用clone(),然後由clone()去調用do_fork()。

do_fork()完成了建立中的大部分工作,它定義在kernel/fork.c中。該函數調用copy_process(),接下來copy_process()實作的工作如下

1.調用dup_task_struct()為新程序建立一個核心棧,threaad_info結構和task_struct,這些值與目前程序的值相同。此時,子程序和父程序的描述符是完全相同的。

2.檢查新建立的這個子程序後,目前使用者所擁有的程序數目沒有超出給他配置設定的資源的限制。

3.現在,子程序着手使自己與父程序差別拷來。程序描述符内的許多成員都要被清0或設為初始值。程序描述符的成員值并不是繼承而來的,而主要是統計資訊,程序描述符中大多數的資料都是共享的。

4.接下來,子程序的狀态被設定為TASK_UNINTERRUPTIBLE(不可中斷)以保證它不會投入運作。

5.copy_process()調用copy_flags()以更新task_struct的flags成員。表明程序是否擁有超級使用者權限的PF_SUPERPRIV标志被清0.表名程序還沒有調用exec()函數的PF_FORKNOEXEC标志被設定。

6.調用get_pid()為新程序擷取一個有效的PID

7.根據傳遞給clone()的參數标志,copy_process()拷貝或共享打開的檔案,檔案系統資訊,信号處理函數,程序位址空間和命名空間等。在一般情況下,這些資源會被給定程序的所有線程共享;否則,這些資源對每個程序是不同的,是以被拷貝到這裡。

8.讓父程序和子程序平分剩餘的時間片

9.最後copy_process()做掃尾工作并傳回一個指向子程序的指針

再回到do_fork()函數,如果copy_process函數成功傳回,新建立的子程序被喚醒并讓其投入運作。核心有意選擇子程序首先執行。因為一般子程序都會馬上調用exec()函數,這樣可以避免寫時拷貝的額外開銷,如果父程序首先執行的話,有可能會開始向位址空間寫入。

繼續閱讀