天天看點

[linux c/c++] linux程式設計中的信号

前言:

使用者通過 signal 和 sigaction 向核心注冊自己的信号處理函數,當核心達到某個條件或者使用者自行觸發某個信号時,程序會周遊自己所有線程的信号屏蔽字(每個線程有自己的信号屏蔽字),從主線程找起,找到第一個不屏蔽此信号的線程,然後打斷這個線程的執行,執行完畢後恢複線程的執行。

多線程場景下需要注意的點:

1)信号是發送給程序的,程序收到信号後,會編譯自己所有線程的信号屏蔽字,從自己開始,找到第一個不屏蔽此信号的線程,然後打斷;

2)建立線程時,子線程會繼承父線程的信号屏蔽字,是以可以設計一個應用場景,讓某一個線程接收所有信号,而其他線程屏蔽所有信号,這樣這個線程就可以以最小的輪訓周期來響應信号,認為這個線程時專門的信号處理線程;

3)如果子線程中重新修改信号處理函數,那麼會影響所有其他線程,即信号處理函數是程序層面的,每個線程無法設定自己的信号處理函數。

其他需要注意的點:

1)程序在執行exec後,會把原先注冊了的信号處理函數都複位,還原成預設或者忽略(程式上下文都變了,信号處理函數跟着變也是情理之中);

2)signal函數應當禁止使用,因為其不是标準實作,每個系統都有自己的實作,而且使用風險極大;

3)fork動作會繼承父程序的信号處理函數;

4)早期的信号處理機制,在收到信号并進入信号處理函數之前,核心會先把信号的處理函數設定為預設,而某些信号的預設處理方式是終止程序,那麼在“信号處理函數中把信号重新注冊”和“核心自動置預設”這之間,就有可能因為在此觸發信号而導緻進行了預設動作。(核心之前由于存在自動設定的機制在,是以在編寫信号處理函數的時候,會在剛進函數的時候就把信号處理函數再注冊一遍)

5)慢速系統調用 與 中斷,如果系統停在一個慢速系統調用中,此時有一個信号發生了,那麼此系統調用會傳回一個錯誤碼EINTR,表示自己在執行期間被一個信号中斷了,那麼程式應當在此調用此系統調用。

6)對于#5,因為存在上述問題,是以又如下程式設計範式:

again:
        if((n=read(fd,n,buff))<0)
          {
            if(errno=EINTR)
              goto again;
            {other wrong dealing}
          }      

針對如上會被中斷打斷的系統調用情況,核心提供了自動恢複機制,這樣就不用處理EINTR錯誤碼,核心在檢測到系統調用錯誤後,首先會檢查錯誤碼是不是EINTR,如果是就繼續系統調用,這樣使用者就不用處理EINTR了。但是有些時候,就是想處理這些錯誤碼,那麼可以再sigaction中設定,把這個機制打開或者關閉。這裡的系統調用指慢速系統調用,比如read,write,ioctl,wait,waitpid,readv,writev。(以後盡量使用sigaction而不是signal)

7)注意:在進入信号處理函數之前,千萬要先儲存errno的值,因為在信号處理函數中,這個值可能會被改變。

   設定的方法:進入終端處理函數就把這個值指派給一個局部變量,在離開終端處理函數之前再把這個值指派給errno

8)什麼是“可重入函數”?

進入中斷處理函數後,使用這些函數,不會導緻系統的資料區發生改變的函數。比如malloc就不行,如果在進中斷之前做了malloc,然後這個函數剛執行一半,被一個中斷插進來,而且又在中斷中使用了malloc,那麼從中斷中出來後,外面的malloc将會失敗。   有一個可重入函數的清單,大緻總結下就是,凡是使用了malloc和free的,全是不可重入函數,非系統調用的I/O函數基本上全是不可重入(因為他們肯定會使用malloc和free)

9)信号的産生:硬體異常、軟體條件(比如alarm)、終端産生、主動遞送(比如kill)

     信号的遞送:信号産生以後,核心會搜尋程序表,在其中找到信号的歸屬,然後在程序控制表(pct)中相應的标志位置位,置位成功後,就是遞送完成之時。

在信号的 “産生” 到 “遞送(完成)” 之間,這段時間信号是  "未決的"。程序可選擇(可能需要調用核心的某個設定函數)“阻塞信号遞送”,即在“未決”時間段内,先不注冊信号處理方式(預設的/信号處理函數,這裡不包含忽略,可見忽略的情況提前會被阻擋掉),等到信号不阻塞了(可以被處理了),在決定處理方式。可使用sigpending來檢視哪些信号處于“阻塞未決狀态”。

10)信号的排隊

對于非實時信号,相同信号不能在信号隊列中排隊,也就是說多個相同信号會被合并成一個,是以會被認為隻發生了一次;

對于實時信号,相同信号可以在信号隊列中排隊,多個信号不會被合并成一個,會被認為發生了多次。

如果信号隊列中有多個實時以及非實時信号排隊,實時信号并不會先于非實時信号被取出,信号數字小的會先被取出:如 SIGUSR1(10)會先于 SIGUSR2 (12),SIGRTMIN(34)會先于 SIGRTMAX (64), 非實時信号因為其信号數字小而先于實時信号被取出。

11)kill指令發送信号

     kill  pid>0 發給pid程序

           pid==0 發送給目前程序所在程序組的所有程序

           pid<0  發送給目前程序所在程序組中程序号為 -pid 的程序

           pid==-1  看不懂

繼續閱讀