APUE 3 - 信號 (signal): 可靠信號

  一個事件可使一個信號發送給一個進程,這個事件能夠是硬件異常,能夠是軟件條件觸發,能夠是終端產生信號,也能夠是一個kill函數調用。當信號產生後,內核一般會在進程表中設置某種形式的標誌(flag)。咱們能夠認爲當進程中的信號處理函數被觸發的時候認爲信號下達到了(delivered)這個進程。從信號產生到信號下達到進程這段期間,信號被認爲是掛起狀態(pending)。進程擁有阻塞信號下達的選項。若是一個阻塞信號要發送給一個進程,並且信號的處理方式是默認處理或者被進程捕獲,那麼這個信號將一直處於掛起狀態(pending)直到這個進程將信號設置成非阻塞狀態或將信號的處理方式改成忽略。操做系統在信號下達(delivered)的時候決定如何處理這個阻塞信號,而不是在信號產生的時候。這樣作容許進程在信號下達前更改信號的處理方式。編程

  若是一個阻塞信號在進程解除它的阻塞前產生屢次,那麼unix內核也僅僅會向進程下發一次這個信號。POSIX並無規定信號下發到進程的順序,然而與進程當前狀態相關的信號會較先到達進程。數組

  每一個進程都有一個信號掩碼(signal mask),它定義了一個當前被阻塞發送到該進程的信號的集合。咱們能夠認爲這個掩碼對於每一個可能下發到該進程的信號有一個與之對應的bit位,對於一個給定的信號來講,若是與之對應的bit位是打開狀態,那麼意味着這個信號當前應處於阻塞狀態。進程能夠經過sigprocmask來檢查並更改他當前的信號掩碼。由於信號的數量是有可能超過一個整數正bit位位數的,全部POSIX規範定義了一個叫作sigset_t的數據類型,它包含了全部信號集。信號掩碼就是存儲在這其中一個信號集中的。多線程

 

 

發送信號函數

 

kill & raisespa

可使用kill函數向一個進程或進程組發送信號。raise函數容許進程給他本身發送信號。操作系統

1 #include <signal.h>
2 
3 /* return 0 if OK, -1 on error */
4 int kill(pid_t pid, int signo);
5 
6 /* return 0 if OK, -1 on error */
7 int raise(int signo);

函數調用 raise(signo);  等同於 kill(getpid(), signo); 。線程

對於kill函數,pid有如下四種不一樣的選擇:unix

  1. pid > 0: 信號被髮送給進程號爲pid的進程
  2. pid == 0: 信號被髮送給全部進程組Id等於發送者進程組的進程(即發送給發送進程所屬進程組下的全部進程),前提是發送進程有權限將信號發送給該進程而且該進程不是系統進程。
  3. pid < 0: 信號被髮送給進程組Id爲pid絕對值的進程組下的全部進程,前提是發送進程有權限發送信號到該進程而且該進程不是系統進程。
  4. pid == -1: 信號被髮送給發送進程有權限發送的全部進程,但不包含系統進程。

正如以前咱們提到的,進程須要足夠的權限才能向另外一個進程發送信號,超級用戶能夠向任何進程發送信號。對於其餘用戶,能夠發送信號的基本準則是:發送進程的真實用戶Id(real user Id)或有效用戶Id(effective user Id)必須等於接收進程的真實用戶Id或有效用戶Id。若是實現支持 _POSIX_SAVED_IDS的話,系統會檢查接收進程的saved set-user-ID而不是有效用戶Id。關於發送信號權限的一個特殊情景是:若是待發送信號是SIGCONT,那麼發送進程能夠將信號發送給他所屬會話下的任何進程。rest

POSIX規範定義信號值爲0的信號爲空信號(null signal)。它能夠用於經過kill函數來檢測某一進程是否存在。kill函數在收到值爲0的信號後會進程正常的錯誤檢查,可是不會發送此信號。所以咱們能夠經過 kill(pid, 0) 來判斷進程id爲pid的進程是否存在。然而, UNIX系統在必定時間後會循環使用進程 IDs,因此經過pid檢查出來的進程未必真的是你認爲的那個進程(即函數調用時,經過pid查詢到的進程未必會是你認爲的那個進程)。另外kill函數不是原子的,當kill函數返回時,有可能被髮送信號的進程已經結束。code

 

alarm & pause

alarm函數容許咱們設置一個定時器,這個定時器在將來某個時間點觸發,這個定時器觸發後會產生一個SIGALRM信號,此信號的默認處理方式是結束進程。

1 #include <unistd.h>
2 
3 /* 若是以前沒有設置alarm返回0,不然返回以前
4 設置的alarm所剩餘的秒數 */
5 unsigned int alarm(unsigned int seconds);

一旦到了alarm所設置的時間點,內核就會發送alarm信號,而因爲處理器調度延時進程此時可能還沒法獲取到此信號處理的控制權。每一個進程中只有一個alarm時鐘。當咱們調用alarm時,若是當前進程以前註冊的鬧鐘還未到期,那麼此函數返回以前鬧鐘距到期剩餘的秒數,而且以前註冊的鬧鐘會被這個新的鬧鐘值取代。另外,若是以前註冊的鬧鐘還未到期而且新註冊的鬧鐘值爲0的話,那麼以前註冊的鬧鐘會被取消。

pause函數的調用會阻塞調用進程直到調用進程捕獲到一個信號。

1 #include <unistd.h>
2 
3 /*Returns: -1 with errno set to EINTR*/
4 int pause(void);

 

信號集

像咱們以前提到的,不一樣信號的數量可能會超過一個整數的bit位所能表示的信號數量。POSIX規範定義了sigset_t類型用來表示信號集,並使用下面的5個函數來管理信號集:

 1 #include <unistd.h>
 2 
 3 /*All four return 0 if OK, -1 on error*/
 4 
 5 /*清空set信號集中的全部信號*/
 6 int sigemptyset(sigset_t* set);
 7 /*使set包含全部信號*/
 8 int sigfillset(sigset_t* set);
 9 /*將信號signo加入到信號集set中*/
10 int sigaddset(sigset_t* set, int signo);
11 /*從信號集set中刪除signo*/
12 int sigdelset(sigset_t* set, int signo);
13 
14 /*Returns 1 if true, 0 if false, -1 on error*/
15 int sigismember(const sigset_t* set,  int signo);

 

 

sigprocmask

一個進程的信號掩碼是指被阻止下發到此進程的全部信號的集合。進程能夠檢查並更改他的信號掩碼。

#include <signal.h>

/*
  若是oset不爲空,那麼此進程信號掩碼以前的值會被複制到oset中。
  若是set爲空,那麼此進程的信號掩碼不會被更改,而信號掩碼的當前值
  也不會複製到oset中。
*/ int sigprocmask(int how, const sigset_t* restrict set, sigset_t* restrict oset);

how參數的值指明如何修改信號掩碼:

  • SIG_BLOCK: set包含另外的咱們想阻塞的信號
  • SIG_UNBLOCK:set包含咱們想要解除阻塞的信號
  • SIG_SETMASK:使用set替代進程當前信號掩碼

sigprocmask 不支持多線程環境。

 

sigpending

#include <unistd.h>

/*經過set返回發送給當前進程但被阻塞的信號*/
int sigpending(sigset_t* set);

 

 

sigaction

咱們能夠經過sigaction方法檢查並修改特定信號的處理方式(action)。他是早期sinal函數的取代版本。

#include <unistd.h>

/*若oact不爲空,函數經過oact返回當前signo的action
 * 若act不爲空,則修改signo的當前action*/
int sigaction(int signo, const struct sigaction* restrict act,
struct sigaction* restrict oact);

struct sigaction{
   void (*sa_handler)(int);    /*addr of signal handler, 
or SIG_IGN or SIG_DFL
*/ sigset_t sa_mask; /*additional signals to block*/ int sa_flag; /*signal options*/ /*alternate handler*/ void (*sa_sigaction)(int,siginfo_t *, void *); };

當使用sigaction改變signo的action時,若是sa_handler指向了一個信號處理函數(SIG_IGN和SIG_DFL除外),sigaction函數會將sa_mask指向的信號集合在這個信號處理函數(sa_handler)被調用前加入到當前進程掩碼中,當信號處理函數返回時,進程的信號掩碼會恢復爲他原來的值。這樣,使咱們可以在信號處理函數被調用是阻塞一部分信號的到達。一旦咱們爲一個信號安裝了action, 那麼對於這個信號這個action將一直處於安裝狀態,除非咱們使用sigaction方法明確的更改可它。

sa_flags:

 

sigsuspend

等待信號到達的一個整潔而可靠地方式是先阻塞這個信號而後使用sigsuspend。

#include <signal.h>

/*
   將當前進程信號掩碼設置爲sigmask,
   函數返回後將進程掩碼恢復爲調用前
   的值, 該函數老是返回-1,並設置
   errno 爲 -1
*/
int sigsuspend(const sigset_t* sigmask);

 sigsuspend 可用於等待指定信號的到達,他的經常使用用法以下:

 1 sigset_t mask, oldmask;
 2 
 3  4 
 5 /* Set up the mask of signals to temporarily block. */
 6 sigemptyset (&mask);
 7 sigaddset (&mask, SIGUSR1);
 8 
 9 10 
11 /* Wait for a signal to arrive. */
12 sigprocmask (SIG_BLOCK, &mask, &oldmask);
13 while (!usr_interrupt)
14   sigsuspend (&oldmask);
15 sigprocmask (SIG_UNBLOCK, &mask, NULL);

經過user_interrupt 判斷是否等待的SIGUSR1信號已到達,sigsuspend再返回時將進程信號掩碼設置爲他被調用前的值,所以咱們最後須要將添加mask移除掉。

 

Signal Names and Numbers


數組sys_siglist能夠幫助咱們匹配信號與信號名:

 1 extern char* sys_siglist[]; 數組索引爲信號值, 數組元素值爲信號名。

信號與信號名的轉換:

 1 #include <signal.h>
 2 
 3 /* 若是msg不爲空,則向stderr 輸出msg緊跟一個冒號加一個空着在加信號描述;若是msg爲空則只向stderr輸出信號描述*/
 4 void psignal(int signo, const char* msg);
 5 
 6 void psiginfo(const siginfo_t info, const char* msg);
 7 
 8 /*獲取信號描述*/
 9 char* strsignal(int signo);
10 
11 void sig2str(int signo, char* str);
12 void str2sig(const char* str, int* signop);

 

 

總結

  信號一般用於一些相對複雜的程序, 理解如何及爲什麼處理信號對於UNIX高級編程是必要的。

相關文章
相關標籤/搜索