在http://www.CUOXin.com/nufangrensheng/p/3512291.html中已經有了一個system函數的實現,但是該版本并不執行任何信號處理。POSIX.1要求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD。
實例
程序清單10-19使用http://www.CUOXin.com/nufangrensheng/p/3512291.html中的system版本,用其調用ed(1)編輯器。(ed很久以來就是UNIX的組成部分。在這里調用它的原因是:它是捕捉中斷和退出信號的交互式程序。若 從shell調用ed,并鍵入中斷字符,則它捕捉中斷信號并打印問號。它還將對退出符的處理方式設置為忽略。
程序清單10-19 用system調用ed編輯器
#include "apue.h"static voidsig_int(int signo){ PRintf("caught SIGINT/n");}static voidsig_chld(int signo){ printf("caught SIGCHLD/n");}intmain(void){ if(signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); if(signal(SIGCHLD, sig_chld) == SIG_ERR) err_sys("signal(SIGCHLD) error"); if(system("/bin/ed") < 0) err_sys("system() error"); exit(0);}
程序清單10-19用于捕捉SIGINT和SIGCHLD信號。若調用它則可得:
當編輯器終止時,系統向父進程(a.out進程)發送SIGCHLD信號。父進程捕捉它,然后從信號處理程序返回。但是若父進程正在捕捉SIGCHLD信號(因為它創建了子進程,所以應當這樣做以便了解它的子進程在何時終止),那么正在執行system函數時,應當阻塞對父進程遞送SIGCHLD信號。實際上,這就是POSIX.1所說明的。否則,當system創建的子進程結束時,system的調用者可能錯誤地認為,它自己的一個子進程結束了。于是,調用者將會調用一種wait函數以獲得子進程的終止狀態,這樣就阻止了system函數獲得子進程的終止狀態,并將其作為它的返回值。
如果再次執行該程序,在這次運行時將一個中斷信號傳送給編輯器,則可得:
鍵入中斷字符可使中斷信號傳送給前臺進程組中的所有進程。編輯程序正在運行時的各個進程的關系:
登錄shell ---fork/exec---> a.out ---fork/exec---> /bin/sh ---fork/exec---> /bin/ed
后臺進程組 前臺進程組
a.out ---fork/exec---> /bin/sh 是由a.out中調用system函數引起的:http://www.CUOXin.com/nufangrensheng/p/3512291.html
/bin/sh ---fork/exec---> /bin/ed 可參考類shell程序的簡化實現程序清單1-5:http://www.CUOXin.com/nufangrensheng/p/3495129.html
在這一實例中,SIGINT被送給三個前臺進程(shell進程忽略此信號)。從輸出中可見a.out進程和ed進程捕捉該信號。但是,當用system運行另一程序(例如ed)時,不應使父子進程兩者都捕捉中斷產生的兩個信號:中斷和退出。這兩個信號只應送給正在運行的程序:子進程。因為由system執行的命令可能是交互式命令(例如本例中的ed程序),以及因為system的調用者在程序執行時放棄了控制,等待該執行程序的結束,所以system的調用者就不應接收這兩個終端產生的信號。這就是為什么POSIX.1規定system的調用者應當忽略這兩個信號的原因。(糊里糊涂的感覺)
實例
程序清單10-20 system函數的POSIX.1正確實現
#include <sys/wait.h>#include <errno.h>#include <signal.h>#include <unistd.h>intsystem(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) { /* restore previous signal actions & reset signal mask */ sigaction(SIGINT, &saveintr, NULL); sigaction(SIGQUIT, &savequit, NULL); sigprocmask(SIG_SETMASK, &savemask, NULL); /* 在父進程中忽略SIGINT,SIGQUIT,阻塞SIGCHLD,在子進程中恢復 */ execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); _exit(127); /* exec error */ } else { 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); /* 子進程退出后,父進程才恢復SIGINT,SIGQUIT和SIGCHLD */ return(status);}
如果鏈接程序清單10-19與system函數的這一實現,那么所產生的二進制代碼與上一個有缺陷的程序相比較,存在如下差別:
(1)當我們鍵入中斷或退出字符時,不向調用者進程發送信號。
(2)當ed命令終止時,不向調用進程發送SIGCHLD信號。作為替代,在程序末尾的sigprocmask調用對SIGCHLD信號解除阻塞之前,SIGCHLD信號一直被阻塞。而對sigprocmask函數的這一次調用是在system函數調用waitpid取到子進程的終止狀態之后。
system的返回值
注意system的返回值,它是shell的終止狀態,但shell的終止狀態并不總是執行命令字符串進程的終止狀態。
Bourne shell有一個在其文檔中沒有說清楚的特性:當用一個信號終止了正在執行的命令時,其終止狀態是128加上一個信號編號。
用交互方式使用shell可以看到這一點:
在所使用的系統中,SIGINT的值為2,SIGQUIT的值為3,于是給出shell終止狀態130、131.
僅當shell本身異常終止時,system的返回值才報告一個異常終止。
在編寫使用system函數的程序時,一定要正確地解釋返回值。如果直接調用fork、exec和wait,則終止狀態與調用system是不同的。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答