信號

 信號是硬件中斷的軟件模擬,在進程正在執行的時候,任何情況都有可能發生,也就是說,信號是異步的。當信號發生的時候,進程是沒有控制權的。每個信號都以SIG開頭,他們對應正整數常量,成爲信號量。

當進程收到一個信號的時候,可以對信號採取如下三種措施:
`忽略
`捕獲
`默認處理
首先明白幾個常用的術語:當導致信號發生的事件出現時,就產生針對某個進程的信號,當進程對信號採取措施時,就稱該信號被遞送,在產生信號和遞送信號之間的時間間隔,稱爲信號未決,遞送信號可以被阻塞或延遲。信號的部署是指進程如何響應信號,一個被阻塞的信號也被稱爲未決的信號。一個信號集合是一個C數據類型sigset_t,定義在<signal.h>中,能夠表示多種信號。一個進程的信號掩碼是進程目前正阻塞不能遞送的信號的集合。

發送信號
    使用kill命令,或者kill函數,前面已經說過kill了,這裏不再細說。
    在kill失敗後調用waitpid是很安全的,能夠保證子進程不會變成僵進程。
    int raise(int sig);用來向自身發送一個信號。

捕獲信號
    每個進程都能決定如何響應出SIGSTOP和SIGKILL信號之外的其他所有信號,而這兩個信號不能被捕獲或者忽略。
    設置超時
        #include <unistd.h>
        unsigned int alarm(unsigned int seconds);
        一個進程只能設置一次超時,把seconds設置爲0就能取消前面的設置。
    pause函數
        #include <unistd.h>
        int pause(void);
        只有進程捕獲一個信號時,pause纔會返回調用進程,如果遞送到的信號引發了對信號的處理,那麼處理工作將在pause返回之前執行,pause總是返回-1,並把errno設置爲EINTR。

    定義一個信號處理器
        首先使用ANSI C定義的函數,這個比較簡單,只要指出信號與對該信號的處理方式就足以。
            #include <signal.h>
            typedef void (*sighandler_t)(int);
            sighandler_t signal(int signum, sighandler_t handler);
       

    一般的步驟是先創建一個信號集合,設置想要捕獲的信號,向內核登記一個信號處理器,然後等待捕獲信號。
        創建一個信號集合:
            #include <signal.h>
            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);
            前兩個用來初始化信號集合,不同是,第一個清空,第二個填滿。
            然後兩個用來添加和刪除信號集合中的信號,這四個成功返回0,出錯返回-1,
            最後一個是用來測試信號是否在集合中。是返回1,否則返回0;
        登記一個信號處理器:
            首先,必須使用sigprocmask設置或修改當前的信號掩碼--如果還沒有設置一個信號掩碼,那麼所有的信號都具有他們默認的部署。每個進程都會有一個信號屏蔽字,它規定了當前進程要阻塞的信號集。對於每種可能的信號,信號屏蔽字中都會有一位與之對應,如果該位被設置,該信號當前就是阻塞的。進程可以通過sigprocmask()來獲得和修改當前進程的信號屏蔽字。
            #include <signel.h>
            int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);
            根據how的取值不同,sigprocmask設置或檢查當前的信號掩碼,how可取:
            `SIG_BLOCK     set包含其他要阻塞的信號
            `SIG_UNBLOCK   set包含要解除阻塞的信號
            `SIG_SEYMASK   set包含新的信號掩碼
            如果how爲NULL,他就會被忽略,如果set爲NULL,當前的掩碼就保存在oldset中。如果oldset爲NULL,它也被忽略。成功時返回0,失敗返回-1。
            如果我們想阻塞SIGUSR1,有兩種方式。

              // using SIG_BLOCK

              sigset_t sigset;

              sigemptyset(&sigset);

              sigaddset(&sigset, SIGUSR1);

              sigprocmask(SIG_BLOCK, &sigset, NULL);

   

              // or using SIG_SETMASK

              sigset_t set, oldset;

              // get current signal mask

              sigprocmask(SIG_SETMASK, NULL, &set);

              // add SIGUSR1 into the signal mask

              sigaddset(&set, SIGUSR1);

              sigprocmask(SIG_SETMASK, &set, &oldset);

 

            同樣,如果要解除阻塞SIGUSR1,也有兩種方式。

              // using SIG_UNBLOCK

              sigset_t sigset;

              sigemptyset(&sigset);

              sigaddset(&sigset, SIGUSR1);

              sigprocmask(SIG_UNBLOCK, &sigset, NULL);

 

              // or using SIG_SETMASK

              sigset_t set, oldset;

              // get current signal mask

              sigprocmask(SIG_SETMASK, NULL, &set);

              // delete SIGUSR1 from the signal mask

              sigdelset(&set, SIGUSR1);

              sigprocmask(SIG_SETMASK, &set, &oldset);

            登記信號處理器
                #include <signal.h>
                int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
            爲signum指定信號處理器,  
                struct sigaction {
                    void (*sa_handler)(int);
                    void (*sa_sigaction)(int, siginfo_t *, void *);
                    sigset_t sa_mask;
                    int sa_flags;
                    void (*sa_restorer)(void)
                }
            sigaction()的功能是爲信號指定相關的處理程序,但是它在執行信號處理程序時,會把當前信號加入到進程的信號屏蔽字中,從而防止在進行信號處理期間信號丟失。從前面的例子我們可以看到,簡單的signal()函數也具有同樣的功能,這是由於signal()已經被重新實現的緣故,所以如果不在乎對信號的更多的控制,我們儘可放心大膽的使用簡單的signal()函數。signum指定將要改變處理行爲的信號;act指定該信號的處理動作,oldact用於返回該信號先前的處理動作。

            在sigaction結構中,sa_handler和sa_sigaction用於指定信號處理函數,但要注意,二者只能用其一,因爲它們在內部可能會實現爲union結構。除了在爲sa_flags指定SA_SIGINFO標誌時,會使用sa_sigaction字段外,其他情況下都應該只用sa_handler字段。

            sa_mask用於指定在當前信號處理程序執行期間,需要阻塞的信號集。

            sa_flags用於指定信號處理動的選項標誌,詳見手冊。這裏我想說的是SA_RESTART和SA_SIGINFO。SA_RESTART用於控制信號的自動重啓動機制,對signal(),Linux默認會自動重啓動被中斷的系統調用;而對於sigaction(),Linux默認並不會自動重啓動,所以如果希望執行信號處理後自動重啓動先前中斷的系統調用,就需要爲sa_flags指定SA_RESTART標誌。對於SA_SIGINFO,手冊上說此標誌可能會導致對信號的可靠排隊,但是從下面的例子我們將會看到,Linux並沒有對信號進行排隊。
            sa_handler是當信號發生時調用的信號處理函數,他的取值還可以是SIG_DFL默認處理 SIG_IGN忽略這個信號
            當執行信號處理時,出發他的信號會被阻塞,sa_mask定義了在執行處理期間,應該阻塞的其他信號集合的信號掩碼,sa_flags是修正sa_handler行爲的掩碼,他可以是:
                SA_NOCLDSTOP    進程忽略子進程產生的任何SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU信號
                SA_ONESHOT 或SA_RESETHAND   登記的自定義信號處理器只執行一次。在執行完畢後,恢復信號的默認動作。     
                SA_RESTART  讓可重啓的系統調用起作用
                SA_NOMASK或SA_NODEFER不避免在信號自己的處理器中接受信號本身。
            獲得未決的信號sigpending

                int sigpending(sigset_t * set);

                該函數在set中返回進程中當前尚未遞送的信號集。
            信號跳轉函數sigsetjump和siglongjump

                int sigsetjmp(sigjmp_buf env, int savesigs);

                void siglongjmp(sigjmp_buf env, int val);

                    sigsetjmp()有多次返回,對於直接調用者(一般是主程序),它返回0;若從siglongjmp()調用(一般是信號處理程序),則返回返回siglongjmp()中的val值。所以爲了避免混淆,最好不要在調用siglongjmp()時,讓val=0。

                    另外需要說明的是sigsetjmp()的第二個參數,它用於告訴內核,要不要保存進程的信號屏蔽字。當savesigs爲非0時,調用sigsetjmp()會在env中保存當前的信號屏蔽字,然後在調用siglongjmp()時恢復之前保存的信號屏字。由於信號處理函數使用siglongjmp()跳轉時不屬於正常返回,所以在進入信號處理函數時被阻塞的當前信號就沒有機會在返回時恢復。sigsetjmp()的savesigs參數就用於是告訴系統,在調用siglongjmp時,是否需要恢復先前的信號屏蔽字。

           

extern char * sys_siglist[];

這是一個以信號爲索引的字符串數組,通過它可以很容易的找到信號的字符串名稱。

void psignal(int signum, const char * msg);

此函數類似於perror(),輸出對指定信號的字符串描述。

 

char * strsignal(int signum);

返回指定信號的字符串描述。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章