处理僵死进程

僵死进程存在的目的 和 需要处理的原因

设置僵死进程的目的是维护子进程的信息, 以便父进程在以后的某个时刻获取. 但是留存的僵死进程会占用内核的空间, 最终可能导致耗尽进程资源. 所以还是需要处理这些僵死进程.

处理僵死进程的方法

在服务器子进程终止时, 给父进程发送了一个SIGCHLD信号,该信号是在子进程终止时,由内核发送给父进程的一个信号. 每个信号都有一个与之关联的处置, 也称为行为. 我们可以通过调用sigaction函数来设置处置. 设定信号的处置有三种方法: (1)提供信号处理函数,一旦信号被捕获, 就调用该函数.(或者说特定信号一旦发生就立刻调用该信号处理函数) (2)处置设定为SIG_IGN, 一旦信号被捕获就忽略; (3)处置设定为SIG_DFL来启用它的默认处置, 比如默认处置是终止进程.

这里详细介绍第一种方法: 提供信号处理函数 void sig_chld(int signo);

  1. #include "unp.h"
  2. void
  3. sig_chld(int signo)
  4. {
  5. pid_t pid;
  6. int stat;
  7. pid = wait(&stat);
  8. printf("child %d terminated ", pid);
  9. return;
  10. }

调用wait函数来处理已经终止的子进程

那么通过signal函数可以设置上述的sig_chld函数, 之所以不用sigaction函数来设置, 是因为那样操作有点复杂, 因为该函数的参数之一是我们必须分配并填写的结构. 所以不如直接调用sig_chld, 参数简单就两个, 第一个参数是信号名(例如SIGCHLD), 第二个参数或为指向函数的指针(例如指向sig_chld函数的指针), 或为SIG_IGN , 或为SIG_DFL. 

如下是unix网络编程一书中定义的signal函数

  1. /* include signal */
  2. #include "unp.h"
  3. Sigfunc *
  4. signal(int signo, Sigfunc *func)
  5. {
  6. struct sigaction act, oact;
  7. act.sa_handler = func;
  8. sigemptyset(&act.sa_mask);
  9. act.sa_flags = 0;
  10. if (signo == SIGALRM) {
  11. #ifdef SA_INTERRUPT
  12. act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
  13. #endif
  14. } else {
  15. #ifdef SA_RESTART
  16. act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
  17. #endif
  18. }
  19. if (sigaction(signo, &act, &oact) < 0)
  20. return(SIG_ERR);
  21. return(oact.sa_handler);
  22. }
  23. /* end signal */
  24. Sigfunc *
  25. Signal(int signo, Sigfunc *func) /* for our signal() function */
  26. {
  27. Sigfunc *sigfunc;
  28. if ( (sigfunc = signal(signo, func)) == SIG_ERR)
  29. err_sys("signal error");
  30. return(sigfunc);
  31. }

所以综上所属, 函数之间的调用关系是

signal 调用 sig_chld函数和sigaction函数

sig_chld函数调用wait函数

处理被中断的系统调用

SIGCHLD信号在父进程阻塞于慢系统调用(accept)时由父进程捕获且相应的信号处理函数返回时, 内核就会使accept返回一个EINTR错误(被中断的系统调用). 而父进程如果不处理该错误, 就会终止. 那么如何处理被中断的系统调用呢? 有如下两种方法.

1. 如果在signal()函数里设置了SA_RESTART标志, 那么信号中断的系统调用将由内核自动重启.

2. 如果没有没有设置上述标志: 可以用如下程序重启

  1. if((connfd=accept(listenfd, (SA*) &cliaddr, &clilen))<0){
  2. if(errno==EINTR)
  3. continue;// back to for()
  4. else
  5. err_sys("accept error");

父进程会同时收到多个SIGCHLD信号 怎么办?

在一个信号处理函数运行期间, 正被递交的信号是被阻塞的. 如果一个信号在被阻塞期间产生了一次或多次, 那么该信号被解阻塞之后通常只递交一次, 也就是说UNIX信号默认是不排队的. 如果存在多个子进程同时结束, 那么父进程会同时收到多个SIGCHLD信号 , 那么调用wait最终可能只会处理1个信号(5个信号都在信号处理函数执行之前产生), 或者不确定个数的信号(如果多个该信号产生时间有差距).

wait 和 waitpid 都可以处理已终止的子进程, 均返回两个值: 已终止子进程的进程ID号, 以及通过statloc指针返回的子进程终止状态(一个整数). 

  1. #include <sys/wait.h>
  2. pid_t wait(int *statloc);
  3. pid_t waitpid(pid_t pid, int *statloc, int options);

如果调用wait的进程没有已终止的子进程, 不过有一个或多个子进程仍在执行, 那么wait将阻塞到现有子进程第一个终止为止.

waitpid函数就等待哪个进程和是否阻塞给了我们更多的控制, 指定选项WNOHANG就可以告知waitpid在有尚未终止的子进程运行时不要阻塞. 用如下程序可以比较好地解决同时收到多个SIGCHLD信号问题.

  1. void
  2. sig_chld(int signo)
  3. {
  4. pid_t pid;
  5. int stat;
  6. while( (pid=waitpid(-1, &stat, WNOHANG))>0)
  7. printf("child %d terminated ", pid);
  8. return ;
  9. }

TCP服务器程序最终版本

1. 实现了客户发来什么信息返回什么信息的功能;

2. 处理了僵死进程问题---Signal(SIGCHLD, sig_chld);

3. 处理了系统调用中断的问题---SA_RESTART

4. 处理了多个SIGCHLD信号同时到来不能完全处理所有僵死进程的问题---waitpid.

  1. #include "unp.h"
  2. #include <time.h>
  3. void srv_echo(int sockfd)
  4. {
  5. ssize_t n;
  6. char buf[1000];
  7. again:
  8. while((n=read(sockfd,buf,1000))>0)
  9. Writen(sockfd,buf,n);
  10. if(n<0 && errno==EINTR)
  11. goto again;
  12. else if(n<0)
  13. err_sys("srv_echo: read error");
  14. }
  15. void
  16. sig_chld(int signo)
  17. {
  18. pid_t pid;
  19. int stat;
  20. while( (pid=waitpid(-1, &stat, WNOHANG))>0)
  21. printf("child %d terminated ", pid);
  22. return ;
  23. }
  24. int
  25. main(int argc,char ** argv)
  26. {
  27. int listenfd, connfd;
  28. pid_t childpid;
  29. socklen_t clilen;
  30. struct sockaddr_in cliaddr, servaddr;
  31. char buff[1000];
  32. listenfd=Socket(AF_INET, SOCK_STREAM, 0);
  33. bzero(&servaddr, sizeof(servaddr));
  34. servaddr.sin_family=AF_INET;
  35. servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
  36. servaddr.sin_port=htons(1234);
  37. Bind(listenfd, (SA*) &servaddr, sizeof(servaddr));
  38. Listen(listenfd, 100);
  39. Signal(SIGCHLD,sig_chld);
  40. for(;;){
  41. clilen=sizeof(cliaddr);
  42. connfd=Accept(listenfd, (SA*) &cliaddr, &clilen);
  43. /*在Signal()函数里设置了SA_RESTART标志, 那么信号中断的系统调用将由内核自动重启
  44. if((connfd=accept(listenfd, (SA*) &cliaddr, &clilen))<0){
  45. if(errno==EINTR)
  46. continue;// back to for()
  47. else
  48. err_sys("accept error");
  49. }
  50. */
  51. printf("connection from %s , port %d ",
  52. inet_ntop(AF_INET,&cliaddr.sin_addr, buff, sizeof(buff)),
  53. ntohs(cliaddr.sin_port));
  54. if((childpid=Fork())==0){
  55. Close(listenfd);
  56. srv_echo(connfd);
  57. exit(0);
  58. }
  59. Close(connfd);
  60. }
  61. }











原文地址:https://www.cnblogs.com/gremount/p/5773653.html