linux訊號問題網路FAQ整理

來源:互聯網
上載者:User

1.訊號“未決”與“阻塞” 

訊號狀態:
    訊號的”未決“是一種狀態,指的是從訊號的產生到訊號被處理前的這一段時間;訊號的”阻塞“是一個開關動作,指的是阻止訊號被處理,但不是阻止訊號產生。
    APUE例題在sleep前用sigprocmask阻塞了退出訊號,然後sleep,然後在sleep的過程中產生一個退出訊號,但是此時退出訊號被阻塞過,(中文的”阻塞”在這裡容易被誤解為一種狀態,實際上是一種類似於開關的動作,所以說“被阻塞過”,而不是“被阻塞”)所以處於“未決”狀態,在 sleep後又用sigprocmask關掉退出訊號的阻塞開關,因為之前產生的退出訊號一直處於未決狀態,當關上阻塞開關後,馬上退出“未決”狀態,得到處理,這一切發生在sigprocmask返回之前。

訊號生命週期:
    對於一個完整的訊號生命週期(從訊號發送到相應的處理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:1.訊號誕生;2. 訊號在進程中註冊完畢;3.訊號在進程中的登出完畢;4.訊號處理函數執行完畢。相鄰兩個事件的時間間隔構成訊號生命週期的一個階段。
    下面闡述四個事件的實際意義:
1.訊號"誕生"。訊號的誕生指的是觸發訊號的事件發生(如檢測到硬體異常、定時器逾時以及調用訊號發送函數kill()或sigqueue()等)。

2.訊號在目標進程中"註冊";
    進程的task_struct結構中有關於本進程中未決訊號的資料成員:
struct sigpending pending;
struct sigpending
{
    struct sigqueue *head, **tail;
    sigset_t signal;
};
第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之為"未決訊號資訊鏈")的首尾,第三個成員是進程中所有未決訊號集,資訊鏈中的每個sigqueue結構體刻畫一個特定訊號所攜帶的資訊,並指向下一個sigqueue結構:
struct sigqueue
{
    struct sigqueue *next;
    siginfo_t info;
};
    訊號在進程中註冊指的就是訊號值加入到進程的未決訊號集中(sigpending結構的第二個成員sigset_t signal),並且訊號所攜帶的資訊被保留到未決訊號資訊鏈的某個sigqueue結構中。只要訊號在進程的未決訊號集中,表明進程已經知道這些訊號的存在,但還沒來得及處理,或者該訊號被進程阻塞。
註:
    當一個即時訊號發送給一個進程時,不管該訊號是否已經在進程中註冊,都會被再註冊一次,因此,訊號不會丟失,因此,即時訊號又叫做"可靠訊號"。這意味著同一個即時訊號可以在同一個進程的未決訊號資訊鏈中佔有多個sigqueue結構(進程每收到一個即時訊號,都會為它分配一個結構來登記該訊號資訊,並把該結構添加在未決訊號鏈尾,即所有誕生的即時訊號都會在目標進程中註冊);
當一個非即時訊號發送給一個進程時,如果該訊號已經在進程中註冊,則該訊號將被丟棄,造成訊號丟失。因此,非即時訊號又叫做"不可靠訊號"。這意味著同一個非即時訊號在進程的未決訊號資訊鏈中,至多佔有一個sigqueue結構(一個非即時訊號誕生後,(1)、如果發現相同的訊號已經在目標結構中註冊,則不再註冊,對於進程來說,相當於不知道本次訊號發生,訊號丟失;(2)、如果進程的未決訊號中沒有相同訊號,則在進程中註冊自己)。

3.訊號在進程中的登出。在目標進程執行過程中,會檢測是否有訊號等待處理(每次從系統空間返回到使用者空間時都做這樣的檢查)。(“sigprocmask返回前,也至少會將其中一個未決且未阻塞的訊號遞送給進程”???)如果存在未決訊號等待處理且該訊號沒有被進程阻塞,則在運行相應的訊號處理函數前,進程會把訊號在未決訊號鏈中佔有的結構卸掉。是否將訊號從進程未決訊號集中刪除對於即時與非即時訊號是不同的。對於非即時訊號來說,由於在未決訊號資訊鏈中最多隻佔用一個sigqueue結構,因此該結構被釋放後,應該把訊號在進程未決訊號集中刪除(訊號登出完畢);而對於即時訊號來說,可能在未決訊號資訊鏈中佔用多個sigqueue結構,因此應該針對佔用gqueue結構的數目區別對待:如果只佔用一個sigqueue結構(進程只收到該訊號一次),則應該把訊號在進程的未決訊號集中刪除(訊號登出完畢)。否則,不在進程的未決訊號集中刪除該訊號(訊號登出完畢)。進程在執行訊號相應處理函數之前,首先要把訊號在進程中登出。

4.訊號生命終止。進程登出訊號後,立即執行相應的訊號處理函數,執行完畢後,訊號的本次發送對進程的影響徹底結束。
註:
1)訊號註冊與否,與發送訊號的函數(如kill()或sigqueue()等)以及訊號安裝函數(signal()及sigaction())無關,只與訊號值有關(訊號值小於SIGRTMIN的訊號最多隻註冊一次,訊號值在SIGRTMIN及SIGRTMAX之間的訊號,只要被進程接收到就被註冊)。
2)在訊號被登出到相應的訊號處理函數執行完畢這段時間內,如果進程又收到同一訊號多次,則對即時訊號來說,每一次都會在進程中註冊;而對於非即時訊號來說,無論收到多少次訊號,都會視為只收到一個訊號,只在進程中註冊一次。

 

不存在編號為0 的訊號

普通訊號不排隊,即時訊號排隊( 但一般我們用不著)

異種訊號允許嵌套,即一個未完也可嵌入另一個;

同種訊號不允許嵌套,同種訊號不排隊,所以為了防止訊號丟失,訊號處理一定要快。

 

 

 

2.訊號處理嵌套問題

現在我又三個進程   A   B   C
其中   A   進程設定了對   SIGUSR1   和   SIGUSR2   的處理:
sigset(SIGUSR1,   a1);        
sigset(SIGUSR2,   a2);    
如果   B   進程首先向   A   進程發送了一個   SIGUSR1   ,A進程會進入a1()函數,
但這時   C   進程   又向   A進程   發送了一個SIGUSER2,   那麼:
1。A進程會中斷a1()的處理然後進入b1()的處理?
2。A進程會先完成a1()   然後再響應SIGUSR2   進入b1()?

回複:

當A進入SIGUSR1訊號處理常式的時候,如果SIGUSR2來了
會立即中斷SIGUSR1的處理,嵌套進入SIGUSR2的處理函數。等SIGUSR2處理完
了才會回頭接著處理SIGUSR1。

除非你在SIGUSR1的SA_MASK中把SIGUSR2屏蔽掉,
那樣SIGUSR2來了也只是暫存在隊列中,等SIGUSR1處理完了才有機會執行

 

 

 

3.“訊號在進程中的登出。在目標進程執行過程中,會檢測是否有訊號等待處理(每次從系統空間返回到使用者空間時都做這樣的檢查)。”?

        訊號的處理是當前進程在中斷時,從核心態返回到使用者態時要處理的第一件事,進程在從核心態返回使用者態時,先檢查進程是否有需要處理的訊號(就是判斷當前進程的進程式控制制塊中的signal訊號位元影像,和blocked阻止位元影像的相與),如果訊號位元影像與阻止位元影像相與後,發現有置位的位,就說明當前進程有訊號需要處理。此時,先判斷是32種訊號中的哪種,然後核心會把這個訊號的處理函數的地址,作為核心返回使用者態時的EIP,因此,當返回使用者態後,使用者態的執行流程就從訊號處理函數開始執行,當執行完了訊號處理函數後,再轉到發生中斷時所保留的下一條語句開始執行。

而進程也許沒有其他的中斷,但是總會有時鐘中斷產生,因此,當有訊號需要處理,而起進程得到執行時,都會在時鐘中斷返回時得到執行。

 

 

 

4. sigprocmask函數對不再阻塞訊號的返回問題(zz)

< <UNIX環境進階編程>>上說:如果在調用s i g p r o c m a s k後有任何未決的、不再阻塞的訊號,則在s i g p r o c m a s k返回前,至少將其中之一遞送給該進程。

小弟用下列程式對這句話進行測試:先屏蔽掉SIGUSR1和SIGUSR2訊號,然後用kill函數發送他們,再開啟它們.
可運行結果卻是:
SIGUSR is blocked
SIGUSR1 function
SIGUSR2 function
SIGUSR1 function
SIGUSR is unblocked

為什麼會返回兩次SIGUSR1函數呢?謝謝回答

程式如下:
#include <stdlib.h>
#include <signal.h>
static void sig_usr1(signo)
{
printf("SIGUSR1 function\n");
}
static void sig_usr2(signo)
{
printf("SIGUSR2 function\n");
}
main()
{
sigset_t newmask,oldmask;
if(signal(SIGUSR1,sig_usr1) <0|signal(SIGUSR2,sig_usr2) <0)
perror("signal\n");
sigemptyset(&newmask);
sigaddset(&newmask,SIGUSR1);
sigaddset(&newmask,SIGUSR2);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
printf("SIGUSR is blocked\n");
kill(getpid(),SIGUSR2);
kill(getpid(),SIGUSR1);

sigprocmask(SIG_SETMASK,&oldmask,NULL);
}

回複:-------------------------------------------------------------------------------------------

signal是不可靠訊號函數,有個"時間窗",漏洞.
具體過程是這樣的:

當調用sigprocmask函數放開訊號屏蔽後,系統會按循序檢查懸掛的的訊號,注意,這個順序不是訊號到來的順序,而是訊號值的順序,即1-64;
當檢查到sigusr1時發現有訊號,進入sigusr1的訊號控制代碼,核心為這個控制代碼函數在使用者空間建立函數棧,因為訊號控制代碼函數在使用者空間,所有當執行時可以被打斷,進入sigusr2控制代碼函數,執行完後,清除sigusr2訊號懸掛標誌,再檢查訊號懸掛情況,發現sigusr1訊號懸掛標誌還有,就再為其建立先後控制代碼,執行(如果只是返回sigusr1被中斷的地方,那麼只會列印一次才對??),執行完後.清除標誌,再接著,回到使用者空間,按正常的函數情況執行,發現有函數棧沒有執行,就再執行.

用sigaction設定函數控制代碼就沒問題了.
struct sigaction act1,act2;
act1.sa_handler=sig_usr1;
sigemptyset(&act1.sa_mask);
act2.sa_handler=sig_usr2;
sigemptyset(&act2.sa_mask);
sigaction(SIGUSR1,&act1,NULL);
sigaction(SIGUSR2,&act2,NULL);
為什麼用sigaction就沒有這個問題??

我在這個問題上也有點糊塗,sigusr1雖然執行了兩遍,但好象執行的是同一個棧.沒仔細研究過.上述意見僅供參考.??????

-------------------------------補充:

如果調用 sigprocmask 放開某個訊號屏蔽而導致任何懸掛的訊號被解除阻塞,則在 sigprocmask 返回之前,這些訊號中至少有一個被交付。

 

 

 

4.APUE2讀書筆記(二):為什麼有了wait函數族還需要SIGCHLD訊號:

首先,在談這個問題時,先說說unix中殭屍進程的含義,APUE2中如下定義:
In UNIX System terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie.
也就是說,但凡是父進程沒有調用wait函數獲得子進程終止狀態的子進程在終止之後都是殭屍進程,這個概念的關鍵一點就是父進程是否調用了wait函數.

而關於SIGCHLD訊號,APUE2中又如是說:
Whenever a process terminates or stops, the SIGCHLD signal is sent to the parent. By default, this signal is ignored, so the parent must catch this signal if it wants to be notified whenever a child's status changes. The normal action in the signal-catching
function is to call one of the wait functions to fetch the child's process ID and termination status.
簡單的說,子進程退出時父進程會收到一個SIGCHLD訊號,預設的處理是忽略這個訊號,而常規的做法是在這個訊號處理函數中調用wait函數擷取子進程的退出狀態.

這裡存在一個疑問,既然在SIGCHLD訊號的處理函數中要調用wait函數族,為什麼有了wait函數族還需要使用SIGCHLD訊號?

我們知道,unix中訊號是非同步處理某事的機制,好比說你準備去做某事,去之前跟鄰居張三說如果李四來找你的話就通知他一聲,這讓你可以抽身出來去做這件事,而李四真正來訪時會有人通知你,這個就是非同步訊號一個較為形象的比喻.

一般的,父進程在產生子進程之後會有兩種情況,一種是父進程繼續去做別的事情,類似上面舉的例子,另一種是父進程啥都不做,一直在wait子進程退出.SIGCHLD訊號就是為這第一種情況準備的,它讓父進程去做別的事情,而只要父進程註冊了處理該訊號的函數,在子進程退出時就會調用該函數,在函數中wait子進程得到終止狀態之後再繼續做父進程的事情.

也就是說,明確以下幾點:
1)凡父進程不調用wait函數族獲得子進程終止狀態的子進程在退出時都會變成殭屍進程.
2)SIGCHLD訊號可以非同步通知父進程有子進程退出.

 

總結:

waitpid等不是依靠SIGCHLD是否到達來判斷子進程是否退出,但是如果設定了SIGCHLD的處理函數,那麼就需要等待SIGCHLD訊號的發生並完成訊號處理函數,waitpid才能接收到子進程的退出狀態。在APUE中的system()實現中阻塞了SIGCHLD訊號,但是並沒有設定訊號處理函數,所以waitpid在阻塞了SIGCHLD的情況下依然能正常返回,因為SIGCHLD在未設定訊號處理函數的情況下不會影響到waitpid的工作。至於為什麼要阻塞SIGCHLD訊號呢?那就是為了防止其他程式(main除了會調用system還會使用其他程式)設定了SIGCHLD的訊號處理函數,如果其他程式設定了SIGCHLD訊號處理函數,在waitpid等待子程式的返回前,要去處理SIGCHLD訊號處理常式,如果阻塞了該訊號,就不會去處理該訊號處理常式,防止多餘資訊在system()中的出現。

例如 while(waitpid(pid, &status,0)<0)這裡不是通過SIGCHLD,而是父進程不斷迴圈檢測子進程pid是否終止來實現的,即不是非同步訊號機制子進程通知父進程,而是父進程不斷迴圈查詢。

 

5.總結:

       中斷返回才活動訊號是否需要處理;訊號處理函數是在使用者態執行;原有訊號處理函數在執行,新訊號產生並又中斷(eg時鐘中斷),那麼新訊號將搶佔嵌套原有訊號處理函數(沒有阻塞時)。

 

 

 

6.疑惑:

    pause()是只要有訊號處理常式返回就返回,所以嵌套的訊號處理函數返回pause亦返回,而不用等到最外層的訊號處理函數返回才返回??  如果這樣,用pause實現sleep,當內部嵌套的訊號處理函數返回時,pause返回,sleep返回,調用sleep的函數接著執行,那麼原來被嵌套的外部訊號處理函數將永遠無法執行??

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.