system分析

原文:https://blog.csdn.net/cdcdsfs/article/details/51155670

  1. 首先我们来看一下,实现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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值