1. 程式人生 > >fork()相關的源碼解析

fork()相關的源碼解析

handler unlikely 接收 增加 共享 禁止 dds tac 成功

fork()的真正執行采用的是do_fork()函數,所以下文將從do_fork()函數對fork()進行源碼解析。下圖是do_fork()的源碼函數設計:

技術分享

從上圖我們可以看到do_fork()涉及到眾多的參數。所以在進入do_fork函數進行分析之前,很有必要了解一下它的參數。

clone_flags:克隆標識;

*
* 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 */
/**
* 設置子進程的父進程為調用進程的父進程。
*/

#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狀態
*/

#define CLONE_STOPPED 0x02000000 /* Start in stopped state */

技術分享

statck_start:子進程用戶態堆棧的地址,即棧的起始位置。

regs:指向pt_regs結構體的指針。當系統發生系統調用,即用戶進程從用戶態切換到內核態時,該結構體保存通用寄存器中的值,並被存放於內核態的堆棧中;

stack_size:棧的大小;

parent_tidptr:父進程在用戶態下pid的地址;

child_tidptr:子進程在用戶態下pid的地址;

了解了參數之後,我們要知道PID一列代表了各進程的進程ID。也就是說,PID就是各進程的身份標識。 只要運行一程序,系統會自動分配一個標識! 是暫時唯一:進程中止後,這個號碼就會被回收,並可能被分配給另一個新進程。 只要沒有成功運行其他程序,這個pid會繼續分配給當前要運行的程序!! 如果成功運行一個程序,然後再運行別的程序時,系統會自動分配另一個pid! 所以

(1)我們要為子進程分配新的pid參數。 在一開始,該函數定義了一個task_struct類型的指針p,用來接收即將為新進程(子進程)所分配的進程描述符。緊接著使用alloc_pidmap函數為這個新進程分配一個pid。由於系統內的pid是循環使用的,所以采用位圖方式來管理。簡單的說,就是用每一位(bit)來標示該位所對應的pid是否被使用。分配完畢後,判斷pid是否分配成功。

(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)創建描述符成功之後,定義一個vffok來表示完成量,如果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)
{
unsigned long sig = _sig - 1;
if (_NSIG_WORDS == 1)
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中。

fork()相關的源碼解析