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.

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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值