第十章(二) sigsuspend、system等重要信号函数的使用

来源:互联网 发布:做账软件培训 编辑:程序博客网 时间:2024/06/05 17:01
函数    sigsuspend(sigset_t *mask)

    1、之前说过,更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞,这可以用来保护不希望由信号中断的代码临界区
    2、如果希望对一个信号解除阻塞,然后pause以等待以前被阻塞的信号发生,如何实现呢?
        若是直接  sigprocmask(); pause(); 这样可能会导致pause永久阻塞,所以可以使用此函数 sigsuspend。

    进程的信号屏蔽字设置为由 sigmask 指向的值。 在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回。并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。

    此函数经常和pause相比较。
    
    这段愣是没看懂,上网查了一下:
    sigsuspend的原子操作是:

    (1)设置新的mask阻塞当前进程(用参数替换当前屏蔽字,这里的参数屏蔽字包含SIGUSR1)

    (2)收到SIGUSR1信号,阻塞,程序继续挂起;收到其他信号,恢复原先的mask(即包含SIGINT信号的)。
    
    (3)调用该进程设置的信号处理函数(程序中如果先来SIGUSR1信号,然后过来SIGINT信号,则信号处理函数会调用两次,打印不同的内容。第一次打印SIGINT,第二次打印SIGUSR1,因为SIGUSR1是前面阻塞的)

    (4)待信号处理函数返回,sigsuspend返回了。(sigsuspend将捕捉信号和信号处理函数集成到一起了)

     (一)  、  Show how to protect the code area from being interrupted by unexpected signal.

//#include <stdio.h>#include <stdlib.h>#include <signal.h>void pr_mask(char *s){        printf("%s  ",s);        sigset_t set;        if(sigprocmask(0,NULL,&set) < 0)        {                perror("sigprocmask");                exit(1);        }        if(sigismember(&set,SIGUSR1))                printf("SIGUSR1");        if(sigismember(&set,SIGALRM))                printf("SIGALRM");        if(sigismember(&set,SIGINT))                printf("SIGINT");        putchar('\n');}void sig_int(int s){        pr_mask("\nin sig_int");}void sig_usr(int s){}int main(){        sigset_t new,old,wait;        pr_mask("program mask : ");        if(signal(SIGINT,sig_int) == SIG_ERR)        {                perror("signal");                exit(1);        }        if(signal(SIGUSR1,sig_usr) == SIG_ERR)        {                perror("signal");                exit(1);        }        sigemptyset(&wait);        sigaddset(&wait, SIGUSR1);        sigemptyset(&new);        sigaddset(&new, SIGINT);        //Block SIGINT and save curret mask        if(sigprocmask(SIG_BLOCK,&new,&old) < 0)            //在原有屏蔽字中添加SIGINT        {                perror("sigprocmask");                exit(1);        }        //Critical region of code        pr_mask("in critical region");                //通过结果发现现在SIGINT是应该被阻塞在外的        //Pause, allowing all signals except SIGUSR1        if(sigsuspend(&wait) != -1)                    //通过sigsuspend改变当前的屏蔽字为wait的值,现在我们发送SIGINT进程可以接收了        {                perror("sigsuspend");                exit(1);        }        pr_mask("After return from suspend : ");            //结果显示,这里仍然是SIGINT被包含在内        //reset signal mask which unblocks SIGINT        if(sigprocmask(SIG_SETMASK,&old,NULL) < 0)        {                perror("sigprocmask");                exit(1);        }        pr_mask("program exit : ");        return 0;}




结果为:
$ ./SSP &[1] 9123$ program mask :   in critical region  SIGINT            //在调用sigsuspend之前,我们屏蔽字包含了SIGINTkill -USR1 9123                //发送SIGUSR1信号给进程$ kill -USR1 9123                //连发好几个没有响应,确实被阻塞了$ kill -2 9123                //发送SIGINT给进程in sig_int  SIGUSR1SIGINT            //显示当前在信号处理函数中,现在的屏蔽字包含了 SIGINT和SIGUSR1.屏蔽SIGUSR1是因为当前仍处在sigsuspend的原子操作中。屏蔽                                SIGINT是因为signal函数调用的信号处理函数会自动屏蔽本信号After return from suspend :   SIGINT        //sigsuspend结束后将屏蔽字还原为之前的屏蔽字program exit :                       //调用sigprocmask还原最初屏蔽字[1]+  Done                    ./SSP



        由此可见,sigsuspend函数我们可以用来为某一段代码屏蔽某些信号,直到我们所希望的信号来临;
        或者说,我们之前屏蔽字中包含SIGINT,但现在我们希望解除该信号的阻塞,并挂起等待该信号的发生!

    (二)    、sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。

#include <stdio.h>#include <stdlib.h>#include <signal.h>#define errs(m) {perror(m);exit(1);}static int flag;void sig_handler(int s){        if(s == SIGINT)                printf("\ninterrupt\n");        else if(s == SIGQUIT)                flag = 1;}int main(){        sigset_t new,old,zero;        if(signal(SIGINT,sig_handler) == SIG_ERR)                errs("signal")        if(signal(SIGQUIT,sig_handler) == SIG_ERR)                errs("signal")        sigemptyset(&new);        sigemptyset(&zero);        sigaddset(&new,SIGQUIT);        if(sigprocmask(SIG_BLOCK,&new,&old) < 0)                errs("sigprocmask");        while(flag == 0)                                sigsuspend(&zero);                        //不阻塞任何信号,但是只有我的要求达到后才不再阻塞主程序(我觉着这里跟pause一样用啊!)                                            //唯一的区别在于这里可以不受主程序的屏蔽字影响(主程序阻塞SIGQUIT这里都通行)            flag = 0;        if(sigprocmask(SIG_SETMASK,&old,NULL) < 0)                errs("sigprocmask");        return 0;}



结果是:
$ ./SSP2^C                                        //其他任何信号都不能唤醒主程序interrupt^Cinterrupt^Cinterrupt^Cinterrupt^Cinterrupt^Cinterrupt^Cinterrupt^\$                                //除了SIGQUIT

    (三)    、 可以用信号实现父进程、子进程之间的同步
        这里实现的是第八章中关于竞争条件的部分中用信号通知对方的函数:
            即,在fork之后,父进程和子进程都有一些事情要做。这里要求每个进程在执行完它的一套初始化操作后要通知对方,并且在继续运行之前,要等待另一方完成其初始化操作。
        用代码描述即为:

TELL_WAIT();            //set things up for TELL_XXX and WAIT_XXX
if((pid=fork()) < 0)
    err("fork");
else if(pid == 0)
{
    //child does whatever is necessary
    TELL_PARENT(getpid());        //tell parent we're done
    WAIT_PARENT();            //and wait for parent
    //child continues on its way
    exit(0);
}
//parent does whatever is necessary...
TELL_WAIT(pid);                //tell child that we're done
WAIT_CHILD();                //and wait for child
//and parent continues on its way
exit(0);

    比如父子进程都要输出一个字符串,我要求父进程先输出:

#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    TELL_WAIT();
    
    if((pid=fork()) < 0)
        err("fork");
    else if(pid == 0)
    {
        WAIT_PARENT();
        Output_str("hehe");
    }
    Output_str(haha);
    TELL_CHILD(pid);
    exit(0);
    return 0;
}

    那么这之间的 TELL_WAIT, TELL_PARENT, WAIT_CHILD 等函数是如何实现的呢?

static int sigflag;

sigset_t new,old,zero;

void sig_usr(int s)
{
    sigflag = 1;
}

void TELL_WAIT()
{
    if(signal(SIGUSR1,sig_usr) == SIG_ERR)
        err("signal");
    if(signal(SIGUSR2,sig_usr) == SIG_ERR)
        err("signal");
    
    sigemptyset(&new);
    sigemptyset(&zero);
    sigaddset(&new, SIGUSR1);
    sigaddset(&new, SIGUSR2);
    if(sigprocmask(SIG_BLOCK,&new,&old) < 0)
        err("sigprocmask");
}

void TELL_CHILD(pid_t pid)
{
    kill(pid, SIGUSR1);
}

void TELL_PARENT(pid_t pid)
{
    kill(pid, SIGUSR2);
}

void WAIT_CHILD()
{    
    while(sigflag == 0)
        sigsuspend(&zero);
    sigflag = 0;            //重置这个值,以为它是共用的
    
    if(sigprocmask(SIG_SETMASK,&old,NULL) < 0)
        err("sigprocmask");
}

void WAIT_PARENT()
{    
    while(sigflag == 0)
        sigsuspend(&zero);
    sigflag = 0;            //重置这个值,以为它是共用的
    
    if(sigprocmask(SIG_SETMASK,&old,NULL) < 0)
        err("sigprocmask");
}

    写完这几个程序,两点要注意:
        1、 fork出的子程序信号处理函数也还是可以用的,但是如果调用了 exec 就不行了
        2、  为什么 sigsuspend 要和 sigprocmask 一起使用呢?    
            因为如果没有sigprocmask函数的存在的话,若在执行sigsuspend之前函数就接收到了指定信号,假设信号只会被发来一次,那么这个sigsuspend就会永久阻塞在那里了。但是,如果我们使用了 sigprocmask 函数,那么若在sigsuspend之前发生了该信号,该信号会被阻塞,又因为 sigsuspen 里面的一系列原子操作,进程就会接收到那个被阻塞的信号,而不至于丢失该信号了! 不过,钻个牛角尖,信号难道不会在sigprocmask之前被发过来吗 。。。



函数    abort
    使程序异常终止,将 SIGABRT发送给调用进程


函数    system
    当然,在第八章(三) 中我们提及过system函数,但那时的system函数是不完整的,欠缺对信号的处理。

    第八章system如下
int system( const char * cmdstring)
    {
        pid_t        pid;
        int         status;

        if( cmdstring == NULL)
            return(1);

        if( ( pid = fork()) < 0 )
            status = -1;
        else if( pid == 0 )
        {
            execl("/bin/sh","sh","-c",cmdstring,(char *)0);
            _exit(127);
        }
        else
        {
            while( waitpid(pid, &status, 0) < 0 )
            {
                if(errno != EINTR)    
                {
                    status = -1;
                    break;
                }
            }
        }
        return status;
    }
    1、由于system函数的实现基本原理是使用fork函数创建一个子进程,用子进程调用exec函数,之后将子进程运行的内容替换成了目标程序。如果不阻塞SIGCHLD信号,那么如果在调用system函数之前还创建了一个其它的子进程,那么当system函数中fork创建的子进程结束后会给父进程发送SIGCHLD信号,如果此时父进程设置的信号处理方式是捕捉而且在信号处理函数中调用了wait函数,那么system函数就无法根据其函数中创建的子进程返回状态获取相应的返回值。记得有这样的规定,system函数的返回应该是函数中创建子进程的返回状态。所以为了【能保证system能够正确获取system函数中创建的子进程返回状态】,SIGCHLD信号必须被阻塞
    2、如果system中调用的进程是交互式的,那么当我按下ctrl+c的时候,SIGINT就会被发送至前台所有进程,但我们的目的仅仅是终止这个正在运行的前台子进程。

    因此,我们将system修改至以下的样子:

#include <stdio.h>#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

int system(char *cmd)
{
        pit_t pid;
        struct sigaction ignore,saveint,savequit;
        sigset_t set,save;
        int status;

        if(cmd == NULL)
                return 1;

        ignore.sa_handler = SIG_IGN;
        ignore.sa_flags  = 0;
        sigemptyset(&ignore.sa_mask);

        if(sigaction(SIGINT,&ignore,&saveint) < 0)
                return -1;
        if(sigaction(SIGQUIT,&ignore,&savequit))
                return -1;                        //父进程在system期间忽略这两个信号

        sigemptyset(&set);
        sigaddset(&set,SIGCHLD);
        if(sigprocmask(SIG_BLOCK,&set,&save) < 0)            //并且阻塞这个SIGCHLD信号以免影响system函数获取system函数的子进程的终止状态
                return -1;

        if((pid=fork()) < 0)
        {
                status = -1;
        }
        else if(pid == 0)
        {
                sigaction(SIGINT,&saveint,NULL);
                sigaction(SIGQUIT,&savequit,NULL);
                sigprocmask(SIG_SETMASK,&save,NULL);
                execl("/bin/sh","sh","-c",cmd,(char*)0);
                exit(1);
        }
        else
        {
                while(waitpid(pid,&status,0) < 0)
                        if(errno == EINTR)
                        {
                                status  = -1;
                                break;
                        }
        }

        if(sigaction(SIGINT,&saveint,NULL) < 0)                //恢复至system函数调用之前的状态
                return -1;
        if(sigaction(SIGQUIT,&savequit,NULL) < 0)
                return -1;
        if(sigprocmask(SIG_SETMASK,&save,NULL) < 0)
                return -1;
}

    这里我们就要问了,既然是阻塞,那最后还不是要发到父进程去吗?
        POSIX.1说明:在SIGCHLD is pending ,且wait或waitpid返回了子进程的状态,那么SIGCHLD信号不再递送给父进程
        虽然linux3.2.0没有实现这种语义,但在解除对SIGCHLD的阻塞后,它将被送至父进程,且wait将返回-1,并将errno设置为 ECHLD。因为终止状态已经被取走了。

    只是还有个地方我没懂,之前不是说exec函数会使信号处理程序变成默认处理嘛,而且我也尝试过确实如此,这里为什么要再次调用sigaction呢?有什么意义吗?
        书上说这样做:    这就允许在调用者配置的基础上,execl可将它们的配置更改为默认值.
            ---没看懂

    注意system的返回值,它是shell的终止状态,但shell的终止状态并不总是执行命令字符串进程的终止状态。

    这里以后用到再看书  P297


函数    sleep、 nanosleep、 clock_nanosleep
    本章前面一部分有谈及 sleep 函数的实现,调用了 setjmp和 longjmp,因为担心alarm产生的信号与pause函数的竞争关系。
    现在,我们重新认识以下这个函数:
    此函数使调用进程被挂起直到满足以下两个条件之一:
        1、已经过了 seconds 所指定的墙上时钟的时间。    返回值是0
        2、调用进程捕捉到一个信号并从信号处理程序返回。    返回值是未休眠完的秒数

    之前我们用alarm函数实现sleep, 但是这不是必须的。以下先用我们前面所学的几个函数处理sleep函数中可能出现的竞争条件:

void sig_alrm_3(int s){        //nothing ,just to change the default operation}int sleep_3(unsigned int s){        int unsleep;        sigset_t new,old,sus;        struct sigaction newact,oldact;        newact.sa_handler = sig_alrm_3;        newact.sa_flags = 0;        sigemptyset(&sa_mask);        if(sigaction(SIGALRM,&newact,&oldact) < 0)        {                perror("sigaction");                exit(1);        }        sigemptyset(&new);        sigaddset(&new, SIGALRM);        sigprocmask(SIG_BLOCK,&new,&old);        sus = old;        sigdelset(&sus, SIGALRM);        alarm(s);        sigsuspend(&sus);                        //之前我们使用 sigsetjmp和siglongjmp与pause,避免了这之间存在的竞争条件;但是出现的新的问题,即SIGALRM和其他信号之间会影响(即第十章(一)中的 alarm和pause部分)                                        //这里我们并没有使用任何非局部转移,所以<span style="color:#FF0000;">对SIGALRM信号期间可能执行的其他信号处理程序没有任何影响</span>        unsleep = alarm(0);        sigaction(SIGALRM,&oldact,NULL);        sigprocmask(SIG_SETMASK,&old,NULL);        return unsleep;}



    这个函数的实现也完全可以理解了,因为之前也实现过好几次了,这两个 sigprocmask和sigsuspend的组合确实能解决一些问题。

    函数    nanosleep
        与sleep类似,但提供了納秒级的精度
        挂起进程后直到时间到或者某个信号中断了它。



函数    sigqueue(pid, signo, union sigval value)
    在之前的学习中,我们也发现系统不对信号进行排队
    要使用信号排队必须做以下几个操作:
        1、使用 sigaction 时候指定 SA_SIGINFO标志;若没给出,信号会延迟,但是否进入队列看具体实现。
        2、在sigaction中的 sa_sigaction中提供信号处理程序
        3、使用 sigqueue函数发送信号

    sigqueue只能把信号发送给单个进程,可以使用 value参数向信号处理程序传递整数和指针值,除此之外,sigqueue和kill相似。
    信号不能被无限排队,有限制。




0 0
原创粉丝点击