2017-04-06
之前在看LinuxThreads線程模型的時候,看到該模型是通過信號實現線程間的同步,當時沒有多想,直接當做信號量了,現在想起來真是汗顏……后來想想並不是那么回事,於是,就有了今天這篇博文!
其實關於信號的文章,網上有很多,寫的也很好,而筆者僅僅是想把自己的想法記錄下來,一來幫助自己捋順思路,二來說不定還可以幫助他人理解,如有錯誤,還請指正!
一、總體介紹
Linux的信號在實現機制上根中斷很類似,以至於有文獻把linux信號作為軟中斷,這當然在一定程度上是說的過去,但是筆者不建議這么做,因為雖然二者的實現機制又相同之處,但是二者所處理的事務千差萬別;況且軟中斷在Linux中是一個專有名詞,比如中斷處理下半部常作為軟中斷的方式存在,而軟中斷的記錄根CPU相關而根某個進程無關,這里強行把信號作為軟中斷對於初學者的理解也有害無益,至少應該明確說明區別,想當初筆者本科時由老師提到異常就是軟中斷,這概念因為先入為主,在筆者后來看到真正的軟中斷時遲遲不能接受。至於軟中斷,可參考筆者另一篇博文。
當然把信號作為軟中斷也並非空穴來風,自然有其道理。首先,二者均是作為異步事件的處理方式。相對於操作系統來講,中斷作為異步方式存在已經眾人皆知,信號也不例外。但是信號相對的,是進程,而並非操作系統。說到這里,還有文獻說進程其實就是一個虛擬機,當然僅僅是廣義上的。那么從這個角度,信號像是把中斷擴展到更小的單元上的一個方式。
二、信號的實現方式
之前說到,信號是相對於進程存在的,在進程結構體task_struct結構中,信號相關的字段如下:
/* signal handlers */ struct signal_struct *signal; struct sighand_struct *sighand; /*屏蔽的信號*/ sigset_t blocked, real_blocked; sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ /*掛起的信號*/ struct sigpending pending;
同一個進程中的所有線程共享一個signal_struct結構和sighand_struct結構,即這兩個結構都是進程相關的。而之前咱們也說過,Linux中線程在內核中同樣也是由task_struct代表的,下面幾個字段都是針對單個線程的,blocked字段記錄當前線程對信號的屏蔽情況,其類型早期是unsigned long類型,x86架構下就是32位,而現在升級成一個結構,其大小根據具體的信號數量確定,當前最大信號數量為64,以后還可能增加。
#define _NSIG 64 #define _NSIG_BPW 32 #define _NSIG_WORDS (_NSIG / _NSIG_BPW)
#define _NSIG 64
typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t;
利用宏定義擴展位圖。每位對應一個信號,當某個信號對應的位為1時,表明該信號暫時被屏蔽,但這並不意味着信號不能產生,恰恰相反,信號依然可以被傳遞到當前線程,只是當前線程對信號不做處理,把信號阻塞或稱為掛起。待到該信號不被屏蔽了,那么該信號就可以得到處理了。掛起的信號記錄在pending字段,sigpending結構如下:
struct sigpending { struct list_head list; sigset_t signal; };
所有產生的信號都會被掛入這個list為表頭的鏈表中,這樣產生多個信號也不會丟失。signal還是做鏈表中信號標記,表明當前有哪些類型的信號未處理。鏈表中節點結構是sigqueue,只是需要注意,這里信號分為可靠信號和不可靠信號。二者的區別暫時不介紹,不可靠信號是早期支持的32個信號,從0-31,而可靠信號是大於31的信號。不可靠信號在投遞還是采取先前的方式,這是為了和之前保持兼容,即signal位圖中每個位代表一個信號,且一個信號只有一個sigqueue結構與之對應;當目前存在未處理的信號,再次受到這個信號就會忽略。而可靠信號位圖中也有與之對應的位,而且每收到一個信號就生成一個sigqueue結構掛入鏈表,sigqueue結構如下。
struct sigqueue { struct list_head list; int flags; siginfo_t info; struct user_struct *user; };
三、信號的處理
信號的處理時機主要由兩個:
1、從內核空間返回到用戶空間前夕。
2、進程位於內核空間被喚醒。
對於信號的處理主要由兩種處理方式,用戶可以對信號定義自己的處理函數,也可以采用系統默認的處理函數。在上述task_struct結構中有sighand_struct類型的字段 sighand,記錄了信號的處理函數
struct sighand_struct { atomic_t count; struct k_sigaction action[_NSIG]; spinlock_t siglock; wait_queue_head_t signalfd_wqh; };
第一個count應該指action表項的數目,第二個action和每一個信號對應,一個表項記錄一個信號的處理函數,當為0時表示采用系統默認的處理方式。注意,在signal_struct中並沒有設置鎖,源碼注釋解釋為一個共享的signal_struct必定有一個sighand_struct結構與之對應,對sighand_struct加鎖即可實現對signal_struct保護。
struct sigaction { __sighandler_t sa_handler; unsigned long sa_flags; sigset_t sa_mask; /* mask last for extensibility */ };
sa_handler指向一個處理函數,sa_flags記錄信號處理時的一些設置,可以有如下值
- SA_ONSTACK:表明要使用已經注冊的新棧,而不是使用進程自身的棧。
- SA_RESTART:設置在信號被中斷后重啟。
- SA_NOCLDSTOP:當該位設置時,在子進程stop時不產生SIGCHLD信號。
- SA_RESETHAND:當調用信號處理函數時,將信號的處理函數重置為缺省值SIG_DFL
- SA_RESTART:如果信號中斷了進程的某個系統調用,則系統自動啟動該系統調用
- SA_NODEFER :一般情況下, 當信號處理函數運行時,內核將阻塞該給定信號。但是如果設置了 SA_NODEFER標記, 那么在該信號處理函數運行時,內核將不會阻塞該信號
sa_mask就代表在處理當前信號時,可以選擇性的屏蔽一些信號。相當於即時有效的。
在用戶空間可以調用signal和sigaction 函數給一個信號設置處理函數,SIGKILL和SIGSTOP信號除外,這兩個信號必須采用系統默認的函數。當一個信號被設置handler時,已經掛起的該類信號被丟棄。關於signal和sigaction的區別這里不做詳細介紹。對於sa_handler還可以是SIG_DFL和SIG_IGN兩個值,前者為0表示采取默認的處理方式。后者為1,表示忽略該信號。
參考資料:
LInux3.10.1內核源碼
