浅析 Linux/UNIX 信号机制

信号常常被称为“软中断”,和“中断”类似,用来通知程序发生异步事件。对信号的处理一般来说有三种方式:忽略,终止进程以及使用信号处理函数。信号处理函数的方式是从一处执行流断开,转而去运行另外的一处代码(信号处理),当处理函数返回时,继续从断开的地方继续执行。

1、安装信号处理函数

  在系统编程的层面上与信号的处理关系最直接相关的函数有两个,他们用来安装信号处理函数:


      sighandler_t signal(int signum, sighandler_t handler);


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

第一个函数signal比较简单,sighandler_t 是一个别名,其原型是 typedef void (*sighandler_t)(int),他是一个函数指针,接受一个类型为int的参数(信号的编号),返回void。例如要对SIGUSR1信号进行处理:

void handler(int sig)
{
    //strsiganl 功能是把信号的编号转为信号说明的字符串
    printf("Rcv a signal:%s",strsignal(sig));
}

int main()
{
    signal(SIGUSR1,handler);
    while(1)
        ;     
}

(这段程序其实是有问题的,后面会说到)这段程序本来是一段死循环,但是对他发送SIGUSR1信号,程序会从while中“中断”转去执行handler中的代码。在shell中使用kill命令发送信号SIGUSR1 于是程序就答应出了一段这样的信息:Rcv a signal:User defined signal 1。signal()的用法几乎就是这么简单。但是由于可移植的原因,参与项目开发时,应该使用下面的这个函数。

  sigaction()函数的参数中有两个结构体,其man手册原型如下:


struct sigaction {
 void (*sa_handler)(int);
 void (*sa_sigaction)(int, siginfo_t *, void *);
 sigset_t sa_mask;
 int sa_flags;
 void (*sa_restorer)(void);
 };

据我所知sa_handler和sa_sigaction其实是在一个union中,他们都是指向信号处理函数的指针。

  sa_mask 是要屏蔽的信号,sa_flags 有多种选项。(关于这两点后文再细说)。从sigaction()原型中可以发现参数中有两个struct sigaction参数,其中act是要安装的信号处理,而oldact是用来带回原来的处理方式方便我们处理完信号后的恢复。如果不需要拿回之前的信号处理方式可以把第三个参数置为NULL,反之如果只想得到之前的处理方式而不像安装新的信号处理,可以把第二个参数置为NULL,这点用signal()是办不到的。用sigaction()改写上面的例子是这样的:

1 void handler(int sig)
 2 {
 3    printf("Rcv a signal:%s",strsignal(sig));
 4 }
 5
 6 int main()
 7 {
 8    struct sigaction act;
 9    sigemptyset(&act.sa_mask);
10    act.sa_handler = handler;
11    act.sa_flags = 0;
12    sigaction(SIGUSR1,&act,NULL);
13    while(1)
14        ;       
15 }

2、信号阻塞、信号的未决

  sigset_t 是一种将信号类型以为位掩码形式存在的数据类型(下文都称之为信号集),他是多种信号的集合(可以保证容纳所有的信号)。操作系统的PCB为每个进程都维护了一个这样的数据类型,并将其内所有的信号阻塞,使他们不可以实时到达进程。当信号屏蔽解除时他们才被传递到进程。在这之间的状态通常被称为未决(pending)。而在信号阻塞期间多次到来的信号,在信号屏蔽解除时只会被报告一次。

  对sigset_t 处理有一系列函数,其中POSIX标准有5个


int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum);

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);

  这样的函数基本上看参数就能知道怎么用,不在赘述。

  glibc中还实现了3个扩展的函数:


int sigisemptyset(sigset_t *set);

int sigorset(sigset_t *dest, sigset_t *left, sigset_t *right);

int sigandset(sigset_t *dest, sigset_t *left, sigset_t *right);

  sigprocmask()函数可以检测和更改信号屏蔽集。

  每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。  


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

how   说明  
SIG_BLOCK   将set中的信号与原有的取并集,并更新进程的屏蔽字  
SIG_UNBLOCK   解除原有的信号集中包含set中的信号,(set补集的交集)  
SIG_SETMASK   将进程的屏蔽字设置为set  

sigpending函数可以看到信号屏蔽期间那些信号来到过(不计次数的)。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/16149.html