• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • [轉載]Linux的信號機制

    發表于:2007-07-04來源:作者:點擊數: 標簽:
    2003 年 01 月 linux信號機制遠遠比想象的復雜,本文力爭用最短的篇幅,對該機制做了深入細致的分析。信號應用實例將在信號(下)中給出。 一、信號及信號來源 信號本質 信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收

    2003 年 01 月
    linux信號機制遠遠比想象的復雜,本文力爭用最短的篇幅,對該機制做了深入細致的分析。信號應用實例將在信號(下)中給出。
    一、信號及信號來源
    信號本質
    信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。
    信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展后,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。
    信號來源
    信號事件的發生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。
    二、信號的種類
    可以從兩個不同的分類角度對信號進行分類:(1)可靠性方面:可靠信號與不可靠信號;(2)與時間的關系上:實時信號與非實時信號。在《Linux環境進程間通信(一):管道及有名管道》的附1中列出了系統所支持的所有信號。
    1、可靠信號與不可靠信號
    "不可靠信號"
    Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題,因此,把那些建立在早期機制上的信號叫做"不可靠信號",信號值小于SIGRTMIN(Red hat7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是:
    •進程每次處理信號后,就將對信號的響應設置為默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
    •信號可能丟失,后面將對此詳細闡述。
    因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。
    Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數后,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。
    "可靠信號"
    隨著時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。所以,后來出現的各種Unix版本分別在這方面進行了研究,力圖實現"可靠信號"。由于原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,并在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。POSIX.4對可靠信號機制做了標準化。但是,POSIX只對可靠信號機制應具有的功能以及信號機制的對外接口做了標準化,對信號機制的實現沒有作具體的規定。
    信號值位于SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。
    注:不要有這樣的誤解:由sigqueue()發送、sigaction安裝的信號就是可靠的。事實上,可靠信號是指后來添加的新信號(信號值位于SIGRTMIN及SIGRTMAX之間);不可靠信號是信號值小于SIGRTMIN的信號。信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。目前linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函數的結尾也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。
    對于目前linux的兩個信號安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以后的信號都支持排隊。這兩個函數的最大區別在于,經過sigaction安裝的信號都能傳遞信息給信號處理函數(對所有信號這一點都成立),而經過signal安裝的信號卻不能向信號處理函數傳遞信息。對于信號發送函數來說也是一樣的。
    2、實時信號與非實時信號
    早期Unix系統只定義了32種信號,Rethat7.2支持64種信號,編號0-63(SIGRTMIN=31,SIGRTMAX=63),將來可能進一步增加,這需要得到內核的支持。前32種信號已經有了預定義值,每個信號有了確定的用途及含義,并且每種信號都有各自的缺省動作。如按鍵盤的CTRL^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。后32個信號表示實時信號,等同于前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。實時信號是POSIX標準的一部分,可用于應用進程。
    非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
    三、進程對信號的響應
    進程可以通過三種方式來響應一個信號:(1)忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILL及SIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操作,Linux對每種信號都規定了默認操作,詳細情況請參考[2]以及其它資料。注意,進程對實時信號的缺省反應是進程終止。
    Linux究竟采用上述三種方式的哪一個來響應信號,取決于傳遞給相應API函數的參數。
    四、信號的發送
    發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
    1、kill()
    #include
    #include
    int kill(pid_t pid,int signo)
    參數pid的值信號的接收進程
    pid>0進程ID為pid的進程
    pid=0同一個進程組的進程
    pid<0 pid!=-1進程組ID為 -pid的所有進程
    pid=-1除發送進程自身外,所有進程ID大于1的進程
    Sinno是信號值,當為0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用于檢查目標進程是否存在,以及當前進程是否具有向目標發送信號的權限(root權限的進程可以向任何進程發送信號,非root權限的進程只能向屬于同一個session或者同一個用戶的進程發送信號)。
    Kill()最常用于pid>0時的信號發送,調用成功返回 0; 否則,返回-1。注:對于pid<0時的情況,對于哪些進程將接受信號,各種版本說法不一,其實很簡單,參閱內核源碼kernal/signal.c即可,上表中的規則是參考red hat 7.2。
    2、raise()
    #include
    int raise(int signo)
    向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1。
    3、sigqueue()
    #include
    #include
    int sigqueue(pid_t pid, int sig, const union sigval val)
    調用成功返回 0;否則,返回 -1。
    sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。
    sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。

    typedef union sigval {
    int sival_int;
    void *sival_ptr;
    }sigval_t;
    sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用于檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。
    在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由sigaction安裝,并設定了sa_sigaction指針,稍后將闡述)的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由于sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。
    注:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,即在信號處理函數執行過程中到來的所有相同信號,都被合并為一個信號。
    4、alarm()
    #include
    unsigned int alarm(unsigned int seconds)
    專門為SIGALRM信號而設,在指定的時間seconds秒后,將向進程本身發送SIGALRM信號,又稱為鬧鐘時間。進程調用alarm后,任何以前的alarm()調用都將無效。如果參數seconds為零,那么進程內將不再包含任何鬧鐘時間。
    返回值,如果調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩余時間,否則返回0。
    5、setitimer()
    #include
    int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
    setitimer()比alarm功能強大,支持3種類型的定時器:
    •ITIMER_REAL: 設定絕對時間;經過指定的時間后,內核將發送SIGALRM信號給本進程;
    •ITIMER_VIRTUAL 設定程序執行時間;經過指定的時間后,內核將發送SIGVTALRM信號給本進程;
    •ITIMER_PROF 設定進程執行以及內核因本進程而消耗的時間和,經過指定的時間后,內核將發送ITIMER_VIRTUAL信號給本進程;
    Setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval形式見附錄1。第三個參數可不做處理。
    Setitimer()調用成功返回0,否則返回-1。
    6、abort()
    #include
    void abort(void);
    向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程設置為阻塞信號,調用abort()后,SIGABORT仍然能被進程接收。該函數無返回值。
    五、信號的安裝(設置信號關聯動作)
    如果進程要處理某一信號,那么就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關系,即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。
    linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現,是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用于前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue()系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優于signal()主要體現在支持信號帶有參數。
    1、signal()
    #include
    void (*signal(int signum, void (*handler))(int)))(int);
    如果該函數原型不容易理解的話,可以參考下面的分解方式來理解:
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler));
    第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,可以忽略該信號(參數設為SIG_IGN);可以采用系統默認方式處理信號(參數設為SIG_DFL);也可以自己實現處理方式(參數指定一個函數地址)。
    如果signal()調用成功,返回最后一次為安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。
    2、sigaction()
    #include
    int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
    sigaction函數用于改變進程接收到特定信號后的行為。該函數的第一個參數為信號的值,可以為除SIGKILL及SIGSTOP外的任何一個特定有效的信號(為這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存原來對相應信號的處理,可指定oldact為NULL。如果把第二、第三個參數都設為NULL,那么該函數可用于檢查信號的有效性。
    第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等等。
    sigaction結構定義如下:

    struct sigaction {
    union{
    __sighandler_t _sa_handler;
    void (*_sa_sigaction)(int,struct siginfo *, void *);
    }_u
    sigset_t sa_mask;
    unsigned long sa_flags;
    void (*sa_restorer)(void);
    }

    其中,sa_restorer,已過時,POSIX不支持它,不應再被使用。
    1、聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了可以是用戶自定義的處理函數外,還可以為SIG_DFL(采用缺省的處理方式),也可以為SIG_IGN(忽略信號)。
    2、由_sa_handler指定的處理函數只有一個參數,即信號值,所以信號不能傳遞除信號值之外的任何信息;由_sa_sigaction是指定的信號處理函數帶有三個參數,是為實時信號而設的(當然同樣支持非實時信號),它指定一個3參數信號處理函數。第一個參數為信號值,第三個參數沒有使用(posix沒有規范使用該參數的標準),第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構如下:

    siginfo_t {
    int si_signo; /* 信號值,對所有信號有意義*/
    int si_errno; /* errno值,對所有信號有意義*/
    int si_code; /* 信號產生的原因,對所有信號有意義*/
    union{ /* 聯合數據結構,不同成員適應不同信號 */
    //確保分配足夠大的存儲空間
    int _pad[SI_PAD_SIZE];
    //對SIGKILL有意義的結構
    struct{
    ...
    }...

    ... ...
    ... ...
    //對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構
    struct{
    ...
    }...
    ... ...
    }
    }

    注:為了更便于閱讀,在說明問題時常把該結構表示為附錄2所表示的形式。
    siginfo_t結構中的聯合數據成員確保該結構適應所有的信號,比如對于實時信號來說,則實際采用下面的結構形式:

    typedef struct {
    int si_signo;
    int si_errno;
    int si_code;
    union sigval si_value;
    } siginfo_t;

    結構的第四個域同樣為一個聯合數據結構:

    union sigval {
    int sival_int;
    void *sival_ptr;
    }
    采用聯合數據結構,說明siginfo_t結構中的si_value要么持有一個4字節的整數值,要么持有一個指針,這就構成了與信號相關的數據。在信號的處理函數中,包含這樣的信號相關數據指針,但沒有規定具體如何對這些數據進行操作,操作方法應該由程序開發人員根據具體任務事先約定。
    前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發是非常有意義的。
    信號參數的傳遞過程可圖示如下:

    3、sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標志位。
    注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。
    4、sa_flags中包含了許多標志位,包括剛剛提到的SA_NODEFER及SA_NOMASK標志位。另一個比較重要的標志位是SA_SIGINFO,當設定了該標志位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該為sigaction結構中的sa_sigaction指定處理函數,而不應該為sa_handler指定信號處理函數,否則,設置該標志變得毫無意義。即使為sa_sigaction指定了信號處理函數,如果不設置SA_SIGINFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤(Segmentation fault)。
    注:很多文獻在闡述該標志位時都認為,如果設置了該標志位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:自己實現一個單一參數信號處理函數,并在程序中設置該標志位,可以察看程序的運行結果。實際上,可以把該標志位看成信號是否傳遞參數的開關,如果設置該位,則傳遞參數;否則,不傳遞參數。
    六、信號集及信號集操作函數:
    信號集被定義為一種數據類型:

    typedef struct {
    unsigned long sig[_NSIG_WORDS];
    } sigset_t
    信號集用來描述信號的集合,linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是為信號集操作定義的相關函數:

    #include
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset(sigset_t *set, int signum)
    int sigdelset(sigset_t *set, int signum);
    int sigismember(const sigset_t *set, int signum);

    sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集里面的所有信號被清空;
    sigfillset(sigset_t *set)調用該函數后,set指向的信號集中將包含linux支持的64種信號;
    sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號;
    sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號;
    sigismember(const sigset_t *set, int signum)判定信號signum是否在set指向的信號集中。
    七、信號阻塞與信號未決:
    每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程后都將被阻塞。下面是與信號阻塞相關的幾個函數:

    #include
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
    int sigpending(sigset_t *set));
    int sigsuspend(const sigset_t *mask));
    sigprocmask()函數能夠根據參數how來實現對信號集的操作,操作主要有三種:
    參數how進程當前信號集
    SIG_BLOCK在進程當前阻塞信號集中添加set指向信號集中的信號
    SIG_UNBLOCK如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
    SIG_SETMASK更新進程阻塞信號集為set指向的信號集
    sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的所有信號,在set指向的信號集中返回結果。
    sigsuspend(const sigset_t *mask))用于在接收到某個信號之前, 臨時用mask替換進程的信號掩碼,并暫停進程執行,直到收到信號為止。sigsuspend返回后將恢復調用之前的信號掩碼。信號處理函數完成后,進程將繼續執行。該系統調用始終返回-1,并將errno設置為EINTR。
    附錄1:結構itimerval:

    struct itimerval {
    struct timeval it_interval; /* next value */
    struct timeval it_value; /* current value */
    };
    struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
    };
    附錄2:三參數信號處理函數中第二個參數的說明性描述:

    siginfo_t {
    int si_signo; /* 信號值,對所有信號有意義*/
    int si_errno; /* errno值,對所有信號有意義*/
    int si_code; /* 信號產生的原因,對所有信號有意義*/
    pid_t si_pid; /* 發送信號的進程ID,對kill(2),實時信號以及SIGCHLD有意義 */
    user_id_t si_uid; /* 發送信號進程的真實用戶ID,對kill(2),實時信號以及SIGCHLD有意義 */
    int si_status; /* 退出狀態,對SIGCHLD有意義*/
    clock_t si_utime; /* 用戶消耗的時間,對SIGCHLD有意義 */
    clock_t si_stime; /* 內核消耗的時間,對SIGCHLD有意義 */
    sigval_t si_value; /* 信號值,對所有實時有意義,是一個聯合數據結構,可以為一個整數(由si_int標示,也可以為一個指針,由si_ptr標示)*/

    void * si_addr; /* 觸發fault的內存地址,對SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號有意義*/
    int si_band; /* 對SIGPOLL信號有意義 */
    int si_fd; /* 對SIGPOLL信號有意義 */
    }
    實際上,除了前三個元素外,其他元素組織在一個聯合結構中,在聯合數據結構中,又根據不同的信號組織成不同的結構。注釋中提到的對某種信號有意義指的是,在該信號的處理函數中可以訪問這些域來獲得與信號相關的有意義的信息,只不過特定信號只對特定信息感興趣而已。
    參考文獻:
    1.linux內核源代碼情景分析(上),毛德操、胡希明著,浙江大學出版社,當要驗證某個結論、想法時,最好的參考資料;
    2.UNIX環境高級編程,作者:W.Richard Stevens,譯者:尤晉元等,機械工業出版社。對信號機制的發展過程闡述的比較詳細。
    3.signal、sigaction、kill等手冊,最直接而可靠的參考資料。
    4.http://www.linuxjournal.com/modules.php?op=modload&;name=NS-help&file=man提供了許多系統調用、庫函數等的在線指南。
    5.http://www.opengroup.org/onlinepubs/007904975/可以在這里對許多關鍵函數(包括系統調用)進行查詢,非常好的一個網址。
    6.http://unix.org/whitepapers/reentrant.html對函數可重入進行了闡述。
    7.http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM對實時信號給出了相當好的描述。
    關于作者:
    鄭彥興,國防科大攻讀博士學位。聯系方式: mlinux@163.com



    鄭彥興 (mlinux@163.com)
    2003 年 01 月
    在信號(上)中,討論了linux信號種類、來源、如何安裝一個信號以及對信號集的操作。本部分則首先討論從信號的生命周期上認識信號,或者宏觀上看似簡單的信號機制(進程收到信號后,作相應的處理,看上去再簡單不過了),在微觀上究竟是如何實現的,也是在更深層次上理解信號。接下來還討論了信號編程的一些注意事項,最后給出了信號編程的一些實例。
    一、信號生命周期
    從信號發送到信號處理函數的執行完畢
    對于一個完整的信號生命周期(從信號發送到相應的處理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:信號誕生;信號在進程中注冊完畢;信號在進程中的注銷完畢;信號處理函數執行完畢。相鄰兩個事件的時間間隔構成信號生命周期的一個階段。

    下面闡述四個事件的實際意義:
    1.信號"誕生"。信號的誕生指的是觸發信號的事件發生(如檢測到硬件異常、定時器超時以及調用信號發送函數kill()或sigqueue()等)。
    2.信號在目標進程中"注冊";進程的task_struct結構中有關于本進程中未決信號的數據成員:
    3.struct sigpending pending:
    4.struct sigpending{
    5.struct sigqueue *head, **tail;
    6.sigset_t signal;
    7.};
    第三個成員是進程中所有未決信號集,第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結構刻畫一個特定信號所攜帶的信息,并指向下一個sigqueue結構:
    struct sigqueue{
    struct sigqueue *next;
    siginfo_t info;
    }
    信號在進程中注冊指的就是信號值加入到進程的未決信號集中(sigpending結構的第二個成員sigset_tsignal),并且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。只要信號在進程的未決信號集中,表明進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。
    注:
    當一個實時信號發送給一個進程時,不管該信號是否已經在進程中注冊,都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中占有多個sigqueue結構(進程每收到一個實時信號,都會為它分配一個結構來登記該信號信息,并把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中注冊);
    當一個非實時信號發送給一個進程時,如果該信號已經在進程中注冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味著同一個非實時信號在進程的未決信號信息鏈中,至多占有一個sigqueue結構(一個非實時信號誕生后,(1)、如果發現相同的信號已經在目標結構中注冊,則不再注冊,對于進程來說,相當于不知道本次信號發生,信號丟失;(2)、如果進程的未決信號中沒有相同信號,則在進程中注冊自己)。
    8.信號在進程中的注銷。在目標進程執行過程中,會檢測是否有信號等待處理(每次從系統空間返回到用戶空間時都做這樣的檢查)。如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中占有的結構卸掉。是否將信號從進程未決信號集中刪除對于實時與非實時信號是不同的。對于非實時信號來說,由于在未決信號信息鏈中最多只占用一個sigqueue結構,因此該結構被釋放后,應該把信號在進程未決信號集中刪除(信號注銷完畢);而對于實時信號來說,可能在未決信號信息鏈中占用多個sigqueue結構,因此應該針對占用sigqueue結構的數目區別對待:如果只占用一個sigqueue結構(進程只收到該信號一次),則應該把信號在進程的未決信號集中刪除(信號注銷完畢)。否則,不應該在進程的未決信號集中刪除該信號(信號注銷完畢)。
    進程在執行信號相應處理函數之前,首先要把信號在進程中注銷。
    9.信號生命終止。進程注銷信號后,立即執行相應的信號處理函數,執行完畢后,信號的本次發送對進程的影響徹底結束。
    注:
    1)信號注冊與否,與發送信號的函數(如kill()或sigqueue()等)以及信號安裝函數(signal()及sigaction())無關,只與信號值有關(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被注冊)。
    2)在信號被注銷到相應的信號處理函數執行完畢這段時間內,如果進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中注冊;而對于非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中注冊一次。
    二、信號編程注意事項
    1.防止不該丟失的信號丟失。如果對八中所提到的信號生命周期理解深刻的話,很容易知道信號會不會丟失,以及在哪里丟失。
    2.程序的可移植性
    考慮到程序的可移植性,應該盡量采用POSIX信號函數,POSIX信號函數主要分為兩類:
    o POSIX 1003.1信號函數:Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
    o POSIX 1003.1b信號函數。POSIX 1003.1b在信號的實時性方面對POSIX 1003.1做了擴展,包括以下三個函數:sigqueue()、sigtimedwait()、sigwaitinfo()。其中,sigqueue主要針對信號發送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函數,后面有相應實例。
    o#include
    oint sigwaitinfo(sigset_t *set, siginfo_t *info).
    該函數與sigsuspend()類似,阻塞一個進程直到特定信號發生,但信號到來時不執行信號處理函數,而是返回信號值。因此為了避免執行相應的信號處理函數,必須在調用該函數前,使進程屏蔽掉set指向的信號,因此調用該函數的典型代碼是:
    sigset_t newmask;
    int rcvd_sig;
    siginfo_t info;

    sigemptyset(&newmask);
    sigaddset(&newmask, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &newmask, NULL);
    rcvd_sig = sigwaitinfo(&newmask, &info)
    if (rcvd_sig == -1) {
    ..
    }
    調用成功返回信號值,否則返回-1。sigtimedwait()功能相似,只不過增加了一個進程等待的時間。
    3.程序的穩定性。
    為了增強程序的穩定性,在信號處理函數中應使用可重入函數。
    信號處理程序中應當使用可再入(可重入)函數(注:所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯)。因為進程在收到信號后,就將跳轉到信號處理函數去接著執行。如果信號處理函數中使用了不可重入函數,那么信號處理函數可能會修改原來進程中不應該被修改的數據,這樣進程從信號處理函數中返回接著執行時,可能會出現不可預料的后果。不可再入函數在信號處理函數中被視為不安全函數。
    滿足下列條件的函數多數是不可再入的:(1)使用靜態的數據結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函數實現時,調用了malloc()或者free()函數;(3)實現時使用了標準I/O函數的。The Open Group視下列函數為可再入的:
    _exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown()、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
    即使信號處理函數使用的都是"安全函數",同樣要注意進入處理函數時,首先要保存errno的值,結束時,再恢復原值。因為,信號處理過程中,errno值隨時可能被改變。另外,longjmp()以及siglongjmp()沒有被列為可再入函數,因為不能保證緊接著兩個函數的其它調用是安全的。
    三、深入淺出:信號應用實例
    linux下的信號應用并沒有想象的那么恐怖,程序員所要做的最多只有三件事情:
    1.安裝信號(推薦使用sigaction());
    2.實現三參數信號處理函數,handler(int signal,struct siginfo *info, void *);
    3.發送信號,推薦使用sigqueue()。
    實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式采用缺省或忽略)。其他可能要做的無非是與信號集相關的幾種操作。
    實例一:信號發送及處理
    實現一個信號接收程序sigreceive(其中信號安裝由sigaction())。
    #include
    #include
    #include
    void new_op(int,siginfo_t*,void*);
    int main(int argc,char**argv)
    {
    struct sigaction act;
    int sig;
    sig=atoi(argv[1]);

    sigemptyset(&act.sa_mask);
    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=new_op;

    if(sigaction(sig,&act,NULL) < 0)
    {
    printf("install sigal error\n");
    }

    while(1)
    {
    sleep(2);
    printf("wait for the signal\n");
    }
    }
    void new_op(int signum,siginfo_t *info,void *myact)
    {
    printf("receive signal %d", signum);
    sleep(5);
    }
    說明,命令行參數為信號值,后臺運行sigreceive signo &,可獲得該進程的ID,假設為pid,然后再另一終端上運行kill -s signo pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。
    注:可以用sigqueue實現一個命令行信號發送程序sigqueuesend,見附錄1。
    實例二:信號傳遞附加信息
    主要包括兩個實例:
    1.向進程本身發送信號,并傳遞指針參數;
    2.#include
    3.#include
    4.#include
    5.void new_op(int,siginfo_t*,void*);
    6.int main(int argc,char**argv)
    7.{
    8.struct sigaction act;
    9.union sigval mysigval;
    10.int i;
    11.int sig;
    12.pid_t pid;
    13.char data[10];
    14.memset(data,0,sizeof(data));
    15.for(i=0;i < 5;i++)
    16.data[i]='2';
    17.mysigval.sival_ptr=data;
    18.
    19.sig=atoi(argv[1]);
    20.pid=getpid();
    21.
    22.sigemptyset(&act.sa_mask);
    23.act.sa_sigaction=new_op;//三參數信號處理函數
    24.act.sa_flags=SA_SIGINFO;//信息傳遞開關
    25.if(sigaction(sig,&act,NULL) < 0)
    26.{
    27.printf("install sigal error\n");
    28.}
    29.while(1)
    30.{
    31.sleep(2);
    32.printf("wait for the signal\n");
    33.sigqueue(pid,sig,mysigval);//向本進程發送信號,并傳遞附加信息
    34.}
    35.
    36.}
    37.
    38.void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現
    39.{
    40.int i;
    41.for(i=0;i<10;i++)
    42.{
    43.printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
    44.}
    45.printf("handle signal %d over;",signum);
    46.}
    47.
    這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決于具體的應用。
    48.2、 不同進程間傳遞整型參數:把1中的信號發送和接收放在兩個程序中,并且在發送過程中傳遞整型參數。
    信號接收程序:
    49.#include
    50.#include
    51.#include
    52.void new_op(int,siginfo_t*,void*);
    53.int main(int argc,char**argv)
    54.{
    55.struct sigaction act;
    56.int sig;
    57.pid_t pid;
    58.
    59.pid=getpid();
    60.sig=atoi(argv[1]);
    61.
    62.sigemptyset(&act.sa_mask);
    63.act.sa_sigaction=new_op;
    64.act.sa_flags=SA_SIGINFO;
    65.if(sigaction(sig,&act,NULL)<0)
    66.{
    67.printf("install sigal error\n");
    68.}
    69.while(1)
    70.{
    71.sleep(2);
    72.printf("wait for the signal\n");
    73.}
    74.
    75.}
    76.void new_op(int signum,siginfo_t *info,void *myact)
    77.{
    78.printf("the int value is %d \n",info->si_int);
    79.}
    80.
    信號發送程序:命令行第二個參數為信號值,第三個參數為接收進程ID。
    #include
    #include
    #include
    #include
    main(int argc,char**argv)
    {
    pid_t pid;
    int signum;
    union sigval mysigval;

    signum=atoi(argv[1]);
    pid=(pid_t)atoi(argv[2]);
    mysigval.sival_int=8;//不代表具體含義,只用于說明問題

    if(sigqueue(pid,signum,mysigval)==-1)
    printf("send error\n");
    sleep(2);
    }

    注:實例2的兩個例子側重點在于用信號來傳遞信息,目前關于在linux下通過信號傳遞信息的實例非常少,倒是Unix下有一些,但傳遞的基本上都是關于傳遞一個整數,傳遞指針的我還沒看到。我一直沒有實現不同進程間的指針傳遞(實際上更有意義),也許在實現方法上存在問題吧,請實現者email我。
    實例三:信號阻塞及信號集操作
    #include "signal.h"
    #include "unistd.h"
    static void my_op(int);
    main()
    {
    sigset_t new_mask,old_mask,pending_mask;
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=(void*)my_op;
    if(sigaction(SIGRTMIN+10,&act,NULL))
    printf("install signal SIGRTMIN+10 error\n");

    sigemptyset(&new_mask);
    sigaddset(&new_mask,SIGRTMIN+10);
    if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
    printf("block signal SIGRTMIN+10 error\n");

    sleep(10);
    printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
    if(sigpending(&pending_mask)<0)
    printf("get pending mask error\n");
    if(sigismember(&pending_mask,SIGRTMIN+10))
    printf("signal SIGRTMIN+10 is pending\n");

    if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
    printf("unblock signal error\n");
    printf("signal unblocked\n");

    sleep(10);
    }
    static void my_op(int signum)
    {
    printf("receive signal %d \n",signum);
    }

    編譯該程序,并以后臺方式運行。在另一終端向該進程發送信號(運行kill -s 42 pid,SIGRTMIN+10為42),查看結果可以看出幾個關鍵函數的運行機制,信號集相關操作比較簡單。
    注:在上面幾個實例中,使用了printf()函數,只是作為診斷工具,pringf()函數是不可重入的,不應在信號處理函數中使用。
    結束語:
    系統地對linux信號機制進行分析、總結使我受益匪淺!感謝王小樂等網友的支持!
    Comments and suggestions are greatly welcome!
    附錄1:
    用sigqueue實現的命令行信號發送程序sigqueuesend,命令行第二個參數是發送的信號值,第三個參數是接收該信號的進程ID,可以配合實例一使用:
    #include
    #include
    #include
    int main(int argc,char**argv)
    {
    pid_t pid;
    int sig;
    sig=atoi(argv[1]);
    pid=atoi(argv[2]);
    sigqueue(pid,sig,NULL);
    sleep(2);
    }

    參考文獻:
    •linux內核源代碼情景分析(上),毛德操、胡希明著,浙江大學出版社,當要驗證某個結論、想法時,最好的參考資料;
    •UNIX環境高級編程,作者:W.Richard Stevens,譯者:尤晉元等,機械工業出版社。對信號機制的發展過程闡述的比較詳細。
    •signal、sigaction、kill等手冊,最直接而可靠的參考資料。
    •http://www.linuxjournal.com/modules.php?op=modload&;name=NS-help&file=man提供了許多系統調用、庫函數等的在線指南。
    •http://www.opengroup.org/onlinepubs/007904975/可以在這里對許多關鍵函數(包括系統調用)進行查詢,非常好的一個網址。
    •http://unix.org/whitepapers/reentrant.html對函數可重入進行了闡述。
    •http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM對實時信號給出了相當好的描述。
    關于作者
    鄭彥興,國防科大攻讀博士學位。聯系方式: mlinux@163.com.

    原文轉自:http://www.kjueaiud.com

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>