sigal & mq_notify

sigal & mq_notify - [学习笔记]

 

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://yangdk.blogbus.com/logs/65056733.html

信号是软件中断,它提供了一种处理异步事件的方法

当信号产生时有3种方法可以处理这个信号:
1.忽略,如signal(SIGINT, SIG_IGN)
2.捕捉,如signal(SIGINT, sigint_handler) sigint_handler是处理函数
3.系统默认动作,如signal(SIGINT, SIG_DFL)

void (*signal(int signo, void (*func) (int))) (int);
 returns: the older hander if OK, SIG_ERR if error
signo是要捕捉的信号。
func则是signal handler(信号处理函数),

#define SIG_ERR (void (*) ()) -1
#define SIG_DFL (void (*) ()) 0
#define SIG_IGN (void (*) ()) 1
这是<signal.h>头文件中的定义,也说明了为什么SIG_DFL和SIG_IGN可以作为signal函数的第二个参数,SIG_ERR可以是signal函数的返回值。

fork之后,子进程集成父进程的信号处理方式,而调用exec创建新进程之后,新进程并没有继承父进程设置的信号处理方式。

在早期的UNIX版本中,信号是不可靠的(不可靠是指有可能丢失),下面两个经典实例将会说明这个问题:

实例1:
int sig_int(); /* my signal handling function */
...
signal(SIGINT, sig_int); /* estableish handler */
...

sig_int()
{
 signal(SIGINT, sig_int) /* reestablish handler for next time */
 ...   /* process the signal ... */
}
从信号产生到信号处理函数重新设置信号处理函数这段时间内有一个时间窗口,如果在这段时间内再次收到信号,程序会按照SIGINT的默认处理,也就是终止进程


实例2:
int sig_int_flag; /* set nonzero when signal occurs */

main()
{
 int sig_int(); /* my signal handling function */
 ...
 signal(SIGINT, sig_int); /* establish handler */
 while(sig_int_flag == 0)
  pause();  /* goto sleep, waiting for signal */
 ...
}

sig_int()
{
 signal(SIGINT, sig_int); /* reestablish handler for next time */
 sig_int_flag = 1;  /* set flag for main loop to examine */
}
程序的本意是等信号发生后,将标志设为1,并把把程序从pause中唤醒,唤醒后程序判断标志已经被改变,于是退出while循环,继续执行下面的操作,可是一旦消息在判断标志是否为0与pause()之间产生,该进程将被终止或者是永远pause下去(假设不在产生信号),这个信号也就丢失了。

中断的系统调用
早期UNIX的一个特性是,如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。
如若read系统调用已接受并传送数据至应用程序缓冲区,但尚未接收到应用程序请求的全部数据,此时被中断,操作系统可以认为系统调用失败,并将errno设置为EINTR,另一种处理方法是允许该系统调用成功返回,返回已接收到的部分数据量,write同理。
对某些系统可以通过判断函数返回值和错误码来判断是否低速系统调用被中断,代码如下:

again:
 if((n = read(fd, buf, BUFFSIZE)) < 0){
  if(errno == EINTR)
   goto again; /* just an interrupted system call */
  /* handle other errors */
 }
为了帮助应用程序不必处理被中断的系统调用,4.2BSD引入了某些被中断系统调用的自动重启动的功能。自动重启动的系统调用包括ioctl,read,readv,write,writev,wait和waitpid。
系统V的默认工作方式是从不重启系统调用。而BSD则重启动被信号中断的系统调用。FreeBSD5.2.1, linux 2.4.22和Mac OS X 10.3的默认方式是重启被中断的系统调用,而Solaris 9的默认方式是出错返回,并将errno设置为EINTR,这样看只有在Solaris上才需要上面的代码。

信号集
int sigemptyset(sigset_t *set);   /* 清空所有信号 */
int sigfillset(sigset_t *set);   /* 包括所有信号 */
int sigaddset(sigset_t *set, int signo); 
int sigdelset(sigset_t *set, int signo);
 returns: 0 if OK, -1 if error

int sigismember(const sigset_t *set, int signo);
 returns: 1 if TRUE, 0 if FALSE, -1 if error

int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
 returns: 0 if OK, -1 if error

如果oset非空,则oset返回当前的信号屏蔽字

如果set非空,则how的取值决定了该函数的动作
SIG_BLOCK  使该进程的信号屏蔽字为当前屏蔽字与set的并集  set包含了我们希望阻塞的附加信号
SIG_UNBLOCK  使该进程的信号屏蔽字为当前屏蔽字与set的补集的交集  set包含了我们希望解除阻塞的信号
SIG_SETMASK  将set设置为当前信号屏蔽字

如果set为空,how的值也就没有意义

int sigpending(sigset_t *set);
 returns: 0 if OK, -1 if error
该函数通过set返回当前未决的信号,即阻塞而没有递送的信号。

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
 returns: 0 if OK, -1 if error
如果act非空,则要修改其动作
如果oact非空,则返回该信号的上一动作

struct sigaction{
        void            (*sa_handler) (int);    /* addr of signal handler, or SIG_IGN, or SIG_DFL */
        sigset_t        sa_mask;                /* or SIG_IGN, or SIG_DFL */
        int             sa_flags;               /* additional signals to block */
        
        /* alternate handler */
        void            (*sa_sigaction) (int, sig_info_t, void *);
};
sa_handler为信号捕捉函数的地址
sa_mask 在调用信号捕捉函数之前,这个信号集要加入到进程的信号屏蔽字中,从信号捕捉函数返回后再将进程的信号屏蔽字复位为原先值。
sa_flags指定对信号处理的各个选项
sa_sigaction 当sa_flags为SA_SIGINFO时,sa_sigaction是替代sa_handler成为信号处理函数

int sigsuspend(const sigset_t *sigmask);
 return: always be -1, set errno to EINTR

这个函数作用是:先将当前进程的信号屏蔽字设为sigmask,然后挂起等待一个信号,捕捉到信号后从sigsuspend返回,并恢复信号屏蔽字为调用此函数之前的状态。

该函数是为了解决下面的问题而产生的:

sigset_t newmask, oldmask;

sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);

/* block SIGINT and save current signal mask */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");

/* critical region of code */

/* reset signal mask, which unblocks SIGINT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

/* window is open, error is here */
pause();        /* wait for signal to occur */

/* continue processing */

如果信号在sigprocmask(SIG_SETMASK, &oldmask, NULL)与pause之间deliver,那么该信号就会丢失,pause函数有可能永远等待下去,sigsuspend就相当于把这两个函数合并,成为一个原子操作。

这个函数一般的应用场景:
sigset_t newmask, oldmask, waitmask;

sigemptyset(&newmask);
sigemptyset(&waitmask);
sigaddset(&newmask, SIGINT);
sigaddset(&waitmask, SIGUSR1);

/* block SIGINT and save current signal mask */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");

/* critical region of code */

/* pause, allowing all signals except SIGUSR1 */
if(sigsuspend(&waitmask) != -1)
        err_sys("sigsuspend error");

/* reset signal mask, which unblocks SIGINT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

/* continue processing */


下面是关于mq_notify和signal的介绍
我们在实现mq的接收模块时,可能会采用msgrcv函数,但是在没有消息的时候就得阻塞在msgrcv函数上,如果设置了nonblock标识,我们就得不停地调用msgrcv查看是否有消息,这种轮询非常地消耗CPU。
Posix message queue提供了这样一个功能,当有消息被插入到一个空的mq时,我们可以选择向一个我们注册的进程发送一个信号或者创建一个线程,这样就提供了一种异步处理消息的机制,在没有消息的时候,进程可以执行别的操作,有消息后,mq会给注册进程发送信号,注册进程调用信号处理函数或接收消息或者新创建的线程接收消息。

int mq_notify(mqd_t, const struct sigevent *notification);
 returns: 0 if OK, -1 on error

以下是几种典型的利用mq_notify实现异步事件的例子:

1.      /* no signals blocked */
        sigemptyset(&zeromask);
        sigemptyset(&newmask);
        sigemptyset(&oldmask);
        sigaddset(&newmask, SIGUSR1);

        /* establish signal handler, enable notification */
        signal(SIGUSR1, sig_usr1);
        sigev.sigev_notify = SIGEV_SIGNAL;
        sigev.sigev_signo = SIGUSR1;
        stat = mq_notify(mqd, &sigev);
        if(0 != stat){
                perror("mq_notify");
                exit(-1);
        }

        for( ; ; ){

                /* block SIGUSR1 阻塞SIGUSR1信号,保护代码临界区 */
                sigprocmask(SIG_BLOCK, &newmask, &oldmask);

                while(mqflag == 0){
                        sigsuspend(&zeromask);/* 解除对SIGUSR1的阻塞并等待信号,返回时对SIGUSR1继续阻塞 */
                }
                mqflag = 0;             /*reset flag*/

                stat = mq_notify(mqd, &sigev);
                if(0 != stat){
                        perror("mq_notify");
                        exit(-1);
                }

                /* 接收全部消息,否则以后可能永远收不到信号 */
                while((n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0){
                        printf("read %ld bytes\n", (long) n);
                };

                if(errno != EAGAIN){
                        perror("mq_receive error");
                        exit(-1);
                }

                sigprocmask(SIG_UNBLOCK, &newmask, NULL);       /* unblock SIGUSR1 */
        }

        exit(0);
}

static void sig_usr1(int signo) /* 信号处理函数内必须用可重入的函数,因此不能在这里面调用mq_receive接收消息 */
{
        signal(SIGUSR1, sig_usr1);
        mqflag = 1;

        return;
}


2.      /* no signals blocked */
        sigemptyset(&newmask);
        sigaddset(&newmask, SIGUSR1);
        sigprocmask(SIG_BLOCK, &newmask, NULL);

        /* establish signal handler, enable notification */
        sigev.sigev_notify = SIGEV_SIGNAL;
        sigev.sigev_signo = SIGUSR1;
        stat = mq_notify(mqd, &sigev);
        if(0 != stat){
                perror("mq_notify");
                exit(-1);
        }

        for( ; ; ){

                sigwait(&newmask, &signo);/* 等待产生在newmask中的未决的信号 */
                if(signo == SIGUSR1){

                        stat = mq_notify(mqd, &sigev);
                        if(0 != stat){
                                perror("mq_notify");
                                exit(-1);
                        }

                        while((n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0){
                                printf("read %ld bytes\n", (long) n);
                        };

                        if(errno != EAGAIN){
                                perror("mq_receive error");
                                exit(-1);
                        }
                }

        }

        exit(0);
}


3.      pipe(pipefd);

        /* establish signal handler, enable notification */
        signal(SIGUSR1, sig_usr1);
        sigev.sigev_notify = SIGEV_SIGNAL;
        sigev.sigev_signo = SIGUSR1;
        stat = mq_notify(mqd, &sigev);
        if(0 != stat){
                perror("mq_notify");
                exit(-1);
        }

        for( ; ; ){

                FD_SET(pipefd[0], &rset);

                /* 这种方法的优点是,可以设置超时时间,超时后可以去执行其他的操作,执行完后继续监听 */
                nfds  = select(pipefd[0] + 1, &rset, NULL, NULL, NULL);

                if(FD_ISSET(pipefd[0], &rset)){
                        read(pipefd[0], &c, 1);


                        stat = mq_notify(mqd, &sigev);
                        if(0 != stat){
                                perror("mq_notify");
                                exit(-1);
                        }

                        while((n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0){
                                printf("read %ld bytes\n", (long) n);
                        };

                        if(errno != EAGAIN){
                                perror("mq_receive error");
                                exit(-1);
                        }

                }
        }

        exit(0);
}

static void sig_usr1(int signo)
{
        signal(SIGUSR1, sig_usr1);
        write(pipefd[1], "", 1);

        return;
}


Posix Realtime Signals(Posix 实时信号)

实时信号是值在SIGRTMIN和SIGRTMAX之间的信号,下面是调用sigaction时的几种动作情况

signal          指定SA_SIGINFO标识           未指定SA_SIGINFO标识

实时信号         确保实时信号动作              不能确保实时信号动作
一般信号         不能确保实时信号动作          不能确保实时信号动作

如果我们想要实现实时信号动作,在用sigaction设置信号处理动作时必须要用实时信号,而且要指定SA_SIGINFO标识,到底什么是实时信号动作呢?

实时信号动作可以保证以下几点:
1.信号排队,比如,产生3次SIGRTMIN信号,SIGRTMIN信号就被递送3次,而且是先进先出(FIFO)。
2.当多个不阻塞的实时信号排队时,越小的优先级越高,先被递送,比如,排队时SIGRTMIN就要排到SIGRTMAX的前面。
3.执行普通信号的处理函数时,处理函数只能得到一个参数,即信号值,实时信号在执行信号处理函数时则可以得到更多的信息
4.实时信号定义了很多新的函数,例如sigqueue函数实现了kill函数的功能,并能够携带一个sigval结构体。

typedef struct {
        int             si_signo;       /* same value as signo argument */
        int             si_code;        /* SI(USER, QUEUE, TIMEER, ASYNCIO, MESGQ) */
        union sigval    si_value;       /* integer or pointer value from sender */
} siginfo_t;

si_code的值是由系统设定的:
SI_ASYNCIO      异步IO请求完成时,Posix的aio_XXX函数
SI_MESGQ        消息被插入到空的mq时
SI_QUEUE        用sigqueue发送消息时
SI_TIMER        timer_setting函数设置的时钟过期时
SI_USER         用kil发送信号时

si_value是由用户设置的参数,它仅在si_code为QUEUE, TIMEER, ASYNCIO, MESGQ这四个值时才有意义。

最后是一个实时信号的例子:
#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

typedef void sigfunc_rt(int, siginfo_t *, void *);

static void sig_rt(int, siginfo_t *, void *);
sigfunc_rt * signal_rt(int signo, sigfunc_rt *func, sigset_t *mask);

#define MAX SIGRTMAX
//#define MAX 40

int main(int argc, char **argv)
{
        int i, j;
        pid_t pid;
        int stat = 0;
        sigset_t newset;
        union sigval val;

        printf("SIGRTMIN = %d, SIGRTMAX = %d\n", (int)SIGRTMIN, (int) SIGRTMAX);

        if((pid = fork()) == 0){

                /* child:block three realtime signals */
                sigemptyset(&newset);
                sigaddset(&newset, MAX);
                sigaddset(&newset, MAX - 1);
                sigaddset(&newset, MAX - 2);
                sigprocmask(SIG_BLOCK, &newset, NULL);

                signal_rt(MAX, sig_rt, &newset);
                signal_rt(MAX - 1, sig_rt, &newset);
                signal_rt(MAX - 2, sig_rt, &newset);

                /* let parentsend add the signals */
                sleep(6);

                sigprocmask(SIG_UNBLOCK, &newset, NULL);
                /* let add queued signals be delivered */
                sleep(3);
                exit(0);
        }

        sleep(3);
        for(i = MAX; i >= MAX -2; i--){
                for(j = 0; j <= 2; j++){
                        val.sival_int = j;
                        sigqueue(pid, i, val);
                        printf("sent signal %d, val = %d\n", i, j);
                }
        }
        exit(0);
}

static void sig_rt(int signo, siginfo_t *info, void *contest)
{
        printf("received signal #%d, code = %d, ival = %d\n",
                        signo, info->si_code, info->si_value.sival_int);

        return;
}

sigfunc_rt * signal_rt(int signo, sigfunc_rt *func, sigset_t *mask)
{
        struct sigaction act, oact;

        act.sa_sigaction = func;        /* must stort function addr here */
        act.sa_mask = *mask;            /* signals to block */
        act.sa_flags = SA_SIGINFO;      /* must specify this for realtime */
        if(signo == SIGALRM){
#ifdef SA_INTERRUPT
                act.sa_flags |= SA_INTERRUPT;   /*SUNOS 4.x*/
#endif
        }else{
#ifdef SA_RESTART
                act.sa_flags |= SA_RESTART;     /*SVR4, 4.4BSD*/
#endif
        }

        if(sigaction(signo, &act, &oact) < 0)
                return ((sigfunc_rt *)SIG_ERR);
        return (oact.sa_sigaction);
}

这个程序在SOL10上执行的结果是:
SIGRTMIN = 41, SIGRTMAX = 48
sent signal 40, val = 0
sent signal 40, val = 1
sent signal 40, val = 2
sent signal 39, val = 0
sent signal 39, val = 1
sent signal 39, val = 2
sent signal 38, val = 0
sent signal 38, val = 1
sent signal 38, val = 2
/export/home/yangdk/test/unp/s5/rtsignal>received signal #38, code = -2, ival = 0
received signal #38, code = -2, ival = 1
received signal #38, code = -2, ival = 2
received signal #39, code = -2, ival = 0
received signal #39, code = -2, ival = 1
received signal #39, code = -2, ival = 2
received signal #40, code = -2, ival = 0
received signal #40, code = -2, ival = 1
received signal #40, code = -2, ival = 2

由此可见相同德实时信号以FIFO排序,小的实时信号比大的实时信号的优先级要高。

posted @ 2013-08-05 09:53  月光技术杂谈  阅读(1143)  评论(0编辑  收藏  举报