(六)信號

目錄

1. 信號

1.1 什麼是信號

1.2 信號的命名 

1.3 誰會向進程發送信號

1.4 進程收到信號後,進程會如何處理

1.5 都有哪些信號

1.5.1 信號列表

1.5.2 常用信號

2. signal函數

函數原型

調用捕獲函數的過程

值得強調的地方

3. 子進程對父進程信號的繼承情況

3.1 fork創建子進程,但是沒有exec加載新程序時,信號的繼承情況

3.2 當有調用exec加載新程序時

3.3 總結

4、kill、raise、alarm、pause、abort函數

4.1 kill、raise

4.2 alarm、pause

4.3 abort函數    

5. 使用信號喚醒休眠函數

5.1 會導致休眠的函數

5.2 喚醒的方法

5.3 喚醒的過程

5.4 我想繼續休眠怎麼辦

5.5 休眠函數自動重啓

6. 信號的發送、接收和處理的過程  

6.1 信號屏蔽字

6.1.1 信號屏蔽字的作用,以及它被放在了哪裏

6.1.2 屏蔽字長啥樣子

6.1.3 我們可不可以自己修改信號屏蔽字,實現某個信號的打開和屏蔽呢?

6.2 未處理信號集

6.2.1 作用

6.2.2 什麼時候會記錄

6.2.3 什麼時候處理記錄的“未處理信號”

6.3 信號處理的完整過程

7. 修改信號屏蔽字的API

7.1 修改的原理

7.2 設置變量的API    

7.3 使用變量修改屏蔽字的API

8.sigaction函數


1. 信號

1.1 什麼是信號

信號是一種向進程發送通知,告訴其某件事情發生了的一種簡單通信機制。

1.2 信號的命名 

Linux下邊定義了很多的信號,所有的信號都是一個整數編號,不過爲了好辨識,Linux系統給這些整數編號都定義了對應的宏名, 宏名都是以SIG開頭

1.3 誰會向進程發送信號

總結起來,會有三個“人”會向進程發送信號,分別是“另一進程”、“OS內核”、“硬件”。

            

1.4 進程收到信號後,進程會如何處理

三種處理方式,分別是忽略、捕獲、默認。

1.5 都有哪些信號

1.5.1 信號列表

(1)35~64:這些信號是Linux後期增設的信號,這些個信號不需要關心

(2)1~34:也不是所有的信號都要掌握,我們只關心其中常用的信號

1.5.2 常用信號

(1)爲什麼當進程收到某些信號時,會被終止呢?

因爲你發送的這些信號的處理方式是終止,所以進程會被終止掉。

(2)kill命令

1)kill的作用

2)pkill

(3)信號的發送與接收

1)發送

     一般來說,大多數發送信號的原因,都是因爲內核、硬件發生了某些事件時,纔會向某個進程發送

我們自己發送信號的原因無非如下幾種情況:

2)接收

   對於我們自己寫的進程來說,最常見信號操作的還是接收信號,不過在一般情況下,我們進程並不會去重新設置信號的處理方式,而是使用信號的默認處理方式來處理信號

(4)core文件

1)什麼是core文件

      用於保存程序(進程)在當前結束的這一刻,進程在內存中的代碼和數據,core文件可以用於分析進程在結束時的狀況,不過由於進程代碼和數據都是二進制的,一般需要特殊軟件翻譯後才能看懂。

2)並不是所有的信號在終止進程時都會產生core文件

3)如果你不想丟棄core文件怎麼辦

     對相關的系統文件進行設置就可以了,core文件一般默認保存在當前路徑下。

2. signal函數

函數原型

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

  

調用捕獲函數的過程

  1. 當信號沒有發生時,進程正常運行,當信號發生時,進程的正常運行會被中斷,然後去處理信號
  2. 一看信號的處理方式是捕獲,就會從“信號處理方式登記表”中將捕獲函數的地址取出並執行捕獲函數,
  3. 捕獲函數執行完畢後,恢復進程的正常運行。

不過當信號來時,如果當前有一條指令正在運行,會先等這條指令運行執行完畢後再去調用信號處理函數。  

不過如果捕獲函數有調用exit或者_exit的話,進程會被終止,不過是正常終止。

值得強調的地方

(1)信號被設置爲SIG_DFL時,表示將處理方式設置爲默認

           其實在不做任何處理方式設置的情況下,信號的處理方式就是系統設置的默認處理方式。

(2)信號被設置爲SIG_IGN(忽略)時

           進程將不會再接收到這個信號,這信號對進城沒有任何影響。

(3)設置爲捕獲時,需要將handler設置爲捕獲函數的地址,類型爲void (*)(int)

           爲了確保和捕獲函數的類型統一,SIG_DFL、SIG_IGN和SIG_ERR宏的類型也必須是void (*)(int)。

(4)除了SIGKILL這兩個信號外,其它所有的信號都可被忽略和捕獲。

3. 子進程對父進程信號的繼承情況

父進程fork出子進程時,子進程會繼承父進程很多的屬性,其中就包括信號

3.1 fork創建子進程,但是沒有exec加載新程序時,信號的繼承情況

在fork子進程之前,如果父進程調用signal設置了某個信號的處理方式的話,那麼fork出的子進程會繼承父進程對該信號設置的處理方式

父進程將信號的處理方式設置爲捕獲時,捕獲函數對子進程也是有效的。

再次強調,只有在fork之前,父進程所設置的信號處理方式,纔會被子進程繼承。

 

(1)爲什麼捕獲函數在子進程裏面依然有效。

            因爲子進程複製了父進程的代碼和數據,子進程自然也會包含信號處理函數的代碼,所在子進程中依然有效。

(2)子進程可以自己調用signal函數,修改掉所繼承的處理方式。

(3)那如果父進程是在if(ret > 0){}裏面設置得呢?

            這就是父進程自己的設置,跟子進程沒有關係。

3.2 當有調用exec加載新程序時

3.2.1 fork之前,父進程設置的處理方式是忽略 或 默認時

exec加載新程序後,忽略和默認設置依然有效。

3.2.2 fork之前,父進程設置處理方式是捕獲時

新程序的代碼會覆蓋子進程中原有的父進程的代碼,信號捕獲函數的代碼也會被覆蓋,

既然捕獲函數已經不存在了,捕獲處理方式自然也就沒有意義了,所以信號的處理方式會被還原爲默認處理方式。

 

終之,如果子進程所繼承的信號處理方式是捕獲的話,exec加載新程序後,捕獲處理方式會被還原爲默認處理方式。    

3.3 總結

  

4、kill、raise、alarm、pause、abort函數

4.1 kill、raise

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

//kill命令就是調用這個函數來實現。

#include <signal.h>

int raise(int sig);

  

4.2 alarm、pause

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

int pause(void);

  

4.3 abort函數    

也被稱爲叫自殺函數,之所以稱爲自殺函數,是因爲調用該函數時,會向當前進程發一個SIGABRT信號

這個信號的默認處理方式是終止,因此如果不忽略和捕獲的話,會將當前進程終止掉。

5. 使用信號喚醒休眠函數

5.1 會導致休眠的函數

我們調用sleep、pause等函數時,這些函數會使進程進入休眠狀態,如果你不想繼續休眠時怎麼辦?

可以使用信號將其喚醒。

5.2 喚醒的方法

給信號登記一個空捕獲函數即可,當然你也可以在捕獲函數寫你要的代碼,不過如果僅僅只是用於喚醒的話,捕獲函數的內容一般都是空的。

5.3 喚醒的過程

當信號發送給進程後,會中斷當前休眠的函數,然後去執行捕獲函數,捕獲函數執行完畢返回後,不再調用休眠函數,而是執行休眠函數之後的代碼,這樣函數就被喚醒了。

5.4 我想繼續休眠怎麼辦

自己手動重新啓動休眠函數(重新調用休眠函數)。

 

根據被信號捕獲後 pause的返回值 調用goto

5.5 休眠函數自動重啓

比如使用read從鍵盤讀取數據,當鍵盤沒有輸入數據時,read會休眠,不過函數被信號喚醒後,會自動重啓read的調用。

當read函數休眠時,如果被信號喚醒了,當捕獲函數返回後,read會自動重啓。

 

對於絕大多數休眠函數來說,被信號中斷後,如果你想繼續休眠的話,需要自己去手動重啓

6. 信號的發送、接收和處理的過程  

6.1 信號屏蔽字

6.1.1 信號屏蔽字的作用,以及它被放在了哪裏

6.1.2 屏蔽字長啥樣子

爲了方便理解,我們簡單地認爲屏蔽字就是一個64位的unsigned int數,每一位對應着一個信號

0表示信號可以 被立即處理    1表示該信號被屏蔽了,暫不處理。

6.1.3 我們可不可以自己修改信號屏蔽字,實現某個信號的打開和屏蔽呢?

可以

只不過在默認情況下,信號屏蔽字中所有的位都爲0,也就說默認將所有的信號都打開了。

6.2 未處理信號集

6.2.1 作用

跟屏蔽字一樣,也一個64位的無符號整形數,專門用於記錄未處理的信號。

“未處理信號集”同樣也是被放在了進程的進程表中(task_struct)。

6.2.2 什麼時候會記錄

6.2.3 什麼時候處理記錄的“未處理信號”

當屏蔽字中該信號的位變成0時(被打開了),此時就回去檢查“未處理信號”,看該信號有沒有未決情況,有的話就處它。

6.3 信號處理的完整過程

    

7. 修改信號屏蔽字的API

7.1 修改的原理

(1)定義一個64位的與屏蔽字類似的變量

(2)將該變量設置爲要的值 ----------將某信號對應的位設置爲0或者爲1。

(3)使用這個變量中的值來修改屏蔽字

   

7.2 設置變量的API    

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

   set就是我們前面說的變量,至於變量名也可以定義爲其它的名字,不一定非要叫set。

  

7.3 使用變量修改屏蔽字的API

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

  

8.sigaction函數

sigaction函數相當於是signal函數的複雜版

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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