unix环境信号机制

UNIX/Linx的信号处理非常强大,它可以完成许多顺序编程不能提供的功能,比如下面这个读终端超时的程序,因为信号的到达的时间是不确定的,系统依靠中断机制来处理函数,所以不能依赖信号的到达时间编程。

[cpp]  view plain copy
  1. /* 
  2.  * 该程序从标准输入中读,如果10内没有输入(或者输入没有按回车)则报告超时,否则打印用户 
  3.  * 输入。 
  4.  */  
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11. #define MAXLINE 1024  
  12. static void  sig_alrm(int);  
  13. static jmp_buf   evn_alrm;  
  14. int main()  
  15. {  
  16.     int n = 0;  
  17.     char line[MAXLINE];  
  18.     if(signal(SIGALRM, sig_alrm) == SIG_ERR)  
  19.     {  
  20.         printf("signal bind error/n");  
  21.         exit(1);  
  22.     }  
  23.     /* 
  24.      *使用jmp的原因是防止在一个频繁的系统,alarm()超时而还没有执行read操作, 
  25.      *依然会永远堵塞在read函数上,使用jmp避免了这一点 
  26.      *注意系统调用也可能是自动再启动的,即信号处理结束后,read并不会终止,这种 
  27.      *情况下设置时间并不起作用,而用jmp就避免了这个问题 
  28.      */   
  29.     if(setjmp(evn_alrm) != 0)  
  30.     {  
  31.         printf("time out/n");  
  32.         exit(1);  
  33.     }  
  34.     alarm(10);  
  35.     if((n = read(STDIN_FILENO, line, MAXLINE)) < 0)  
  36.     {  
  37.         printf("read error/n");  
  38.         exit(1);  
  39.     }  
  40.     alarm(0);  
  41.     write(STDOUT_FILENO, line, n);  
  42.     return 0;  
  43. }  
  44. static void sig_alrm(int signo)  
  45. {  
  46.     longjmp(evn_alrm, 1);  
  47. }  

超时信号的产生和处理。

 

Unix对信号的处理分以下几种情况:

 

1)如果你不指定任何信号的处理方式,系统将会按照对每个信号的默认情况进行处理

 

2)   如果用signal或者sigaction针对特定信号绑信号处理函数,指定信号到来时会按照绑定的处理函数处理。注意调用不良的设计可能会涉及到与其他函数的互相作用问题。比如两个信号处理函数一个代码需要很长的执行时间,另一个执行时间很短而且执行完后退出程序那后一个信号就会影响前一个信号的处理,一个简单的解决办法是在处理一个信号的时候屏蔽掉可能出现的其他信号。你可以用sigprocmask函数来屏蔽,或者直接使用sigaction。在使用sigaction的时候在调用信号捕捉函数之前,struct sigaction有一个sa_mask信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,系统建立的新信号屏蔽字会自动包括正被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们排队,所以如果在某种信号被阻塞时,它发生了五次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。

注意这里的不排队,是指unix下面的信号不进行排队,因为unix如BSD等只有31种信号。他们是非实时信号,而linux系统中存在更多的信号,31以后的被认为是实时信号。你可以通过kill -l查看你系统的信号。

 

3) 如果你屏蔽了一些信号,当这些信号到来时会处于pending状态,他们会等待下面两种情况
a)系统直接调用sigwait等函数对特定信号进行直接捕捉

b)系统取消对他们的屏蔽时他们再到达进程
在这个期间这些信号就在进程外等待,进程编号小于等于31的信号多次到达时只保留一个,31号以后的信号会全部保留。如果取消了对信号的屏蔽,这些信号被取出的顺序posix标准没有要求,而从实验程序来看(针对2.6kernel,实验主机fedora11)会按照信号顺序从小到大取出信号。

[c-sharp]  view plain copy
  1. /* 
  2.  * 这个测试程序发现以下规则: 
  3.  *  
  4.  * 特别注意这个测试只针对基于kernel2.6的linux,因为在大多数unix中信号只有31个,应该尽量避免 
  5.  * 利用第二个特性,因为posix并没有规定这些排队信号传递的顺序。 
  6.  * 
  7.  * 1)对于非实时信号,相同信号不能在信号队列中排队;对于实时信号,相同信号可以在队列中排队 
  8.  * 2)如果信号队列中有多个实时及非实时信号排队,实时信号并不会先于非实时信号被取出,信号数字 
  9.  * 小的会先被取出:如SIGUSR1(10)会先于SIGUSR2(12),SIGRTMIN(34)会先于SIGRTMAX(64),非实时信号 
  10.  * 因为其信号数字小而先于实时信号被取出。 
  11.  * 
  12.  * ?什么是实时信号,非实时信号? 
  13.  * 31之前为非实时之后实时 
  14.  */  
  15. #include   
  16. #include   
  17. #include   
  18. #include   
  19. #include   
  20. #include   
  21. #include   
  22. void sig_handler(int signum)  
  23. {  
  24.     printf("Receive signal. %d/n", signum);  
  25. }  
  26. void* sigmgr_thread()  
  27. {  
  28.     sigset_t   waitset;  
  29.     int        sig;  
  30.     int        rc;  
  31.     pthread_t  ppid = pthread_self();  
  32.     pthread_detach(ppid);  
  33.     sigemptyset(&waitset);  
  34.     sigaddset(&waitset, SIGRTMIN);  
  35.     sigaddset(&waitset, SIGRTMIN+2);  
  36.     sigaddset(&waitset, SIGRTMAX);  
  37.     sigaddset(&waitset, SIGUSR1);  
  38.     sigaddset(&waitset, SIGUSR2);  
  39.     while (1)  {  
  40.         rc = sigwait(&waitset, &sig);  
  41.         if (rc != -1) {  
  42.             sig_handler(sig);  
  43.         } else {  
  44.             printf("sigwaitinfo() returned err: %d; %s/n", errno, strerror(errno));  
  45.         }  
  46.     }  
  47. }  
  48. int main()  
  49. {  
  50.     sigset_t bset, oset;  
  51.     pid_t           pid = getpid();  
  52.     pthread_t       ppid;  
  53.     sigemptyset(&bset);  
  54.     sigaddset(&bset, SIGRTMIN);  
  55.     sigaddset(&bset, SIGRTMIN+2);  
  56.     sigaddset(&bset, SIGRTMAX);  
  57.     sigaddset(&bset, SIGUSR1);  
  58.     sigaddset(&bset, SIGUSR2);  
  59.     if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)  
  60.         printf("!! Set pthread mask failed/n");  
  61.     kill(pid, SIGRTMAX);  
  62.     kill(pid, SIGRTMAX);  
  63.     kill(pid, SIGRTMIN+2);  
  64.     kill(pid, SIGRTMIN);  
  65.     kill(pid, SIGRTMIN+2);  
  66.     kill(pid, SIGRTMIN);  
  67.     kill(pid, SIGUSR2);  
  68.     kill(pid, SIGUSR2);  
  69.     kill(pid, SIGUSR1);  
  70.     kill(pid, SIGUSR1);  
  71.     // Create the dedicated thread sigmgr_thread() which will handle signals synchronously  
  72.     pthread_create(&ppid, NULL, sigmgr_thread, NULL);  
  73.     sleep(10);  
  74.     return 0;  
  75. }  

这个程序来自IBM developerworks. 请留意我在这个程序中的注释。

 

信号屏蔽是一个很重要的概念。下面是一个例子说明了一些信号处理函数的作用,首先进程阻塞了S I G Q U I T信号,保存了当前信号屏蔽字(以便以后恢复),然后睡眠5秒钟。在此期间所产生的退出信号都被阻塞,不递送至该进程,直到该信号不再被阻塞。在5秒睡眠结束后,检查是否有信号pending,然后将S I G Q U I T设置为不再阻塞。

注意,在设置S I G Q U I T为阻塞时,我们保存了老的屏蔽字。为了解除对该信号的阻塞,用老的屏蔽字重新设置了进程信号屏蔽字( S I G _ S E T M A S K)。另一种方法是用S I G _ U N B L O C K使阻塞的信号不再阻塞。但是,应当了解如果编写一个可能由其他人使用的函数,而且需要在函数中阻塞一个信号,则不能用S I G _ U N B L O C K解除对此信号的阻塞,这是因为此函数的调用者在调用本函数之前可能也阻塞了此信号。在这种情况下必须使用S I G _ S E T M A S K将信号屏蔽字恢复为原先值。

在睡眠期间如果产生了退出信号,那么此时该信号是未决的,但是不再受阻塞,所以在s i g p r o c m a s k返回之前,它被递送到本进程。从程序的输出中可以看到这一点: S I G Q U I T处理程序(s i g _ q u i t)中的p r i n t f语句先执行,然后再执行s i g p r o c m a s k之后的p r i n t f语句。

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. static void sig_quit(int);  
  6. int main()  
  7. {  
  8.     sigset_t newmask, oldmask, pendmask;  
  9.     /*SIGQUIT是由键盘要求停止,CTRL+/可以产生这个信号 */  
  10.     if(signal(SIGQUIT,sig_quit) == SIG_ERR )  
  11.     {  
  12.         printf("signal error/n");  
  13.         exit(1);  
  14.     }  
  15.     sigemptyset(&newmask);  
  16.     sigaddset(&newmask, SIGQUIT);  
  17.     //block SIGQUIT and save current mask  
  18.     if(sigprocmask(SIG_BLOCK, &newmask, &oldmask)<0)  
  19.     {  
  20.         printf("sigprocmask error/n");  
  21.         exit(1);  
  22.     }  
  23.     sleep(5);//SIGQUIT here will remain pending  
  24.     if(sigpending(&pendmask) < 0)      
  25.     {  
  26.         printf("sigpending error/n");  
  27.         exit(1);  
  28.     }  
  29.     if(sigismember(&pendmask, SIGQUIT))   
  30.         printf("/nSIGQUIT pending/n");  
  31.     if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  
  32.     {  
  33.         printf("sigprocmask error/n");  
  34.         exit(1);  
  35.     }  
  36.     printf("SIGQUIT unblock/n");  
  37.     sleep(5);//SIGQUIT here will terminate with core file  
  38.     return 0;  
  39. }  
  40. static void sig_quit(int signo)  
  41. {  
  42.     printf("Caught SIGQUIT/n");  
  43.     if(signal(SIGQUIT, SIG_DFL) == SIG_ERR)  
  44.     {  
  45.         printf("signal error in sig_quit/n");  
  46.         exit(1);  
  47.     }  
  48.     return;  
  49. }  

 本文为转载,原文地址为:http://blog.csdn.net/luoleicn/article/details/4311632

你可能感兴趣的:(linux&shell,unix,信号)