没有什么是「一颗赛艇」不能解决的,如果有,那就两颗

前几天深谈了关于 fuck 的那些事,今天来聊一聊 fuck 它远房表哥:fork

forkfuck 实为远亲,彼此往来也不多,只在饶舌上有异曲同工之妙。

fuck 常出没于美式口头俚语,fork 则在计算机世界纵横驰骋。

fork 是 Unix 内核创建一个新进程的唯一方式,新建的子进程 拷贝 父进程的数据空间、堆、栈等信息,这也是 fork 函数命名的由来。

fork 完成之后,究竟是父进程先执行还是子进程先执行是不确定的,这取决于内核的调度算法,因此,有可能出现这些情况:

孤儿进程

孤儿进程是指父进程在子进程结束之前 over (returnexit)。

但是孤儿进程并不会像上面画的那样持续很长时间,当系统发现孤儿进程时,init 进程就收养孤儿进程,成为它的父亲,child 进程 exit 后的资源回收就都由 init 进程来完成。

僵尸进程

僵尸进程是指子进程在父进程结束之前 over 了,但是父进程没有用 waitwaitpid 回收子进程。

子进程在结束的时候会给其父进程发送一个 SIGCHILD 信号,父进程默认是忽略 SIGCHILD 信号的,如果父进程通过 signal() 函数设置了 SIGCHILD 的信号处理函数,则在信号处理函数中可以回收子进程的资源。

事实上,即便是父进程没有设置 SIGCHILD 的信号处理函数,也没有关系,因为在父进程结束之前,子进程可以一直保持僵尸状态,当父进程结束后,init 进程就会负责回收僵尸子进程。

但是,如果父进程是一个服务器进程,一直循环着不退出,那子进程就会一直保持着僵尸状态。虽然僵尸进程不会占用任何内存资源,但是过多的僵尸进程总还是会影响系统性能的,极端情况下会耗尽系统的可用进程数,导致无法再新建进程。

黔驴技穷的情况下,该怎么办呢?

两次fork

为了防止系统中产生僵尸进程, 两次fork 是常用的技法,原理如下:

《UNIX环境高级编程》这本书里提供了现成的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# include <sys/types.h>
# include <sys/wait.h>
# include "ourhdr.h"

int main(void)
{
pid_t pid;

if ( (pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) { /* first child */
if ( (pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */

/* We're the second child; our parent becomes init as soon
as our real parent calls exit() in the statement above.
Here's where we'd continue executing, knowing that when
we're done, init will reap our status. */

sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}

if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error");

/* We're the parent (the original process); we continue executing,
knowing that we're not the parent of the second child. */

exit(0);
}

执行程序得:

1
$ second child, parent pid = 1

这里有个需要注意的地方:

第二个子进程中调用 sleep 保证在打印其父进程时第一个子进程已终止。因为 fork 之后,父、子进程都会继续执行,我们无法确保孰先孰后。如果不使第二个子进程 sleep 一段时间,则 fork 之后,它可能会比其父进程先执行,于是,它打印的父进程将是创建它的进程,而不是 init 进程(进程ID为 1)。

可以看出,两次 fork 之后,系统没有产生僵尸进程,子进程死了也就死了吧,孙子进程进孤儿院了,最重要的是,我们那个永不死的服务器进程甜蜜地笑到了最后。

你死我都未死啊!

彦祖老师 wechat