歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux信號編程實踐(二)信號發送函數和可重入函數

Linux信號編程實踐(二)信號發送函數和可重入函數

日期:2017/3/1 12:25:07   编辑:關於Linux

在早期的UNIX中信號是不可靠的,不可靠在這裡指的是:信號可能丟失,一個信號發生了,但進程卻可能一直不知道這一點。

現在Linux 在SIGRTMIN實時信號之前的都叫不可靠信號,這裡的不可靠主要是不支持信號隊列,就是當多個信號發生在進程中的時候(收到信號的速度超過進程處理的速度的時候),這些沒來的及處理的信號就會被丟掉,僅僅留下一個信號。

可靠信號是多個信號發送到進程的時候(收到信號的速度超過進程處理信號的速度的時候),這些沒來的及處理的信號就會排入進程的隊列。等進程有機會來處理的時候,依次再處理,信號不丟失。

同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。

sigaction和signal函數都是調用內核服務do_signal函數;[內核服務函數,應用程序無法調用該函數

非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。

關於信號發送的一些API:

1.kill

int kill(pid_t pid, int signo); 

參數:
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 所指定的進程或進程組不存在。

void handler(int sig)
{
        printf("recvv a sig =%d\n",sig);
}
int main()
{
        if(signal(SIGUSR1,handler)==SIG_ERR)
        ERR_EXIT("signal error!");
        pid_t pid=fork();
        if(pid==-1)
                ERR_EXIT("fork error!");
        else if(pid==0)
        {
                                //等於 killpg(getpgrp(),SIGUSR1);
                pid=getpgrp();
                kill(-pid,SIGUSR1);
                /*
                   kill(getppid(),SIGUSR1);
                 */
                                //以上
                exit(EXIT_SUCCESS);
        }
        int n=5;
        do{
                n=sleep(n);
        }while(n>0);//
        return 0;
}

注意點:

(1)sleep函數會被信號打斷,進行完信號的處理函數後不再睡眠,而是繼續執行sleep函數以後的操作。如果我們就是想要程序睡眠一段時間呢?通過man手冊發現,sleep函數的返回值是 還剩余的秒數,所以可以采用循環的形式,即:

while(n=sleep(n));
(2)如果發出信號的目標是進程組,那麼子進程fork的時候會繼承信號,從而會發生兩次信號處理。

2.raise

raise()給自己發送信號,等價於raise(getpid(),sig)

3.killpg

killpg()給進程組發送信號,killpg(pgrp,sig)等於kill(pgrp,sig)

4.sigqueue

int sigqueue(pid_t pid, int sig, const union sigval value); 
給進程發送信號,支持排隊,可以附帶信息。
5.alarm

alarm函數,設置一個鬧鐘延遲發送SIGALRM信號(告訴Linux內核n秒中以後,發送SIGALRM信號);
但是alarm函數一次只能發送一個信號,所以必須遞歸調用才可以實現間歇性發送信號的功能。

void handler(int sig)
{
        printf("recvv a sig =%d\n",sig);
        alarm(1); //間接遞歸,持續發送信號
}
int main()
{
        if(signal(SIGALRM,handler)==SIG_ERR)
        ERR_EXIT("signal error!");
        alarm(1);
        while(1)
                pause();
        return 0;
}

可重入/不可重入函數

主要用於多任務環境中,一個可重入的函數簡單來說就是可以被中斷的函數,也就是說,可以在這個函數執行的任何時刻中斷它,轉入OS調度下去執行另外一段代碼,而返回控制時不會出現什麼錯誤;而不可重入的函數由於使用了一些系統資源,比如全局變量區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。

也可以這樣理解,重入即表示重復進入,首先它意味著這個函數可以被中斷,其次意味著它除了使用自己棧上的變量以外不依賴於任何環境(包括static),這樣的函數就是purecode(純代碼)可重入,可以允許有該函數的多個副本在運行,由於它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全局變量(包括static),一定要注意實施互斥手段。可重入函數在並行運行環境中非常重要,但是一般要為訪問全局變量付出一些性能代價。

編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護。

說明:若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變為不可知狀態。

為了增強程序的穩定性,在信號處理函數中應使用可重入函數。

下面給出大家一個不可重入函數在信號處理程序中可能發生的錯誤:

ypedef struct
{
        int a;
        int b;
        int c;
        int d;
}TEST;
TEST g_data;
void handler(int sig)
{
       // printf("recvv a sig =%d\n",sig);
        printf("%d %d %d %d\n",g_data.a,g_data.b,g_data.c,g_data.d);
        alarm(1);
}
int main()
{
        TEST zeros={0,0,0,0};
        TEST ones={1,1,1,1};
        if(signal(SIGALRM,handler)==SIG_ERR)
        ERR_EXIT("signal error!");
 
        g_data=zeros;
        alarm(1);
        while(1)
        {
                g_data=zeros;
                g_data=ones;
        }
 
        return 0;
}
\
如圖,會出現類似0011的交叉結果,原因是g_data=ones內部對a,b,c,d分別賦值時,有可能被信號中斷,造成一部分舊值還沒來的及替換,發生錯誤。這說明進行的操作不是原子操作,是不可重入函數。不要在信號處理程序中使用。

我們可以使用man 7 signal查看常用的可重入函數:

\

不可重入函數

滿足下列條件的函數多數是不可重入的:

(1)使用靜態數據結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;

(2)函數實現時,調用了malloc()或者free()函數;

(3)實現時使用了標准I/O函數

Copyright © Linux教程網 All Rights Reserved