Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1175767
  • 博文数量: 573
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 66
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-28 16:21
文章分类

全部博文(573)

文章存档

2018年(3)

2016年(48)

2015年(522)

分类: LINUX

2015-12-09 09:46:34

sigaction函数解析

sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)。

他是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该使用这个接口)

给信号signum设置新的信号处理函数act, 同时保留该信号原有的信号处理函数oldact

int sigaction(int signo,const struct sigaction *restrict act,

              struct sigaction *restrict oact);

结构sigaction定义如下:

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

sa_handler字段包含一个信号捕捉函数的地址

sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加进进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。

sa_flag是一个选项,主要理解两个

SA_INTERRUPT 由此信号中断的系统调用不会自动重启
SA_RESTART 由此信号中断的系统调用会自动重启

SA_SIGINFO 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针

最后一个参数是一个替代的信号处理程序,当设置SA_SIGINFO时才会用他。

例子:

#include
#include
#include

void show_handler(int sig)
{
    printf("I got signal %d\n", sig);
    int i;
    for(i = 0; i < 5; i++) {
        printf("i = %d\n", i);
        sleep(1);
    }
}

int main(void)
{
    int i = 0;
    struct sigaction act, oldact;
    act.sa_handler = show_handler;
    sigaddset(&act.sa_mask, SIGQUIT); //见注(1)
    act.sa_flags = SA_RESETHAND | SA_NODEFER; //见注(2)
    //act.sa_flags = 0; //见注(3)

    sigaction(SIGINT, &act, &oldact);
    while(1) {
        sleep(1);
        printf("sleeping %d\n", i);
        i++;
    }
}


注:
(1)    如果在信号SIGINT(Ctrl + c)的信号处理函数show_handler执行过程中,本进程收到信号SIGQUIT(Crt+\),将阻塞该信号,直到show_handler执行结束才会处理信号SIGQUIT。


(2)    SA_NODEFER       一般情况下, 当信号处理函数运行时,内核将阻塞<该给定信号 -- SIGINT>。但是如果设置了SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号。 SA_NODEFER是这个标记的正式的POSIX名字(还有一个名字SA_NOMASK,为了软件的可移植性,一般不用这个名字)    
       SA_RESETHAND    当调用信号处理函数时,将信号的处理函数重置为缺省值。 SA_RESETHAND是这个标记的正式的POSIX名字(还有一个名字SA_ONESHOT,为了软件的可移植性,一般不用这个名字)   


(3)    如果不需要重置该给定信号的处理函数为缺省值;并且不需要阻塞该给定信号(无须设置sa_flags标志),那么必须将sa_flags清零,否则运行将会产生段错误。但是sa_flags清零后可能会造成信号丢失!

http://webcache.googleusercontent.com/search?q=cache:B2HsD1Zf2f8J:hi.baidu.com/operationsystem/blog/item/bb215411f4dc61f4c2ce79e6.html/cmtid/c150423c8b8feae13d6d97b0+sigaction&cd=1&hl=zh-CN&ct=clnk




正文 
我觉得这是挺好理解的,就好比在系统这个大进程里运行许多派生的进程,为了协调这些派生出的子进程,就必然要使用一些手段来通知监视。而信号就是这样一种系统级别的全局变量的通知。想想在写程序中,多个函数协调一个全局函数的情形。。。 
the signal is an event generated by the UNIX and Linux systems in response to some condition,upon receipt of which a process may in turn take some action. 


函数 
我想我需要如下系列的函数,修改本身的信号处理函数,对其他进程发送信号, 
#include  
void (*signal(int sig, void (*func)(int)))(int); 

which 
is the previous value of the function set up to handle this signal, or one of these two special values: 

Java代码  收藏代码
  1. SIG_IGN             Ignore the signal.  
  2. SIG_DFL             Restore default behavior.  


比如想捕捉SIGINT信号,但是我们又只想捕捉一次,就可以用到DFL信号来恢复之前的行为,可能会是这样 
但要注意的是,It is no safe to call all function, such as printf, from within a signal handler.A useful technique is to use a signal handler to set flag and then check that flag from the main 
pg and print a message if required.Toward the end of the chapter, you will find a list of calls that can safely be made inside signal handlers. 
Java代码  收藏代码
  1. void ouch(int sig)  
  2. {  
  3.     printf("OUCH ! - I got signal %d\n", sig);  
  4.     signal(SIGINT, SIG_DFL);      
  5. }  
  6.   
  7. int main(int argc, char **argv)  
  8. {  
  9.     signal(SIGINT, ouch);     
  10.       
  11.     while(1)  
  12.     {  
  13.         printf("Hello World!\n");  
  14.         sleep(1);     
  15.     }  
  16.       
  17.     return 0;  
  18. }  


来点强壮的 
X/Open规范的更新更健壮的接口 
#include  
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); 
oact如果不为空,将把前次act的状态保存下来。 
这个函数主要的是加入了信号集(sa_mask)这个功能。比如前面提到的,如果信号先发出而后调用pause(),则遗失掉这个信号。采用信号集他可以先收集或者说阻塞不传递给主进程,由主进程再来自主调用和处理。 

Java代码  收藏代码
  1. void (*) (int) sa_handler     /* function, SIG_DFL or SIG_IGN  
  2. sigset_t sa_mask          /* signals to block in sa_handler  
  3. int sa_flags             /* signal action modifiers,SA_RESETHAND,具有reset功能  


对这个信号集有如下几种操作: 
初始为空集,初始为所有已有的信号,增加新信号,删除指定信号 
Java代码  收藏代码
  1. #include   
  2. int sigaddset(sigset_t *set, int signo);  
  3. int sigemptyset(sigset_t *set);  
  4. int sigfillset(sigset_t *set);  
  5. int sigdelset(sigset_t *set, int signo);  

然后是一个批处理的函数 
Java代码  收藏代码
  1. int sigprocmask(int how, const sigset_t *set, sigset_t *oset);  
  2. SIG_BLOCK   The signals in set are added to the signal mask.  
  3. SIG_SETMASK     The signal mask is set from set.  
  4. SIG_UNBLOCK     The signals in set are removed from the signal mask.  



判断是否是当前信号集里的信号 
int sigismember(sigset_t *set, int signo); 


最后就是最重要的,对信号集里的信号进行操作,假设已经把信号收集到,主程序可以阻塞/非阻塞式的调用。(不过非阻塞的调用好象不能自动把收到的信号清除掉,阻塞式的就可以自动清除)。 

If a signal is blocked by a process, it won’t be delivered, but will remain pending. A program can determine 
which of its blocked signals are pending by calling the function sigpending. 
#include  
int sigpending(sigset_t *set); 


A process can suspend execution until the delivery of one of a set of signals by calling sigsuspend. This 
is a more general form of the pause function we met earlier. 
#include  
int sigsuspend(const sigset_t *sigmask); 

sigaction Flags 
The sa_flags field of the sigaction structure used in sigaction may contain the following values to 
modify signal behavior: 

Java代码  收藏代码
  1. SA_NOCLDSTOP        Don’t generate SIGCHLD when child processes stop.  
  2. SA_RESETHAND        Reset signal action to SIG_DFL on receipt.  
  3. SA_RESTART      Restart interruptible functions rather than error with EINTR.  
  4. SA_NODEFER      Don’t add the signal to the signal mask when caught.  



通过以上的操作介绍,似乎已经很完美了。但是最大的问题来了,先了解几个概念: 
不完全重入函数:即可能被其他信号中断触发EINTR 

假设我们现在正在执行一个信号处理函数,突然发生一个中断,那么该信号函数将被打断。在一次情况下似乎没什么问题。但是假如连续性的被信号打断,而导致函数不断重启。就比如执行到一半退出,又执行,又退,又执行。。那么可能就有问题发生。有两个方法可以解决 
1、根本方法,用完全重入函数。如下图所示英文版 P474 
2、如果非不得以,那就不让信号来阻断信号函数的运行。可以用SA_RESTART标志把信号先放到屏蔽集的缓冲区里 

Java代码  收藏代码
  1. SA_RESTART(似乎默认也是这个行为)  
  2. void ouch(int sig)  
  3. {  
  4.  //..  
  5.  select()//10秒  
  6.  //..  
  7. }  
  8.   
  9. int main(int argc, char **argv)  
  10. {  
  11.     struct sigaction act, act_g;  
  12.     act.sa_handler = ouch;  
  13.     act.sa_flags = SA_RESTART;   //设置重启  
  14.     sigemptyset(&act.sa_mask);  
  15.     sigaddset(&act.sa_mask, SIGTERM);  
  16.   
  17.     if( -1==sigaction(SIGTERM, &act, 0))  //捕捉SIGTERM  
  18.     {  
  19.         perror("sigaction\n");    
  20.     }  
  21.   
  22.     select//10秒  
  23.   
  24. }  

操作如下,首先运行该程序,然后在另一个tty里发送一个kill命令(在第一个select未结束前),那么将进入信号处理函数ouch,接着在ouch的select运行之际,再次发送kill,因为select也不完全函数,将会被再次打断,而又一次进入信号。但是因为设置了SA_RESTART,ouch将不会被打断,而是执行完后,接着执行响应第二次的信号函数。 

SA_NODEFER 
和上面一样的操作,发送第二次KILL信号时,第一个信号函数马上中断(由于是select),又再次进入信号函数。这里加点东西打印可以更清楚些。 

流程图 
以上这两种实现,有个东西起了至关重要的作用。那就是进程的信号屏蔽字。原理流程大概是这样的,当向一个进程发送信号时,可根据是否被屏蔽掉而发送至进程信号屏蔽字集合中或者进程本身。对于后者,sigaction可直接捕捉到,而前者,可以看成是一个暂存的集合,可用sigpending来取得。通常的情况是,已经用了sigaction函数直接获取信号,如果再次触发信号会马上跳出当前正在执行的信号函数而再次执行信号函数。而SA_RESTART这个标志应该是将信号保存到被屏蔽的信号集合里等待下次取出后执行,保证了当前函数的运行。SA_NODEFER就不做这一操作,直接发送到进程,让sigaction继续马上捕捉。      
整个走向可看下图,当然这里没有参考系统源码,必然存在着一定的疏忽 

 
目前已知的对屏蔽集的操作有pending/suspend函数,SA_RESTART,SA_NODEFER标志操作。写到这里,把屏蔽集合集看成一个临时的信号存放缓冲区更形象点。 


http://blog.chinaunix.net/uid-1877180-id-3011232.html

http://lin-style.iteye.com/blog/296917

阅读(761) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~