信號

1、產生信號

  1. 如何產生信號呢?

    <1> 鍵盤。當某個進程正在運行時,按下Ctrl_C便可終止進程。
    <2> 命令。如kill -9 3212 將id為3212的進程終止。
    <3> 函數。如kill,alarm,abort函數。
    <4> 操作系統捕捉。軟件,硬件異常
    2、如何處理信號 ?
    <1> 忽略信號
    <2> 執行該信號的默認動作,一般是將該進程終止
    <3> 捕捉。自定義函數。
    3、進程捕捉到信號時,并不是立即處理,而是在合適的時候處理。應先將所接受到的信號保存在自己的PCB中。等待合適的時機去處理(具體的處理時機,下面會有專門的講解)。

2、信號阻塞

1、基本概念:
信號遞達:實際執行信號的處理動作稱為信號遞達。忽略是遞達的一種。
信號未決:信號從產生到遞達之間的狀態稱為信號未決。
信號阻塞 進程可以選擇阻塞某一信號。被阻塞的信號將保持在未決狀態,直到進程解除對此信號的阻塞,才能執行遞達的動作。

2、 信號在內核中的表示示意圖(圖片來源于網絡):


信號在內核中示意圖.png

3、每個信號都有兩個標志位分別為block(阻塞)和pending(未決) + 一個函數指針表示處理動作
1、SIGUP:信號未產生也為阻塞,當它遞達時執行默認處理動作
2、SIGINT:信號產生,但是阻塞。它的處理動作是忽略。阻塞不解除,都不會執行處理動作。
3、SIGQUIT:沒有信號產生,一旦產生就會被阻塞,處理動作是用戶自定義函數sighandler。

1)每個信號只有一個bit的 “未決標志” 和 “阻塞標志”,非0即1,不記錄該信號產生的次數。因此,未決和阻塞標志可以用相同的數據類型sigset_t儲存,sigset_t稱為信號集。這個類型表示信號的有效和無效。
2)在阻塞信號集(信號屏蔽字)中即block表,1表示信號阻塞,0表示不阻塞。在未決信號集即pending表,1表示信號產生未決狀態,0表示沒有產生信號。總而言之,信號集為能夠表示信號類型的0,1序列。

注:不可使用位操作操作信號集,有專有的函數操作

4、信號集操作函數:
縮略圖(信號集操作函數包含在signal.h中,需要導入該頭文件):


信號集操作函數.png

1、sigemptyset: 初始化set所指向的信號集,使其中所有的信號對應的標志位置為0。
2、sigfillset: 初始化set所指向的信號集,使其中所有的信號對應的bit置1。
3、sigaddset: 在該信號集中 添加 某一信號。
4、sigdelset: 在該信號集中刪除某一信號。
5、sigismember: 判斷一個信號集的有效信號中 是否包含某種信號,若包含返回1,不包含返回0,出錯返回-1。
6、sigprocmask函數: 讀取或更改進程的信號屏蔽字(阻塞信號集)
返回值:成功為0,失敗為-1.

sigprocmask函數原型.png

(1)若oset是非空指針,那么進程的當前信號屏蔽字通過oset返回。
(2)若set是一個非空指針,則參數how指示如何修改當前信號屏蔽字。
(3)下圖說明了how參數可以取用的值:
how參數可選值及作用.png

如果set是空指針,則不改變該進程的信號屏蔽字,how的值也無意義。

關于sigpromask()函數的詳細使用,這有一篇講的比較細的文章:
http://blog.csdn.net/big_bit/article/details/51338523

7、sigpending函數:讀取當前進程的未決信號集
int sigpending(sigset_t *set);
返回值:成功為0,出錯為-1

3、信號捕捉

信號捕捉過程<4次權限轉換>:


信號捕捉過程.png

前面提到:當進程捕捉到信號后,并不是立即處理,而是在合適的時候進行處理,這個合適的時機就是:從內核態返回到用戶態時。

1、內核處理完異常或者中斷時,先檢查當前進程中是否有可以被遞達的信號。如果有則處理。(一般產生異常或者中斷時都會隨之產生相應的信號)
2、如果信號的處理方式為自定義的函數:則返回用戶態(第二次權限變更),執行自定義的信號處理函數(注:不是回到主控制流程)。執行完處理函數后,通過調用sigreturn再次進入內核(第三次權限變更),最后從內核態返回主控制流程中上次被中斷的地方執行。(第四次權限變更)。

為什么要有第四次權限變更?筆者認為,在異常或者中斷產生的時候,直接進入了內核態,那么再次回到中斷處時,自然應該也是從內核態回去,而不是從用戶態回去。這樣相當于把信號的處理操作封裝成黑盒操作。

3、如果信號的處理方式為默認:則終止進程。
4、如果信號的處理方式為忽略:從未決表(pending表)中刪除該信號,即將1變為0,直接跳到用戶態。

4、兩個特殊的函數及區別:

1、signal函數:指定某一信號的處理函數。

函數原型:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(in signum,sighanler_t handler);

2、sigaction函數

函數原型:

intsigaction(int signo, conststruct sigaction*restrict act,
 struct sigaction*restrict oact);

//結構體sigaction定義如下:
struct sigaction {
  void (*sa_handler)(int);
   sigset_t sa_mask;
  int sa_flag;
  void (*sa_sigaction)(int,siginfo_t*,void*);
};

作用:讀取和修改與指定信號相關聯的處理動作(處理函數)。
返回值:成功為0,失敗為-1
實際使用:
在構造sigaction這個結構體時,需要注意一下幾點:
1、sa_mask成員的賦值:必須用sigemptyset函數初始化act結構的sa_mask成員。不能保證:act.sa_mask = 0;會做同樣的事情。
2、sa_flags成員值:設置不同的值系統會有不同的處理:

1、SA_ONSTACK:捕獲在信號調用棧中的信號。
2、SA_RESTART:由此信號中斷的系統調用會自動重啟。
3、SA_NODEFER: 一般情況下, 當信號處理函數運行時,內核將阻塞<該給定信號 -- SIGINT>。但是如果設置了SA_NODEFER標記, 那么在該信號處理函數運行時,內核將不會阻塞該信號。 
4、SA_RESETHAND: 當調用信號處理函數時,將信號的處理函數重置為缺省值。(一般我們不需要在處理函數被調用后,立馬設置為缺省值)
這兩個標志對應早期的不可靠信號機制,除非明確要求使用早期的不可靠信號,否則不應該使用。這里也不多做介紹。
5、SA_NOCLDSTOP:  一般當進程終止或停止時都會產生SIGCHLD信號,但若對SIGCHLD信號設置了該標志,當**子進程停止**時不產生此信號。當子進程終止時才產生此信號。
6、SA_NOCLDWAIT: (字面理解是:子進程不等待,也就是直接退出 ,這樣就不會存在僵尸進城) 若信號是 SIGCHLD時,當使用此標志時,
1 )當調用進程的子進程終止時不創建僵尸進程。
2 )若調用進程在后面調用wait。則調用進程阻塞,直到其所有子進程都終止

7、SA_SIGINFO:簡單講就是,可是使我們定義的處理函數中,多一個info參數,這個參數中包含了,信號的相關信息。
在開頭我們看到 struct sigacton結構有一個  void    (*sa_tramp)(void *, int, int, siginfo_t *, void *); 字段,該字段是一個 替代的信號處理函數。
當我們沒有使用 SA_SIGINFO 標志時,調用的是 sa_handler指定的信號處理函數。
當指定了該標志后,該標志對信號處理函數提供了附加的信息,一個指向siginfo結構的消息和一個指向進程上下文標識符
的指針這時我們就能調用sa_sigaction指定的信號處理函數

8、SA_USERTRAMP:字面意思理解:不從內核態跳出。(信號捕捉過程中,有四次權限變更,該標志位的具體作用,暫時還不清楚....)

以上是對sa_flags設置不同值,會帶來的影響到簡單總結,這里涉及到內核編程,一定注意在不同平臺上會有不同的影響,這里只是基于蘋果的signal.h文件來編寫。
筆者在調研過程中,發現有一篇不錯的學習文檔,可更參考:
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28852942&id=3754478/

3、附上sigaction的使用:使用sigaction來模仿signal的實現:

signal(int signo, Sigfunc *func) {
    struct sigaction    act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask); //這里需要注意
    act.sa_flags = 0; //flags值的設置,參考上面的講解
    if(signo == SIGALRM) {
#ifdef    SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else {
#ifdef    SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }
    if(sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}

4、關于signal 和sigaction 二者區別:簡單提一下,二者實際上都是用來指定信號的處理函數的,只不過前者是較早版本中的提供一種方式。此處可參考下文:http://blog.csdn.net/wangzuxi/article/details/44814825
所以這里筆者只重點對sigaction函數講解。
5、筆者之所以會對這里做深入調研,是因為在做iOS平臺的crash捕捉以及防護時,需要用到signal相關知識,因此做了系統學習并總結出來分享給大家,當然對于這塊知識,如有理解不準確,還希望指正。

參考:http://en.cppreference.com/w/c/program/SIG_types

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容