Linux中的訊號處理機制 [四]
訊號與執行緒
Unix的訊號機制在誕生之初,生活在只有程序(process)的相對單純的環境中。自從Unix世界有了執行緒(thread)的概念,訊號就被賦予了發往程序中某個特定執行緒的能力,當然,這也增加了整個訊號機制實現的複雜度。本系列的前面三篇文章都是基於程序進行的訊號實現機制的討論,本文將著重介紹Linux中訊號和執行緒之間的互動。
傳送訊號給執行緒
無論是kill()還是sigqueue(),都只能向程序傳送訊號,在Linux中,要向程序內的執行緒傳送訊號,需要使用tkill()或者tgkill():
int
tkill
(
int
tid
,
int
sig
);
int
tgkill
(
int
tgid
,
int
tid
,
int
sig
);
兩個函式中,“tid”都是代表目標執行緒的PID,但tgkill()比tkill()多了一個“tgid”的引數。“tgid”是目標執行緒所在程序的PID,它可以用來防止向錯誤的執行緒傳送訊號。
傳送方給目標執行緒傳送訊號時,可能目標執行緒已經因為某種原因退出了,按照Linux中PID的分配規則,退出執行緒/程序的PID可被分配給其他的執行緒/程序使用。這種情況下,如果使用tkill(),就可能出現將訊號傳送到不相干的執行緒上。引入“tgid”可以幫助進行目標執行緒所在程序的校驗,這樣出現錯發的可能性就被大大地降低了。
執行緒對訊號的接收
根據POSIX標準的定義,程序內的所有執行緒共享程序的訊號處理函式,當程序內的一個執行緒為某個訊號註冊了處理函式,另一個執行緒可以更改這個處理函式。在Linux的實現中,執行緒作為獨立的排程實體也有自己的task_struct,同一程序的不同執行緒的task_struct的“sighand”將指向同一個包含訊號處理函式列表的sighand_struct。
但是,每個執行緒可以有單獨的pending點陣圖/佇列和block點陣圖。如果一個訊號是傳送給執行緒的,那麼核心在遞送該訊號時,會將它放入執行緒私有的pending點陣圖/佇列中,之後根據目標執行緒的block點陣圖的設定,直接由目標執行緒處理就可以了。
如果訊號是傳送給一個程序的,那麼該訊號在遞送時將被核心放入程序的pending點陣圖/佇列中,由程序內的所有執行緒共享。接下來,核心會從程序的各個執行緒中,挑選一個block點陣圖中沒有遮蔽該訊號的執行緒,來執行對應的訊號處理函式,其中,程序的主執行緒將被核心優先選擇。
但是有一些訊號是需要程序內的全體執行緒都做出響應的,比如前面提到的令程序聞風喪膽的SIGKILL,它一旦到來,就不會留下一個活口。
當一個執行緒即將被核心排程執行,而該執行緒私有的penging點陣圖/佇列和所在程序共享的penging點陣圖/佇列上都有待處理的訊號時,核心將優先向執行緒遞送私有的penging點陣圖/佇列上的訊號:
int
dequeue_signal
(
struct
task_struct
*
tsk
,
sigset_t
*
mask
,
kernel_siginfo_t
*
info
)
{
signr
=
__dequeue_signal
(
&
tsk
->
pending
,
mask
,
info
,
&
resched_timer
);
if
(
!
signr
)
{
signr
=
__dequeue_signal
(
&
tsk
->
signal
->
shared_pending
,
mask
,
info
,
&
resched_timer
);
。。。
}
作為Unix時代的產物,前面講到的sigprocmask()函式最初是用來設定程序的block點陣圖的,但是同pending點陣圖/佇列不同的是,並沒有一個所謂的執行緒共享的block點陣圖的概念,所以在多執行緒環境下,sigprocmask()的語義也就變成了設定執行緒的block點陣圖。當然,為了語義更加明顯,你可以使用POSIX執行緒庫提供的pthread_procmask()函式,兩者的引數和行為都是一樣的。
參考:
《Linux環境程式設計:從應用到核心》第6章
Understanding the Linux Kernel
第3版第11章
UNIX Internals: The New Frontiers
第4章
http://
kernel。meizu。com/linux-
signal。html
https://www。
ibm。com/developerworks/
cn/linux/l-ipc/part2/index1。html?ca=drs-
原創文章,轉載請註明出處。