1. 程式人生 > >系統呼叫fork()、vfork()與clone()

系統呼叫fork()、vfork()與clone()

系統呼叫clone()的主要用途是建立一個執行緒,可以根據引數選擇性複製程序的資源。fork(),全面複製父程序的資源。vfork(),與父程序共享使用者空間,當建立了子程序,子程序先執行,等子程序退出後父程序再執行(與execve()配合使用)。

clone、fork、vfork都呼叫do_fork()。

do_fork()中的標誌位引數:

 // 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? */


sys_fork() > do_fork() > alloc_task_struct( )

為子程序分配兩個連續的物理頁面。低端用作子程序的task_struct結構,高階用作其系統空間堆疊。


sys_fork() > do_fork() >get_pid( )

根據clone_flags中的標誌位CLONE_PID的值,或返回父程序(當前程序)的pid,或返回一個新的pid放在子程序的task_struct中。

程序號常數PID_MAX定義為0x8000。程序號的最大值是0x7fff,即32767.程序號0~299是為系統程序(包括核心執行緒)保留的。主要用於各種“保護神”程序。

sys_fork() > do_fork() >copy_files()

有條件的複製已開啟檔案的控制結構。當引數CLONE_FILES標誌位為1,就只能通過atomic_inc()遞增當前程序的files_struct結構中的共享計數。如果引數CLONE_FILES標誌位為0,那就要複製,首先通過kmem_cache_alloc()為子程序分配一個files_struct資料結構作為newf,然後將oldf把內容複製給newf。

sys_fork() > do_fork() >copy_fs()

與檔案系統有關,當CLONE_FS為0時才複製,task_struct結構指標中的指標指向一個fs_struct資料結構,結構中記錄的是程序的根目錄root、當前工作目錄pwd、一個用於操作許可權管理的umask,還有一個計數器。

sys_fork() > do_fork() >copy_sighand()

關於對訊號的處理,是否複製父程序對訊號的處理是由標誌位CLONE_SIGHAND控制的。如果一個程序設定了訊號處理程式,其task_struct結構中的指標sig就指向一個signal_struct資料結構。

sys_fork() > do_fork() >copy_mm()

關於程序使用者空間的處理。當CLONE_VM標誌位為0時才真正進行。其中最重要的vm_area_struct資料結構和頁面對映表,由dup_mmap()複製。

sys_fork() > do_fork() >copy_fs() >dup_mmap()>copy_page_range( )

逐級處理頁面目錄項和頁面表項。

(1)表項的內容為全0,表明頁面未對映,不要處理。

(2)表項不為0,P位為0;說明對映已經建立,但是該頁面目前不在記憶體中。已經被調出到交換裝置上。通過swap_duplicate()遞增它的共享計數。然後就轉到cont_copy_pte_range將此表項複製到子程序的頁面表中。

(3)需要從父程序複製的可寫頁面。採用“copy_on_write”技術(寫時拷貝),先通過複製頁面表項暫時共享這個頁面,到子程序(或父程序)真的要寫這個頁面時再來分配頁面和複製。只要一個虛存區間的性質時可寫(VM_MAYWRITE為1)而又不是共享(VM_SHARED為0),就屬於copy_on_write區間。再通過複製頁面表項暫時共享一個頁面表項時要做兩件重要的事。1、父程序的頁面表項改成防寫,然後把已經改成防寫的表項設定到子程序的頁面表中。相應的頁面在兩個程序中都變成了“只讀”。當父程序或是子程序企圖寫入該頁面時,都會引起一次頁面異常,異常處理程式對此的反應則是另行分配一個物理頁面,並將內容真正地“複製”到新的物理頁面中。讓父、子程序各自擁有自己的物理頁面。然後將兩個表中相應的表項改成可寫。

(4)父程序的只讀頁面。複製頁面表項共享物理頁面。

sys_fork() > do_fork() >copy_thread()

複製父程序的系統空間堆疊。將該結構的eax置成0(fork()返回值)。將task_struct結構中esp置成這裡的引數esp,他決定了程序在使用者空間的堆疊位置。

sys_fork() > do_fork() 

task_struct結構中counter欄位的值就是程序的執行時間配額,這裡將父程序的事件配額分成兩半,讓父、子程序各有原值的一半。如果建立的是執行緒,則還要通過task_struct結構中的佇列頭thread_group與父程序連結起來,形成一個“執行緒組”。接著就讓子程序進入他的關係網。先通過SET_LINKS(p)將子程序的task_struct結構鏈入核心的程序佇列,然後通過hash_pid()將其鏈入按其pid計算得的雜湊佇列。

結束。