信號的基本屬性:軟中斷,由內核發送,內核處理。某個進程通過內核向另一個進程發送信號時(引起信號產生的五個因素),另一個進程將會陷入內核進行中斷處理,未決信號集中相應信號置1,當遞達后,置0。如果阻塞信號集相應信號為1,則該信號處于未決狀態。處于未決狀態中的信號,多次發送時,只是執行一次,因為在未決信號集中只是記錄了該信號的狀態,沒有記錄發送的次數。信號抵達后,內核進行處理。處理方式有三:默認處理方式(5種);忽略(丟棄)和捕捉。下面說明捕捉機制。
signal和sigaction函數只是完成對一個信號進行注冊的功能,而對信號的捕捉的處理都是由內核完成的。當對一個信號進行注冊后,內核對其捕捉同時調用其注冊時對應的用戶處理函數。
(1)signal函數
typedef void (*sighandler_t)(int);? //定義一個函數類型 sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
作用:注冊一個信號捕捉函數
返回值:成功返回sighandler_t類型的函數(或函數首地址);失敗則返回一個宏:SIG_ERR。注意判斷該函數的返回值: sighandler ret = signal(·······);if(ret==SIG_ERR)
第一個參數為信號;第二個參數為sighandler_t類型函數(即返回值為void,形參為int)。
注意:該函數由ANSI定義,由于歷史原因在不同版本的Unix和不同版本的Linux中可能有不同的行為。因此應該盡量避免使用它,取而代之使用sigaction函數。
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>typedef void (*sighandler_t) (int); //定義sighandler_t類型void catchsigint(int signo)
{printf("-----------------catch\n");
}int main(void)
{sighandler_t handler;handler = signal(SIGINT, catchsigint); //注冊2號信號if (handler == SIG_ERR) {perror("signal error");exit(1);} //判斷返回值while (1);return 0;
}
[root@localhost 01_signal_test]# ./signal2
^C-----------------catch???????????????? //Ctrl+C?
^C-----------------catch??????????? ?????//Ctrl+C?
^\Quit (core dumped) ????????????????//Ctrl+\
只要一發送2號信號,就會執行相應函數。
(2)sigaction函數
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);?
作用:對某個信號進行注冊(同signal),即對某個信號之前對應的處理方式(函數)進行修改。
返回值:成功0;失敗-1,設置errno。
參數:
act:傳入參數,新的處理方式。
oldact:傳出參數,舊的處理方式。
struct 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_restorer:該元素是過時的,不應該使用,POSIX.1標準將不指定該元素。(棄用)
sa_sigaction:當sa_flags被指定為SA_SIGINFO標志時,使用該信號處理程序。(很少使用)?
重點掌握:
sa_handler:指定信號捕捉后的處理函數名(即注冊函數)。也可賦值為SIG_IGN表忽略 或 SIG_DFL表執行默認動作;
sa_mask: 調用信號處理函數時,所要屏蔽的信號集合(信號屏蔽字)。注意:僅在處理函數被調用期間屏蔽生效,是臨時性設置;sa_mask也是一個字(64位),只是在執行相應的用戶處理函數期間生效。即在執行用戶處理函數期間, sa_mask屏蔽的信號也不能遞達,處于未決狀態。如果sa_mask未屏蔽,則響應信號,中斷嵌套。相當于此期間,sa_mask代替了mask。
sa_flags:通常設置為0,表示用默認屬性。默認屬性即為:sa_mask中將自己屏蔽,即該信號的注冊函數執行期間,再次向進程發送該信號,該信號不能遞達,處于未決狀態。
最后一個參數如果不關心之前的處理方式,可以為NULL。
(3)信號捕捉機制
進程正常運行時,默認PCB中有一個信號屏蔽字,假定為mask,它決定了進程自動屏蔽哪些信號。當注冊了某個信號捕捉函數,捕捉到該信號以后,要調用該函數。而該函數有可能執行很長時間,在這期間所屏蔽的信號不由mask來指定。而是用sa_mask來指定。調用完信號處理函數,再恢復為mask。
sa_flags為0時,XXX信號捕捉函數執行期間,XXX信號自動被屏蔽。
阻塞的常規信號(1-31)不支持排隊,產生多次只記錄一次。(后32個實時信號支持排隊)
內核實現信號捕捉的過程如下:
首先,處于用戶態(user mode)的某個進程在執行到某個指令時突然接收某個信號(軟中斷,終端按鍵產生;硬件異常產生;命令產生;系統調用產生或者軟件條件產生),會暫停執行下一條指令而陷入內核進入內核態。
內核在處理這一異常后,在準備會用戶態之前先處理可以遞達該進程的信號。
如果該信號的處理方式為捕捉,則內核對該信號進行捕捉,同時調用相應的用戶處理函數,回到用戶態執行相應的用戶處理函數(注意不是回到主控制流程)。
在用戶處理函數執行完返回時,再次執行系統調用sigretum再次進入內核。因為函數執行完需要返回到該函數的調用點,而該函數是內核調用的,因此需要再次返回到內核。
最后,從內核再次返回到用戶模式,從上次中斷處繼續執行下一條指令。
?
//練習1:為某個信號設置捕捉函數;驗證在信號處理函數執行期間,該信號多次遞送,那么只在處理函數之行結束后,處理一次;驗證sa_mask在捕捉函數執行期間的屏蔽作用。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>void docatch(int signo) //用戶處理函數
{printf("the %dth signal is catched\n", signo);sleep(10); printf("-------finish------\n");
}
int main(void)
{int ret;struct sigaction act;act.sa_handler = docatch;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); //sa_mask屏蔽字中,3號信號置1act.sa_flags = 0; //默認屬性 信號捕捉函數執行期間,自動屏蔽本信號ret = sigaction(SIGINT, &act, NULL); //注冊2號信號if (ret == -1) {perror("sigaction error");exit(1);}while (1);return 0;
}
[root@localhost 01_signal_test]# ./test_sigac
^Cthe 2th signal is catched ??// 發2號信號 Ctrl +C
-------finish------
^Cthe 2th signal is catched ?// 發2號信號 Ctrl +C
^C^C^C^C^C^C^C^C^C^C^C^C^C-------finish------? ?// 執行期間,發多個2號信號
the 2th signal is catched
-------finish------???????? ???//但是只是執行了一次
^Cthe 2th signal is catched
^\^\^\^\^\^\^\^\^\^\^\^\-------finish------?? // 執行期間,發多個3號信號
Quit (core dumped)?? //2號信號處理完后,處理2號,則退出進程,結束。