歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

使用system函數時應該忽略兩個信號

在8.13節,我們展示了一個system函數的實現。然而,那個版本沒有處理信號。POSIX.1要求system忽略SIGINT和SIGQUIT並阻塞SIGCHLD。在展示正確處理這些信號的版本之前,我們看下為什麼需要擔心這些信號的處理。

下 面的代碼使用了8.13節的system版本來調用ed編輯器。(這個編輯器作為UNIX系統的一部分已經有很長時間了。我們在這裡使用它是因為它是一個 捕獲中斷和退出信號的交互式程序。如果我們調用一個外殼並輸入中斷符,那麼它捕獲這個中斷符並打印一個問號。ed程序也設置了退出信號的布署以便它被忽 略。)

<span style="font-size:18px;"><strong>#include <signal.h>  
      
static void
sig_int(int signo)  
{  
    printf("caught SIGINT\n");  
}  
      
static void
sig_chld(int signo)  
{  
    printf("caught SIGCHLD\n");  
}  
      
int
main(void)  
{  
    if (signal(SIGINT, sig_int) == SIG_ERR) {  
        printf("signal(SIGINT) error\n");  
        exit(1);  
    }  
    if (signal(SIGCHLD, sig_chld) == SIG_ERR) {  
        printf("signal(SIGCHLD) error\n");  
        exit(1);  
    }  
    if (system("/bin/ed") < 0) {  
        printf("system() error");  
        exit(1);  
    }  
    exit(0);  
}</strong></span>

上面的代碼同時捕獲了SIGINT和SIGCHLD。運行結果為:
$ ./a.out
a (添加文本命令)
Here is one line of text
. (終止添加模式)
1,$p (從第一行開始打印)
Here is one line of text
w temp.foo (把緩沖寫入文件)
25 (寫了25個字節)
q (退出)
caught SIGCHLD

當 編輯器終止時,系統向父進程(a.out進程)發送SIGCHLD信號。我們捕獲它並從信號處理器返回。但是如果它正在捕獲SIGCHLD信號,父進程應 該正這樣做,因為它已經創建了它自己的子進程,以便知道它的子進程何時終止。在system函數執行時這個信號的分發應該在父進程裡被阻塞。事實上,這是 POSIX.1規定的。否則,當system創建的子進程終止時,它將誤導system的調用者認為它自己的一個子進程終止了。調用者然後會使用某個 wait函數來得到子進程的終止狀態,因而避免system函數得到子進程的終止狀態作為它的返回值。

如果我們再次運行程序,這次向編輯器發送一個中斷信號,會有:

$ ./a.out
a
hello, world
.
1,$p
hello, world
w temp.foo
13
^Ccaught SIGINT

?
q
caught SIGCHLD

回想9.6節,輸入中斷符會導致中斷信號被發送給前台進程組的所有進程。前台進程有a.out,/bin/sh和/bin/ed。

在 這個例子裡,SIGINT被發送給所有這三個前台進程。(後台的外殼忽略這個信號。)正如我們能從輸出看到的,a.out進程和編輯器捕獲了這個信號。但 是當我們用system函數運行另一個程序時,我們不該讓父進程和子進程同時捕獲兩個終端產生的信號:中斷和退出。這兩個信號應該被發送給實際正在運行的 程序:子進程。

因為system執行的命令可以是一個交互式命令(這個例子裡是ed程序),而且system的調用者在程序執行時放棄了控制而等待它的結 束,所以system的調用者不應該收到這兩個終端產生的信號。這是為什麼POSIX.1規定system函數應該在等待命令完成時忽略這兩個信號。

下面的代碼展示了含所需的信號處理的system函數的一個實現:

<span style="font-size:18px;"><strong>#include <sys/wait.h>  
#include <errno.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);  
}</strong></span>

如果我們使用這個版本的system,得到的結果和前面(有缺陷的)那個的結果不同在於:

1、沒有信號被發送給調用進程,當我們輸入中斷和退出符;

2、當ed命令退出時,SIGCHLD不會被發送到調用進程。事實上,它被阻塞,直到我們在最後一個sigprocmask的調用裡反阻塞它,在system函數通過調用waitpid得到子進程的終止狀態之後。

POSIX.1 指出如果wait或waitpid在SIGCHLD待定時返回了一個子進程的狀態,那麼SIGCHLD不應該被分發給進程,除非另一個子進程的狀態也可 用。本書的四個實現沒有一個實現了這個語義。相反,在system函數調用waitpid後SIGCHILD仍保持待定;當信號被反阻塞時,它被分發給了 調用者。如果我們在sig_chld裡調用wait,它將返回-1,errno被設為ECHILD,因為system函數已經得到了子進程的終止狀態。
許多老的書本都用如下方式忽略中斷和退出信號:

<span style="font-size:18px;"><strong>if ((pid = fork()) < 0) {  
  err_sys("fork error");  
} else if (pid == 0) {  
  /* child */
  execl(...);  
  _exit(127);  
}  
      
/* parent */
old_intr = signal(SIGINT, SIG_IGN);  
old_quit = signal(SIGQUIT, SIG_IGN);  
waitpid(pid, &status, 0);  
signal(SIGINT, old_intr);  
signal(SIGQUIT, old_quit);</strong></span>

這個代碼的問題是我們不能保證在fork後父子進程誰先運行。如果子進程先運行而父進程在之後一段時間之內沒有運行,那麼一個中斷信號可能在父進程改變它的布署為被忽略是被產生。由於這個原因,我們新的system函數裡在fork之間改變信號的布署。

注意我們必須在子進程裡調用execl之前重置這兩個信號的布署。這允許execl改變它們的布署為默認,基於調用者的布署,如在8.10節裡描述的。

sytem的返回值

注 意system的返回值。它是外殼的終止狀態,並不總是命令字符串的終止狀態。我們在第8章看到過一些例子,而且結果和我們預料的一樣:如果我們執行一個 簡單的命令,比如date,那麼終止狀態是0。執行外殼命令exit 44給我們一個44的終止狀態。用信號會發生什麼呢?

讓我們運行第8章的程序並發送一些信號給正在執行的命令:
$ tsys "sleep 30"
^Cnormal termination, exit status = 130
$ tsys "sleep 30"
^\sh: 946 quit
normal termination, exit status = 131

(我系統上沒有這個問題。pr_exit打印出期望的值:異常退出。可能我的系統的system運行時,中斷信號由“sh -c sleep 30”,而不是“sleep 30”響應。

當 我們用中斷信號終止sleep時,pr_exit函數認為它正常終止。當我們用退出鍵殺死sleep時會發生同樣的事。這裡發生的事是Bourne外殼有 一個糟糕文檔的特性,它終止狀態是128加上一個信號號,當它正在執行的命令被一個信號終止時。我們可以用外殼交互地看下這個:
$ sh -c "sleep 30"
^C
$ ehco $?
130
$ sh -c "sleep 30"
^\sh: 962 Quit - core dumped
$ ehco $?
131
$ exit

在被使用的系統上,SIGINT的值為2,SIGQUIT的值為3,所以給了我們130和131的終止狀態。

讓我們嘗試一個相似的例子,但是這次我們將直接向外殼發送一個信號並看system返回了什麼:

$ ./tsys "sleep 30" &
$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
tommy     8956  8949  0 12:04 pts/0    00:00:00 bash
tommy     9122  8956  0 12:23 pts/0    00:00:00 sh
tommy     9135  9122  0 12:25 pts/0    00:00:00 ./tsys sleep 30
tommy     9136  9135  0 12:25 pts/0    00:00:00 sh -c sleep 30
tommy     9137  9136  0 12:25 pts/0    00:00:00 sleep 30
tommy     9138  9122  0 12:25 pts/0    00:00:00 ps -f
$ kill -KILL 9136 (殺死“sh -c sleep 30”)
$ Killed
abnormal termination, signal number = 9

這裡,我們可以看到system的返回值只當外殼自身異常終止時報告一個異常終止。如果殺死“sleep 30”而不是“sh -c sleep 30”:

$ ./tsys "sleep 30" &
$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
tommy     8956  8949  0 12:04 pts/0    00:00:00 bash
tommy     9356  8956  0 12:47 pts/0    00:00:00 sh
tommy     9357  9356  0 12:47 pts/0    00:00:00 ./tsys sleep 30
tommy     9358  9357  0 12:47 pts/0    00:00:00 sh -c sleep 30
tommy     9359  9358  0 12:47 pts/0    00:00:00 sleep 30
tommy     9360  9356  0 12:47 pts/0    00:00:00 ps -f
$ kill -KILL 9359
$ Killed
normal termination, exit status = 137

一個使用system函數的程序時,要確保正確地解釋返回值。如果你調用fork、exec和wait,終止狀態和你調用system時的並不相同。

Copyright © Linux教程網 All Rights Reserved