Linux信号编程实践(三)信号在内核中的表示(sigaction&sigqueue)


信号在内核中的表示

实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:

\

1)block集(阻塞集、屏蔽集):一个进程所要屏蔽的信号,在对应要屏蔽的信号位置1

2)pending集(未决信号集):如果某个信号在进程的阻塞集中,则也在未决集中对应位置1,表示该信号不能被递达,不会被处理

3)handler(信号处理函数集):表示每个信号所对应的信号处理函数,当信号不在未决集中时,将被调用。

 

4)block状态字、pending状态字均64位(bit);

5)block状态字用户可以读写,pending状态字用户只能读;这是信号设计机制。

那么我们该如何对信号的屏蔽字状态进行改变和读取呢?接下来我们介绍一组信号集操作函数:

 

#include   
int sigemptyset(sigset_t *set); //把信号集清零;(64bit/8=8字节)  
int sigfillset(sigset_t *set);  //把信号集64bit全部置为1  
int sigaddset(sigset_t *set, int signo);    //根据signo,把信号集中的对应位置成1  
int sigdelset(sigset_t *set, int signo);    //根据signo,把信号集中的对应位置成0  
int sigismember(const sigset_t *set, int signo);    //判断signo是否在信号集中  

sigprocmask 功能:读取或者更改进程的信号屏蔽字(Block)

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);  

返回值:若成功则为0,若出错则为-1

读取:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。

更改:如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

\

sigpending获取信号未决状态字(pending)信息,保存至set态,NSIG信号的最大值=64。

 

#include   
int sigpending(sigset_t *set);  

 

sigismember函数

用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1,否则返回0。如果有错误则返回-1。出错的情况及其错误代码见下:

 

EFAULT 参数set指针地址无法存取
EINVAL 参数signum 非合法的信号编号

int sigismember(const sigset_t *set,int signum);
我们注册一个SIGINT信号,打印出pending的状态,结果如下:

 

void handler(int sig)
{
        printf("recv a sig=%d\n",sig);
}
void printsigset(sigset_t *set)
{
        int i;
        for(i=1;i\

信号没有阻塞,不会发生未决状态,直接递达。

 

在接下来的例子中,我们先屏蔽SIGINT信号, 但是如果该进程接收到了SIGQUIT信号, 则将对SIGINT信号的屏蔽节解除,当然,我们需要先注册SIGINT和SIGQUIT信号。

 

/*开始阻塞信号的程序,产生未决状态*/
void handler(int sig)
{
        if(sig==SIGINT)
                printf("recv a sig=%d\n",sig);
        else if(sig==SIGQUIT) //解除SIGINT的屏蔽
        {
                sigset_t uset;
                sigemptyset(&uset);
                sigaddset(&uset,SIGINT);
                sigprocmask(SIG_UNBLOCK,&uset,NULL);
 
        }
//        printf("recv a sig=%d\n",sig);
}
void printsigset(sigset_t *set)
{
        int i;
        for(i=1;i\

    当我们按下ctrl+c产生信号时,信号被阻塞,处于未决状态。接收到SIGQUIT信号时,解除阻塞,进入递达状态,但是只会对信号做出一次反应,即使你按了很多次ctrl+c,原因就在于,SIGINT是不可靠信号,不支持排队,只保留了一个。

如果我们采用实时信号的话,例如SIGRTMIN,那么对信号来说是支持排队的,不会发生丢失的情况,在解除阻塞后,会对每个信号做出处理。

Sigaction

前面我们讲过了使用signal安装不可靠信号,虽然signal不如sigaction功能丰富,但是也可以安装可靠信号;

#include   
int sigaction(int signum, const struct sigaction *act,  
                     struct sigaction *oldact);  

功能:

sigaction函数用于改变进程接收到特定信号后的行为。

\

简而言之参数就是(信号,指针,原行为)

关于sigaction结构体

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等

struct sigaction {  
//信号处理程序 不接受额外数据(比较过时)  
    void (*sa_handler)(int);  
  
//信号处理程序能接受额外数据,和sigqueue配合使用(支持信号排队,信号传送其他信息),推荐使用  
    void (*sa_sigaction)(int, siginfo_t *, void *);           
  
sigset_t sa_mask;   //屏蔽  
    int sa_flags;       //表示信号的行为:SA_SIGINFO表示能接受数据  
    void (*sa_restorer)(void); //废弃不用了  
};  

sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;

  sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。

  sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

  sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

  sa_restorer已过时,POSIX不支持它,不应再使用。

注意:回调函数sa_handler和sa_sigaction只能选一个

  因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,

实例1:利用sigaction实现了signal函数的功能

 

__sighandler_t my_signal(int sig,__sighandler_t handler)
{
        struct sigaction act;
        struct sigaction oldact;
        act.sa_handler=handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags=0;
        if(sigaction(sig,&act,&oldact)<0)
                return SIG_ERR;
        return oldact.sa_handler;
}
void handler(int sig)
{
        printf("recv a sig=%d\n",sig);
}
int main()
{
  /*      struct sigaction act;
        act.sa_handler=handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags=0;
        if(sigaction(SIGINT,&act,NULL)<0)
                ERR_EXIT("sigaction error\n");
*/
        my_signal(SIGINT,handler);
 
        while(1)
                pause();
        return 0;
 
}
sa_mask选项

 

 

在执行handler 的时候, 如果此时进程收到了sa_mask所包含的信号, 则这些信号将不会被响应, 直到handler函数执行完毕。

 

sigprocmask使其即使发生了也不能递达,但是sa_mask 仅是在处理handler是屏蔽外来的信号;两者的区别还是要好好搞一搞的。

void handler(int sig)
{
        printf("recv a sig=%d\n",sig);
        sleep(5);
}
int main()
{
        struct sigaction act;
        act.sa_handler=handler;
        sigemptyset(&act.sa_mask);
        sigaddset(&act.sa_mask,SIGQUIT);//屏蔽SIGQUIT信号
        act.sa_flags=0;
        if(sigaction(SIGINT,&act,NULL)<0)
                ERR_EXIT("sigaction error\n");
 
        while(1)
                pause();
        return 0;
 
}
\
在响应SIGINT信号即handler处理时,SIGQUIT暂时被屏蔽,但是一旦handler函数处理完后,立即对SIGQUIT进行响应。

siginfo_t结构:

siginfo_t{  
    int      si_signo;    /* Signal number */  
    int      si_errno;    /* An errno value */  
    int      si_code;     /* Signal code */  
    int      si_trapno;   /* Trap number that caused 
                                        hardware-generated signal 
                                        (unused on most architectures) */  
    pid_t    si_pid;      /* Sending process ID */  
    uid_t    si_uid;      /* Real user ID of sending process */  
    int      si_status;   /* Exit value or signal */  
    clock_t  si_utime;    /* User time consumed */  
    clock_t  si_stime;    /* System time consumed */  
    sigval_t si_value;    /* Signal value */  
    int      si_int;      /* POSIX.1b signal */  
    void    *si_ptr;      /* POSIX.1b signal */  
    int      si_overrun;  /* Timer overrun count; POSIX.1b timers */  
    int      si_timerid;  /* Timer ID; POSIX.1b timers */  
    void    *si_addr;     /* Memory location which caused fault */  
    long     si_band;     /* Band event (was int in 
                                        glibc 2.3.2 and earlier) */  
    int      si_fd;       /* File descriptor */  
    short    si_addr_lsb; /* Least significant bit of address 
                                        (since Linux 2.6.32) */  
}  

sigqueue

 

#include   
int sigqueue(pid_t pid, int sig, const union sigval value);  

 

功能

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

和kill函数相比多了一个参数:const union sigval value(int kill(pid_t pid, int sig)),因此sigqueue()可以比kill()传递更多的信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

参数

参数1是指定接收信号的进程id,参数2确定即将发送的信号;

参数3是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

注意:要想在进程间通信的话,sa_flags要置为SA_SIGINFO

sigval联合体

 

typedef union sigval{  
    int sival_int;  
    void *sival_ptr;  
} sigval_t;  
接下来我们模拟一下进程间通信的实例:

先运行hello开启接收,然后使用send发送信号;通过这种方式可以达到进程见通信的目的。

Hello.c
void handler(int sig,siginfo_t *info,void *ctx)
{
 
        printf("recv a sig=%d data=%d\n",sig,info->si_value.sival_int);
         
}
int main()
{
        struct sigaction act;
        act.sa_sigaction=handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags=SA_SIGINFO;
        if(sigaction(SIGINT,&act,NULL)<0)
                ERR_EXIT("sigaction error\n");
 
        while(1)
                pause();
        return 0;
 
}

Send
int main(int argc,char *argv[])
{
        if(argc!=2)
        {
                fprintf(stderr,"Usage %s pid\n",argv[0]);
                exit(EXIT_FAILURE);
        }
        pid_t pid=atoi(argv[1]);
        union sigval v;
        v.sival_int=100;
        sigqueue(pid,SIGINT,v);
        return 0;
}

相关内容