Linux signal那些事兒__Linux

來源:互聯網
上載者:User

Linux signal 那些事兒(1)

http://blog.chinaunix.net/uid-24774106-id-4061386.html

Bean_lee

Linux編程,訊號是一個讓人愛恨交加又不得不提的一個領域。最近我集中學習了Linux的signal相關的內容,分享出來,也為防止自己忘記。
    訊號的本質是非同步。非同步一這個詞,聽著高端大氣上檔次,又讓人云山霧繞,其則不然。其實我們想想,我們這個世界是非同步,每個人幹事兒,並不總是A->B->C->D這種。比如我在網上買了東西,我其實並不知道快遞幾時能到。我可能在公司裡面,在喝水,在回郵件,在查bug,在寫代碼,突然收到了快遞小哥的電話,注意這就是訊號的delivery。由於快遞的到來,我不得不停下我手頭的活兒,去簽收快遞。這就是傳說中的典型的非同步。我不知道快遞小哥幾時給我電話,但是我收到電話就去簽收,這是我的訊號處理函數。更進階一點,如果我在參加重要的會議,我可能需要屏蔽快遞小哥的電話(假如我知道其電話),這已經是linux下訊號的進階應用程式(sigprocmask)了。
    訊號是一種機制,是在軟體層次對中斷機制的一種類比,核心讓某進程意識到某特殊事情發生了。強迫進程去執行相應的訊號處理函數。至於訊號的來源可能來自硬體如按下鍵盤或者硬體故障(如ctrl+c發送SIGINT),可能來自其他進程(kill,sigqueue),可能來自自己進程(raise)。 
    訊號的本質是一種進程間的通訊,一個進程可以向另一個進程發送訊號,至少傳遞了signo這個int值。實際上,通訊的內容,可以遠不止是signo,可以通過SA_SIGINFO標誌位通知進程去取額外的資訊。
    我痛恨片湯話兒,可是上面一大坨片湯話兒,卻真真的道出了訊號的本質。
    前面也提到了,signal是個讓人愛恨交加的feature,原因在於沉重的曆史包袱。下面我將一一道來。
    在上古時期,UNIX就已經有了signal這個feature,但是當時的signal存在幾個問題:
   1 傳統的訊號處理函數是一次性的,而非永久性的。
    linux為了向下相容,依然實現了這個有缺陷的signal系統調用。你可看到signal系統調用的核心代碼中有SA_ONESHOT這個標誌位。
#ifdef __ARCH_WANT_SYS_SIGNAL
/*
 * For backwards compatibility. Functionality superseded by sigaction.
 */
SYSCALL_DEFINE2(signal,int, sig, __sighandler_t, handler)
{
    struct k_sigaction new_sa, old_sa;
    int ret;

    new_sa.sa.sa_handler= handler;
    new_sa.sa.sa_flags= SA_ONESHOT | SA_NOMASK;
    sigemptyset(&new_sa.sa.sa_mask);

    ret = do_sigaction(sig, &new_sa, &old_sa);

    return ret ? ret :(unsigned long)old_sa.sa.sa_handler;
}
#endif /* __ARCH_WANT_SYS_SIGNAL*/

    這個SA_ONESHOT標誌位,等同於SA_RESETHAND標誌:在arch/x86/include/uapi/asm/signal.h中有:
#define SA_ONESHOT    SA_RESETHAND

    訊號產生,到訊號處理函數開始執行,中間肯定是有時間差的。核心開始開始強迫進程對訊號作出響應,這叫作訊號的傳遞。也就是說訊號產生,核心只是在進程描述符記錄了一筆,該進程收到訊號X一枚,並沒有馬上強迫進程對訊號作出響應。已經產生但尚未傳遞的訊號叫掛起訊號。對於非即時而言,訊號不排隊,位元影像佔個位即可。對於即時訊號,則排隊,同一訊號可能有多個掛起訊號。這個不多說,後面自然提到。
    
    上圖反映了核心如何傳遞訊號。基本就是選擇一個掛起訊號,然後處理一個訊號。get_signal_to_deliver 是在進程中選擇一個訊號來handle。代碼在kernel/signal.c,其中有如下code:
        if (ka->sa.sa_handler== SIG_IGN)/* Do nothing.*/
            continue;
        if (ka->sa.sa_handler!= SIG_DFL){
            /* Run the handler.*/
            *return_ka = *ka;

            if (ka->sa.sa_flags& SA_ONESHOT)
                ka->sa.sa_handler= SIG_DFL;

            break; /* will return non-zero"signr" value */
        }

    我們看到了linux也實現了signal這個有缺陷的系統調用。傳統的signal系統調用,他的訊號處理函數是一次性的,執行過後,該訊號的訊號處理函數就變成了SIG_DFL。
    值得一提的是,glibc的signal函數,調用的已經不是傳統的signal系統調用,而是rt_sigaction系統調用,這種一次性的缺陷早已經解決了。怎麼證明: 
manu@manu-hacks:~/code/c/self/signal$ cat signal_fault_1.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

#define MSG "OMG , I catch the signal SIGINT\n"
#define MSG_END "OK,finished process signal SIGINT\n"
int do_heavy_work()
{
    int i ;
    int k;
    srand(time(NULL));

    for(i= 0 ; i < 100000000;i++)
    {
        k = rand()%1234589;
    }

}

void signal_handler(int signo)
{
    write(2,MSG,strlen(MSG));
    do_heavy_work();
    write(2,MSG_END,strlen(MSG_END));
}

int main()
{
    char input[1024]= {0};

#if defined TRADITIONAL_SIGNAL_API
    if(syscall(SYS_signal ,SIGINT,signal_handler) == -1)
#elif defined SYSTEMV_SIGNAL_API
    if(sysv_signal(SIGINT,signal_handler) == -1)
#else
    if(signal(SIGINT,signal_handler) == SIG_ERR)
#endif

    {
        fprintf(stderr,"signal failed\n");
        return -1;
    }

    printf("input a string:\n");
    if(fgets(input,sizeof(input),stdin)==NULL)
    {
        fprintf(stderr,"fgets failed(%s)\n",strerror(errno));
        return -2;
    }
    else
    {
        printf("you entered:%s",input);
    }

    return 0;

    
}

    編譯的時候,我沒有定義SYSTEMV_SIGNAL_API,就是標準的glibc的signal函數,我用strace跟蹤glibc的signal函數調用的系統調用是: 
rt_sigaction(SIGINT, {0x8048736,[INT], SA_RESTART}, {SIG_DFL,[], 0}, 8)= 0

    測試結果如下:
manu@manu-hacks:~/code/c/self/signal$ gcc-o signal_glibc signal_fault_1.c
manu@manu-hacks:~/code/c/self/signal$./signal_glibc
input a string:
input^COMG , I catch the signal SIGINT
^COK,finished process signal SIGINT
OMG , I catch the signal SIGINT
OK,finished process signal SIGINT
^COMG , I catch the signal SIGINT
OK,finished process signal SIGINT
^COMG , I catch the signal SIGINT
OK,finished process signal SIGINT
^Z
[1]+ Stopped./signal_glibc

    我們安裝的訊號處理函數並不是一次性的,原因就是glibc的signal函數調用的函數並非是signal系統調用,並沒有SA_ONESHOT標誌位。
    我們如何體驗下老古董的signal,glibc提供了一個sysv_signal介面,manual中這樣描述:
   However sysv_signal() provides the System V unreliable signal semantics, that is: a) the disposition of the sig‐
   nal is reset to the default when the handler is invoked; b) deliveryof further instances of the signal is not
   blocked while the signal handler is executing;and c) if the handler interrupts (certain) blocking system calls,
   then the system call is not automatically restarted.

    對於我們的例子只需要:
gcc -DSYSTEMV_SIGNAL_API-o signal_sysv signal_fault_1.c

    我們看下:
manu@manu-hacks:~/code/c/self/signal$./signal_sysv
input a string:
^COMG , I catch the signal SIGINT
^C
manu@manu-hacks:~/code/c/self/signal$ man sysv_signal

    第二個ctrl+C導致了進程的推出,原因是sysv_signal這種傳統的signal的安裝函數是一次性的。strace也證明了這一點:
rt_sigaction(SIGINT, {0x8048756,[], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, {SIG_DFL, [], 0}, 8)= 0

    還記得麼:
#define SA_ONESHOT SA_RESETHAND

    我們發現sysv調用的不是signal系統調用,而是rt_sigaction系統調用。如果你非要品嘗傳統的signal系統調用,這也不難。
gcc -DTRADITIONAL_SIGNAL_API  -o signal_traditional signal_fault_1.c 

    我們發現第二個SIGINT訊號的訊號處理函數已經SIG_DFL,使進程退出了。
manu@manu-hacks:~/code/c/self/signal$./signal_traditional
input a string:
^COMG , I catch the signal SIGINT
^C

    我們通過strace可以證明,的確調用了signal系統調用: 
signal(SIGINT, 0x8048736)= 0 (SIG_DFL) 

    2早期的訊號,沒有屏蔽正在處理的訊號。
   
如何證明這一點呢。我上面的例子中故意在訊號處理函數中做了很heavy很耗時的操作,從而容易造出處理訊號A的時候,另一訊號A又被deliver的情境。
    因為do_heavy_work是個很耗費時間的操作,訊號處理完成我們會在標準錯誤上輸出處理完成的語句,這就表徵了訊號處理結束了沒有。
    我們看下傳統signal的,收到一個SIGINT的訊號的情況:
manu@manu-hacks:~/code/c/self/signal$./signal_traditional
input a string:
^COMG , I catch the signal SIGINT
OK,finished process signal SIGINT
fgets failed(Interrupted system call)
manu@manu-hacks:~/code/c/self/signal$

    如果我在進程處理訊號處理函數的時候,再次發送一個SIGINT,這個SIGINT也可能被核心deliver。那麼訊號處理函數就被中斷掉,
manu@manu-hacks:~/code/c/self/signal$./signal_traditional
input a string:
^COMG , I catch the signal SIGINT
^C
manu@manu-hacks:~/code/c/self/signal$

    我們看到我們收到了I catch the signal SIGINT的列印,但是,並沒有收到OK,I finished process signal SIGINT,這表明傳統的signal並沒有屏蔽正在處理的訊號。
    那麼我們現在的glibc的signal函數如何。
    strace又來幫忙了?

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.