linux 信號處理

前言linux

     Linux中的信號是向進程異步發送的事件通知,通知進程有事件(硬件異常、程序執行異常、外部發出信號)發生。當信號產生時,內核向進程發送信號(在進程所在的進程表項的信號域設置對應於該信號的位)。內核處理一個進程收到的信號的時機是在一個進程從內核態返回用戶態時,當一個進程在內核態運行時,軟中斷信號並不當即起做用,要等到將返回用戶態時才處理,進程只有處理完信號纔會返回用戶態,進程在用戶態下不會有未處理完的信號。內核爲每一個進程維護一個(未處理)的信號隊列,信號產生後首先被放入到未決隊列中,若是進程選擇阻塞信號,那麼若是某個信號發生屢次,未決隊列中僅保留相同的信號(不可靠信號類型)中的一個,而可靠信號則會被保留。多線程

 

 1、進程信號處理  異步

1
2
3
4
5
6
7
int  pause( void );      //將調用進程/線程 掛起sleep,直到有信號產生且在信號處理函數完成後返回
int  kill(pid_t pid,  int  sig);      //將sig信號發送到pid進程
int  raise ( int  sig);    //向調用進程/線程發送sig信號
  
sigemptyset, sigfillset, sigaddset, sigdelset, sigismember用來操做信號集合sigset_t,該信號集合能夠用於sigwait、sigaction等操做
  
int  sigwait( const  sigset_t *set,  int  *sig);      //阻塞等待set中的信號,sig保存發生的信號;同類函數有sigtimedwait,sigwaitinfo

 

1
2
3
4
5
sighandler_t  signal ( int  signum, sighandler_t handler);
  
示例:  signal (SIGUSR1, myfunc);     //註冊SIGUSR1的信號處理函數myfunc
  
//注:該函數在不一樣的linux、unix版本實現方式不太同樣,爲了保證程序的通用性,建議使用sigaction

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int  sigaction( int  signum,  const  struct  sigaction *act,  struct  sigaction *oldact);
  
示例: struct  sigaction act, oldact;
      //註冊的信號處理函數類型爲void (*sa_handler)(int);
      //act.sa_handler = show_handler;
  
      //註冊的信號處理函數,類型爲void (*sa_sigaction)(int, siginfo_t *, void *);這種方法功能與sa_handler相同,可是從siginfo_t結構體參數中獲取產生該信號的詳細信息,特別是對於錯誤分析特別有用,建議採用這種。
      act.sa_sigaction = show_handler;
  
      sigaddset(&act.sa_mask, SIGQUIT);  //在SIGINT的信號處理函數執行時,阻塞SIGQUIT信號,直到函數執行完成
      act.sa_flags = 0;
      sigaction(SIGINT, &act, &oldact);    //設置SIGINT信號新的處理方法,將老的處理方法保留到oldact中,方便在適當的時候還原以前的信號處理方法
  
//注:siginfo_t結構體的具體參數以及sa_flags的一些標誌位的含義,參加man手冊 man sigaction

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int  sigpending(sigset_t *set);      //獲取當前阻塞的信號集
  
int  sigprocmask( int  how,  const  sigset_t *set, sigset_t *oldset);
//how包含SIG_BLOCK(將set中包含的信號添加到已有的阻塞信號集合中), SIG_UNBLOCK(將set中信號從阻塞的信號集合中移除), SIG_SETMASK(將阻塞信號集合修改爲set中的信號)
  
int  sigsuspend( const  sigset_t *mask);      //將調用進程的信號集替換成mask指向的信號集,而後掛起,直到有信號(不在mask中)產生且對應的信號處理函數返回,此時將原有的信號集還原
  
//注:sigsuspend一般配合sigprocmask使用,用於保證臨界區代碼執行。
示例:sigemptyset(&new_mask);
sigemptyset(&zero_mask);       // 清空信號集zero_mask
sigaddset(&new_mask, SIGQUIT);
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);    // 阻塞SIGQUIT
  
while ( quitflag == 0 ) {
     sigsuspend(&zero_mask);    // 將信號掩碼替換爲空,等待SIGQUIT信號處理函數將quitflag置1
}
  
sigprocmask(SIG_SETMASK, &old_mask, NULL);       // 恢復信號掩碼

 

2、多線程信號處理ide

     多線程信號處理跟單線程的程序最大的區別就是全部的線程共享信號處理函數,每一個線程對信號處理函數的修改,都會同步到其餘線程。linux環境下線程是經過輕量級進程(有興趣能夠查資料)實現的,所以內核爲每一個線程維護一個未決信號隊列。建立新的線程時,新線程繼承主線程的信號屏蔽字,可是新線程的未決信號隊列被清空(防止同一信號被多個線程處理)。各個線程的信號屏蔽字(sigmask)是獨立的,能夠經過pthread_sigmask函數來控制線程級別的sigmask。函數

     若是是硬件故障(如SIGBUS/SIGFPE/SIGILL/SIGSEGV)或定時器超時觸發的信號,該信號會發往引發該事件的線程;其他的全部狀況產生的信號都會發送到主線程。所以要想讓特定線程處理信號,須要主線程將這些信號屏蔽。測試

 

1
2
3
4
int  pthread_sigmask( int  how,  const  sigset_t *set, sigset_t *oldset);      //線程級別的sigprocmask
int  pthread_kill(pthread_t  thread int  sig);      //線程級別的kill
  
注:進程信號處理中講到的大部分函數都是能夠在多線程程序中使用的

 

3、踩坑教訓ui

     一、在一個多線程程序中,線程A中會設置定時器,若是超時就會觸發SIGALRM的信號處理函數sig_alarm_func,該函數執行了pthread_cancel(A);pthread_create(B);的操做。在測試過程當中發現進程中同時存在A, B兩個線程。查看pthread_cancel 說明,phtread_cancel是個異步的,須要等到線程A執行到cancellation point才能結束退出。利用gdb查看A的函數調用棧發現,阻塞到了信號處理函數sig_alarm_func中,即發生了「本身取消本身」的問題。根據第二部分講到的信號通告機制,定時器信號被髮往了調用定時器的線程,於是信號處理函數也是在調用線程的上下文中執行,因此出現了異常。spa

     解決方法:單獨設置一個信號處理線程,阻塞除該線程外的其餘全部線程的信號。在信號處理線程中,利用while+sigwait 對信號進行同步處理代替註冊信號處理函數的異步處理方式。線程

 

     二、在處理一個程序堆棧時,發現程序在malloc函數中發生了死鎖。進一步分析發現信號處理函數在保存函數調用堆棧時調用了malloc,而信號產生時正好也在執行malloc操做。經過查看malloc的相關文檔發現,malloc在申請內存的時候,有加鎖操做。unix

     解決方法:信號處理函數中取消malloc這類不可重入的有鎖函數。之後編寫信號處理函數的時候,在函數內部盡少作一些耗時處理儘快返回,在調用函數時必須調用可重入(reentrant)函數(即不能夠有static、global等全局變量,不能夠分配、釋放內存,不要修改errno等)。

相關文章
相關標籤/搜索