zoukankan      html  css  js  c++  java
  • 处理僵死进程

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

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

    处理僵死进程的方法

    在服务器子进程终止时, 给父进程发送了一个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. }











  • 相关阅读:
    Android虚拟机 修改IMEI
    Android 真机调试缺少sqlite3
    DouBan FM API
    MySQL 常用命令[不断更新中]
    通过QRCode生成二维码与解码
    Ant 批量打包Android Umeng多渠道版本
    Centos服务器常用配置集合
    MTU详解
    openvswitch-with-conntrack_nat
    NAT介绍及NAT设备类型
  • 原文地址:https://www.cnblogs.com/gremount/p/5773653.html
Copyright © 2011-2022 走看看