內核如何實現信號的捕捉
如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱為捕捉信號。
由於信號處理函數的代碼是在用戶空間的,處理過程比較復雜,舉例如下:
1. 用戶程序注冊了SIGQUIT信號的處理函數sighandler。
2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。
3. 在中斷處理完畢後要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達。
4. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關系,是 兩個獨立的控制流程。
5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態。
6. 如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了。
相關函數:
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函數可以讀取和修改與指定信號相關聯的處理動作。調用成功則返回0,出錯則返回- 1。signo是指定信號的編號。若act指針非空,則根據act修改該信號的處理動作。若oact指針非 空,則通過oact傳出該信號原來的處理動作。act和oact指向sigaction結構體:
將sa_handler賦值為常數SIG_IGN傳給sigaction表示忽略信號,賦值為常數SIG_DFL表示執行系統默認動作,賦值為一個函數指針表示用自定義函數捕捉信號,或者說向內核注冊了一個信號處理函數,該函數返回值為void,可以帶一個int參數,通過參數可以得知當前信號的編號,這樣就可以用同一個函數處理多種信號。顯然,這也是一個回調函數,不是被main函數調用,而是被系統所調用。當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函 數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那麼它會被阻塞到當前處理結束為止。如果在調用信號處理函數時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時自動恢復原來的信號屏蔽字。sa_sigaction是實時信號的處理函數,這裡暫不討論。
kill函數
#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);
參數說明pid:可能選擇有以下四種1. pid大於零時,pid是信號欲送往的進程的標識。
2. pid等於零時,信號將送往所有與調用kill()的那個進程屬同一個使用組的進程。3. pid等於-1時,信號將送往所有調用進程有權給其發送信號的進程,除了進程1(init)。
4. pid小於-1時,信號將送往以-pid為組標識的進程。sig:准備發送的信號代碼,假如其值為零則沒有任何信號送出,但是系統會執行錯誤檢查,通常會利用sig值為零來檢驗某個進程是否仍在執行。返回值說明: 成功執行時,返回0。失敗返回-1,errno被設為以下的某個值 EINVAL:指定的信號碼無效(參數 sig 不合法) EPERM;權限不夠無法傳送信號給指定進程 ESRCH:參數 pid 所指定的進程或進程組不存在示例:
#include <stdio.h> #include <sys/types.h> #include <signal.h> void catch(int sig) { printf("haha catch a sig : %d \n", sig); } int main() { struct sigaction act, oact; act.sa_handler = catch; sigemptyset(&act.sa_mask); act.sa_flags = 0; int i = 1; for(; i <= 31; ++i) { sigaction(i, &act, &oact); } pid_t id = getpid(); for(i = 1; i <= 31; ++i) { if(i == 9 || i == 19) { continue; } kill(id, i); sleep(1); } return 0; }上述代碼中,利用kill函數給主進程發送信號,主進程捕捉到信號後顯示該信號的編碼
注意:9號信號和19號信號會終止進程,因此捕捉不到!
利用信號捕捉模擬實現一個sleep()函數pause函數
#include <unistd.h> int pause(void); pause函數使調用進程掛起直到有信號遞達。如果信號的處理動作是終止進程,則進程終止,pause函數沒有機會返回;如果信號的處理動作是忽略,則進程繼續處於掛起狀態,pause 不返回;如果信號的處理動作是捕捉,則調用了信號處理函數之後pause返回-1,errno設置為EINTR, 所以pause只有出錯的返回值。錯誤碼 EINTR表示“被信號中斷”。 代碼:
#include <stdio.h> #include <sys/types.h> #include <signal.h> #include <unistd.h> void catch(int sig) { //do nothing } void my_sleep(int nsecs) { struct sigaction new, old; new.sa_handler = &catch; sigemptyset(&new.sa_mask); new.sa_flags = 0; sigaction(SIGALRM, &new, &old); alarm(nsecs); pause(); sigaction(SIGALRM, &old, NULL); } int main() { while(1) { printf("hello world\n"); my_sleep(1); } return 0; }上述代碼的運行過程如下: main函數調用mysleep函數,後者調用sigaction注冊了SIGALRM信號的處理函數 catch。 調用alarm(nsecs)設定鬧鐘。
調用pause等待,內核切換到別的進程運行。
nsecs秒之後,鬧鐘超時,內核發SIGALRM給這個進程。
從內核態返回這個進程的用戶態之前處理未決信號,發現有SIGALRM信號,其處理函數是catch。
切換到用戶態執行catch函數,進入catch函數時SIGALRM信號被自動屏蔽, 從catch函數返回時SIGALRM信號自動解除屏蔽。然後自動執行系統調sigreturn再次進入內核,再返回用戶態繼續執進程的主控制流程(main函數調用myslee函數)。
pause函數返回-1,然後調用alarm(0)取消鬧鐘,調用sigaction恢復SIGALRM信號以前的處理動作。
運行結果:每個1秒打印一條“hello world”消息
[b]
[/b]
本文出自 “11408774” 博客,請務必保留此出處http://11418774.blog.51cto.com/11408774/1831121