天天看點

linux核心中異步通信機制--信号處理機制【轉】

什麼是異步通信?很簡單,一旦裝置準備好,就主動通知應用程式,這種情況下應用程式就不需要查詢裝置狀态,就像硬體上常提的“中斷的概念”。比較準确的說法其實應該叫做“信号驅動的異步I/O”,信号是在軟體層次上對中斷機制的一種模拟。阻塞I/O意味着一直等待裝置可通路再通路,非阻塞I/O意味着使用poll()來查詢是否可通路,而異步通信則意味着裝置通知應用程式自身可通路。

一、系統中存在的異步機制

  我認為異步機制是一種理念,并不是某一種具體實作,同步/異步的核心了解應該是如何擷取消息的問題,你自身(在計算機中當然是程序本身了)親自去擷取消息,那麼就是同步機制,但是如果别人使用某種方式通知你某一個消息,那麼你采用的就是異步機制。核心中使用到異步機制的大概有:信号,這是一種程序間通信的異步機制;epoll,這是一種高效處理IO的異步通信機制。也就是從通信和IO兩個方面通過不同的方式使用了異步機制。

  下面進入正題:

二、信号的基本概念

  1)信号的本質

  軟中斷信号(signal ,又簡稱為信号)用來通知程序發生了異步事件。在軟體層次上是對中斷機制的一種模拟,在原理上,一個程序收到一個信号與處理器收到一個中斷請求可以說是一樣的。 信号是程序間通信機制中唯一的異步通信機制 , 一個程序不必通過任何操作來等待信号的到達,事實上,程序也不知道信号到底什麼時候到達。程序之間可以互相通過系統調用kill發送軟中斷信号 。核心也可以因為内部事件而給程序發送信号,通知程序發生了某個事件。信号機制除了基本通知功能外, 還可以傳遞附加資訊。

  收到信号的程序對各種信号有不同的處理方法。處理方法可以分為三類:

第一種是類似中斷的處理程式,對于需要處理的信号,程序可以指定處理函 數,由該函數來處理。

第二種方法是,忽略某個信号,對該信号不做任何處理,就象未發生過一樣。

第三種方法是,對該信号的處理保留系統的預設值,這種預設操作,對大部分的信号的預設操作是使得程序終止。程序通過系統調用signal來指定程序對某個信号的處理行為。

  在程序表的表項中有一個軟中斷信号域,該域中每一位對應一個信号,當有信号發送給程序時,對應位置位。由此可以看出,程序對不同的信号可以同時保留,但對于同一個信号,程序并不知道在處理之前來過多少個。

  2)信号的種類

可以從兩個不同的分類角度對信号進行分類:

可靠性方面:可靠信号與不可靠信号;

與時間的關系上:實時信号與非實時信号。

  3)可靠信号與不可靠信号

  Linux信号機制基本上是從Unix系統中繼承過來的。早期Unix 系統中的信号機制比較簡單和原始, 信号值小于SIGRTMIN的信号都是不可靠信号。 這就是”不可靠信号”的來源。它的主要問題是信号可能丢失。

  随着時間的發展,實踐證明了 有必要對信号的原始機制加以改進和擴充 。由于原來定義的信号已有許多應用,不好再做改動,最終隻好又新增加了一些信号,并在一開始就把它們定義為 可靠信号,這些信号支援排隊,不會丢失 。

  信号值位于SIGRTMIN和SIGRTMAX之間的信号都是可靠信号,可靠信号克服了信号可能丢失的問題。Linux在支援新版本的信号安裝函數sigation()以及信号發送函數sigqueue()的同時,仍然支援早期的signal()信号安裝函數,支援信号發送函數kill()。

  信号的可靠與不可靠隻與信号值有關,與信号的發送及安裝函數無關 。目前linux中的signal()是通過sigation()函數實作的,是以,即使通過signal()安裝的信号,在信号處理函數的結尾也不必再調用一次信号安裝函數。同時,由signal()安裝的實時信号支援排隊,同樣不會丢失。

  對于目前linux的兩個信号安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信号變成可靠信号(都不支援排隊,仍有可能丢失,仍然是不可靠信号) ,而且對SIGRTMIN以後的信号都支援排隊。這兩個函數的最大差別在于,經過sigaction安裝的信号都能傳遞資訊給信号處理函數,而經過signal安裝的信号不能向信号處理函數傳遞資訊。對于信号發送函數來說也是一樣的。

  4)實時信号與非實時信号

  早期Unix系統隻定義了32種信号,前32 種信号已經有了預定義值,每個信号有了确定的用途及含義, 并且每種信号都有各自的預設動作。如按鍵盤的CTRL ^C時 ,會産生SIGINT信号,對該信号的預設反應就是程序終止。後32個信号表示實時信号,等同于前面闡述的可靠信号。這保證了發送的多個實時信号都被接收。

  非實時信号都不支援排隊,都是不可靠信号;實時信号都支援排隊,都是可靠信号。

  5)linux 下信号的生命周期如下:

  在目的程序中安裝該信号。即是設定捕獲該信号時程序程序該執行的操作碼。采用signal();sigaction()系統調用來實作。 

信号被某個程序産生,同時設定該信号的目的程序(使用pid),之後交給作業系統進行管理。采用kill()、arise()、alarm()等系統調用來實作。 

信号在目的程序被注冊。信号被添加進程序的PCB(task_struct)中相關的資料結構裡——未決信号的資料成員。信号在程序中注冊就是把信号值加入到程序的未決信号集裡。 

并且,信号 攜帶的其他資訊被保留到未決信的隊列的某個sigqueue結構中。 

信号在程序中登出。在執行信号處理函數前,要把信号在程序中登出。對于非實時信号(不可靠信号),其在信号未決信号資訊鍊中最多隻有一個sigqueue結構,是以該結構被釋放後,相應的信号要在未決信号集删除。而實時信号(可靠信号),如果有多個sigqueue,則不會把信号從程序的未決信号集中删除。 

信号生命的終結。程序終止目前的工作,保護上下文,執行信号處理函數,之後回複。如果核心是可搶占的,那麼還需要排程。

三、信 号 機 制

  上一節中介紹了信号的基本概念,在這一節中,我們将介紹核心如何實作信号機制。即核心如何向一個程序發送信号、程序如何接收一個信号、程序怎樣控制自己對信 号的反應、核心在什麼時機處理和怎樣處理程序收到的信号。還要介紹一下setjmp和longjmp在信号中起到的作用。 

1、核心對信号的基本處理方法

  核心給一個程序發送軟中斷信号的方法,是在程序所在的程序表項的信号域設定對應于該信号的位。這裡要補充的是,如果信号發送給一個正在睡眠的程序,那麼要看 該程序進入睡眠的優先級,如果程序睡眠在可被中斷的優先級上,則喚醒程序;否則僅設定程序表中信号域相應的位,而不喚醒程序。這一點比較重要,因為程序檢 查是否收到信号的時機是:一個程序在即将從核心态傳回到使用者态時;或者,在一個程序要進入或離開一個适當的低排程優先級睡眠狀态時。

  核心處理一個程序收到的信号的時機是在一個程序從核心态傳回使用者态時。是以,當一個程序在核心态下運作時,軟中斷信号并不立即起作用,要等到将傳回使用者态時才處理。程序隻有處理完信号才會傳回使用者态,程序在使用者态下不會有未處理完的信号。

  核心處理一個程序收到的軟中斷信号是在該程序的上下文中,是以,程序必須處于運作狀态。前面介紹概念的時候講過,處理信号有三種類型:程序接收到信号後退 出;程序忽略該信号;程序收到信号後執行使用者設定用系統調用signal的函數。當程序接收到一個它忽略的信号時,程序丢棄該信号,就象沒有收到該信号似 的繼續運作。如果程序收到一個要捕捉的信号,那麼程序從核心态傳回使用者态時執行使用者定義的函數。而且執行使用者定義的函數的方法很巧妙,核心是在使用者棧上創 建一個新的層,該層中将傳回位址的值設定成使用者定義的處理函數的位址,這樣程序從核心傳回彈出棧頂時就傳回到使用者定義的函數處,從函數傳回再彈出棧頂時, 才傳回原先進入核心的地方。這樣做的原因是使用者定義的處理函數不能且不允許在核心态下執行(如果使用者定義的函數在核心态下運作的話,使用者就可以獲得任何權限)。

  在信号的處理方法中有幾點特别要引起注意。第一,在一些系統中,當一個程序處理完中斷信号傳回使用者态之前,核心清除使用者區中設 定的對該信号的處理例程的位址,即下一次程序對該信号的處理方法又改為預設值,除非在下一次信号到來之前再次使用signal系統調用。這可能會使得程序 在調用signal之前又得到該信号而導緻退出。在BSD中,核心不再清除該位址。但不清除該位址可能使得程序因為過多過快的得到某個信号而導緻堆棧溢 出。為了避免出現上述情況。在BSD系統中,核心模拟了對硬體中斷的處理方法,即在處理某個中斷時,阻止接收新的該類中斷。

  第二個要引起注意的是,如果要捕捉的信号發生于程序正在一個系統調用中時,并且該程序睡眠在可中斷的優先級上,這時該信号引起程序作一次longjmp,跳出睡眠狀态,傳回使用者态并執行信号處理例程。當從信号處理例程傳回時,程序就象從系統調用傳回一樣,但傳回了一個錯誤代碼,指出該次系統調用曾經被中斷。這要注意的是,BSD系統中核心可以自動地重新開始系統調用。

  第三個要注意的地方:若程序睡眠在可中斷的優先級上,則當它收到一個要忽略的信号時,該程序被喚醒,但不做longjmp,一般是繼續睡眠。但使用者感覺不到程序曾經被喚醒,而是象沒有發生過該信号一樣。

  第四個要注意的地方:核心對子程序終止(SIGCLD)信号的處理方法與其他信号有所差別。當程序檢查出收到了一個子程序終止的信号時,預設情況下,該程序 就象沒有收到該信号似的,如果父程序執行了系統調用wait,程序将從系統調用wait中醒來并傳回wait調用,執行一系列wait調用的後續操作(找出僵死的子程序,釋放子程序的程序表項),然後從wait中傳回。SIGCLD信号的作用是喚醒一個睡眠在可被中斷優先級上的程序。如果該程序捕捉了這個信号,就象普通信号處理一樣轉到處理例程。如果程序忽略該信号,那麼系統調用wait的動作就有所不同,因為SIGCLD的作用僅僅是喚醒一個睡眠在可被 中斷優先級上的程序,那麼執行wait調用的父程序被喚醒繼續執行wait調用的後續操作,然後等待其他的子程序。

  如果一個程序調用signal系統調用,并設定了SIGCLD的處理方法,并且該程序有子程序處于僵死狀态,則核心将向該程序發一個SIGCLD信号。 2、setjmp和longjmp的作用

  前面在介紹信号處理機制時,多次提到了setjmp和longjmp,但沒有仔細說明它們的作用和實作方法。這裡就此作一個簡單的介紹。

  在介紹信号的時候,我們看到多個地方要求程序在檢查收到信号後,從原來的系統調用中直接傳回,而不是等到該調用完成。這種程序突然改變其上下文的情況,就是 使用setjmp和longjmp的結果。setjmp将儲存的上下文存入使用者區,并繼續在舊的上下文中執行。這就是說,程序執行一個系統調用,當因為資 源或其他原因要去睡眠時,核心為程序作了一次setjmp,如果在睡眠中被信号喚醒,程序不能再進入睡眠時,核心為程序調用longjmp,該操作是核心 為程序将原先setjmp調用儲存在程序使用者區的上下文恢複成現在的上下文,這樣就使得程序可以恢複等待資源前的狀态,而且核心為setjmp傳回1,使得程序知道該次系統調用失敗。這就是它們的作用。

本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sky-heaven/p/5847492.html,如需轉載請自行聯系原作者

繼續閱讀