Linux 線程信號處理

轉自:http://blog.chinaunix.net/uid-10231348-id-3036633.html

另一篇文章是比較好的示例:http://blog.csdn.net/intrepyd/article/details/4473533

線程與信號

UNIX信號以前是專爲進程設計的,它比線程的出現早了很多年。當線程模型出現後,專家們試圖也在線程上實現信號,這導致了一個問題:如果要在線程模型中保持原來在進程中信號語意不變,是相當困難的。

避免信號和線程一起使用是明智的選擇。但是,將他們分開又是不可能或不實際的。只要有可能的話,僅僅在主線程內使用pthread_sigmask()來屏蔽信號,然後同步地在專用線程中使用sigwait()來處理信號。

 

爲了理解信號模型是怎樣映射到線程模型的,我們需要知道信號模型的哪些方面是影響進程層面的(process-wide),哪些方面只會影響某個線程的。下面列出幾點:

1.signal actions process-wide。如果一個沒有處理的信號的默認動作是停止SIGSTOP或終止SIGKILL(該動作是讓整個進程停止或終止,而不是隻針對某個線程),那麼不管這個信號是發送給哪個線程,整個進程都會停止或終止。

2.signal dispositions信號部署是process-wide。一個進程中的所有線程對某個信號都共享相同的信號處理函數。如果線程A使用sigaction()對某個信號,比如SIGINT,建立了一個信號處理函數。那麼當SIGINT發送到線程B時,信號處理函數也會被調用。

3.下面幾種情況,把信號發送到某個指定的線程。

       A..某個特定硬件指令執行後(在該線程內執行的),產生的信號,將會發送到該線程內。比如SIGBUSSIGFPESIGILLSIGSEGV

       B.當線程嘗試向一個broken pipe寫數據時,會產生一個SIGPIPE.

       C.使用pthread_kill()或者pthread_sigqueue()。這些函數允許一個線程發送信號到另一個線程(同一進程中)。

其他情況都是把信號發送到整個進程(比如,kill()sigqueue())。

4.當一個信號被髮送到一個多線程的進程中(注意是發送到進程)。內核會選擇該進程中的

任意線程來處理該信號。這種做法是爲了保持進程中信號的語意,保證不會在多線程進程中一個信號多次被執行。

5.信號掩碼(signal mask)是線程私用的。在多線程的進程中,不存在process-wide的信號掩碼。線程可以使用pthread_sigmask()來獨立的屏蔽某些信號。通過這種方法,程序員可以控制那些線程響應那些信號。當線程被創建時,它將繼承創建它的線程的信號掩碼

6.內核爲每個線程和進程分別維護了一個未決信號的表。當使用sigpending()時,該函數返回的是整個進程未決信號表和調用該函數的線程的未決信號表的並集。當新線程被創建時,線程的pending signals被設置爲空。當線程A阻塞某個信號S後,發送到A中的信號S將會被掛起,直到線程取消了對信號S的阻塞。

7.如果一個信號處理函數打斷了pthread_mutex_lock(),該函數會自動的重新執行。如果信號處理函數打斷了pthread_cond_wait()(參見POSIX線程-條件變量),該函數要麼自動重新自行(linux是這樣實現的),或者返回0(這時應用要檢查返回值,判斷是否爲假喚醒)。

8.可選的信號棧是線程私有的(將會在線程總結時討論)。新創建的線程不會繼承創建它的線程的信號棧。

 

信號掩碼的操作

int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldest)

注意點:

1.當新線程創建後,它將繼承創建它的線程的信號掩碼。

2.pthread_sigmask()sigprocmask()linux上的實現是一樣的。但是在SUSv3標準中規定:在多線程程序中使用sigprocmask(),結果是不定的,這會多程序的可移植性造成影響。

 

發送信號到線程

int pthread_kill(pthread_t thread, int sig)

int pthread_sigqueue(pthread_t thread, int sig, const union sigval value)

注意點:

1.pthread_kill()函數在底層使用的是tgkill系統調用。

2.pthread_sigqueue()函數是在glibc-2.11時加入的。它要求linux2.6.31,因爲該版本提供了rt_tgsigqueueinfo()系統調用的。

 

異步信號的處理

一個函數要麼是可重入的(reentrant,要麼是不能被信號處理函數打斷的,我們把這種函數叫做是async-signal-safe的(在信號的總結中討論)。調用非async-signal-safe的函數是危險的,比如,考慮在線程A中,我們調用malloc()來進行內存分配,malloc()剛用互斥量鎖住了全局鏈表,這是異步信號到達,在信號處理函數中也調用malloc(),這時該函數會阻塞在互斥量上,形成死鎖(這個例子在單線程的進程中也會出現)。Pthread API不是async-signal-safe的,也就是說在信號處理函數中不要使用pthread相關的函數。

 

解決這個問題的最好辦法是,在不打斷正常程序的前提下,把所有的異步信號都在同一處處理。在單線程程序中,這是做不到的,因爲所有發送的信號都會打斷程序。而在多線程程序中,我們可以單獨創建一個線程來接受信號,處理需要的信號,而不會打斷其他線程的工作。

 

上面舉的這個例子中還有一點沒說到,就是信號處理函數也會被其他信號所打斷。那我們怎麼處理這個問題呢?在處理信號之前,對所有的異步信號進行阻塞,等工作處理完畢後,再恢復阻塞的信號。這個工作就靠下面這個函數執行:

int sigwait(const sigset_t *set, int *sig)

可能會被其他信號打斷

注意點:

1.調用sigwait()等待的信號必須在調用線程中屏蔽,通常我們在所有線程中都會屏蔽。

2.信號僅僅被交付一次。如果兩個線程在sigwait()上阻塞,只有一個線程(不確定的線程)將收到送給進程的信號。這意味着不能讓兩個獨立的子系統使用sigwait()來捕獲相同的信號。

使用方法:

在主線程中:

  1. sigset_t signal_set;

  2. sigemptyset(&signal_set)

  3. sigfillset(&signal_set);

  4. pthread_sigmask(SIG_BLOCK, &signal_set, NULL)

  5. pthread_create();

專門處理信號的線程(響應SIGINT

  1. sigset_t signal_set;

  2. int sig_num;

  3. sigemptyset(&signal_set);

  4. sigaddset(&signal_set, SIGINT);

  5. while(1){

  6.        sigwait(&signal_set, &sig_num);

  7.        if (sig_num == SIGINT)

  8.               /*do some thing*/

  9.               /*這裏可以調用非asyn-signal-safe的函數*/

  10. }

注意信號處理函數是安全的!

 

 

線程和exec()
當在線程中調用exec()時,該線程被完全的被替代,線程ID不確定。除了被替代的線程,其他線程都被銷燬。線程的thread-specific data銷燬函數和清除函數都不會被調用。所有進程的互斥量和條件變量消失。

 

線程和fork()

當在多線程進程中調用fork(),只有調用fork()的線程被複制到子進程(子進程中線程的ID)。注意以下情況:

1.雖然只有調用fork()的線程被複制到子進程,但是子進程的全局變量,互斥量,條件變量的狀態都將和在父進程中的一樣。也就是說,如果它在父進程中被鎖住,則它在子進程中也是被鎖住的。

2. thread-specific data的銷燬函數和清除函數都不會被調用。在多線程中調用fork()可能會引起內存泄露。比如在其他線程中創建的thread-specific data,在子進程中將沒有指針來存取這些數據,造成內存泄露。

因爲以上這些問題,在線程中調用fork()的後,我們通常都會在子進程中調用exec()。因爲exec()能讓父進程中的所有互斥量,條件變量(pthread objects)在子進程中統統消失(用新數據覆蓋所有的內存)。對於那些要使用fork()但不使用exec()的程序,pthread API提供了一個新的函數

pthread_atfor(void (*prepare_func)(void), void(*parent_func)(void), void (*child_func)(void))

prepare_func在父進程調用fork之前調用,parent_funcfork執行後在父進程內被調用,child_funcfork執行後子進程內被調用。除非你打算很快的exec一個新程序,否則應該避免在一個多線程的程序中使用fork—POSIX多線程程序設計-chapter6.2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章