10.18 system函数

在8.13节中,我们展示了一个system函数的实现,在哪一个版本中,我们并没有对信号做任何处理。POSIX.1要求system函数需要忽略SIGINT以及SIGQUIT信号,并且阻塞信号SIGCHLD.在展示一个正确处理这些信号的函数实现之前,让我们一起看一下为什么需要担心信号的处理。

Example

图10.26中显示的例子使用了8.13中的system函数调用了函数vi(1)编辑器.

  1. #include "apue.h"
  2. static void sig_int(int signo)
  3. {
  4. printf("caught SIGINT\n");
  5. }
  6. static void sig_child(int signo)
  7. {
  8. printf("caught SIGCHLD\n");
  9. }
  10. int main(void)
  11. {
  12. if(signal(SIGINT, sig_int) == SIG_ERR)
  13. err_sys("signal(SIGINT) error");
  14. if(signal(SIGCHLD, sig_child) == SIG_ERR)
  15. err_sys("signal(SIGCHLD) error");
  16. if(system("vi tempfile") < 0)
  17. err_sys("system() error");
  18. exit(0);
  19. }

Figure 10.26 Using system to invoke the vi editor

如果我们调用这个程序并在vi程序退出之后,执行效果如下图所示:

  1. [email protected]:~/UnixProgram/Chapter10$ ./10_26.exe
  2. caught SIGCHLD

可见当vi终止的时候,系统发送了一个信号SIGCHLD到了父进程,我们成功捕获到了该信号并且执行了信号处理函数。但是如果父进程正在捕获SIGCHLD信号,比如说父进程还另外创建了子进程且正在等待子进程的终止,那么附近和那个就应该这样做,以至于它直到其子进程合适结束。在父进程中该信号的发送应该在system函数的执行期间被阻塞,实际上,这也正是POSIX.1所要求的。否则的话,system执行的子进程可能会欺骗调用进程认为其自身的子进程终止了。

在9.6节中提到,键入中断字符将会造成中断信号被发送到前台进程组的所有进程。图10.27显示了当编辑器(书中使用的是/bin/ed,而不是vi)正在运行的时候进程的安排。

如果键入中断按键,那么SIGINT信号就会被发送到全部三个前台进程.(shell进程将会忽略该信号),即是说a.out以及编辑器都将会捕获到这一信号,但是当我们正在使用system运行其他程序的时候,我们不应该让父进程和子进程都捕获到中断产生的信号:中断和停止。取而代之的是:这两个信号应该被发送到正在运行的进程:也就是子进程编辑器,因为system执行的命令可能是一个交互式命令(比如说本例中的ed程序),因为system的调用进程在子进程运行的时候放弃了控制权,等待者子进程的结束,因此system的调用进程不应该搜到这两个信号。基于这个原因,POSIX.1指出system函数在等待命令完成的期间应该忽略这两个信号。

Example

  1. #include <sys/wait.h>
  2. #include <errno.h>
  3. #include <signal.h>
  4. #include <unistd.h>
  5. int system(const char *cmdstring) /*with appropriate signal handling*/
  6. {
  7. pid_t pid;
  8. int status;
  9. struct sigaction ignore, saveintr, savequit;
  10. sigset_t chldmask, savemask;
  11. if(cmdstring == NULL)
  12. {
  13. return (1); /* always a command processor with UNIX */
  14. }
  15. ignore.sa_handler = SIG_IGN; /*ignore SIGINT and SIGQUIT*/
  16. sigempty(&ignore.sa_mask);
  17. ignore.sa_flags = 0;
  18. if(sigaction(SIGINT, &ignore, &saveintr) < 0)
  19. return -1;
  20. if(sigaction(SIGQUIT, &ignore, &savequit) < 0)
  21. return -1;
  22. sigemptyset(&chldmask); /*now block ISGCHLD*/
  23. sigaddset(&chldmask, SIGCHLD);
  24. if(sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
  25. return -1;
  26. if((pid = fork()) < 0)
  27. status = -1; /*probably out of processes */
  28. else if(pid == 0)
  29. {
  30. /* child */
  31. /*restore previous signal actions * reset signal mask*/
  32. sigaction(SIGINT, &saveintr, NULL);
  33. sigaction(SIGQUIT, &savequit, NULL);
  34. sigprocmask(SIG_SETMASK, &savemask, NULL);
  35. execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
  36. _exit(127);
  37. }
  38. else
  39. {
  40. while(waitpid(pid, &status, 0) < 0)
  41. {
  42. if(errno != EINTR)
  43. {
  44. status = -1; /*error other than EINTR from waitpid()*/
  45. break;
  46. }
  47. }
  48. }
  49. /*restore previous signal actions & reset signal mask*/
  50. if(sigaction(SIGINT, &saveintr, NULL) < 0)
  51. return -1;
  52. if(sigaction(SIGQUIT, &savequit, NULL) < 0)
  53. return -1;
  54. if(sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)
  55. return -1;
  56. return status;
  57. }

Figure 10.28 正确的POSIX.1的system函数实现

如果我们将图10.26中的程序与上述程序实现的system相链接,生成的二进制代码与之前的又如下差异:

  1. 当我们输入中断或者停止信号的时候,不会有信号被发送到调用进程;
  2. 当ed命令退出的时候,SIGCHLD并不会被发送到调用进程,直到我们在system函数中通过调用函数waitpid接收到父进程的终止状态以后,在最后调用函数sigprocmask解除阻塞。

POSIX.1声明如果函数wait或者是waitpid返回了子进程的状态,而此时信号SIGCHLD被挂起,那么信号SIGCHLD将不会被发送到进程,除非另外一个子进程的状态再次发出信号SIGCHLD.FreeBSD8.0,Mac OS X10.6.8以及Solaris 10全部都实现了上述定义,但是对于Linux3.2.0而言,并没有按照上述规定实现,也就是说在system函数调用waitpid以后,SIGCHLD仍然保持挂起状态,当信号被接触阻塞的时候,将被发送到调用进程,如果我们在sig_chld函数中调用wait函数的话(图10.26中的),Linux系统将会返回-1并且errno将被设置为ECHLD,因为system函数已经收到了子进程的终止状态。

许多早期的书中展示的终端与停止信号的忽略代码如下所示:

  1. if((pid = fork()) < 0)
  2. {
  3. err_sys(""fork error");
  4. }
  5. else if(pid == 0)
  6. {
  7. /*child*/
  8. execl(...);
  9. _exit(127);
  10. }
  11. /*parent*/
  12. old_intr = signal(SIGINT, SIG_IGN);
  13. old_quit = signal(SIGQUIT, SIG_IGN);
  14. waitpid(pid, &status, 0);
  15. signal(SIGINT, old_intr);
  16. signal(SIGQUIT, old_quit);

上述执行序列存在一个问题:我们不能保证fork之后是父进程先运行还是子进程先运行,如果子进程先运行,并且父进程在一段时间内没有运行,那么中断信号可能会在父进程能够改变其信号处理方式为忽略之前出现,基于这一原因,我们在图10.28所示的程序中,我们在调用fork函数之前就先修改了信号的处理函数。

注意,我们必须在子进程中调用函数execl之前将这两个信号的处理函数恢复,这允许execl函数将它们的处理基于调用进程的处理函数改为默认处理。正如在8.10节中讲述的那样。

Return Value from system

函数system的返回值是shell的终止状态,并不一定是命令行的终止状态,在图8.23中我们看到:如果我们执行简单命令比如说date,其终止状态是0,执行命令exit 44,其终止状态就是44.那么如果是信号终止了进程那么会发生什么呢?

让我们运行图8.24中的程序,并且发送一些信号到正在执行的命令:

  1. $ tsys "sleep 30"
  2. ^Cbormal termination, exit status = 130 ~~we press the interrupt key~~
  3. ¥tsys "sleep 30"
  4. ^\sh:946 Quit ~~we press the quit key~~
  5. normal termination, exit status = 131

当我们使用终止信号终止sleep命令执行的时候,函数pr_exit函数(图8.5)认为命令正常终止,当我们使用停止键终止sleep命令执行的时候,也会发生相同的事情。在这个例子中,可以看到,Bourne shell中显示功能比较弱,其终止状态是128加上信号编号,在编者使用的电脑上,SIGINT的数值是2,SIGQUIT信号的编号是3,因此系统给出的shell的终止状态时130以及131

让我们在执行一个类似的例子,但是这次我们将直接发送一个信号给shell,看一下system函数将会返回什么:

  1. $ tsys "sleep 30" &
  2. 9257
  3. $ ps -f
  4. UID PID PPID TTY TIME CMD
  5. sar 9260 949 pts/5 0:00 ps-f
  6. sar 9258 9257 pts/5 0:00 sh -c sleep 30
  7. sar 949 947 pts/5 0:01 /bin/sh
  8. sar 9257 949 pts/5 0:00 tsys sleep 30
  9. sar 9259 9258 pts/5 0:00 sleep 30
  10. $ kill -KILL 9258
  11. abnormal termination, signal number = 9

在此,我们可以看出在shell本身异常终止的时候system的返回值报告了一次异常终止。

当写程序使用到了system函数的时候,注意正确地解释返回值,如果你调用了fork,exec自身,并wait。其终止状态与调用system函数并不相同。

来自为知笔记(Wiz)

时间: 2024-10-19 02:05:05

10.18 system函数的相关文章

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&quo

10.3 signal函数

UNIX系统的信号特性的最简单的接口就是signal函数: #include <signal.h> void (*signal(int signo, void(* func)(int)))(int); Returns:previous disposition of signal(see following)if OK,SIG_ERR on error. 函数signal由ISO C定义,并不涉及到多进程,进程组终端IO等等,因此,其信号的定义的模糊不清的,因此对于UNIX系统来说几乎没有用.

SUID或SGID程序中能不能用system函数

system()函数的声明和说明如下: 注意它的描述那里,system()执行一个由command参数定义的命令,通过调用/bin/sh -c命令来实现这个功能.也就是说它的逻辑是这样的! 进程调用system函数,system函数调用fork创建一个子进程,然后再调用exec函数来把这个子进程的正文段替换成/bin/sh命令的正文段.然后再由sh来执行exec将程序的正文段替换成command参数所代表的命令的正文段,例如,我的一个程序a.out来调用system函数来执行sleep 20命令

C语言中system()函数的用法总结(转)

system()函数功能强大,很多人用却对它的原理知之甚少先看linux版system函数的源码: 1 #include <sys/types.h> 2 #include <sys/wait.h> 3 #include <errno.h> 4 #include <unistd.h> 5 6 int system(const char * cmdstring) 7 { 8 pid_t pid; 9 int status; 10 11 12 if(cmdstri

system 函数

1.函数详解 system函数需加头文件<stdlib.h>后方可调用 功能:发出一个DOS命令用法:int system(char *command); 程序例: 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 printf("Run a DOS command\n"); 7 system("pause"); //暂停屏幕刷新 8 return 0;

6 10 18 32 下一个数?编程实现输入任意一个N位置,该数是多少?java实现

6 10 18 32 下一个数?编程实现输入任意一个N位置,该数是多少? 10 = 6 + 4         4 18 = 10 + 8        4 + 4 32 = 18 + 14       8 + 6 ? = 32 + 22       14 + 8 ? = 54 + 32       22 + 10 ? = 86 + 44       32 + 12 分析特点就是 f(x) = f(x-1)+ M; 其中M又是可递归的 4 8 14 22 f(N)=f(N-1)+2*N f(1)

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

转载自:http://my.oschina.net/renhc/blog/53580 linux尽量避免使用system. 曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点.当初因为这个函数风险较多,故抛弃不用,改用其他的方法.这里先不说我用了什么方法,这里必须要搞懂system()函数,因为还是有很多人用了system()

使用system()函数一定要谨慎-1

曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点.当初因为这个函数风险较多,故抛弃不用,改用其他的方法.这里先不说我用了什么方法,这里必须要搞懂system()函数,因为还是有很多人用了system()函数,有时你不得不面对它. 先来看一下system()函数的简单介绍: 1 #include <stdlib.h> 2 i

Linux下使用system()函数一定要谨慎 【转载】

出处:http://blog.csdn.net/kyokowl/article/details/8823334 C/C++]Linux下使用system()函数一定要谨慎 曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点.当初因为这个函数风险较多,故抛弃不用,改用其他的方法.这里先不说我用了什么方法,这里必须要搞懂syste