前几天深谈了关于 fuck
的那些事,今天来聊一聊 fuck
它远房表哥:fork
。
fork
和 fuck
实为远亲,彼此往来也不多,只在饶舌上有异曲同工之妙。
fuck
常出没于美式口头俚语,fork
则在计算机世界纵横驰骋。
fork 是 Unix 内核创建一个新进程的唯一方式,新建的子进程 拷贝 父进程的数据空间、堆、栈等信息,这也是 fork 函数命名的由来。
fork 完成之后,究竟是父进程先执行还是子进程先执行是不确定的,这取决于内核的调度算法,因此,有可能出现这些情况:
孤儿进程
孤儿进程是指父进程在子进程结束之前 over
(return
或 exit
)。
但是孤儿进程并不会像上面画的那样持续很长时间,当系统发现孤儿进程时,init
进程就收养孤儿进程,成为它的父亲,child
进程 exit
后的资源回收就都由 init
进程来完成。
僵尸进程
僵尸进程是指子进程在父进程结束之前 over
了,但是父进程没有用 wait
或 waitpid
回收子进程。
子进程在结束的时候会给其父进程发送一个 SIGCHILD
信号,父进程默认是忽略 SIGCHILD
信号的,如果父进程通过 signal()
函数设置了 SIGCHILD
的信号处理函数,则在信号处理函数中可以回收子进程的资源。
事实上,即便是父进程没有设置 SIGCHILD
的信号处理函数,也没有关系,因为在父进程结束之前,子进程可以一直保持僵尸状态,当父进程结束后,init
进程就会负责回收僵尸子进程。
但是,如果父进程是一个服务器进程,一直循环着不退出,那子进程就会一直保持着僵尸状态。虽然僵尸进程不会占用任何内存资源,但是过多的僵尸进程总还是会影响系统性能的,极端情况下会耗尽系统的可用进程数,导致无法再新建进程。
黔驴技穷的情况下,该怎么办呢?
两次fork
为了防止系统中产生僵尸进程, 两次fork
是常用的技法,原理如下:
《UNIX环境高级编程》这本书里提供了现成的代码:
1 |
|
执行程序得:
1 | $ second child, parent pid = 1 |
这里有个需要注意的地方:
第二个子进程中调用 sleep
保证在打印其父进程时第一个子进程已终止。因为 fork
之后,父、子进程都会继续执行,我们无法确保孰先孰后。如果不使第二个子进程 sleep
一段时间,则 fork
之后,它可能会比其父进程先执行,于是,它打印的父进程将是创建它的进程,而不是 init
进程(进程ID为 1)。
可以看出,两次 fork 之后,系统没有产生僵尸进程,子进程死了也就死了吧,孙子进程进孤儿院了,最重要的是,我们那个永不死的服务器进程甜蜜地笑到了最后。
你死我都未死啊!