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

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


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

git clone https://git.oschina.net/ivan_allen/unp.git

如果你已经 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.

思考:为什么在信号处理函数中处理子进程时,需要循环操作?(提示:参考《标准信号及其不可靠性》

说明:本文转自--Allen--,用于学习交流分享,仅代表原文作者观点。如有侵权,请联系我们删除~