进程在它被创建的时刻开始存活,在 linux 系统中,这通常是调用 fork() 系统调用的结果,该系统调用通过复制一个现有进程来创建一个全新的进程。调用 fork() 的进程被称为父进程,新产生的进程被称为子进程。在该调用结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行。fork() 系统调用从内核返回两次:一次回到父进程,另一次回到新诞生的子进程。
通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用 exec*() 这族函数就可以创建新的地址空间,并把新的程序载入。
最终,程序通过 exit() 系统调用退出执行。这个函数会终结进程并将其占用的资源释放掉。父进程可以通过 wait4() 系统调用查询子进程是否终结,这其实使得进程拥有了等待特定进程执行完毕的能力。进程退出执行后被设置为僵死状态,直到它的父进程调用 wait() 或 waitpid() 为止。
一个进程在调用 exit 命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(zombie)的数据结构(系统调用 exit(),它的作用是使进程退出,但它仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
在 linux 进程的状态中,僵尸进程使非常特殊的一种,它已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间(如《写一个僵尸进程》这篇文章最后通过 ps 查看的状态一样,僵尸进程的 VSZ RSS 均为 0)。它需要它的父进程来为它收尸,如果它的父进程没有安装 SIGCHLD 信号处理函数调用 wait() 或 waitpid() 等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么 init 进程自动接手这个子进程,为它收尸,它还是能被清除的。但是如果父进程是一个死循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多僵尸进程的原因。
在UNIX 系统中,一个进程结束了,但是它的父进程没有等待(调用 wait() / waitpid())它, 那么它将变成一个僵尸进程. 在 fork()/execve() 过程中,假设子进程结束时父进程仍存在,而父进程 fork() 之前既没安装SIGCHLD 信号处理函数调用 waitpid() 等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程。
因此,一个僵尸进程产生的过程是:父进程调用 fork() 创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。子进程的状态变成 EXIT_ZOMBIE,并且向父进程发送 SIGCHLD 信号,父进程此时应该调用 wait() 系 统调用来获取子进程的退出状态以及其它的信息。在 wait() 调用之后,僵尸进程就完全从内存中移除。因此一个僵尸进程存在于其终止到父进程调用 wait() 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait() 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。