Linux多进程拷贝fork,浅析linux中fork函数
Linux通過clone()系統調用實現fork()、vfork()和__clone()庫函數創建新的進程,這個調用通過一系列的參數標志來指明父子進程的共享資源,終將各自的參數標志位傳遞給clone,由clone()去調用do_fork()來實現創建新的進程的目的。
do_fork的實現源碼在kernel/fork.c文件中,其主要的作用就是復制原來的進程成為另一個新的進程,它完成了整個進程的創建過程。do_fork()的實現主要由以下5個步驟,在分析代碼之前,先了解以下do_fork()函數的參數的含義,其參數的含義如下。
clone_flags:該標志位的4個字節分為兩部分。低的一個字節為子進程結束時發送給父進程的信號代碼,通常為SIGCHLD;剩余的三個字節則是各種clone標志的組合。通過clone標志可以有選擇的對父進程的資源進行復制。例如CLONE_VM表示共享內存描述符合所有的頁表; CLONE_FS共享根目錄和當前工作目錄所在的表以及權限掩碼。
statck_start:子進程用戶態堆棧的地址;
regs:指向pt_regs結構體的指針。當系統發生系統調用,即用戶進程從用戶態切換到內核態時,該結構體保存通用寄存器中的值,并被存放于內核態的堆棧中;
stack_size:未被使用,通常被賦值為0;
parent_tidptr:父進程在用戶態下pid的地址,該參數在CLONE_PARENT_SETTID標志被設定時有意義;
child_tidptr:子進程在用戶態下pid的地址,該參數在CLONE_CHILD_SETTID標志被設定時有意義。
函數原型及實現為:
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)
{
struct task_struct *p;
p = copy_process(clone_flags, stack_start, regs, stack_size,
child_tidptr, NULL, trace); (1)
if (!IS_ERR(p)) {
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
……
}
一、首先調用copy_process()函數
copy_process()函數實現了進程的大部分拷貝工作。
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start,struct pt_regs *regs,unsigned long stack_size, int __user *child_tidptr, struct pid *pid,int trace)
{
//對傳入的clone_flag進行檢查
//為新進程創建一個內核棧、thread_info結構和task_struct;其值域當前進程的值完全相同(父子進程的描述符此時也相同)
p = dup_task_struct(current);
//判斷是否超出進城用戶可以擁有的總進城數量,檢查是否有權對指定的資源進行操作
if (atomic_read(&p->real_cred->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->real_cred->user != INIT_USER)
goto bad_fork_free;
}
//在task_struct結構中有一個指針user,該指針指向一個user_struct結構,一個用戶的多個進程可以通過user指針共享該用戶的資源信息,該結構定義在include/linux/sched.h中,
retval = copy_creds(p, clone_flags);
//copy_creds函數中調用:
//檢查創建的進程是否超過了系統進程總量
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
//獲得進程執行域
if (!try_module_get(task_thread_info(p)->exec_domain->module))
goto bad_fork_cleanup_count;
//調用copy_flags函數更新task_struct結構中flags成員。表明進程是否擁有超級用戶權限的PF_SUPERPPRIV標志被清除,表明進程還沒有exec()的PF_FORKNOEXEC被設置
copy_flags(clone_flags, p);
//根據clone的參數標志,拷貝或共享打開的文件、文件系統信息、信號處理函數、進程地址空間和命名空間,代碼如下圖所示。
//為新進程獲取一個有效的PID,調用pid = alloc_pidmap();緊接著使用alloc_pidmap函數為這個新進程分配一個pid。由于系統內的pid是循環使用的,所以采用位圖方式來管理,用每一位(bit)來標示該位所對應的pid是否被使用。分配完畢后,判斷pid是否分配成功。
pid = alloc_pid(p->nsproxy->pid_ns);
//父子進程平分共享的時間片
sched_fork(p, clone_flags);
//返回子進程的指針。
return p;
}
再回到do_fork函數,如果copy_process函數成功返回,新創建的子進程被喚醒并投入運行。內核有意選擇子進程首先執行。因為一般子進程都會馬上調用exec函數,這樣可以避免寫時拷貝的額外開銷,如果父進程首先執行的話,有可能開始向地址空間寫入。
二、init_completion(&vfork);
如果clone_flags包含CLONE_VFORK標志,那么將進程描述符中的vfork_done字段指向這個完成量,之后再對vfork完成量進行初始化。完成量的作用是,直到任務A發出信號通知任務B發生了某個特定事件時,任務B才會開始執行;否則任務B一直等待。我們知道,如果使用vfork系統調用來創建子進程,那么必然是子進程先執行。究其原因就是此處vfork完成量所起到的作用:當子進程調用exec函數或退出時就向父進程發出信號。此時,父進程才會被喚醒;否則一直等待。此處的代碼只是對完成量進行初始化,具體的阻塞語句則在后面的代碼中有所體現。
三、檢查子進程是否設置了CLONE_STOPPED標志。
設置了CLONE_STOPPED標志通過sigaddset函數為子進程增加掛起信號。signal對應一個unsigned long類型的變量,該變量的每個位分別對應一種信號。具體的操作是,將SIGSTOP信號所對應的那一位置1。
如果子進程并未設置CLONE_STOPPED標志,那么通過wake_up_new_task函數使得父子進程之一優先運行;否則,將子進程的狀態設置為TASK_STOPPED。
四、檢查CLONE_VFORK標志被設置
如果CLONE_VFORK標志被設置,則通過wait操作將父進程阻塞,直至子進程調用exec函數或者退出。
五、返回pid
return nr; //其中nr后一次賦值為:nr = task_pid_vnr(p);即子進程的pid號。
至此,fork函數的系統調用過程結束,子進程和父進程各返回一次,子進程返回值為0,父進程返回值為子進程的pid號。應用程序可通過fork的返回值來判斷是在子進程中還是父進程中,從而實現多進程程序的編寫。
總結
以上是生活随笔為你收集整理的Linux多进程拷贝fork,浅析linux中fork函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蓝屏代码0X000000f4的分析与解决
- 下一篇: LOL狐狸出装/阿狸出装