信號是什麼?
- 信號實質是一種軟中斷,用於通知進程發生了某些事件,實際上信號也可以算作進程間通信的一種方式,因爲我們可在進程通過另一個進程發送信號,來告訴另一個進程發生什麼事。
這樣來講我們聽起來可能還會比較暈。
深入理解信號:
- 在我們生活中其實就有信號的例子,例如:當我們經過十字路口時會看到紅綠燈,紅綠燈其實就可以類比信號。它來告知進程1(行人)此時應該停下還是通過該路口;同時它也告知進程2(車輛)是否需要停下還是繼續經過;這是信號對行人和車輛的作用。
信號的生命週期又是怎樣的呢?
-
信號的一生經歷了這樣的過程:
產生->註冊->註銷->處理
類比於我們生活中的紅綠燈同樣也經過了這樣的過程:產生(紅綠燈的每個顏色的出現即信號的產生)->註冊(紅綠燈最終都是要被行人和車輛注意的,當人們和車輛在腦海中形成了這個意識的時候,也就是信號註冊成功)->註銷(當我們腦海中有了這個意識的時候,我們就不需要在關注紅綠燈了,此時我們就已經做出了處理,停止或通過路口不過在信號中和人的思維有一些不同它們是先將信號從“腦”中踢了出去,然後在處理的。)->處理(當我們看到紅綠燈時,我們的一種動作即是對信號的處理,停止或者繼續通過)。
-
其實在上述過程中還有一個過程或者狀態:阻塞/屏蔽
還是紅綠燈例子:當我們看到了紅燈亮了的時候,我們是立即停下了嗎?其實我們並沒有,當我們腦海有了這個意識(即信號的註冊)時,我們可能還低頭看手機或者思索一些問題,突然當我們多向前走了幾步,然後突然又想起是紅燈,然後才停下來的;其實進程也是這樣的它們並不會看到信號立即就會處理,而是先放在“腦海”,在處理完某些事之後處理信號的。
那麼信號的生命週期應該是這樣的:
產生->註冊->“阻塞/屏蔽”->註銷->處理
而阻塞/屏蔽並不是信號所硬性要求和必須的所以對它打起了引號。 -
說了這麼多讓我們先來看看Linux下面都有那些信號
我們可以通過kill命令-l選項查看linux下所有的信號:
紅色方框裏面的 1—31的31個信號爲非可靠信號,其餘的34—64的 31個信號爲可靠信號由此可知Linux下一共有62個信號。關於可靠信號和非可靠信號留在下面講。關於這31個非可靠信號是從unix下繼承過來的,後來由於信號的不足,Linux有添加了後面的34到64這31個可靠信號。
對信號各個階段理解: -
1.信號的產生:
- 信號的產生有三中方式:
1.硬件中斷 如:當我們在Linux下運行某一個程序時按下ctrl+c時終止了該程序(進程)SIGINT信號。
2.硬件異常 如:程序發生某些錯誤如我麼常見的(segment fault(core dumped))。
3.軟件中斷:如kill、raise、alarm、sigqueue(在後面一一介紹)。
下面瞭解一下一些關於信號的接口:
1.kill函數:
功能:向指定進程(pid)發送指定信號(sig)。int kill(pid_t pid, int sig);
返回值:成功返回0;失敗返回-1。
sig:可以爲上述62個信號中的任意一個。
2.raise函數:
功能:等同於kill(getpid(),sig),給進程/線程自身發送。int raise(int sig);
返回值:成功返回0;失敗返回一個非0值。
3.sigqueue函數:
功能:給指定進程發送指定的信號,同時可以通過value攜帶 一個參數過去。int sigqueue(pid_t pid, int sig, const union sigval value);
返回值:成功返回0;失敗返回-1並設置errno值
4.alarm函數:
功能:指定在seconds秒後發送一個SIGALRM時鐘信號給進程。unsigned int alarm(unsigned int seconds);
返回值:返回seconds當前剩餘的秒數,返回0表示沒有時鐘。
注意: 當seconds傳爲0時,取消前面所設置的鬧鐘。
5.abort函數:
功能:類似於kill(getpid(),SIGABRT);向調用進程發送一個SIGABRT信號。void abort(void);
SIGABRT爲6號信號,當我們對正在運行的進程發送這個信號時,我們的進程會結束掉,並報錯(core dumped),core dump會產生一個核心轉儲文件,但我們的Linux通常是默認關閉的,但我們可以通過ulimit -c [重新所要設置存儲核心轉儲文件的大小];即可開啓核心轉儲文件的生成,此時我們給進程發送這個信號的時候,就會在當前目錄下看到例如:core.3954這樣的一個文件3954告知我們是哪個進程產生的。說到這裏,我們就有必要知道這個核心轉儲文件是幹嘛的了:當程序異常的時候,會記錄一個核心轉儲文件,該文件中記錄的是程序的運行數據,當我們的程序異常崩潰時,而這個錯誤出現的時間可能間隔很長,此時我們的調試就很難做,錯誤很難定位時,此時核心轉儲文件就可以幫助我們使用gdb調試查看錯誤信息,定位錯誤。
gdb 調試:file 加載可執行程序
core-file core.pidjia加載core文件就可以看到錯誤的定位。
由於核心轉儲文件可能含有系統安全信息,以及文件增多帶來的開銷,linux默認情況是關閉的。
- 信號的產生有三中方式:
-
2.信號的註冊:
-
信號的註冊即將這個信號傳遞給進程,將這個信號記錄到進程中,讓進程知道有這麼個信號。
信號是記錄在進程PCB中。信號集合sigset_t結構體中,可以在/usr/include/bits目錄中看到sigset.h,在這個頭文件中即可看到該結構體。其實進程記錄一個信號是通過這個結構體位圖記錄的,這個位圖就是指定信號的存儲位置strcut sigpending pending的結構體。可以在/usr/src/kernels/3.10.0-327.el7.x86_64/include/linux這個目錄下看到sched.h頭文件,在這裏面就有PCB信息(注意:內核版本我們·可能是不一樣的哦!!)。struct sigpending{ struct sigqueue *head, *tail; sigset_t signal; };
struct sigqueue{ struct sigqueue *next; siginfo_t info; }
信號註冊過程:
1.將信號對應位圖signal置1
2.加入未決信號信息鏈表中
-
-
3.“信號的阻塞與屏蔽”:
在PCB中還有一個sigset_t blocked的位圖,用來標識那些信號要被阻塞。在進程看到pending集合中收到了那些信號,然後就開始處理這些信號,但是在處理這些信號之前,進程會先對比一下信號有沒有在blocked位圖裏面,如果在裏面意味着這個信號將不被處理,直到解除阻塞。
其過程是這樣的:
信號阻塞先關接口:
在此之前得有一些設置:sigset_t mask-
sigemptyset函數:
int sigemptyset(sigset_t *set);
功能:清空集合set中的信號,防止已經存在在set中其它信號造成未知錯誤。
返回值:成功返回0,失敗返回-1; -
sigfillset函數:
int sigismember(const sigset_t *set, int signum);
功能:向集合中添加所有信號。
返回值:成功返回0,失敗返回-1; -
sigaddset函數:
int sigaddset(sigset_t *set, int signum);
功能:向集合中添加指定信號。
返回值:成功返回0,失敗返回-1; -
sigprocmask函數:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
參數:how:對集合中信號所要做的操作:SIG_BLOCK(將set集合中的信號設置爲新的信號屏蔽字)、SIG_UNBLOCK(解除set集合中的信號屏蔽字)、SIG_SETMASK(將set集合中的信號設置到blocked集合中)。
oldset:保存原有的信號集合。
功能:一個進程的信號屏蔽字規定了當前阻塞而給該進程的信號集。調用函數sigprocmask可以檢測或更改其信號屏蔽字,或者在一個步驟中同時執行這兩個操作。
返回值:成功返回0,失敗返回-1。 -
sigismember函數:
int sigismember(const sigset_t *set, int signum);
參數:signum 表示所要檢測的信號值(或信號的宏)。
功能:檢測一個信號是否在set中。
返回值:成功返回0,失敗返回-1。 -
sigpending函數:
int sigpending(sigset_t *set);
功能:將當前(信號註冊集合)中的信號取出放入set中。
-
-
4.信號的註銷:
從pending集合中將要處理的信號移除,該過程就是信號的註銷。
注意:信號實現將要處理的信號移除,然後才處理的。
移除分爲兩種情況:
-
5.信號處理:
信號處理方式分爲:默認處理、忽略處理、自定義處理。
默認處理:操作系統原有定義好的對於一個信號的處理方式。
忽略處理:切記忽略和阻塞不是一回事,一個信號如果被忽略了就等於直接被丟棄,不會修改位圖,不會被註冊。
自定義處理:我們用戶自己定義一個信號的處理方式,然後告訴操作系統該信號來了就按自定義方式處理。
信號是在進程從內核態切換到用戶態的時候去檢測一下有沒有信號需要被處理,然後才處理的。- 關於信號處理的接口:
signal函數:
參數:signum:要處理的信號(宏/信號值)。typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
handler:SIG_IGN 忽略處理;SIG_DFL 默認處理;函數指針,該信號的自定處理方式。
sigaction函數:
參數:act處理方式只不過是封裝在這個結構體中,下面詳細說明。int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
oldact:用於保存原先對該信號的處理方式。
參數:sa_flags:0 時使用sa_handler ;1 時使用sa_sigaction,這種處理方式會結收信號攜帶的個參數。struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };
sa_mask:在處理信號的時候,希望這個處理不會被其它信號的到來而打擾,因此sa_mask就是用於在處理信號時要阻塞,就把其他信號添加到這個集合中暫時阻塞。
有兩個比較特殊的信號: SIGSTOP、SIGKILL這兩個信號無法被忽略,也無法被自定義處理。
- 關於信號處理的接口:
-
本片博客講述信號的一生和各個階段的處理方式以及部分簡單原理,由於時間和篇幅問題,很多問題還未能解決,會在下一篇博客中繼續更新。