Linux下的信號機制


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內核源碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM