首页 > 代码库 > APUE学习笔记——10.18 system函数 与waitpid

APUE学习笔记——10.18 system函数 与waitpid

system函数

system函数用方便在一个进程中执行命令行(一行shell命令)。
用法如下:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("Hello\n");
    system("sleep 5");
    return 0;
}
在程序中通过system调用了命令行 sleep 5。(这里知识举一个例子,当然可以执行一个类似“ bash test.sh”之类的脚本
在这个小程序的运行时,可以通过ps -aux 看到新增加了三个进程。
一个是我们程序本身a.out
一个是shell进程:sh -c ***
一个是我们执行的命令行进程:sleep进程


system函数的实现:

《APUE》中给出了system的一中实现:

#include        <sys/wait.h>
#include        <errno.h>
#include        <signal.h>
#include        <unistd.h>

int
system(const char *cmdstring)   /* with appropriate signal handling */
{
        pid_t                           pid;
        int                                     status;
        struct sigaction        ignore, saveintr, savequit;
        sigset_t                        chldmask, savemask;

        if (cmdstring == NULL)
                return(1);              /* always a command processor with UNIX */

        ignore.sa_handler = SIG_IGN;    /* ignore SIGINT and SIGQUIT */
        sigemptyset(&ignore.sa_mask);
        ignore.sa_flags = 0;
        if (sigaction(SIGINT, &ignore, &saveintr) < 0)
                return(-1);
        if (sigaction(SIGQUIT, &ignore, &savequit) < 0)
                return(-1);
        sigemptyset(&chldmask);                 /* now block SIGCHLD */
        sigaddset(&chldmask, SIGCHLD);
        if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
                return(-1);

        if ((pid = fork()) < 0) {
                status = -1;    /* probably out of processes */
        } else if (pid == 0) {                  /* child */
                /* restore previous signal actions & reset signal mask */
                sigaction(SIGINT, &saveintr, NULL);
                sigaction(SIGQUIT, &savequit, NULL);
                sigprocmask(SIG_SETMASK, &savemask, NULL);

                execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
                _exit(127);             /* exec error */
        } else {                                                /* parent */
                while (waitpid(pid, &status, 0) < 0)
                        if (errno != EINTR) {
                                status = -1; /* error other than EINTR from waitpid() */
                                break;
                        }
        }

        /* restore previous signal actions & reset signal mask */
        if (sigaction(SIGINT, &saveintr, NULL) < 0)
                return(-1);
        if (sigaction(SIGQUIT, &savequit, NULL) < 0)
                return(-1);
        if (sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)
                return(-1);

        return(status);
}
很多人对这个实现有不少的疑问。疑问点主要在下面几个方面:
首先:为什么要忽略中断信号SIGINT和停止信号SIGQUIT。
其次:为什么要锁住子进程结束信号SIGCHLD
另外:为什么锁住了子进程结束信号SIGCHLD后,waitpid还能正确返回。


1. 为什么要忽略中断信号SIGINT和停止信号SIGQUIT。
前面已经介绍,./a.out执行时,产生了三个进程:a.out、(shell进程)sh -c、system调用的命令行产生的进程(如sleep 5, 在apue中是ed进程)。
如果不忽略SIGINT和SIGQUIT会出现上面情况呢,《APUE》中通过自己设计的不屏蔽SIGINT和SIGQUIT信号的实现进行了测试,发现SIGINT和SIGQUIT信号会向./a.out产生的三个进程发送,即会发送给a.out、sh -c、ed。(其中sh -c默认忽略此信号),这样会捕获信号的有a.out、ed。这样在实际运行中会出现上面情况呢:在我们要通过Ctl+c关闭ed(这种交互式程序经常会用ctl+c来关闭)时,信号也被发送给了a.out,也就是a.out和ed都被关闭了,这样可能a.out还有其他工作也不能完成了。
出于以上的原因,我们要求system屏蔽SIGINT和SIGQUIT。

2.为什么要锁住子进程结束信号SIGCHLD
首先明白,如果不锁住SIGCHLD,那么在system执行的命令行创建的子进程(如ed)结束时,会向./a.out也发送SIGCHLD信号。这会产生什么问题呢?
加入我们在调用system之前,./a.out创建了一个子进程(子进程A),并且是通过wait函数等待他结束。但是我们的ed进程结束时就像a.out发送了SIGCHLD,导致wait直接返回,由此a.out就无法等到子进程A结束了。

3.为什么锁住了子进程结束信号SIGCHLD后,waitpid还能正确返回。
这一部分我还没找到确切的证据,但描述的也都是一些合理的推断:
我认为wait和waitpid并不是通过捕获SIGCHLD来返回子进程结束状态的。理由如下:
a.Linux系统对SIGCHLD的默认处理方式就是ignore
b.经常看到一些code里,捕获SIGCHLD,然后再SIGCHLD的信号处理函数中调用wait,来使得父进程不阻塞。
c.waitpid中的option选项为WNOHANG时,waitpid可以立即返回。也就是可以循环调用waitpid来判断子进程是否退出。
基于以上三点,基本上可以退出waitpid并不是通过捕获SIGCHLD来判断子进程状态的。而应该是通过阻塞读取内核某个状态值










APUE学习笔记——10.18 system函数 与waitpid