fork
函数是unistd
头文件中提供的一个复制调用进程到子进程的工具。
/* Clone the calling process, creating an exact copy.
Return -1 for errors, 0 to the new process,
and the process ID of the new process to the old process. */
extern __pid_t fork (void) __THROWNL;
通常我们这样调用fork
函数:
pid_t p = fork();
调用fork
函数的进程称为 Calling Process,姑且称之为父进程。
调用fork
函数会复制父进程创建一个一模一样的子进程,子进程会从程序中调用fork
的位置开始执行。
调用fork
函数会拿到三个可能的返回值:
- -1: 克隆子进程失败
- 0: 在克隆的子进程中也会调用一遍这个
fork
函数,但是返回 0 - PID: 在父进程中调用
fork
函数会返回克隆的子进程的进程号
fork
函数创建的子进程与父进程共用同一个程序(同一套代码),那么怎么区分哪些代码是在主进程中执行,哪些代码是在父进程中执行的呢?答案是通过fork()
的返回值。
父进程和子进程都会执行fork()
,但是能拿到不同的返回值,这是由操作系统实现的:
- 父进程中拿到的返回值是子进程的进程号
- 子进程中拿到的返回值是 0
于是就有下面这种写法:
printf("main process pid: %d\n", getpid());
pid_t p = fork();
printf("fork returns %d\n", p);
if (p < 0)
printf("error in fork!");
else if (p == 0)
printf("child process pid: %d\n", getpid());
else
printf("parent process pid: %d\n", getpid());
getpid
函数在 unistd.h 中声明,用来获取调用此函数的进程的进程号pid_t
类型在 unistd.h 中声明,本质上还是一个整数,用来表示进程号,只是在不同的操作系统上可能对应不同的整型,例如 int, long
上述代码的打印结果如下:
main process pid: 30039
fork returns 30040
parent process pid: 30039
fork returns 0
child process pid: 30040
父进程执行到fork()
之后复制出一个子进程,并拿到子进程的进程号,然后跳过if
和else if
块进入else
块打印自己的进程号。同时子进程从fork()
处开始执行,其返回值是 0,因此进入else if
块,打印自己的进程号。
父进程在调用fork
后克隆出一个子进程,然后两个进程“并行”执行,因此后面的打印顺序并不是确定的,有可能子进程先打印结果。
另一个例子是父进程如何克隆出两个子进程?先看这样一段代码
printf("main process pid: %d\n", getpid()); // print1
pid_t p1 = fork(); // fork1
printf("fork returns %d\n", p1); // print2
if (p1 < 0)
printf("error in fork!");
else if (p1 == 0)
printf("child process pid: %d\n", getpid()); // print3
else
printf("parent process pid: %d\n", getpid()); // print4
pid_t p2 = fork(); // fork2
printf("fork returns %d\n", p2); // print5
if (p2 < 0)
printf("error in fork!");
else if (p2 == 0)
printf("child process pid: %d\n", getpid()); // print6
else
printf("parent process pid: %d\n", getpid()); // print7
return 0;
打印结果如下:
main process pid: 31329 # main.print1
fork returns 31330 # main.print2
parent process pid: 31329 # main.print4
fork returns 31331 # main.print5
parent process pid: 31329 # main.print7
fork returns 0 # p1.print2
child process pid: 31330 # p1.print3
fork returns 0 # p2.print5
child process pid: 31331 # p2.print6
fork returns 31332 # p1.print5
parent process pid: 31330 # p1.print7
fork returns 0 # p1_p2.print5
child process pid: 31332 # p1_p2.print6
调用关系如下:
可以看到,最后生成了四个进程(一个父进程,两个直接子进程,一个子进程的子进程),而我们期望的只有三个(一个父进程,两个直接子进程)。
下面灰色的 p1_p2 孙子进程显然不是我们向要的,为了避免这个进程生成,我们应该避免让程序第一个 fork 出的子进程不要执行第二个 fork,这样就能保证主进程每次调用fork
生成的子进程都不会调用后面的fork
生成自己的子进程:
printf("main process pid: %d\n", getpid());
pid_t p1 = fork();
printf("fork returns %d\n", p1);
if (p1 < 0)
printf("error in fork!");
else if (p1 == 0)
printf("child process pid: %d\n", getpid());
else
{
printf("parent process pid: %d\n", getpid());
pid_t p2 = fork();
printf("fork returns %d\n", p2);
if (p2 < 0)
printf("error in fork!");
else if (p2 == 0)
printf("child process pid: %d\n", getpid());
else
printf("parent process pid: %d\n", getpid());
}
打印结果如下:
main process pid: 7185
fork returns 7186
parent process pid: 7185
fork returns 0
child process pid: 7186
fork returns 7187
parent process pid: 7185
fork returns 0
child process pid: 7187
可以看到,主进程 7185 只 fork 出了 7186 和 7187 两个子进程。
示例代码参考了 <>https://blog.csdn.net/wanghaobo920/article/details/8018552
评论区