fork()的真正執行採用的是do_fork()函數,因此下文將從do_fork()函數對fork()進行源碼解析。下圖是do_fork()的源碼函數設計:併發
從上圖咱們能夠看到do_fork()涉及到衆多的參數。因此在進入do_fork函數進行分析以前,頗有必要了解一下它的參數。函數
clone_flags:克隆標識;this
*
* cloning flags:
*/
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
/**
* 共享內存描述符和全部頁表
*/
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
/**
* 共享根目錄和當前工做目錄所在的表,以及用於屏蔽新文件初始許可權的位掩碼值
*/
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
/**
* 共享打開文件表
*/
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
/**
* 共享信號處理程序的表、阻塞信號表和掛起信號表
* 若是該標誌爲1,那麼必定要設置CLONE_VM
*/
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
/**
* 若是父進程被跟蹤,那麼,子進程也被跟蹤。
* 尤爲是,debugger程序可能但願以本身做爲父進程來跟蹤子進程。在這種狀況下,內核把該標誌強設爲1
*/
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
/**
* 在發出vfork系統調用時設置
*/
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
/**
* 設置子進程的父進程爲調用進程的父進程。
*/spa
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
/**
* 把子進程插入到父進程的同一線程組中。並使子進程共享父進程的信號描述符。所以也設置子進程的tgid字段和group_leader字段。
* 若是這個標誌位爲1,則必須設置CLONE_SIGHAND標誌。
*/
#define CLONE_THREAD 0x00010000 /* Same thread group? */
/**
* 當clone須要本身的命名空間時,設置該標誌。不能同時設置本標誌和CLONE_FS。
*/
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
/**
* 共享System V IPC取消信號量的操做。
*/
#define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */
/**
* 爲輕量級進程建立新的線程局部存儲段(TLS),該段由參數tls所指向的結構進行描述。
*/
#define CLONE_SETTLS 0x00080000 /* create a new TLS for the child */
/**
* 把子進程的PID寫入由ptid參數所指向的父進程的用戶態變量。
*/
#define CLONE_PARENT_SETTID 0x00100000 /* set the TID in the parent */
/**
* 若是該標誌被設置,則內核創建一種觸發機制,用在子進程要退出或要開始執行新程序。線程
* 在這些狀況下,內核將清除由參數ctid所指向的用戶態變量,並喚醒等待這個事件的任何進程
*/
#define CLONE_CHILD_CLEARTID 0x00200000 /* clear the TID in the child */
/**
* 遺留標誌,將被內核忽略
*/
#define CLONE_DETACHED 0x00400000 /* Unused, ignored */
/**
* 內核設置這個標誌以使CLONE_PTRACE標誌推進做用(禁止內核線程跟蹤進程)
*/
#define CLONE_UNTRACED 0x00800000 /* set if the tracing process can't force CLONE_PTRACE on this clone */
/**
* 把子進程的PID寫入由ctid參數所指向的子進程的用戶態變量中
*/
#define CLONE_CHILD_SETTID 0x01000000 /* set the TID in the child */
/**
* 強迫子進程開始於TASK_STOPPED狀態
*/debug
#define CLONE_STOPPED 0x02000000 /* Start in stopped state */設計
statck_start:子進程用戶態堆棧的地址,即棧的起始位置。指針
regs:指向pt_regs結構體的指針。當系統發生系統調用,即用戶進程從用戶態切換到內核態時,該結構體保存通用寄存器中的值,並被存放於內核態的堆棧中;調試
stack_size:棧的大小;blog
parent_tidptr:父進程在用戶態下pid的地址;
child_tidptr:子進程在用戶態下pid的地址;
瞭解了參數以後,咱們已經知道PID表明了各進程的進程ID。也就是說,PID就是各進程的身份標識。 只要運行一程序,系統會自動分配一個標識! 是暫時惟一:進程停止後,這個號碼就會被回收,並可能被分配給另外一個新進程。 只要沒有成功運行其餘程序,這個pid會繼續分配給當前要運行的程序!! 若是成功運行一個程序,而後再運行別的程序時,系統會自動分配另外一個pid! 因此,這個函數內部實際進行了如下過程:
(1)咱們要爲子進程分配新的pid參數。 在一開始,該函數定義了一個task_struct類型的指針p,用來接收即將爲新進程(子進程)所分配的進程描述符。緊接着使用alloc_pidmap函數爲這個新進程分配一個pid。因爲系統內的pid是循環使用的,因此採用位圖方式來管理。簡單的說,就是用每一位(bit)來標示該位所對應的pid是否被使用。分配完畢後,判斷pid是否分配成功,其中,EAGAIN表示當前的進程數已經達到了系統規定的上限。
(2) 接下來檢查當前進程(父進程)的ptrace字段。ptrace是用來標示一個進程是否被另一個進程所跟蹤。所謂跟蹤,最多見的例子就是處於調試狀態下的進程被debugger進程所跟蹤。父進程的ptrace字段非0時說明debugger程序正在跟蹤父進程,那麼接下來經過fork_traceflag函數來檢測子進程是否也要被跟蹤。若是trace爲1,那麼就將跟蹤標誌CLONE_PTRACE加入標誌變量clone_flags中。 一般上述的跟蹤狀況是不多發生的,所以在判斷父進程的ptrace字段時使用了unlikely修飾符。使用該修飾符的判斷語句執行結果與普通判斷語句相同,只不過在執行效率上有所不一樣。正如該單詞的含義所表示的那樣,current->ptrace不多爲非0。所以,編譯器儘可能不會把if內的語句與當前語句以前的代碼編譯在一塊兒,以增長cache的命中率。與此相反,likely修飾符則表示所修飾的代碼極可能發生。
(3)建立一個子進程的進程描述符。主要使用的函數設計原型是:p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);若是成功的話,則返回該進程描述符。
(4)建立描述符成功以後,定義一個vfok來表示完成量,若是clone_flags中和CLONE_VFORK同樣,則將這個完成量賦給vfork_done.並初始化這個完成量。
if (!IS_ERR(p)) {
struct completion vfork;
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
(5)若是子進程被跟蹤或者設置了CLONE_STOPPED標誌,那麼經過sigaddset函數爲子進程設置掛起信號。其中,signal對應一個unsigned long類型的變量,該變量的每一個位分別對應一種信號。具體的操做是,將SIGSTOP信號所對應的那一位置1。並將p所表明的子線程的線程標誌置爲有掛起信號。
/**
* 若是設置了CLONE_STOPPED,或者必須跟蹤子進程.
* 就設置子進程爲TASK_STOPPED狀態,併發送SIGSTOP信號掛起它.
*/
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
sigaddset源代碼以下:
/**
* 把nsig信號在set變量中對應的位置爲1
*/
static inline void sigaddset(sigset_t *set, int _sig)//19
{
unsigned long sig = _sig - 1; //18
if (_NSIG_WORDS == 1) //2
set->sig[0] |= 1UL << sig;
else
set->sig[sig / _NSIG_BPW] |= 1UL << (sig % _NSIG_BPW);
}
其中
#define _NSIG 64
#define _NSIG_BPW 32
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
/**
* 進程有掛起信號
*/
#define TIF_SIGPENDING 2 /* signal pending */
(6)沒有設置CLONE_STOPPED,就調用wake_up_new_task,使得父子進程之一優先運行;不然,將子進程的狀態設置爲TASK_STOPPED。
(7)若是子進程正被跟蹤,則把子進程的PID賦給父進程的ptrace_message,並調用ptrace_notify。ptrace_notify使當前進程中止運行,並向當前進程的父進程發送SIGCHLD信號.子進程的祖父進程是跟蹤父進程的debugger進程.如此一來,祖父進程可獲取到當前子進程的PID
(8) 若是CLONE_VFORK標誌被設置,則經過wait操做將父進程阻塞,直至子進程調用exec函數或者退出.若是copy_process()在執行的時候發生錯誤,則先釋放已分配的pid;再根據PTR_ERR()的返回值獲得錯誤代碼,保存於pid中。