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”之类的脚本
在这个小程序的运行时,可以通过ps -aux 看到新增加了三个进程。
一个是我们程序本身a.out
一个是shell进程:sh -c ***
一个是我们执行的命令行进程:sleep进程
system函数的实现:
《APUE》中给出了system的一中实现:
#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) { /* 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);
}
很多人对这个实现有不少的疑问。疑问点主要在下面几个方面:
首先:为什么要忽略中断信号SIGINT和停止信号SIGQUIT。
其次:为什么要锁住子进程结束信号SIGCHLD
另外:为什么锁住了子进程结束信号SIGCHLD后,waitpid还能正确返回。
1. 为什么要忽略中断信号SIGINT和停止信号SIGQUIT。
前面已经介绍,./a.out执行时,产生了三个进程:a.out、(shell进程)sh -c、system调用的命令行产生的进程(如sleep 5, 在apue中是ed进程)。
如果不忽略SIGINT和SIGQUIT会出现上面情况呢,《APUE》中通过自己设计的不屏蔽SIGINT和SIGQUIT信号的实现进行了测试,发现SIGINT和SIGQUIT信号会向./a.out产生的三个进程发送,即会发送给a.out、sh -c、ed。(其中sh -c默认忽略此信号),这样会捕获信号的有a.out、ed。这样在实际运行中会出现上面情况呢:在我们要通过Ctl+c关闭ed(这种交互式程序经常会用ctl+c来关闭)时,信号也被发送给了a.out,也就是a.out和ed都被关闭了,这样可能a.out还有其他工作也不能完成了。
出于以上的原因,我们要求system屏蔽SIGINT和SIGQUIT。
2.为什么要锁住子进程结束信号SIGCHLD
首先明白,如果不锁住SIGCHLD,那么在system执行的命令行创建的子进程(如ed)结束时,会向./a.out也发送SIGCHLD信号。这会产生什么问题呢?
加入我们在调用system之前,./a.out创建了一个子进程(子进程A),并且是通过wait函数等待他结束。但是我们的ed进程结束时就像a.out发送了SIGCHLD,导致wait直接返回,由此a.out就无法等到子进程A结束了。
3.为什么锁住了子进程结束信号SIGCHLD后,waitpid还能正确返回。
这一部分我还没找到确切的证据,但描述的也都是一些合理的推断:
我认为wait和waitpid并不是通过捕获SIGCHLD来返回子进程结束状态的。理由如下:
a.Linux系统对SIGCHLD的默认处理方式就是ignore
b.经常看到一些code里,捕获SIGCHLD,然后再SIGCHLD的信号处理函数中调用wait,来使得父进程不阻塞。
c.waitpid中的option选项为WNOHANG时,waitpid可以立即返回。也就是可以循环调用waitpid来判断子进程是否退出。
基于以上三点,基本上可以退出waitpid并不是通过捕获SIGCHLD来判断子进程状态的。而应该是通过阻塞读取内核某个状态值