歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> 信號捕捉與模擬實現sleep()函數

信號捕捉與模擬實現sleep()函數

日期:2017/3/3 11:07:14   编辑:Linux技術

內核如何實現信號的捕捉

如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱為捕捉信號。

由於信號處理函數的代碼是在用戶空間的,處理過程比較復雜,舉例如下:

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

Copyright © Linux教程網 All Rights Reserved