天天看點

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來判斷子程序狀态的。而應該是通過阻塞讀取核心某個狀态值

繼續閱讀