zoukankan      html  css  js  c++  java
  • UNIX环境高级编程——sigqueue、sigsuspend函数

    一、sigqueue函数

    功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用

    int sigqueue(pid_t pid, int sig, const union sigval value);
    参数:

         sigqueue的第一个参数是指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

         在调用sigqueue时,sigval指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

    信号参数的传递过程可图示如下:


    返回值:成功返回0,失败返回-1

    typedef union sigval
     { 
    int sival_int; 
    void *sival_ptr; 
    }sigval_t; 

         sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

         注意:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。

    写两个小程序测试一下:

    首先是接收信号:

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<signal.h>
    
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)
    
    void handler(int, siginfo_t *, void *);
    
    
    int main(int argc, char *argv[])
    {
        struct sigaction act;
        act.sa_sigaction = handler; //sa_sigaction与sa_handler只能取其一
        //sa_sigaction多用于实时信号,可以保存信息
        sigemptyset(&act.sa_mask);
        act.sa_flags = SA_SIGINFO; // 设置标志位后可以接收其他进程
        // 发送的数据,保存在siginfo_t结构体中
    
        if (sigaction(SIGINT, &act, NULL) < 0)
            ERR_EXIT("sigaction error");
    
        for (; ;)
            pause();
    
        return 0;
    
    }
    
    void handler(int sig, siginfo_t *info, void *ctx)
    {
        printf("recv a sig=%d data=%d data=%d
    ",
               sig, info->si_value.sival_int, info->si_int);
    
    }

         在前面的《UNIX环境高级编程——信号(API)》中说过,sa_sigaction与SA_SIGINFO要配合使用,如上所示,siginfo_t 结构体也可以参见这篇文章。

    然后是信号发送:

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<signal.h>
    
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)
    
    
    
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            fprintf(stderr, "Usage %s pid
    ", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        pid_t pid = atoi(argv[1]); //字符串转换为整数
        union sigval val;
        val.sival_int = 100;
        sigqueue(pid, SIGINT, val); // 只可以发信号给某个进程,而不能是进程组
    
        return 0;
    
    }

    测试如下:

    先运行recv程序:

    huangcheng@ubuntu:~$ ./sigqueue_recv

    再ps出recv进程的pid,然后运行send程序:

    huangcheng@ubuntu:~$ ./sigqueue_send 2222
    huangcheng@ubuntu:~$ ./sigqueue_send 2222
    huangcheng@ubuntu:~$ ./sigqueue_send 2222
    huangcheng@ubuntu:~$ ./sigqueue_send 2222

    则recv进程会输出一条recv语句,当然我们也可以ctrl+c 给自己发送信号,如下所示,结果是一样的。

    huangcheng@ubuntu:~$ ./sigqueue_recv
    recv a sig=2 data=100 data=100
    recv a sig=2 data=100 data=100
    recv a sig=2 data=100 data=100
    recv a sig=2 data=100 data=100
    ^Crecv a sig=2 data=100 data=100
    ^Crecv a sig=2 data=100 data=100
    ^Crecv a sig=2 data=100 data=100
    ^Crecv a sig=2 data=100 data=100
    ............................................................

    需要提醒一下的是siginfo_t 结构体的两个参数
    (int      si_int;      /* POSIX.1b signal */      
      void    *si_ptr;      /* POSIX.1b signal */)的值也会与si_value 一致,取决于发送的是sival_int 还是 sival_ptr。


    二、利用pause和alarm函数实现sleep函数

    #include <unistd.h>
    int pause(void);
    pause函数使调用进程挂起直到信号递达如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR,所以pause只有出错的返回值。错误码EINTR表示“被信号中断”。

    下面使用pause和alarm实现sleep(3)函数,称为mysleep:

    #include<stdio.h>
    #include<signal.h>
    #include<unistd.h>
    
    void sig_alrm(int signo)
    {
        /* nothing to do */
    }
    
    
    unsigned int mysleep(unsigned int nsecs)
    {
        struct sigaction newact, oldact;
        unsigned int unslept;
    
        newact.sa_handler = sig_alrm;
        sigemptyset(&newact.sa_mask);
        newact.sa_flags = 0;
        sigaction(SIGALRM, &newact, &oldact);
    
        alarm(nsecs);
        pause();
    
        unslept = alarm(0);
        sigaction(SIGALRM, &oldact, NULL);
    
        return unslept;
    }
    
    int main(void)
    {
        while (1)
        {
            mysleep(2);
            printf("Two seconds passed
    ");
        }
        return 0;
    }
    1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
    2. 调用alarm(nsecs)设定闹钟。
    3. 调用pause等待,内核切换到别的进程运行。
    4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
    5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
    6. 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
    7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。


          需要注意的是虽然sig_alrm函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到nsecs而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。如sleep(3)的man 手册写的返回值:

    RETURN VALUE: Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted  by  a signal handler.

    当然如果是被SIGALRM handler所中断,则表示睡眠时间到,mysleep返回值为0。


    三、竞态条件与sigsuspend函数

    现在重新审视上面的mysleep函数,设想这样的时序:

    1. 注册SIGALRM信号的处理函数。
    2. 调用alarm(nsecs)设定闹钟。
    3. 内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间
    4. nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。
    5. 优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数sig_alrm之后再次进入内核。
    6. 返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。
    7. 可是SIGALRM信号已经处理完了,还等待什么呢?
          出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件(Race Condition)


    如何解决上述问题呢?我们可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。看看以下方法可行吗?
    1. 屏蔽SIGALRM信号;
    2. alarm(nsecs);
    3. 解除对SIGALRM信号的屏蔽;
    4. pause();


    从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙,我们把解除屏蔽移到pause后面可以吗?
    1. 屏蔽SIGALRM信号;
    2. alarm(nsecs);
    3. pause();
    4. 解除对SIGALRM信号的屏蔽;


    这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

    #include <signal.h>

    int sigsuspend(const sigset_t *sigmask);


    和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。
    调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

    以下用sigsuspend重新实现mysleep函数:

    #include<stdio.h>
    #include<signal.h>
    #include<unistd.h>
    
    void sig_alrm(int signo)
    {
        /* nothing to do */
    }
    
    unsigned int mysleep(unsigned int nsecs)
    {
        struct sigaction newact, oldact;
        sigset_t newmask, oldmask, suspmask;
        unsigned int unslept;
    
        /* set our handler, save previous information */
        newact.sa_handler = sig_alrm;
        sigemptyset(&newact.sa_mask);
        newact.sa_flags = 0;
        sigaction(SIGALRM, &newact, &oldact);
    
        /* block SIGALRM and save current signal mask */
        sigemptyset(&newmask);
        sigaddset(&newmask, SIGALRM);
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    
        alarm(nsecs);
    
        suspmask = oldmask;
        sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't block */
    
        sigsuspend(&suspmask); /* wait for any signal to be caught */
    
        /* some signal has been caught. SIGALRM is now blocked */
        unslept = alarm(0);
        sigaction(SIGALRM, &oldact, NULL); /* reset previous action */
    
        /* reset signal mask, which unblocks SIGALRM */
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
        return(unslept);
    }
    
    int main(void)
    {
        while (1)
        {
            mysleep(2);
            printf("Two seconds passed
    ");
        }
        return 0;
    }
    如果在调用mysleep函数时SIGALRM信号没有屏蔽:
    1. 调用sigprocmask(SIG_BLOCK, &newmask, &oldmask);时屏蔽SIGALRM。
    2. 调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。
    3. SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
    4. 调用sigprocmask(SIG_SETMASK, &oldmask, NULL);时再次解除对SIGALRM的屏蔽。








  • 相关阅读:
    浙大数据结构课后习题 练习二 7-2 Reversing Linked List (25 分)
    浙大数据结构课后习题 练习二 7-2 一元多项式的乘法与加法运算 (20 分)
    浙大数据结构课后习题 练习一 7-1 Maximum Subsequence Sum (25 分)
    浙大数据结构课后习题 练习一 7-1 最大子列和问题 (20 分)
    PAT Basic 1019 数字黑洞 (20 分)
    PAT Basic 1017 A除以B (20 分)
    PAT Basic 1013 数素数 (20 分)
    PAT Basic 1007 素数对猜想 (20 分)
    PAT Basic 1003 我要通过! (20 分)
    自动化运维——HelloWorld(一)
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6172788.html
Copyright © 2011-2022 走看看