您的位置:首页 > 理论基础 > 计算机网络

36-多进程并发服务器(僵尸进程与信号处理)

2017-04-21 09:59 316 查看
在上一篇文章中,最后遗留了一个僵尸进程的问题。一旦客户端关闭连接,服务器子进程就会退出,然而父进程仍然存在,就产生了“白发人送黑发人”的场景。如果父进程没有主动回收(wait)子进程,或者没有忽略 SIGCHLD 信号,退出的子进程就会成为僵尸进程。

代码托管在 gitos 上,请使用下面的命令获取:

git clone https://git.oschina.net/ivan_allen/unp.git[/code] 
如果你已经 clone 过这个代码了,请使用 Git pull 更新一下。本文所使用的程序路径是:

unp/program/echo/processzombie


1. 处理僵尸进程

1.1 方案一:忽略 SIGCHLD 信号

这是最简单的方案,直接忽略掉 SIGCHLD 就不会产生僵尸进程了。只要在程序初始化阶段调用:

signal(SIGCHLD, SIG_IGN);


1.2 方案二:捕捉 SIGCHLD 并 wait

实际上,在我们学习 Linux 环境编程的时候,就已经学习过处理办法了,只要让父进程 wait 子进程就行了。不过,比较推荐的方案是使用捕捉 SIGCHLD 信号,然后在信号处理函数里异步 wait 子进程。

// 信号处理函数

void handler(ing sig) {
pid_t pid;
int stat;
if (sig == SIGCHLD) {
// 想一想,为什么要循环?
while(1) {
// WNOHANG 表示不阻塞
pid = waitpid(-1, &stat, WNOHANG);
// 返回 -1 表示没有子进程了,返回 0 表示有子进程,但是子进程没有退出。
if (pid <= 0) break;
printf("child %d terminated\n", pid);
}
}
}


2. 信号打断低速系统调用

这个是一个坑,之前我们也有讲过,请参考《中断系统调用与自动重启动》,这里我简单总结一下:

进程捕捉到信号后,会打断某些正在阻塞中的函数(低速系统调用,比如 read,accept,connect 等),这种函数如果被信号打断,会直接返回错误,同时设置 errno = EINTR.

实际上,这并不是错误,如果我们没有处理这种情况,让程序直接退出,会让一个运行的很好的服务器停止。所以为了让服务器或客户端更加健壮,我们需要额外的处理这种错误,比如:

// accept 返回错误,可能是被信号打断。如果被打断,不认为它是错误。
ret = accept(listenfd, ...);
if (ret < 0) {
if (errno == EINTR) contine;
else exit(1);
}


3. 实验

在 sun 主机上启动服务器

$ ./echo -s -h sun


在 flower 主机上启动客户端

$ ./echo -h sun


随便输入一些数据后,按下 CTRL D 让客户端主动退出。

运行结果



图1 服务器运行结果

从图 1 中我们可以看到,子进程退出时,给父进程发送了 SIGCHLD 信号,信号处理函数执行完后,发现 accept 直接返回一个错误,屏幕打印“Interrupted system call(被中断的系统调用)”。但这并不是一个错误,我们需要让服务器继续运行。



图2 客户端按下 CTRL D 退出

4. 总结

掌握处理僵尸进程的办法

知道低速系统调用会被信号打断,同时 errno = EINTR.

思考:为什么在信号处理函数中处理子进程时,需要循环操作?(提示:参考《标准信号及其不可靠性》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息