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