fork()
函数用于在已有的进程中再创建一个新的进程。这个被创建的新的进程被视为子进程,而调用进程成为其父进程。
#include <unistd.h> pid_t fork(void);点击复制复制失败已复制
Linux
官方手册解释了 fork()
函数创建新进程的方式,子进程被创建是通过对父进程进行复制得来的,即子进程是父进程的复制品。进程都会有属于自己的虚拟地址空间,用以保存进程的各种信息,详见进程的内存布局笔记。因此,子进程对父进程的复制,实质上来讲,是复制父进程的整个地址空间。其中包括了进程的上下文、代码段、进程堆栈、内存信息、文件描述符、信号处理函数等;而子进程所独有的只有它的进程号、资源使用、计时器等。
注意
子进程虽然复制了父进程所使用的地址空间,但子进程创建成功之后,子进程访问的虚拟地址空间一定是属于自己的,而非与父进程共享空间。其本质是父子进程映射到不同物理地址空间。
因为子进程是对父进程的精准复制,所以父子进程的程序代码段是一样的。因此,需要某一种方式来区分父子进程。否则,父子进程执行的代码是一致的,创建子进程没有任何意义。
那么区分父子进程,保证父子进程执行的代码段不相同,是通过 fork()
函数返回值来判定的。 Linux
官方手册对函数的返回值解释为:子进程通过对父进程的精准复制产生, fork()
函数如果执行失败,父进程得到返回值为 -1
;如果执行成功,在子进程中得到一个值为 0
,在父进程中得到一个值为子进程的 ID
(一定大于 0
的整数)。一定注意的是, fork()
函数不是在父进程中得到两个返回值,而是父子进程分别得到 fork()
函数返回的一个值。
关于父子进程的区分,可以通过一个简单的示例进行解释,如下所示:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { perror("fork error"); return -1; } else if (pid == 0) { /* child子进程执行代码区 */ printf("The child process\n"); } else { /* parent父进程执行代码区 */ printf("The parent process\n"); } return 0; }点击复制复制失败已复制
上述代码是典型的创建子进程的框架代码,编译运行,结果如下:
$ gcc main.c && ./a.out The parent process The child process点击复制复制失败已复制
根据运行结果可以看出,两个输出打印函数 printf()
均有打印。注意一个函数不可能在一个进程中返回两个值,因此两句输入打印不是出自于一个进程。根据之前的描述,创建子进程成功,则子进程获得一个返回值为 0
,父进程获得返回值为子进程的 ID
(大于 0
)。因此上述代码应该被这样解读:
- 程序在执行
fork()
函数时,开始创建子进程,子进程开始对父进程进行复制。 - 在变量
pid
接收fork()
函数的返回值之前,子进程创建结束。 - 由于子进程几乎复制了父进程的所有地址空间(包括栈区),因此父子进程都有局部变量
pid
,子进程的pid
接收的返回值为0
,而在父进程的pid
接收的返回值是子进程的ID
。 - 由于父子进程的变量
pid
接收的值不同,因此,根据代码的分支判断,可以看出父子进程虽然代码相同,但是执行的内容却不同。 - 父进程执行的代码是判断
pid
大于0
的部分,以及fork()
函数。fork()
函数之前所有的执行代码,称为父进程的执行代码区。子进程执行的代码是判断pid
等于0
的部分,称为子进程的执行代码区。注意:子进程不执行fork()
函数以及fork()
函数以上所有的执行代码,否则子进程会被无限创建。
为了更加明显可以理解代码的框架,可以输出进程的 ID
来查看。获得进程的 ID
使用 getpid()
函数和 getppid()
函数。
#include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void);点击复制复制失败已复制
getpid()
函数用于获得调用(自身)进程的 ID
。
getppid()
函数用于获得调用进程的父进程的 ID
。
对上例进行修改,如下所示:父子进程最后都执行 while
死循环不退出,父进程打印自己的 ID
,子进程打印自己的 ID
及父进程的 ID
。
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { perror("fork error"); return -1; } else if (pid == 0) { /* child子进程执行代码区 */ printf("The child process, id = %d parent id = %d\n", getpid(), getppid()); while(1); } else { /* parent父进程执行代码区 */ printf("The parent process, id = %d\n", getpid()); while(1); } return 0; }点击复制复制失败已复制
编译运行,结果如下:
$ gcc main.c && ./a.out The parent process, id = 12207 The child process, id = 12208 parent id = 12207点击复制复制失败已复制
由此可以看出,在子进程的执行代码区中打印出的父进程 ID
为 12207
,与父进程的执行代码区打印自身的 ID
是一样的,同为 12207
。
通过终端输入 $ ps axj
也可查看当前系统中的进程信息:
$ ps axj PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 11284 12207 12207 11284 pts/0 12207 R+ 1000 0:11 ./a.out 12207 12208 12207 11284 pts/0 12207 R+ 1000 0:11 ./a.out点击复制复制失败已复制
提示
PPID
(process parent ID):父进程IDPID
(process ID):进程IDPGID
(process group ID):进程组IDSID
(session ID):会话组ID