深入理解進程間通信之信號


信號及信號源

信號本質

  信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。

  信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展后,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。

信號來源

  信號事件的發生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。

信號的種類

  系統支持的所有信號如下所示:

  進程可以屏蔽掉大多數信號,除了SIGSTOP和SIGKILL。

進程對信號的響應

  進程可以通過三種方式來響應一個信號:(1)忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILL及SIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操作,Linux對每種信號都規定了默認操作。注意,進程對實時信號的缺省反應是進程終止。

  Linux究竟采用上述三種方式的哪一個來響應信號,取決於傳遞給相應API函數的參數。

信號的安裝

  如果進程要處理某一信號,那么就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關系,即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。

  Linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。

signal

  typedef void (*sighandler_t)(int);
  sighandler_t signal(int signum, sighandler_t handler));

   第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,可以忽略該信號(參數設為SIG_IGN);可以采用系統默認方式處理信號(參數設為SIG_DFL);也可以自己實現處理方式(參數指定一個函數地址)。

  如果signal()調用成功,返回最后一次為安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。

sigaction

  int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

  sigaction函數用於改變進程接收到特定信號后的行為。該函數的第一個參數為信號的值,可以為除SIGKILL及SIGSTOP外的任何一個特定有效的信號(為這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構 sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存原來對相應信號的處理,可指定oldact為NULL。如果把第二、第三個參數都設為NULL,那么該函數可用於檢查信號的有效性。

  第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等等。

信號的發送

  發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

kill

  int kill(pid_t pid, int signo);

  signo是信號值,當為0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用於檢查目標進程是否存在,以及當前進程是否具有向目標發送信號的權限(root權限的進程可以向任何進程發送信號,非root權限的進程只能向屬於同一個session或者同一個用戶的進程發送信號)。

raise

  int raise(int signo);

  向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1。

sigqueue

  int sigqueue(pid_t pid, int signo, const union sigval val);

  sigqueue()與函數sigaction()配合使用,sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。

  sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由 sigaction安裝,並設定了sa_sigaction指針)的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由於 sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。

  注:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,即在信號處理函數執行過程中到來的所有相同信號,都被合並為一個信號。

alarm

  unsigned int alarm(unsigned int seconds);

  專門為SIGALRM信號而設,在指定的時間seconds秒后,將向進程本身發送SIGALRM信號,又稱為鬧鍾時間。進程調用alarm后,任何以前的alarm()調用都將無效。如果參數seconds為零,那么進程內將不再包含任何鬧鍾時間。返回值,如果調用alarm()前,進程中已經設置了鬧鍾時間,則返回上一個鬧鍾時間的剩余時間,否則返回0。

settimer

  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的一個實例;第三個參數可不做處理。setitimer()調用成功返回0,否則返回-1。

abort

  void abort(void); 

  向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程設置為阻塞信號,調用abort()后,SIGABORT仍然能被進程接收。該函數無返回值。

信號生命周期

  對於一個完整的信號生命周期(從信號發送到相應的處理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:信號誕生;信號在進程中注冊完畢;信號在進程中的注銷完畢;信號處理函數執行完畢。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM