处理SIGCHLD信号
|
|
wait版本行不通的原因
若服务器和客户端在同一主机上,则该信号处理函数只执行过一次(根据UNP的描述是这样的,但是本人实测,信号处理器函数执行了两次)。 但是如果我们在不同的主机上运行客户和服务器,那么信号处理器函数一般执行两次:一次是由第一个产生的信号引起的,由于另外4个信号在信号处理函数第一次执行时发生,因为该处理函数仅仅再被调用一次,从而留下3个僵死进程。 不过有的时候,根据FIN到达主机的时机,信号处理函数可能会执行3次甚至4次
根据本人猜想,如果在相同主机测试,由于没有网络数据传播时延的影响,本机的客户端所有五个FIN都几乎在同一时间传递给服务器, 从而使得服务器的5个子进程基本在同一时刻终止,从而5个SIGCHLD在同一时刻发送给父进程,都在第一个信号处理函数执行之前发生, 而Unix信号一般是不排队的(这里详见TLPI对应的小节),因此信号处理函数只执行一次。
但是如果在不同主机测试,由于网络数据传播,各个FIN到达服务器的时间差较大一些,导致FIN以不可忽略的时差到达服务器。 例如在处理第一个SIGCHLD信号时,其他FIN才到达,从而引起信号处理函数执行多次。
|
|
waitpid()
版本行得通
清理僵尸进程
|
|
waitpid(-1, &stat, WNOHANG)
:这部分代码是关键。waitpid
函数用于等待子进程的状态改变。pid = -1
:表示等待任何子进程。&stat
:是一个指向int
的指针,用于存储子进程的终止状态。WNOHANG
:这个选项告诉waitpid
非阻塞运行。如果没有子进程终止,waitpid
将立即返回,而不是阻塞等待。
while
循环:这个循环会一直执行,直到没有子进程终止为止。waitpid
返回值会是:- 大于 0 的值:表示终止的子进程的 PID,表明有一个子进程结束。
- 0:表示没有子进程终止。
- -1:表示没有更多的子进程或者调用失败。
代码总结
这段代码实现了一个信号处理函数 sig_chld
,用于处理 SIGCHLD
信号。当一个子进程终止时,父进程会收到 SIGCHLD
信号,触发这个处理函数。该函数使用 waitpid
结合 WNOHANG
选项来处理所有终止的子进程,并避免产生僵尸进程。僵尸进程会占用系统资源,因此在接收到 SIGCHLD
信号时及时清理子进程的退出状态是很重要的。
这种处理方式确保了父进程可以继续处理多个子进程的终止,而不会因为某个子进程的终止而导致阻塞,从而避免产生僵尸进程。
运行结果:
|
|
不在循环内部使用wait()
的原因
在信号处理函数中使用 waitpid()
而不是 wait()
的原因主要涉及到以下几个方面:
1. 避免遗漏子进程的终止
waitpid()
在循环中与WNOHANG
选项一起使用,可以确保所有已终止的子进程都被处理。因为waitpid()
在每次调用时返回一个已终止的子进程的 PID,当没有更多子进程终止时返回 0。通过循环调用waitpid()
,你可以确保每个已终止的子进程都被正确处理,从而避免遗漏。相比之下,
wait()
只能一次等待一个子进程终止。如果有多个子进程在短时间内终止,而wait()
只被调用一次,可能会遗漏处理其中的一些子进程。这些未处理的子进程就会成为僵尸进程,浪费系统资源。
2. 非阻塞的等待
waitpid()
与WNOHANG
选项结合使用是非阻塞的,它允许信号处理程序检查所有子进程是否终止,而不会因没有终止的子进程而阻塞。这样,信号处理函数可以迅速返回,继续处理其他任务。在没有循环时,调用
wait()
不会导致出现阻塞等待,因为当进入该处理函数时,说明必然有函数终止了,只不过是1个还是多个的问题 无论是一个还是多个,wait()
都只能处理掉一个,然后返回,退出处理器函数如果在循环中调用
wait()
,一旦没有子进程终止,wait()
就会阻塞,这会导致整个信号处理程序挂起,不能立即返回。 例如,第一个进程终止后,父进程进入了处理器函数,处理了第一个进程之后,便会继续循环等待下一个终止进程, 若是下一个进程迟迟不进入终止状态,则父进程则需要一直阻塞等待。 阻塞在信号处理程序中通常是不被推荐的,因为这可能导致系统其他部分的延迟或不良的响应时间。
3. 处理多个子进程的终止
- 在某些情况下,多个子进程可能在非常短的时间内几乎同时终止。如果仅调用
wait()
,信号处理程序可能只能处理一个子进程的终止。使用waitpid()
的循环可以确保处理所有已终止的子进程。
4. 控制和灵活性
waitpid()
提供了更多的控制和灵活性。例如,使用waitpid()
可以指定等待特定的子进程,或根据不同的选项处理子进程。相比之下,wait()
的功能比较有限,只能简单地等待任意一个子进程的终止。