linux c监控子进程

2018-06-09 23:14:28

接下来描述两种监控子进程的技术:系统调用wait()及其变体,SIGCHLD信号。

系统调用wait()

#include <sys/wait.h>

pid_t wait(int *status);

//返回终止子进程id,失败-1

1.如果没有子进程终止,则一直阻塞,直至某个子进程终止。如果调用时已经有子进程终止,wait()则立即返回。
2.如果status非空,则子进程的终止状态会返回到status。
3.内核将会为父进程下所有子进程的运行总量追加cpu时间以及资源使用数据。
4.将终止子进程的id作为wait()的结果返回。

出错时返回-1,如果没有需要等待的子进程,errno置为ECHILD。

系统调用waitpid()

wait()存在诸多限制:

1.如果父进程已经有多个子进程,wait()无法等待特定的子进程。
2.如果没有子进程退出,wait()总是保持阻塞。
3.wait()只能发现那些已经终止的子进程,对于子进程因某个信号(如 SIGSTOP 或 SIGTTIN)而停止,或是已停止子进程收到SIGCONT 信号恢复执行的情况就无能为力了。

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

//返回子进程id,失败-1

//pid > 0 等待进程id为pid的进程
//pid = 0 等待当前组下的所有进程
//pid < -1 等待进程组标识符为pid绝对值的进程
//pid = -1 所有子进程 = wait(status) = waitpid(-1, status, 0);

options是一个位掩码,可以包含0个或多个如下标志:

WUNTRACED 除了返回终止子进程信息外,还返回因信号而停止的子进程信息。
WCONTINUED 返回被信号SIGCONT导致的继续进行子进程信息。
WNOHANG 非阻塞,如果pid存在但是状态没改变(也就是还在正常执行),返回0,改变了返回子进程的pid,如果没有与pid匹配的子进程,报错并将错误号置为ECHILD。

头文件<sys/wait.h> 定义了用于解析等待状态值的一组标准宏:

WIFEXITED(status) 若子进程正常结束返回true。WEXITSTATUS(status) 返回子进程的退出状态。
WIFSIGNALED(status) 若通过信号杀死子进程则返回true。WTERMSIG(status)返回导致子进程终止的信号编号。若子进程产生了core dump文件,WCOREDUMP(status)返回真。
WIFSTOPPED(status) 若子进程因信号而停止返回true,WSTOPSIG(status)返回导致进程停止的信号编号。
WIFCONTINUED(status) 若进程收到 SIGCONT 而恢复执行,返回true。


例子

#include <sys/wait.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

void printWaitStatus(const char *msg, int status);

void printWaitStatus(const char *msg, int status){
    if (msg != NULL)
        printf("%s", msg);

    if (WIFEXITED(status)) {
        printf("child exited, status=%d\n", WEXITSTATUS(status));

    } else if (WIFSIGNALED(status)) {
        printf("child killed by signal %d (%s)",
                WTERMSIG(status), strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP        /* Not in SUSv3, may be absent on some systems */
        if (WCOREDUMP(status))
            printf(" (core dumped)");
#endif
        printf("\n");

    } else if (WIFSTOPPED(status)) {
        printf("child stopped by signal %d (%s)\n",
                WSTOPSIG(status), strsignal(WSTOPSIG(status)));

#ifdef WIFCONTINUED     /* SUSv3 has this, but older Linux versions and
                           some other UNIX implementations don't */
    } else if (WIFCONTINUED(status)) {
        printf("child continued\n");
#endif

    } else {            /* Should never happen */
        printf("what happened to this child? (status=%x)\n",
                (unsigned int) status);
    }
}

int
main(int argc, char *argv[])
{
    int status;
    pid_t childPid;

    if (argc > 1 && strcmp(argv[1], "--help") == 0){
        printf("%s [exit-status]\n", argv[0]);
        return 1;
    }  

    switch (fork()) {
    case -1: perror("fork");

    case 0:
        
        //子进程可以立马结束,也可以等待信号,有你决定
        printf("Child started with PID = %ld\n", (long) getpid());
        if (argc > 1)                   /* Status supplied on command line? */
            exit(atoi(argv[1]));
        else                            /* Otherwise, wait for signals */
            for (;;)
                pause();
        exit(EXIT_FAILURE);             /* Not reached, but good practice */

    default:
        for (;;) {
            childPid = waitpid(-1, &status, WUNTRACED
#ifdef WCONTINUED       /* Not present on older versions of Linux */
                                                | WCONTINUED
#endif
                    );
            if (childPid == -1)
                perror("waitpid");

            printf("waitpid() returned: PID=%ld; status=0x%04x (%d,%d)\n",
                    (long) childPid,
                    (unsigned int) status, status >> 8, status & 0xff);
            printWaitStatus(NULL, status);

            if (WIFEXITED(status) || WIFSIGNALED(status))
                exit(EXIT_SUCCESS);
        }
    }
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 1
Child started with PID = 4698
waitpid() returned: PID=4698; status=0x0100 (1,0)
child exited, status=1

[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 4
Child started with PID = 4700
waitpid() returned: PID=4700; status=0x0400 (4,0)
child exited, status=4

[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out
Child started with PID = 4702

waitpid() returned: PID=4702; status=0x147f (20,127)
child stopped by signal 20 (Stopped)

waitpid() returned: PID=4702; status=0xffff (255,255)
child continued

waitpid() returned: PID=4702; status=0x000f (0,15)
child killed by signal 15 (Terminated)
[root@izj6cfw9yi1iqoik31tqbgz ~]# kill -20 4702
[root@izj6cfw9yi1iqoik31tqbgz ~]# kill -18 4702
[root@izj6cfw9yi1iqoik31tqbgz ~]# kill 4702


孤儿进程和僵尸进程

孤儿进程就是父进程终止了,而子进程继续存在,那么这个进程即为孤儿进程,会被pid为1的进程收养。

僵尸进程(zombie)即子进程终止了,而父进程没有调用wait(),那么内核为了让父进程能读取到子进程终止状态相关信息,会释放进程的大部分资源,但是会保留内核进程表中的一条记录,其中包含子进程的进程id、终止状态、资源使用数据等信息。所以如果存在大量的僵尸进程,它们势必会填满内核进程表。从而阻碍新进程的创建。

僵尸进程无法被信号杀死,只能通过父进程调用wait()或杀死父进程然后被init进程收养后从系统中将他们清理。

#include <signal.h>
#include <libgen.h>             /* For basename() declaration */
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

#define CMD_SIZE 200

int
main(int argc, char *argv[])
{
    char cmd[CMD_SIZE];
    pid_t childPid;

    setbuf(stdout, NULL);       /* Disable buffering of stdout */

    printf("Parent PID=%ld\n", (long) getpid());

    switch (childPid = fork()) {
    case -1:
        perror("fork");

    case 0:     /* Child: immediately exits to become zombie */
        printf("Child (PID=%ld) exiting\n", (long) getpid());
        _exit(EXIT_SUCCESS);

    default:    /* Parent */
        sleep(3);               /* Give child a chance to start and exit */
        snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0]));
        system(cmd);            /* View zombie child */

        /* Now send the "sure kill" signal to the zombie */

        if (kill(childPid, SIGKILL) == -1)
            perror("kill");
        sleep(3);               /* Give child a chance to react to signal */
        printf("After sending SIGKILL to zombie (PID=%ld):\n", (long) childPid);
        system(cmd);            /* View zombie child again */

        exit(EXIT_SUCCESS);
    }
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
Parent PID=4748
Child (PID=4749) exiting
 4748 pts/2    00:00:00 a.out
 4749 pts/2    00:00:00 a.out <defunct>
After sending SIGKILL to zombie (PID=4749):
 4748 pts/2    00:00:00 a.out
 4749 pts/2    00:00:00 a.out <defunct>


SIGCHLD信号

子进程终止属于异步事件。父进程无法预知其子进程何时终止。即使向子进程发送SIGKILL信号,子进程终止时间还依赖于系统的调度。

父进程应使用wait()来防止僵尸进程的累积,以及采用如下两种方法来避免这一问题。

1.父进程调用不带 WNOHANG 标志的wait()或waitpid() 方法,如果尚无进程终止则阻塞。
2.周期性的调用带有WNOHANG标志的waitpid(),执行非阻塞式检查(轮询)。

这两种方式用起来都有所不便。一方面不想阻塞父进程,一方面又不想轮询浪费cpu时间。

为 SIGCHLD建立信号处理程序

对信号不了解的可以参考 linux信号 3篇文章。 无论一个子进程于何时终止,系统都会向其父进程发送SIGCHLD信号。对该信号的默认处置是忽略。当然我们可以设置信号处理器函数,但是当信号处理程序时,会暂时将引发的信号阻塞起来(除非指定SA_NODEFER标志),且不会对SIGCHLD之类的标准信号进行排队处理。这样假如同时有多个子进程终止,父进程只能捕获到一次。

解决方案是:在SIGCHLD处理程序内部循环以WNOHANG标志来调用waitpid(),直至所有子进程终止。

在创建子进程之前,应该先设置好SIGCHLD处理程序。

#include <signal.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */
#define BUF_SIZE 1000

static volatile int numLiveChildren = 0;
                /* Number of children started but not yet waited on */

void printWaitStatus(const char *msg, int status);
char *currTime(const char *fmt);

char * currTime(const char *format){
    static char buf[BUF_SIZE];  /* Nonreentrant */
    time_t t;
    size_t s;
    struct tm *tm;

    t = time(NULL);
    tm = localtime(&t);
    if (tm == NULL)
        return NULL;

    s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);

    return (s == 0) ? NULL : buf;
}

void printWaitStatus(const char *msg, int status){
    if (msg != NULL)
        printf("%s", msg);

    if (WIFEXITED(status)) {
        printf("child exited, status=%d\n", WEXITSTATUS(status));

    } else if (WIFSIGNALED(status)) {
        printf("child killed by signal %d (%s)",
                WTERMSIG(status), strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP        /* Not in SUSv3, may be absent on some systems */
        if (WCOREDUMP(status))
            printf(" (core dumped)");
#endif
        printf("\n");

    } else if (WIFSTOPPED(status)) {
        printf("child stopped by signal %d (%s)\n",
                WSTOPSIG(status), strsignal(WSTOPSIG(status)));

#ifdef WIFCONTINUED     /* SUSv3 has this, but older Linux versions and
                           some other UNIX implementations don't */
    } else if (WIFCONTINUED(status)) {
        printf("child continued\n");
#endif

    } else {            /* Should never happen */
        printf("what happened to this child? (status=%x)\n",
                (unsigned int) status);
    }
}

static void sigchldHandler(int sig){
    int status, savedErrno;
    pid_t childPid;

    savedErrno = errno;         /* In case we modify 'errno' */

    printf("%s handler: Caught SIGCHLD\n", currTime("%T"));

    //一次性处理所有状态改变的子进程
    while ((childPid = waitpid(-1, &status, WNOHANG)) > 0) {
        printf("%s handler: Reaped child %ld - ", currTime("%T"),
                (long) childPid);
        printWaitStatus(NULL, status);
        numLiveChildren--;
    }

    if (childPid == -1 && errno != ECHILD)
        perror("waitpid");

    //延长执行时间
    sleep(5);
    printf("%s handler: returning\n", currTime("%T"));

    errno = savedErrno;
}

int main(int argc, char *argv[]){
    int j, sigCnt;
    sigset_t blockMask, emptyMask;
    struct sigaction sa;

    if (argc < 2 || strcmp(argv[1], "--help") == 0){
        printf("%s child-sleep-time...\n", argv[0]);
        return 1;
    }

    //设置标准输出不缓冲
    setbuf(stdout, NULL);

    sigCnt = 0;
    numLiveChildren = argc - 1;

    //修改 SIGCHLD 的处置
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = sigchldHandler;
    if (sigaction(SIGCHLD, &sa, NULL) == -1)
        perror("sigaction");

    
    //阻塞 SIGCHLD
    sigemptyset(&blockMask);
    sigaddset(&blockMask, SIGCHLD);
    if (sigprocmask(SIG_SETMASK, &blockMask, NULL) == -1)
        perror("sigprocmask");

    for (j = 1; j < argc; j++) {
        switch (fork()) {
        case -1:
            perror("fork");

        case 0:  //子进程睡眠一段时间并终止
            sleep(atoi(argv[j]));
            printf("%s Child %d (PID=%ld) exiting\n", currTime("%T"),
                    j, (long) getpid());
            _exit(EXIT_SUCCESS);

        default:        /* Parent - loops to create next child */
            break;
        }
    }

    //父进程等待 SIGCHLD 信号直到所有子进程终止
    sigemptyset(&emptyMask);
    while (numLiveChildren > 0) {
        if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
            perror("sigsuspend");
        sigCnt++;
    }

    printf("%s All %d children have terminated; SIGCHLD was caught "
            "%d times\n", currTime("%T"), argc - 1, sigCnt);

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 2 3 4
22:50:37 Child 1 (PID=4791) exiting
22:50:37 handler: Caught SIGCHLD
22:50:37 handler: Reaped child 4791 - child exited, status=0
22:50:38 Child 2 (PID=4792) exiting
22:50:39 Child 3 (PID=4793) exiting
22:50:42 handler: returning
22:50:42 handler: Caught SIGCHLD
22:50:42 handler: Reaped child 4792 - child exited, status=0
22:50:42 handler: Reaped child 4793 - child exited, status=0
22:50:47 handler: returning
22:50:47 All 3 children have terminated; SIGCHLD was caught 2 times

阻止停止的子进程向父进程发送 SIGCHLD

调用sigaction() 时未使用 SA_NOCLDSTOP标志,系统会在子进程停止时向父进程发送 SIGCHLD信号。使用了这一标志,那么就不会发送。

当信号 SIGCONT 导致已停止的子进程恢复执行时,会向其父进程发送 SIGCHLD信号。

忽略终止的子进程

将SIGCHLD的处置显式的置为 SIG_IGN,系统会将其后终止的子进程立即删除而不会变为僵尸进程。故而所有后续的wait()调用不会反悔子进程的任何信息。

虽然默认就是忽略 ,但是显式指定行为就会有差异。 

sigaction() 时设置 SA_NOCLDWAIT标志能实现同样功能,唯一的区别是SUSv3并未规定系统在子进程终止时是否向父进程发送SIGCHLD 信号。


总结

本文对linux c监控子进程做了详细的讲解,接下来将介绍exec家族,如果有疑问可以给我留言。


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/52

©著作权归作者所有
收藏
推荐阅读
  • err
    linux c进程的创建、终止

    对进程不是很了解的同学可以参考linux 进程这篇文章。进程的创建系统调用fork() 创建一新进程(子进程)。#include &lt;unistd.h&gt; pi...

  • linux c定时器与休眠

    定时器是进程规划自己在未来某一时刻接获通知的一种机制。休眠则能是进程(或线程)暂停执行一段时间。间隔定时器系统调用 setitimer() 创建一个间隔式定时器,一定时间后到期,这个定时器也可以每个一...

  • linux c信号(3)-高级特性

    core dump文件coredump 文件 内含进程终止时内存映像的一个文件,把它加载到调试器中,即可查明信号到达时程序代码和数据的状态。 引发core dump文件除了程序中调用 abort()外...

  • err
    linux c信号(2)-处理函数

    一般而言信号处理函数设计的越简单越好。其中的一个重要原因在于,这将降低引发竞争条件的风险。下面是信号处理函数的两种常见设计。1.信号处理器函数设置全局性标记变量并退出。...

  • err
    linux c信号(1)-基本概念

    信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。一个(具有合适权限...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。