fork
函數(shù)用于創(chuàng)建一個(gè)與當(dāng)前進(jìn)程映像一樣的子進(jìn)程萎河,所創(chuàng)建的子進(jìn)程將復(fù)制父進(jìn)程的代碼段、數(shù)據(jù)段橙数、BSS段尊流、堆、棧等所有用戶空間信息灯帮,在內(nèi)核中操作系統(tǒng)會(huì)重新為其申請一個(gè)子進(jìn)程執(zhí)行的位置奠旺。
fork
系統(tǒng)調(diào)用會(huì)通過復(fù)制一個(gè)現(xiàn)有進(jìn)程來創(chuàng)建一個(gè)全新的進(jìn)程,新進(jìn)程被存放在一個(gè)叫做任務(wù)隊(duì)列的雙向循環(huán)鏈表中施流,鏈表中的每一項(xiàng)都是類型為task_struct
的進(jìn)程控制塊PCB
的結(jié)構(gòu)响疚。
父子進(jìn)程有什么區(qū)別呢?
- 父進(jìn)程設(shè)置了鎖瞪醋,子進(jìn)程不繼承忿晕。
- 進(jìn)程ID各不相同
- 子進(jìn)程的未決告警被清除
- 子進(jìn)程的未決信號集設(shè)置未空集
函數(shù)原型
pid_t fork(void);
pid_t
是一個(gè)宏定義,其實(shí)質(zhì)是int
被定義在#include <sys/types.h>
頭文件中银受。
頭文件
#include <unistd.h>
#include <sys/types.h>
返回值
若成功調(diào)用一次則返回兩個(gè)值践盼,子進(jìn)程返回0,父進(jìn)程返回子進(jìn)程ID宾巍,否則出錯(cuò)返回-1咕幻。
每個(gè)進(jìn)程都由獨(dú)特?fù)Q不相同的進(jìn)程標(biāo)識符(process ID),通過getpid()
函數(shù)可獲取當(dāng)前進(jìn)程的進(jìn)程標(biāo)識符顶霞,通過getppid()
函數(shù)可獲得父進(jìn)程的進(jìn)程標(biāo)識符肄程。
例如:
$ vim fork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int cnt = 1;
int main(void)
{
//派生進(jìn)程
pid_t pid = fork();
//派生失敗
if(pid < 0)
{
perror("fork error");
exit(1);
}
//派生成功返回值為0表示子進(jìn)程
else if(pid == 0)
{
printf("\nchild: forkval=%d pid=%d cnt=%d\n", pid, getpid(), ++cnt);
sleep(3);//由于父子進(jìn)程是并發(fā)的
}
//派生成功返回值大于0表示父進(jìn)程,父進(jìn)程中將返回子進(jìn)程的PID选浑。
else if(pid > 0)
{
printf("\nparent: forkval=%d pid=%d cnt=%d\n", pid, getpid(), cnt);
while(1);//由于父子進(jìn)程是并發(fā)的
}
return 0;
}
$ gcc fork.c -o fork -Wall -g
$ ./fork
parent: forkval=14058 pid=14057 cnt=1
child: forkval=0 pid=14058 cnt=2
一個(gè)現(xiàn)有的進(jìn)程可通過調(diào)用fork
函數(shù)創(chuàng)建一個(gè)新進(jìn)程蓝厌,由fork
創(chuàng)建的新進(jìn)程稱為子進(jìn)程child process
,fork
函數(shù)被調(diào)用一次但返回兩次古徒,兩次返回的唯一區(qū)別是子進(jìn)程中返回0而父進(jìn)程中返回子進(jìn)程ID拓提。
子進(jìn)程是父進(jìn)程的副本,它將獲得父進(jìn)程數(shù)據(jù)空間隧膘、堆代态、棧等資源的副本。值得注意的是子進(jìn)程持有的是存儲(chǔ)空間的副本疹吃,意味著父子進(jìn)程之間是不會(huì)共享這些存儲(chǔ)空間的蹦疑。
UNIX將復(fù)制父進(jìn)程的地址空間內(nèi)容給子進(jìn)程,因此子進(jìn)程具有獨(dú)立的地址空間互墓。在不同的UNIX系統(tǒng)下必尼,是無法確定fork
后是子進(jìn)程先運(yùn)行還是父進(jìn)程先運(yùn)行蒋搜,這依賴于系統(tǒng)的實(shí)現(xiàn)篡撵。
fork
執(zhí)行執(zhí)行流程
當(dāng)進(jìn)程調(diào)用fork
后控制轉(zhuǎn)入內(nèi)核判莉,內(nèi)核將會(huì)做4件事兒:
- 分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程
- 將父進(jìn)程部分?jǐn)?shù)據(jù)結(jié)構(gòu)內(nèi)容(數(shù)據(jù)空間、堆棧等)拷貝到子進(jìn)程
- 添加子進(jìn)程到系統(tǒng)進(jìn)程列表中
-
fork
返回開始調(diào)度器調(diào)度
為什么fork
會(huì)返回兩次呢育谬?
因?yàn)閺?fù)制時(shí)會(huì)復(fù)制父進(jìn)程的堆棧段券盅,所以兩個(gè)進(jìn)程都停留在fork
函數(shù)中等待返回,因此會(huì)返回兩次膛檀,一個(gè)是在父進(jìn)程中返回锰镀,一次是在子進(jìn)程中返回,兩次返回值是不一樣的咖刃。
- 在父進(jìn)程中將返回新建子進(jìn)程的進(jìn)程ID
- 在子進(jìn)程中將返回0
- 若出現(xiàn)錯(cuò)誤則返回一個(gè)負(fù)數(shù)
因此可以通過fork
的返回值來判斷當(dāng)前進(jìn)程是子進(jìn)程還是父進(jìn)程
為什么pid
在父子進(jìn)程中不同呢泳炉?
其實(shí)就相當(dāng)于鏈表,進(jìn)程形成了鏈表嚎杨,父進(jìn)程的pid
指向子進(jìn)程的進(jìn)程ID花鹅,因此子進(jìn)程沒有子進(jìn)程,所以PID為0枫浙,這里的pid
相當(dāng)于鏈表中的指針刨肃。
fork
派生可能出現(xiàn)的錯(cuò)誤原因是什么呢?
- 當(dāng)前進(jìn)程數(shù)已經(jīng)達(dá)到系統(tǒng)規(guī)定的上限錯(cuò)誤值
errno
會(huì)被設(shè)置為EAGAIN
箩帚。
$ cat /proc/sys/kernel/pid_max
32768
- 系統(tǒng)內(nèi)存不足時(shí)錯(cuò)誤值
errno
會(huì)被設(shè)置為EAGAIN
fork
系統(tǒng)調(diào)用使用注意
-
fork
系統(tǒng)調(diào)用之后父進(jìn)程和子進(jìn)程是交替執(zhí)行真友,父子進(jìn)程是處于不同空間中的 -
fork
系統(tǒng)調(diào)用的一次調(diào)用存在兩次返回,此時(shí)二個(gè)進(jìn)程處于獨(dú)立的空間紧帕,各自執(zhí)行自己的參數(shù)