在http://www.cnblogs.com/nufangrensheng/p/3512291.html中已经有了一个system函数的实现,但是该版本并不执行任何信号处理。POSIX.1要求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD。
实例
程序清单10-19使用http://www.cnblogs.com/nufangrensheng/p/3512291.html中的system版本,用其调用ed(1)编辑器。(ed很久以来就是UNIX的组成部分。在这里调用它的原因是:它是捕捉中断和退出信号的交互式程序。若 从shell调用ed,并键入中断字符,则它捕捉中断信号并打印问号。它还将对退出符的处理方式设置为忽略。
程序清单10-19 用system调用ed编辑器
#include "apue.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) 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.cnblogs.com/nufangrensheng/p/3512291.html
/bin/sh ---fork/exec---> /bin/ed 可参考类shell程序的简化实现程序清单1-5:http://www.cnblogs.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> 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) { /* 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/。