Linux系统编程——监控子进程

在应用程序设计中,有时候父进程需要知道某个子进程的状态(一般都是获取子进程的退出状态,避免其成为僵尸进程)

监控子进程的手段:

  • wait()函数及相关系统调用

  • 信号SIGCHLDSIGHLD(两个信号意义是一样的)**


wait()及相关系统调用

wait()系统调用

//头文件
#include <sys/wait.h>

//调用该函数的进程没有子进程终止,调用将一直阻塞,直至某个子进程终止
//有子进程终止,立即返回
pid_t wait(int *wstatus);
//wstatus —— 保存进程终止信息

RETURN VALUE
    返回子进程的进程ID,错误返回-1

waitpid()系统调用

//头文件
#include <sys/wait.h>

pid_t waitpid (pid_t pid, int *wstatus, int options);
//pid —— 等待指定的子进程
//pid > 0, pid为等待的子进程的进程号
//pid = 0, 等待调用父进程同一进程组的所有子进程
//pid < -1, 等待进程组标识符与pid绝对值相等的所有子进程
//pid = -1, 等待任意子进程,waitpid(-1, &status, 0)与wait(&status)等价 
//wstatus —— 子进程退出状态
//options —— 等待操作选项
//  WNOHANG —— 指定的子进程状态没有改变立即返回,不阻塞
//  WUNTRACED —— 除了返回终止的子进程信息外,还返回由于信号而终止的子进程信息
//  WCONTINUED (since Linux 2.6.10)——返回因受到SIGCONT信号而恢复执行的已终止的子进程的信息

RETURN VALUE
    成功时,返回状态已更改的子进程的进程ID;如果指定options为WNOHANG,以及pid指定一个或多个进程,但是没有子进程状态改变,则返回0。当出现错误时,返回-1

使用以下宏来检查判断子进程退出的原因

WIFEXITED(wstatus) : 
    正常结束返回true,换句话就是, 调用exit(3) or exit(2)这些,或者在main()return返回

WEXITSTATUS(wstatus) :
   返回子程序的退出状态。它由状态参数中最不重要的8位组成,子元素在exit(3)exit(2)调用中指定该参数,或者在main()中作为返回语句的参数。只有当WIFEXITED返回true时,才应该使用这个宏。

WIFSIGNALED(wstatus) : 
   如果子进程被一个信号终止,则返回true。.
       
WTERMSIG(wstatus) : 
    返回导致子进程终止的信号编号。只有当WIFSIGNALED返回true时,才应该使用这个宏。

WIFSTOPPED(wstatus) : 
    如果子进程接收信号终止,返回true;只有当使用WUNTRACED这个options选项进行系统调用,
    或者当子进程在调试被跟踪时(参见ptrace(2)),才有可能这样做。
        
WSTOPSIG(wstatus) : 
     返回导致孩子停止的信号编号。只有当WIFSTOPPED返回true时,才应该使用这个宏。

WIFCONTINUED(wstatus)(since Linux 2.6.10) : 
     如果子进程受到SIGCONT信号被恢复执行返回true


//通过idtype and id 参数选择等待的子进程
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
//idtype —— id类型
//idtype == P_PID 等待进程组ID为id进程的子进程
//idtype == P_PGID 等待进程组ID为id所有进程的所有子进程
//idtype == P_ALL 等待任意进程,忽略id参数
//id —— 指定的进程组id

//options —— 操作选项
// WEXITED   等待已经终止的子进程,无论是否正常退出.
// WSTOPPED    等待通过信号终止的子进程
// WCONTINUED  等待先前是终止但被信号SIGCONT唤醒恢复执行的子进程
//options附加的选项:
//   WNOHANG   与waitpid()的一样.
//   WNOWAIT   返回子进程状态,依然让子进程处于等待状态;稍后的系统调用可用于再次获取子进程状态信息。

//infop —— 子进程信息
//该系统调用执行成功后infop被填充的结构体字段:
//si_pid 子进程ID.
//si_uid 子进程真实用户ID.  
//si_signo 总是被设置为SIGCHLD.
//si_status   要么是子程序的退出状态(如exit(2)(或exit(3)),要么是导致子程序终止、停止或继续的信号。si_code字段可用于确定如何解释该字段。
//si_code字段取值:
// CLD_EXITED(子进程调用exit(2)退出);
// CLD_KILLED (子进程被信号杀死)
// CLD_DUMPED (子进程被信号杀死,及脱离内核)
// CLD_STOPPED (子进程被信号终止)
// CLD_TRAPPED(子进程被调试跟踪)
// CLD_CONTINUED(信号SIGCONT恢复子进程继续执行)。

RETURN VALUE
    成功或者如果WNOHANG被指定进程组ID为id的子进程状态没有更改返回0,执行成子进程信息被填充到结构体指针infop中;当出现错误时,返回-1
    
siginfo_t {
     int      si_signo;     /* Signal number */
     int      si_errno;     /* An errno value */
     int      si_code;      /* Signal code */
     int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
     pid_t    si_pid;       /* Sending process ID */
     uid_t    si_uid;       /* Real user ID of sending process */
     int      si_status;    /* Exit value or signal */
     clock_t  si_utime;     /* User time consumed */
     clock_t  si_stime;     /* System time consumed */
     sigval_t si_value;     /* Signal value */
     int      si_int;       /* POSIX.1b signal */
     void    *si_ptr;       /* POSIX.1b signal */
     int      si_overrun;   /* Timer overrun count;
                 int      si_timerid;   /* Timer ID; POSIX.1b timers */
      void    *si_addr;      /* Memory location which caused fault */
      long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
      int      si_fd;        /* File descriptor */
      short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
      void    *si_lower;     /* Lower bound when address violation
                                         occurred (since Linux 3.19) */
      void    *si_upper;     /* Upper bound when address violation
                                         occurred (since Linux 3.19) */
      int      si_pkey;      /* Protection key on PTE that caused
                                         fault (since Linux 4.6) */
      void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
      int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
      unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
 }

等待所有子进程退出:

while( (childPid = wait(NULL)) != -1 )
    continue;

wait():

#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>


int main(int argc, char **argv)
{
    pid_t pid = fork();

    if (pid  == 0)
    {
        static int cnt = 0;
        while(1)
        {
            cnt++;
            printf("child is running %d\r\n",  cnt);
            if (cnt >= 10)
            {
                cnt = 0;
                exit(0);
            }
            sleep(1);
        }
    }

    if (pid > 0)
    {
        int status;
        pid_t  child_pid;
    
        child_pid =  wait(&status);      //一直阻塞直到子进程退出
        printf("child pid = %d, exit value = %d\r\n", child_pid, status); 
    }

    return 0;
}

waitpid():

#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    pid_t pid = fork();

    if (pid  == 0)
    {
        static int cnt = 0;
        while(1)
        {
            cnt++;
            printf("child is running %d\r\n",  cnt);
            if (cnt >= 10)
            {
                cnt = 0;
                exit(0);
            }
            sleep(1);
        }
    }

    if (pid > 0)
    {
        int status;
        int f_cnt = 0;
        pid_t  child_pid;
        while (1)
        {
            child_pid =  waitpid(pid, &status, WNOHANG);      
            if (child_pid == 0)
            {
                printf("father is runing %d\r\n", ++f_cnt);
            }
            else if (child_pid == -1)
            {
                printf("waitpid error\r\n");
                break;
            }
            else
            {
                printf("child pid = %d, exit value = %d\r\n", child_pid, status); 
                break;
            }
            
            sleep(1);
        }
    }

    return 0;
}

waitid():

#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

int main(int argc, char **argv)
{
    pid_t pid = fork();

    if (pid  == 0)
    {
        static int cnt = 0;
        while(1)
        {
            cnt++;
            printf("child is running %d\r\n",  cnt);
            if (cnt >= 10)
            {
                cnt = 0;
                exit(0);
            }
            sleep(1);
        }
    }

    if (pid > 0)
    {
        siginfo_t info;
        memset(&info, 0, sizeof(siginfo_t));
        int f_cnt = 0;
        int ret;
        while (1)
        {
            if (ret = waitid(P_PGID, getpgid(getpid()), &info, WEXITED | WNOHANG)  == -1)  //执行失败
            {
                perror("waitid\r\n");
                break;
            }
            else if (ret == 0)     //执行成功
            {
                if (info.si_pid == 0) //没有子进程状态发生改变
                {
                   printf("no children changed state %d\r\n", f_cnt++);
                }
                else
                {
                    printf("child pid = %d status = %d\r\n",  pid, info.si_status );
                }
            }
            sleep(1);
        }
    }

    return 0;
}

SIGCHLD信号

子进程终止属于异步事件,父进程无法预知子进程何时终止,即使父进程向子进程发送SIGKILL信号。无论子进程何时终止,系统都会向父进程发送SIGCHLD信号

是信号就默认处理,或忽略,或设置处理程序捕获处理它

问题:

相继多个子进程终止,即使产生多次SIGCHLD信号,父进程也只能捕获一个,那么就会导致一些子进程成为僵尸进程

解决办法:

SIGCHLD信号的处理函数内部以WNOHANG标志调用waitpid()

while(waitpid(-1, NULL, WNOHAND) > 0)
    continue;

waitpid()返回0表明无僵尸子进程存在。返回-1表示发生有错误(可能是ECHILD即没有子进程)

猜你喜欢

转载自blog.csdn.net/qq_36413982/article/details/106947585