目錄
3.1 fork創建子進程,但是沒有exec加載新程序時,信號的繼承情況
4、kill、raise、alarm、pause、abort函數
6.1.3 我們可不可以自己修改信號屏蔽字,實現某個信號的打開和屏蔽呢?
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);
調用捕獲函數的過程
- 當信號沒有發生時,進程正常運行,當信號發生時,進程的正常運行會被中斷,然後去處理信號
- 一看信號的處理方式是捕獲,就會從“信號處理方式登記表”中將捕獲函數的地址取出並執行捕獲函數,
- 捕獲函數執行完畢後,恢復進程的正常運行。
不過當信號來時,如果當前有一條指令正在運行,會先等這條指令運行執行完畢後再去調用信號處理函數。
不過如果捕獲函數有調用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函數的複雜版