在8.13节,我们展示了一个system函数的实现。然而,那个版本没有处理信号。POSIX.1要求system忽略

SIGINT和SIGQUIT并阻塞SIGCHLD。在展示正确处理这些信号的版本之前,我们看下为什么需要担心这些信号的处理。


下 面的代码使用了8.13节的system版本来调用ed编辑器。(这个编辑器作为UNIX系统的一部分已经有很长时

间了。我们在这里使用它是因为它是一个 捕获中断和退出信号的交互式程序。如果我们调用一个外壳并输入中

断符,那么它捕获这个中断符并打印一个问号。ed程序也设置了退出信号的布署以便它被忽 略。)

#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);
}


上面的代码同时捕获了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函数的一个实现:


#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);
}


如果我们使用这个版本的system,得到的结果和前面(有缺陷的)那个的结果不同在于:

1、没有信号被发送给调用进程,当我们输入中断和退出符;

2、当ed命令退出时,SIGCHLD不会被发送到调用进程。事实上,它被阻塞,直到我们在最后一个sigprocmask


的调用里反阻塞它,在system函数通过调用waitpid得到子进程的终止状态之后。

POSIX.1 指出如果wait或waitpid在SIGCHLD待定时返回了一个子进程的状态,那么SIGCHLD不应该被分发给进

程,除非另一个子进程的状态也可 用。本书的四个实现没有一个实现了这个语义。相反,在system函数调用

waitpid后SIGCHILD仍保持待定;当信号被反阻塞时,它被分发给了 调用者。如果我们在sig_chld里调用

wait,它将返回-1,errno被设为ECHILD,因为system函数已经得到了子进程的终止状态。


许多老的书本都用如下方式忽略中断和退出信号:


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);


这个代码的问题是我们不能保证在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时的并不相同。


使用system函数时应该忽略两个信号相关推荐

  1. system函数的总结

    最近在看APUE第10章中关于system函数的POSIX.1的实现.关于POSIX.1要求system函数忽略SIGINT和SIGQUIT,并且阻塞信号SIGCHLD的论述,理解得不是很透彻,本文就 ...

  2. 信号与系统:利用Matlab实现两个信号的卷积

    在泛函分析中,卷积.旋积或摺积(英语:Convolution)是通过两个函数f 和g 生成第三个函数的一种数学算子,表征函数f 与g经过翻转和平移的重叠部分函数值乘积对重叠长度的积分.卷积积分是一种特 ...

  3. python: ipython 控制台粘贴函数时 如何控制缩进?粘贴类时,如何忽略回车?

    python是一门很友好的语言,节约人的时间. 解释型语言,有时候在控制台操作点东西,也很方便.  大多数情况下 iPython 控制台比python好用,比如手敲代码换行时 会自动缩进等等. IPy ...

  4. system()函数实现

    2019独角兽企业重金招聘Python工程师标准>>> system()函数功能强大,很多人用却对它的原理知之甚少,也就有了上面那么多的回帖,我想大家如果知道了 system的具体实 ...

  5. Linux下使用system()函数一定要谨慎

    转载自:http://my.oschina.net/renhc/blog/53580 linux尽量避免使用system. 曾经的曾经,被system()函数折磨过,之所以这样,是因为对system( ...

  6. linux windows c system 函数简介

    windows 在windows下的system函数中命令可以不区别大小写!  功 能: 发出一个DOS命令 #include <stdlib.h>int system(char *com ...

  7. linux下system函数的深入理解

    这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数中调用的命令也都一切正常.就没理这个bug,以为 ...

  8. exec族函数、system函数、popen函数、PATH

    exec族函数函数的作用: 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序.当进程调用exec函数时,该进程被完全替换为新程序(在exec都后面的代码不会被得到执行 ...

  9. 【C/C++】Linux下使用system()函数一定要谨慎

    曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原 ...

最新文章

  1. S - Extended Traffic LightOJ - 1074
  2. [转]SQL SERVER – Find Most Expensive Queries Using DMV
  3. python opencv图片编码为h264文件
  4. Python调用HTTP接口并传递cookie
  5. c语言继续程序指令,C语言预处理程序
  6. html模板 循环里if,django模板里循环变量table里想要两个一行如何控制
  7. 听我的!美国科技公司这样做Code Review
  8. vue加跨域代理静态文件404_解决vue本地环境跨域请求正常,版本打包后跨域代理不起作用,请求不到数据的方法——针对vue2.0...
  9. FLASH的知识【转】
  10. 牛逼!它比传统数据库快 100-1000,真不相信?
  11. Unity如何调用安卓手机摄像头实现拍照和录像
  12. 中国智能家居企业出海,亚马逊云科技为其提供“GPS锦囊”
  13. 淘宝新版打标足迹在哪里浏览?
  14. 129. 求根节点到叶节点数字之和
  15. m4s格式转换mp3_如何将m4a无损转换mp3音频格式
  16. Typora Emoji图标
  17. 我的为人处事真的有问题吗?
  18. 咨询答疑:从产品设计到康威定律
  19. 用Vue实现腾讯新闻页面
  20. 良心的vscode主题推荐

热门文章

  1. CollegeStudent
  2. 快速让你明白Objective-C的语法(和Java、C++对比)
  3. Java Swing 之Timer配合JProgressBar的使用
  4. LDA基本介绍以及LDA源码分析(BLEI)
  5. WINDOWS SERVER 2003 AD中的5种操作主机
  6. Merry Christmas Happy New Year!!
  7. TINYINT,SMALLINT,MEDIUMINT,INT,INTEGER,BIGINT;text,longtext,mediumtext,ENUM,SET等字段类型区别
  8. 算法设计:UNION-FIND算法实现
  9. AI设计师“鹿班”核心技术公开:如何1秒设计8000张海报?
  10. mac finder变慢解决办法