1. 程式人生 > >Linux訊號程式設計實踐(二) 訊號傳送函式和可重入函式

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函式

 關於可重入函式和不可重入函式的相關詳細例項,請參考  http://www.cnblogs.com/luvi/archive/2008/05/09/1190493.html