內核如何實現信號的捕捉
如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱為捕捉信號。
由於信號處理函數的代碼是在用戶空間的,處理過程比較復雜,舉例如下:
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