do_fork 源碼分析



 對do_fork的源碼進行了分析,看了好多遍,昨晚又看了一遍,現在能夠自己順下來大致執行過程,比之前理解更深刻。

先解釋一下ptr_err,這在下面會用到。因爲在內核中,有些函數,比如kmalloc是返回指針的,kmalloc是分配內存的,如果分配不到,就返回null指針。有些函數錯誤時,我們在知道它錯了的基礎上還要獲得錯誤碼,在用戶空間,提供了error變量,獲得錯誤碼,在內核中就相應的有ptr_err,很明顯,也是用來獲得錯誤碼的。在do_fork中,copy_process錯誤時,錯誤碼保存在pid中。

do_fork函數的主要作用就是複製原來的進程使其成爲一個新的進程,它完成了整個進程創建中的大部分工作。

Fork、vfork和clone三個系統調用所對應的系統調用服務例程都調用了do_fork()。只是所傳遞的參數不同,導致父進程和子進程在共享資源的程度上不同。fork是全部複製父進程,傳給子進程,所以fork不帶參數。而clone複製部分父進程資源,也就是說複製是有選擇的。vfork準確來說,它創建的是線程而不是進程。並且vfork創建的子進程先於父進程執行。只有子進程執行結束或退出,父進程才被喚醒。

 

接下來分析do_fork()。

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)

首先解釋以上幾個參數,

clone_flags: 通過clone標誌可以有選擇的對父進程的資源進行復制;fork,vfork和clone就是因爲flag標誌不同才加以區分的。

statck_start:子進程用戶態堆棧的地址;

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

stack_size:未被使用,通常被賦值爲0;

parent_tidptr:父進程在用戶態下pid的地址,只有CLONE_PARENT_SETTID標誌被設定時纔有意義;

child_tidptr:子進程在用戶下pid的地址,和上面的一樣,也是在CLONE_CHILD_SETTID標誌被設定時有意義;

大體執行步驟如下:

1.定義一個task_struct類型的指針p struct task_struct *p;),作用:接收子進程所分配的進程描述符(這兒是用copy_process實現的)。爲新進程分配一個pid。分配完畢後,判斷pid是否分配成功,判斷語句如下:

if (pid < 0)

return -EAGAIN;

2. 檢查當前進程是否被跟蹤


   跟蹤與否是看ptrace的值。父進程非0則未被跟蹤。如果父被跟蹤,就得判斷子進程是否被跟蹤。

如果trace爲1,那麼就將跟蹤標誌CLONE_PTRACE加入標誌變量clone_flags中。

if (unlikely(current->ptrace)) {

trace = fork_traceflag (clone_flags);

if (trace)

clone_flags |= CLONE_PTRACE;

}

3. 通過copy_process()創建子進程的描述符,並創建子進程執行時所需的其他數據結構,最終則會返回這個創建好的進程描述符。

 

p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);

4. copy_process執行不成功,則先釋放已分配的pid,根據ptr_err得到錯誤碼,保存在pid中。

 如果成功,則執行如下代碼:
首先定義了一個完成量vfork,如果clone_flags包含CLONE_VFORK標誌,那麼將進程描述符中的vfork_done字段指向這個完成量,之後再對vfork完成量進行初始化。

if (!IS_ERR(p)) {

struct completion vfork;

if (clone_flags & CLONE_VFORK)

{

p->vfork_done = &vfork;

init_completion(&vfork);

}

5. 如果子進程被跟蹤(在父進程被跟蹤的前提下,一般來說父進程是很少被跟蹤的)或者設置了CLONE_STOPPED標誌,就爲子進程增加掛起信號。

具體操作是,將SIGSTOP信號所對應的那一位置1。

if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {

sigaddset(&p->pending.signal, SIGSTOP);

set_tsk_thread_flag(p, TIF_SIGPENDING);

}

6. 如果子進程未設置CLONE_STOPPED標誌,那麼通過wake_up_new_task函數使得父子進程之一優先運行;否則,將子進程的狀態設置爲TASK_STOPPED。

if (!(clone_flags & CLONE_STOPPED))

wake_up_new_task(p, clone_flags);

else

p->state = TASK_STOPPED;

7. 如果父進程被跟蹤,則將子進程的pid賦值給父進程,存於進程描述符的pstrace_message字段。使得當前進程停止運行,並向父進程的父進程發送SIGCHLD信號。

if (unlikely (trace)) {

current->ptrace_message = pid;

ptrace_notify ((trace << 8) | SIGTRAP);

}

8. 如果CLONE_VFORK標誌被設置,則通過wait操作將父進程阻塞,直至子進程調用exec函數或者退出。(這兒顯示的就是vfork的作用)

if (clone_flags & CLONE_VFORK) {

wait_for_completion(&vfork);

if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))

ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);

}

最後: 返回pid。

這也fork系統調用時父進程會返回子進程pid的原因。

看完do_fork首先明白了父進程爲什麼返回子進程的pid,之前只是被動接受父進程是返回子進程的pid,現在能夠理解這樣的原因。在這之前也只是直接調用,就像之前編fork程序,就幾行程序。看了fork的源代碼,才知道操作系統做了多少,遠遠比想象中多,雖然只是明白了do_fork的大致流程,有些細節,以及這之中調用的函數,如copy_process函數,還沒看明白,但是每一次看都會比之前理解的更深刻。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章