Linux fork() 和 vfork()
代碼
#include <unistd.h> #include <sys/types.h> #include <iostream> #include <errno.h>int main() {pid_t pid;pid = fork();if (pid < 0)std::cout << "error in fork! errno = " << errno << std::endl;else if (pid == 0)std::cout << "I am the child process, my process id is " << getpid() << std::endl;elsestd::cout << "I am the parent process, my process id is " << getpid() << std::endl;return 0; }結(jié)果
I am the parent process, my process id is 24275 I am the child process, my process id is 24276對(duì)于 fork() 函數(shù),在父進(jìn)程中返回子進(jìn)程的 PID( > 0 ),在子進(jìn)程中返回 0 。
1、那么 fork() 函數(shù)是如何實(shí)現(xiàn)在不同的進(jìn)程中返回不同的結(jié)果呢?
答案:
當(dāng)你的程序執(zhí)行到下面的語(yǔ)句:?
int pid = fork();?操作系統(tǒng)創(chuàng)建一個(gè)新的進(jìn)程(子進(jìn)程),并且在進(jìn)程表中相應(yīng)為它建立一個(gè)新的表項(xiàng)。新進(jìn)程和原有進(jìn)程的可執(zhí)行程序是同一個(gè)程序。上下文和數(shù)據(jù),絕大部分就是 原進(jìn)程(父進(jìn)程)的拷貝,但它們是兩個(gè)相互獨(dú)立的進(jìn)程!此時(shí)程序寄存器 pc,在父、子進(jìn)程的上下文中都聲稱(chēng),這個(gè)進(jìn)程目前執(zhí)行到 fork 調(diào)用即將返回(此時(shí)子進(jìn)程不占有CPU,子進(jìn)程的 pc 不是真正保存在寄存器中,而是作為進(jìn)程上下文保存在進(jìn)程表中的對(duì)應(yīng)表項(xiàng)內(nèi))。問(wèn)題是怎么返回,在父子進(jìn)程中就分道揚(yáng)鑣了呢?
父進(jìn)程繼續(xù)執(zhí)行,操作系統(tǒng)對(duì) fork 的實(shí)現(xiàn),使這個(gè)調(diào)用在父進(jìn)程中返回剛剛創(chuàng)建的子進(jìn)程的 PID(一個(gè)正整數(shù)),所以下面的 if 語(yǔ)句中 pid < 0 , pid == 0 的兩個(gè)分支都不會(huì)執(zhí)行。所以輸出 “I am the parent process...”。?
子進(jìn)程在之后的某個(gè)時(shí)候得到調(diào)度,它的上下文被換入,占據(jù) CPU,操作系統(tǒng)對(duì) fork 的實(shí)現(xiàn),使得子進(jìn)程中 fork 調(diào)用返回0。所以在這個(gè)進(jìn)程(注意這不是父進(jìn)程了哦,雖然是同一個(gè)程序,但是這是同一個(gè)程序的另外一次執(zhí)行,在操作系統(tǒng)中這次執(zhí)行是由另外一個(gè)進(jìn)程表示的,從執(zhí)行的角度說(shuō)和父進(jìn)程相互獨(dú)立)中 pid = 0 。這個(gè)進(jìn)程繼續(xù)執(zhí)行的過(guò)程中,if 語(yǔ)句中 pid < 0 不滿(mǎn)足,但是 pid == 0 是 true。所以輸出 “I am the child process...”。?
為什么看上去程序中互斥的兩個(gè)分支都被執(zhí)行了?在一個(gè)程序的一次執(zhí)行中,這當(dāng)然是不可能的;但是你看到的兩行輸出是來(lái)自?xún)蓚€(gè)進(jìn)程,這兩個(gè)進(jìn)程來(lái)自同一個(gè)程序的兩次執(zhí)行。?
fork 之后,操作系統(tǒng)會(huì)復(fù)制一個(gè)與父進(jìn)程完全相同的子進(jìn)程,雖說(shuō)是父子關(guān)系,但是在操作系統(tǒng)看來(lái),他們更像兄弟關(guān)系,這 2 個(gè)進(jìn)程共享代碼空間,但是數(shù)據(jù) 空間是互相獨(dú)立的,子進(jìn)程數(shù)據(jù)空間中的內(nèi)容是父進(jìn)程的完整拷貝,指令指針也完全相同,但只有一點(diǎn)不同,如果 fork 成功,子進(jìn)程中 fork 的返回值是 0 , 父進(jìn)程中 fork 的返回值是子進(jìn)程的進(jìn)程號(hào),如果 fork 不成功,父進(jìn)程會(huì)返回錯(cuò)誤。?
可以這樣想象,2 個(gè)進(jìn)程一直同時(shí)運(yùn)行,而且步調(diào)一致,在 fork 之后,他們分別作不同的工作,也就是分岔了。這也是 fork為什么叫 fork 的原因。
在程序段里用了 fork() 之后程序出了分岔,派生出了兩個(gè)進(jìn)程。具體哪個(gè)先運(yùn)行就看該系統(tǒng)的調(diào)度算法了。
2、fork() 執(zhí)行過(guò)程。
看這一句:pid=fork() 。
當(dāng)執(zhí)行這一句時(shí),當(dāng)前進(jìn)程進(jìn)入 fork() 運(yùn)行,此時(shí),fork() 內(nèi)會(huì)用一段嵌入式匯編進(jìn)行系統(tǒng)調(diào)用:int 0x80(具體代碼可參見(jiàn)內(nèi)核版本 0.11 的 unistd.h 文件的 133 行 _syscall0 函數(shù))。
這時(shí)進(jìn)入內(nèi)核根據(jù)此前寫(xiě)入 eax 的系統(tǒng)調(diào)用功能號(hào) 便會(huì)運(yùn)行 sys_fork 系統(tǒng)調(diào)用。
接著,sys_fork?中首先會(huì)調(diào)用 C 函數(shù) find_empty_process 產(chǎn)生一個(gè)新的進(jìn)程,然后會(huì)調(diào)用 C 函數(shù) copy_process 將父進(jìn)程的內(nèi)容復(fù)制給子進(jìn)程,但是子進(jìn)程 tss 中的 eax 值賦值為 0(這也是為什么子進(jìn)程中返回 0 的原因)。當(dāng)賦值完成后, copy_process 會(huì)返回新進(jìn)程(該子進(jìn)程)的 pid,這個(gè)值會(huì)被保存到 eax 中。
這時(shí)子進(jìn)程就產(chǎn)生了,此時(shí)子進(jìn)程與父進(jìn)程擁有相同的代碼空間,程序指針寄存器 eip 指向相同的下一條指令地址,當(dāng) fork 正常返回調(diào)用其的父進(jìn)程后,因?yàn)?eax 中的值是新創(chuàng)建的子進(jìn)程號(hào)。所以,fork()返回子進(jìn)程號(hào),執(zhí)行 else(pid > 0)。
當(dāng)產(chǎn)生進(jìn)程切換運(yùn)行子進(jìn)程時(shí),首先會(huì)恢復(fù)子進(jìn)程的運(yùn)行環(huán)境即裝入子進(jìn)程的 tss 任務(wù)狀態(tài)段,其中的 eax 值( copy_process 中置為 0 )也會(huì)被裝入 eax 寄存器。所以,當(dāng)子進(jìn)程運(yùn)行時(shí),fork 返回的是 0 執(zhí)行 if( pid == 0 )。
3、fork() 和 vfork()?
二者的功能都是創(chuàng)建一個(gè)新的進(jìn)程,但是二者的區(qū)別還是比較大的,主要部分如下:
(1)前者創(chuàng)建完進(jìn)程之后,父進(jìn)程由于占用了CPU的時(shí)間片,所以一般父進(jìn)程先返回。當(dāng)然了,如果父子進(jìn)程都準(zhǔn)備好了要返回了,但是此時(shí) CPU 開(kāi)始輪詢(xún)了,那么最后誰(shuí)先返回就不確定了。總的來(lái)說(shuō),父子進(jìn)程誰(shuí)先執(zhí)行是不確定的。對(duì)于后者,能夠保證子進(jìn)程執(zhí)行 exec 和 exit 之前,父進(jìn)程處于休眠狀態(tài),即:子進(jìn)程先執(zhí)行。
(2)前者在創(chuàng)建子進(jìn)程時(shí),子進(jìn)程會(huì)復(fù)制掉父進(jìn)程的數(shù)據(jù)段、堆、棧(代碼段共享)(Copy on Write)。后者的子進(jìn)程在執(zhí)行 exec 和 exit 之前,會(huì)與父進(jìn)程共用父進(jìn)程的虛擬地址空間。
?
(SAW:Game Over!)
總結(jié)
以上是生活随笔為你收集整理的Linux fork() 和 vfork()的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux IPC / 分类
- 下一篇: exit()、_Exit() 和 _ex