《TCP/IP网络编程》第 10 章 多进程服务器端 笔记( 二 )


《TCP/IP网络编程》第 10 章 多进程服务器端 笔记

文章插图
可以看出,当执行了 fork 函数之后,此后就相当于有了两个程序在执行代码,对于父进程来说,fork 函数返回的是子进程的ID,对于子进程来说,fork 函数返回 0 。所以这两个变量,父进程进行了 +2 操作 ,而子进程进行了 -2 操作,所以结果是这样 。
10.2 进程和僵尸进程
文件操作中,关闭文件和打开文件同等重要 。同样,进程销毁和进程创建也同等重要 。如果未认真对待进程销毁,他们将变成僵尸进程 。
10.2.1 僵尸()进程
进程的工作完成后(执行完 main 函数中的程序后)应被销毁,但有时这些进程将变成僵尸进程,占用系统中的重要资源 。这种状态下的进程称作「僵尸进程」,这也是给系统带来负担的原因之一 。
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程 。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源
维基百科:
在类UNIX系统中,僵尸进程是指完成执行(通过exit系统调用,或运行时发生致命错误或收到终止信号所致)但在操作系统的进程表中仍然有一个表项(进程控制块PCB),处于"终止状态"的进程 。这发生于子进程需要保留表项以允许其父进程读取子进程的exit :一旦退出态通过wait系统调用读取,僵尸进程条目就从进程表中删除,称之为"回收()" 。正常情况下,进程直接被其父进程wait并由系统回收 。进程长时间保持僵尸状态一般是错误的并导致资源泄漏 。
英文术语 源自丧尸 — 不死之人,隐喻子进程已死但仍然没有被收割 。与正常进程不同,kill命令对僵尸进程无效 。孤儿进程不同于僵尸进程,其父进程已经死掉,但孤儿进程仍能正常执行,但并不会变为僵尸进程,因为被init(进程ID号为1)收养并wait其退出 。
子进程死后,系统会发送 信号给父进程,父进程对其默认处理是忽略 。如果想响应这个消息,父进程通常在 信号事件处理程序中,使用wait系统调用来响应子进程的终止 。
僵尸进程被收割后,其进程号(PID)与在进程表中的表项都可以被系统重用 。但如果父进程没有调用wait,僵尸进程将保留进程表中的表项,导致了资源泄漏 。某些情况下这反倒是期望的:父进程创建了另外一个子进程,并希望具有不同的进程号 。如果父进程通过设置事件处理函数为显式忽略信号,而不是隐式默认忽略该信号,或者具有标志,所有子进程的退出状态信息将被抛弃并且直接被系统回收 。
UNIX命令ps列出的进程的状态(“STAT”)栏标示为 "Z"则为僵尸进程 。[1]
收割僵尸进程的方法是通过kill命令手工向其父进程发送信号 。如果其父进程仍然拒绝收割僵尸进程,则终止父进程,使得init进程收养僵尸进程 。init进程周期执行wait系统调用收割其收养的所有僵尸进程 。
10.2.2 产生僵尸进程的原因
为了防止僵尸进程产生,先解释产生僵尸进程的原因 。利用如下两个示例展示调用 fork 函数产生子进程的终止方式 。
**向 exit 函数传递的参数值和 main 函数的语句返回的值都回传递给操作系统 。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程 。处在这种状态下的进程就是僵尸进程 。**也就是说将子进程变成僵尸进程的正是操作系统 。既然如此,僵尸进程何时被销毁呢?
应该向创建子进程册父进程传递子进程的 exit 参数值或语句的返回值 。
如何向父进程传递这些值呢?操作系统不会主动把这些值传递给父进程 。只有父进程主动发起请求(函数调用)的时候,操作系统才会传递该值 。换言之,如果父进程未主动要求获得子进程结束状态值,操作系统将一直保存,并让子进程长时间处于僵尸进程状态 。也就是说,父母要负责收回自己生的孩子 。接下来的示例是创建僵尸进程: