原文:https://blog.csdn.net/cdcdsfs/article/details/51155670
- 首先我们来看一下,实现system函数的一些要求:
a.在执行system的时候,应该屏蔽父进程的SIGINT,SIGQUIT信号
为什么要这么做呢?
原因:用白话来讲,就是让system函数在执行的时候,获取子进程状态的不受干扰.如果有兴趣的话,可以看一下APUE那本书上的解释,我这里不在赘述.
b.在实现system函数的时候,应该屏蔽SIGCHLD信号,在最后再解开
那么为什么呢?
第一点原因:出在waitpid/wait函数上,(先声明一点,waitpid/wait的返回与SIGCLHD信号没有必然的关系)如果在调用system函数的外面注册了关于SIGCHLD的信号处理函数,waitpid/wait函数会等到信号处理函数结束之后,才会返回,所以如果信号处理函数执行的时间过长,那么对system这个函数是不利的.
第二个原因:
如果在外面有信号处理函数要捕捉SIGCHLD信号的话,我们在先前如果没有屏蔽SIGCHLD信号的话,在信号处理函数里面调用了waitpid()/wait()函数的话(很多种情况下都会这么做),那么在信号处理函数里面就将子进程的资源释放了,在system内部的waitpid()就因为获得不到子进程的状态信息,从而返回-1;
如果你有为什么在system内部有waitpid()函数的调用的疑问的话,我的解释是这样的,如果在外面没有调用waitpid怎么办?
下面我们来看一下,system实现的具体代码(有处理信号的方式)
int mysystem(const char* command)
{
pid_t pid;
int status;
struct sigaction ignore,saveintr,savequit;
sigset_t childmask,savemask;
if(str == NULL)
{
return 1;
}
//首先不能让主进程接收到SIGINT,SIGQUIT信号
ignore.sa_handler = SIG_IGN;
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(&childmask);
sigaddset(&childmask,SIGCHLD);
if(sigprocmask(SIG_BLOCK,&childmask,&savemask)<0)
{
return -1;
}
print_signal("begin:");
pid = fork();
if(pid < 0)
{
return -1;
}
if(pid == 0)
{//在子进程中恢复SIGINT,SIGQUIT的先前状态
sigaction(SIGINT,&saveintr,NULL);
sigaction(SIGQUIT,&savequit,NULL);
sigprocmask(SIG_SETMASK,&savemask,NULL);
execl("/bin/sh","sh","-c",command,NULL);
_exit(127); //是为了防止execl出错的时候用的,如果execl成功则不会执行这条语句
}
else
{
while(waitpid(pid,&status,0)<0) //waitpid与SIGCHLD没有必然的关系
{
if(errno != EINTR)
{
status = -1;
break;
}
}
}
}
//恢复各种信号到调用system之前的状态
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函数的具体实现。
那么我们下面就来讨论一下实现的一些细节,
a.为什么在父进程中要判断errno与EINTR之间的关系?
首先,我先说明一下,EINTR错误是怎么产生的:在WNOHANG(不阻塞,用于waitpid())没有被设置(也就是说waitpid()是在堵塞的情况下),一个不阻塞的信号或者是SIGCHLD信号,被捕捉了,那么waitpid将errno置成EINTR
其次,我们将errrno与EINTR相比的原因是:在实现system的过程中,我们要避免在子进程结束后,由于其他信号被捕捉而引起返回的waitpid的这种效果(前面也提过waitpid的返回与SIGCHLD没有必然的关系),这里的waitpid意义,我觉得是在等待子进程的结束,如果是因为这里的waitpid将错误原因为ENITR的时候,我们需要重试waitpid,因为我们在前面已经屏蔽了SIGCHLD信号,所以这里不可能是捕捉到的是SIGCHLD信号。如果不是EINTR,那就说明是真的出问题了,我们将status改编成-1,这也对应了我们system的返回值需要(在下面会有讲到)。
system这个函数在command有效的时候,有三种情况的返回值
1.如果fork,waitpid函数出错(除了EINTR)以外,都回返回-1
第一种情况最容易理解,因为我们在实现的时候,就是这么写的嘛,没有什么可说的。
2.如果是execl的问题的话,那么就会返回127
第二种情况的话,就是如果execl不成功,那么肯定会继续向下执行,然后执行了_exit(127)这条语句,自然就会返回127
3.否则其他时候,返回的都是shell脚本的状态值(status值)
首先,我们应该理解一点的就是,由于fork出来的子进程,在其执行的时候,就被execl替换了,那么子进程的执行路线,也就自然而然的走向execl所指定的道路,也就是说execl语句后面的语句,子进程不会再执行了.
至于为什么会这样呢?
我是这样认为的,exec函数族的作用就是用于替换进程的用户空间的,在执行execl的路线的时候,因为在每个可执行的二进制程序生成的时候,我们的编译器都回在代码末尾添加exit(0),用来冲洗缓冲区,关闭一些资源用的.所以在执行完execl之后,就肯定会执行到exit(0)这条语句,那么该子进程就会结束.从而就不会执行下面的语句了,在换句话来说,就是不会再返回原程序中了.所以我们在使用waitpid()获得status的时候,获得的是execl函数替换的shell脚本的返回值(状态);
参考链接:
http://blog.csdn.net/astrotycoon/article/details/40626355
http://blog.chinaunix.net/uid-24774106-id-3048281.html?page=3