Unix 系統信號集與編程

Signal Description
SIGABRT 由調用abort函數產生,進程非正常退出
SIGALRM 用alarm函數設置的timer超時或setitimer函數設置的interval timer超時
SIGBUS 某種特定的硬件異常,通常由內存訪問引起
SIGCANCEL 由Solaris Thread Library內部使用,通常不會使用
SIGCHLD 進程Terminate或Stop的時候,SIGCHLD會發送給它的父進程。缺省情況下該Signal會被忽略
SIGCONT 當被stop的進程恢復運行的時候,自動發送
SIGEMT 和實現相關的硬件異常
SIGFPE 數學相關的異常,如被0除,浮點溢出,等等
SIGFREEZE Solaris專用,Hiberate或者Suspended時候發送
SIGHUP 發送給具有Terminal的Controlling Process,當terminal被disconnect時候發送
SIGILL 非法指令異常
SIGINFO BSD signal。由Status Key產生,通常是CTRL+T。發送給所有Foreground Group的進程
SIGINT 由Interrupt Key產生,通常是CTRL+C或者DELETE。發送給所有ForeGround Group的進程
SIGIO 異步IO事件
SIGIOT 實現相關的硬件異常,一般對應SIGABRT
SIGKILL 無法處理和忽略。中止某個進程
SIGLWP 由Solaris Thread Libray內部使用
SIGPIPE 在reader中止之後寫Pipe的時候發送
SIGPOLL 當某個事件發送給Pollable Device的時候發送
SIGPROF Setitimer指定的Profiling Interval Timer所產生
SIGPWR 和系統相關。和UPS相關。
SIGQUIT 輸入Quit Key的時候(CTRL+\)發送給所有Foreground Group的進程
SIGSEGV 非法內存訪問
SIGSTKFLT Linux專用,數學協處理器的棧異常
SIGSTOP 中止進程。無法處理和忽略。
SIGSYS 非法系統調用
SIGTERM 請求中止進程,kill命令缺省發送
SIGTHAW Solaris專用,從Suspend恢復時候發送
SIGTRAP 實現相關的硬件異常。一般是調試異常
SIGTSTP Suspend Key,一般是Ctrl+Z。發送給所有Foreground Group的進程
SIGTTIN 當Background Group的進程嘗試讀取Terminal的時候發送
SIGTTOU 當Background Group的進程嘗試寫Terminal的時候發送
SIGURG 當out-of-band data接收的時候可能發送
SIGUSR1 用戶自定義signal 1
SIGUSR2 用戶自定義signal 2
SIGVTALRM setitimer函數設置的Virtual Interval Timer超時的時候
SIGWAITING Solaris Thread Library內部實現專用
SIGWINCH 當Terminal的窗口大小改變的時候,發送給Foreground Group的所有進程
SIGXCPU 當CPU時間限制超時的時候
SIGXFSZ 進程超過文件大小限制
SIGXRES Solaris專用,進程超過資源限制的時候發送


Linux支持的信號列表如下。很多信號是與機器的體系結構相關的。
首先列出的是POSIX.1中列出的信號:
信號   值  處理動作  發出信號的原因
----------------------------------------------------------------------
SIGHUP  1      A  終端掛起或者控制進程終止
SIGINT  2      A  鍵盤中斷(如ctrl+c被按下)
SIGQUIT 3      C   鍵盤的退出鍵被按下(ctrl+\)
SIGILL  4      C  非法指令
SIGABRT 6      C  由abort(3)發出的退出指令
SIGFPE  8      C  浮點異常
SIGKILL 9      AEF  Kill信號
SIGSEGV 11      C  無效的內存引用
SIGPIPE 13      A  管道破裂: 寫一個沒有讀端口的管道
SIGALRM 14      A  由alarm(2)發出的信號
SIGTERM 15      A  終止信號
SIGUSR1 30,10,16  A  用戶自定義信號1
SIGUSR2 31,12,17  A  用戶自定義信號2
SIGCHLD 20,17,18  B  子進程結束信號
SIGCONT 19,18,25     進程繼續(曾被停止的進程)
SIGSTOP 17,19,23  DEF 終止進程
SIGTSTP 18,20,24  D  控制終端(tty)上按下停止鍵
SIGTTIN 21,21,26  D  後臺進程企圖從控制終端讀
SIGTTOU 22,22,27  D  後臺進程企圖從控制終端寫

=====================================================================================================
SIGHUP     終止進程     終端線路掛斷
SIGINT     終止進程     中斷進程
SIGQUIT   建立CORE文件終止進程,並且生成core文件
SIGILL   建立CORE文件       非法指令
SIGTRAP   建立CORE文件       跟蹤自陷
SIGBUS   建立CORE文件       總線錯誤
SIGSEGV   建立CORE文件       段非法錯誤
SIGFPE   建立CORE文件       浮點異常
SIGIOT   建立CORE文件       執行I/O自陷
SIGKILL   終止進程     殺死進程
SIGPIPE   終止進程     向一個沒有讀進程的管道寫數據
SIGALARM   終止進程     計時器到時
SIGTERM   終止進程     軟件終止信號
SIGSTOP   停止進程     非終端來的停止信號
SIGTSTP   停止進程     終端來的停止信號
SIGCONT   忽略信號     繼續執行一個停止的進程
SIGURG   忽略信號     I/O緊急信號
SIGIO     忽略信號     描述符上可以進行I/O
SIGCHLD   忽略信號     當子進程停止或退出時通知父進程
SIGTTOU   停止進程     後臺進程寫終端
SIGTTIN   停止進程     後臺進程讀終端
SIGXGPU   終止進程     CPU時限超時
SIGXFSZ   終止進程     文件長度過長
SIGWINCH   忽略信號     窗口大小發生變化
SIGPROF   終止進程     統計分佈圖用計時器到時
SIGUSR1   終止進程     用戶定義信號1
SIGUSR2   終止進程     用戶定義信號2
SIGVTALRM 終止進程     虛擬計時器到時

1) SIGHUP 本信號在用戶終端連接(正常或非正常)結束時發出, 通常是在終端的控
制進程結束時, 通知同一session內的各個作業, 這時它們與控制終端
不再關聯.
2) SIGINT 程序終止(interrupt)信號, 在用戶鍵入INTR字符(通常是Ctrl-C)時發出
3) SIGQUIT 和SIGINT類似, 但由QUIT字符(通常是Ctrl-)來控制. 進程在因收到
SIGQUIT退出時會產生core文件, 在這個意義上類似於一個程序錯誤信
號.
4) SIGILL 執行了非法指令. 通常是因爲可執行文件本身出現錯誤, 或者試圖執行
數據段. 堆棧溢出時也有可能產生這個信號.
5) SIGTRAP 由斷點指令或其它trap指令產生. 由debugger使用.
6) SIGABRT 程序自己發現錯誤並調用abort時產生.
6) SIGIOT 在PDP-11上由iot指令產生, 在其它機器上和SIGABRT一樣.
7) SIGBUS 非法地址, 包括內存地址對齊(alignment)出錯. eg: 訪問一個四個字長
的整數, 但其地址不是4的倍數.
8) SIGFPE 在發生致命的算術運算錯誤時發出. 不僅包括浮點運算錯誤, 還包括溢
出及除數爲0等其它所有的算術的錯誤.
9) SIGKILL 用來立即結束程序的運行. 本信號不能被阻塞, 處理和忽略.
10) SIGUSR1 留給用戶使用
11) SIGSEGV 試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據.
12) SIGUSR2 留給用戶使用
13) SIGPIPE Broken pipe
14) SIGALRM 時鐘定時信號, 計算的是實際的時間或時鐘時間. alarm函數使用該
信號.
15) SIGTERM 程序結束(terminate)信號, 與SIGKILL不同的是該信號可以被阻塞和
處理. 通常用來要求程序自己正常退出. shell命令kill缺省產生這
個信號.
17) SIGCHLD 子進程結束時, 父進程會收到這個信號.
18) SIGCONT 讓一個停止(stopped)的進程繼續執行. 本信號不能被阻塞. 可以用
一個handler來讓程序在由stopped狀態變爲繼續執行時完成特定的
工作. 例如, 重新顯示提示符
19) SIGSTOP 停止(stopped)進程的執行. 注意它和terminate以及interrupt的區別:
該進程還未結束, 只是暫停執行. 本信號不能被阻塞, 處理或忽略.
20) SIGTSTP 停止進程的運行, 但該信號可以被處理和忽略. 用戶鍵入SUSP字符時
(通常是Ctrl-Z)發出這個信號
21) SIGTTIN 當後臺作業要從用戶終端讀數據時, 該作業中的所有進程會收到SIGTTIN
信號. 缺省時這些進程會停止執行.
22) SIGTTOU 類似於SIGTTIN, 但在寫終端(或修改終端模式)時收到.
23) SIGURG 有"緊急"數據或out-of-band數據到達socket時產生.
24) SIGXCPU 超過CPU時間資源限制. 這個限制可以由getrlimit/setrlimit來讀取/
改變
25) SIGXFSZ 超過文件大小資源限制.
26) SIGVTALRM 虛擬時鐘信號. 類似於SIGALRM, 但是計算的是該進程佔用的CPU時間.
27) SIGPROF 類似於SIGALRM/SIGVTALRM, 但包括該進程用的CPU時間以及系統調用的
時間.
28) SIGWINCH 窗口大小改變時發出.
29) SIGIO 文件描述符準備就緒, 可以開始進行輸入/輸出操作.
30) SIGPWR Power failure

有兩個信號可以停止進程:SIGTERM和SIGKILL。 SIGTERM比較友好,進程能捕捉這個信號,根據您的需要來關閉程序。在關閉程序之前,您可以結束打開的記錄文件和完成正在做的任務。在某些情況下,假如進程正在進行作業而且不能中斷,那麼進程可以忽略這個SIGTERM信號。

對於SIGKILL信號,進程是不能忽略的。這是一個 “我不管您在做什麼,立刻停止”的信號。假如您發送SIGKILL信號給進程,Linux就將進程停止在那裏。

======================================================================================================
http://bbs.loveunix.net/viewthread.php?tid=35190

信號是進程之間互傳消息的一種方法,俗稱軟件中斷。它提供了一種處理異步事件的方法。

在SCO openserver 5.05上 kill -l得到
CODE
        HUP SYS STOP
        INT PIPE TSTP
        QUIT ALRM CONT
        ILL TERM TTIN
        TRAP USR1 TTOU
        ABRT USR2 VTALRM
        EMT CHLD PROF
        FPE PWR XCPU
        KILL WINCH XFSZ
        BUS URG
        SEGV POLL   

在Aix 4.3上kill -l 得到
CODE
      1) HUP       14) ALRM      27) MSG       40) bad trap  53) bad trap
      2) INT       15) TERM      28) WINCH     41) bad trap  54) bad trap
      3) QUIT      16) URG       29) PWR       42) bad trap  55) bad trap
      4) ILL       17) STOP      30) USR1      43) bad trap  56) bad trap
      5) TRAP      18) TSTP      31) USR2      44) bad trap  57) bad trap
      6) ABRT      19) CONT      32) PROF      45) bad trap  58) bad trap
      7) EMT       20) CHLD      33) DANGER    46) bad trap  59) CPUFAIL
      8) FPE       21) TTIN      34) VTALRM    47) bad trap  60) GRANT
      9) KILL      22) TTOU      35) MIGRATE   48) bad trap  61) RETRACT
     10) BUS       23) IO        36) PRE       49) bad trap  62) SOUND
     11) SEGV      24) XCPU      37) bad trap  50) bad trap  63) SAK
     12) SYS       25) XFSZ      38) bad trap  51) bad trap
     13) PIPE      26) bad trap  39) bad trap  52) bad trap

在Redhat 13 上kill -l 得到
CODE
 1) SIGHUP         2) SIGINT             3) SIGQUIT        4) SIGILL            5) SIGTRAP
 6) SIGABRT         7) SIGBUS        8) SIGFPE            9) SIGKILL        10) SIGUSR1
11) SIGSEGV        12) SIGUSR2        13) SIGPIPE        14) SIGALRM        15) SIGTERM
16) SIGSTKFLT        17) SIGCHLD        18) SIGCONT        19) SIGSTOP        20) SIGTSTP
21) SIGTTIN        22) SIGTTOU        23) SIGURG        24) SIGXCPU        25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF        28) SIGWINCH        29) SIGIO            30) SIGPWR
31) SIGSYS        34) SIGRTMIN        35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    

    信號出現在UNIX 的早期版本中,但早期的信號模型是不可靠的, 信號可能被丟失,也很難處理關鍵段.UNIX 的兩個重要分支BSD 和System V 分別對早期的信號進行了擴展,但這兩個系統的擴展並不兼容,POSIX 統一了這兩種實現, 最終提供了可靠的信號模型。

信號的產生條件
    **用戶按某些終端鍵時,產生信號
    **硬件異常:除0、無效存儲訪問etc.
    **kill函數,發送信號給另一個進程或進程組
    **kill命令,發送信號給其他進程
    **某種軟件條件已經發生,發送信號通知有關進程

接到信號的處理辦法
1    忽略,但是SIGKILL和SIGSTOP不能忽略。
2    捕捉
3    執行系統的默認處理

信號的分類

1.    非可靠信號:早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。
2.    可靠信號: 信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。
注:    信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。當然也可以稱爲實時信號或者非實時信號,非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。

信號的生命週期
    從信號發送到信號處理函數的執行完畢。對於一個完整的信號生命週期(從信號發送到相應的處理函數執行完畢)來說,可以分爲三個重要的階段,這三個階段由四個重要事件來刻畫:信號產生;信號在進程中註冊完畢;信號在進程中的註銷完畢;信號處理函數執行完畢。相鄰兩個事件的時間間隔構成信號生命週期的一個階段。
當一個實時信號發送給一個進程時,不管該信號是否已經在進程中註冊,都會被再註冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味着同一個實時信號可以在同一個進程的未決信號信息鏈中佔有多個sigqueue結構(進程每收到一個實時信號,都會爲它分配一個結構來登記該信號信息,並把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中註冊);當一個非實時信號發送給一個進程時,如果該信號已經在進程中註冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味着同一個非實時信號在進程的未決信號信息鏈中,至多佔有一個sigqueue結構(一個非實時信號產生後,1如果發現相同的信號已經在目標結構中註冊,則不再註冊,對於進程來說,相當於不知道本次信號發生,信號丟失;2如果進程的未決信號中沒有相同信號,則在進程中註冊自己)。

需要注意的要點是:
1)信號註冊與否,與發送信號的函數(如kill()或 sigqueue()等)以及信號安裝函數(signal()及sigaction())無關,只與信號值有關(信號值小於 SIGRTMIN的信號最多隻註冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被註冊)。
2)在信號被註銷到相應的信號處理函數執行完畢這段時間內,如果進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中註冊;而對於非實時信號來說,無論收到多少次信號,都會視爲只收到一個信號,只在進程中註冊一次。當然還有有些需要知道的概念比如 低速系統調用,中斷系統調用,可重入函數等等概念,請查閱環境高級編程。

一些概念性的東西大概就這些東西吧,下面就分類介紹一下具體的函數。
我下面介紹的,如果沒有特殊說明,都是對於linux上的實現,當然都是符合POSIX標準嘍。

二,函數詳細介紹

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

1, int kill(pid_t pid,int signo)

用到的頭文件:
#include <sys/types.h>
#include <signal.h>

參數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。

2, int raise(int signo)
用到的頭文件:
#include <signal.h>

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

3, int sigqueue(pid_t pid, int sig, const union sigval val)

用到的頭文件:
#include <sys/types.h>
#include <signal.h>

調用成功返回 0;否則,返回 -1。

sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,

與函數sigaction()配合使用。sco 5.05沒有此函數。

sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,

指定了信號傳遞的參數,即通常所說的4字節值。

typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;


sigqueue() 比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果sig爲0,

將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。

在調用 sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由sigaction安裝,

並設定了sa_sigaction指針,稍後將闡述)的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由於sigqueue系統調用

支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。

注:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,

即在信號處理函數執行過程中到來的所有相同信號,都被合併爲一個信號。


4, unsigned int alarm(unsigned int seconds)

用到的頭文件:

#include <unistd.h>

專門爲 SIGALRM信號而設,在指定的時間seconds秒後,將向進程本身發送SIGALRM信號,又稱爲鬧鐘時間。進程調用alarm後,

任何以前的alarm()調用都將無效。如果參數seconds爲零,那麼進程內將不再包含任何鬧鐘時間。

函數返回是這樣的,如果調用 alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0。

5, int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));

用到的頭文件:
#include <sys/time.h>

setitimer() 比alarm功能強大,支持3種類型的定時器:

ITIMER_REAL: 設定絕對時間;經過指定的時間後,內核將發送SIGALRM信號給本進程;
ITIMER_VIRTUAL 設定程序執行時間;經過指定的時間後,內核將發送SIGVTALRM信號給本進程;
ITIMER_PROF 設定進程執行以及內核因本進程而消耗的時間和,經過指定的時間後,內核將發送ITIMER_VIRTUAL信號給本進程;

Setitimer() 第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval。

結構 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 */
};




第三個參數可不做處理。

Setitimer() 調用成功返回0,否則返回-1。


這是我man setitimer時看到的

getitimer and setitimer are not part of any currently supported standard;
they were developed at the University of California at Berkeley and are
used by permission.


6, void abort(void);

用到的頭文件:
#include <stdlib.h>


向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。

即使SIGABORT被進程設置爲阻塞信號,調用abort()後,SIGABORT仍然能被進程接收。該函數無返回值。




B:信號的捕獲與安裝(設置信號關聯動作)



如果進程要處理某一信號,那麼就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值

的動作之間的映射關係,

即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。

linux主要有兩個函數實現信號的安裝:signal()、 sigaction()。其中signal()在可靠信號系統調用的基礎上實現,

是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的

函數(由兩個系統調用實現:sys_signal以及 sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與

sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現

在支持信號帶有參數。

1, void (*signal(int signum, void (*handler))(int)))(int);

#include <signal.h>

如果該函數原型不容易理解的話,可以參考下面的分解方式來理解:

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

第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,

a,可以忽略該信號(參數設爲SIG_IGN);

b,可以採用系統默認方式處理信號(參數設爲 SIG_DFL);

b,也可以自己實現處理方式(參數指定一個函數地址)。

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

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

#include <signal.h>


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不支持它,不應再被使用。

a,聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了可以是用戶自

定義的處理函數外,還可以爲SIG_DFL(採用缺省的處理方式),也可以爲 SIG_IGN(忽略信號)。

b,由_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{
...
}...
... ...
}
}




注:爲了更便於閱讀,在說明問題時常把該結構表示爲如下所表示的形式。

siginfo_t {
int si_signo; /* 信號值,對所有信號有意義*/
int si_errno; /* errno值,對所有信號有意義*/
int si_code; /* 信號產生的原因,對所有信號有意義*/
pid_t si_pid; /* 發送信號的進程ID,對kill(2),實時信號以及SIGCHLD有意義 */
uid_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信號有意義 */

}


實際上,除了前三個元素外,其他元素組織在一個聯合結構中,在聯合數據結構中,又根據不同的信號組織成不同的結構。

註釋中提到的對某種信號有意義指的是,在該信號的處理函數中可以訪問這些域來獲得與信號相關的有意義的信息,只不過特定信號只

對特定信息感興趣而已。

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時,該數據結構中的數據就將

拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發是非常有意義

的。


c,sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定

SA_NODEFER或者SA_NOMASK標誌位。

注: 請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。

d,sa_flags 中包含了許多標誌位,包括剛剛提到的SA_NODEFER及SA_NOMASK標誌位。另一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌

位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該爲sigaction結構中的sa_sigaction指定處理函數,而不應該爲 sa_handler

指定信號處理函數,否則,設置該標誌變得毫無意義。即使爲sa_sigaction指定了信號處理函數,如果不設置 SA_SIGINFO,信號處理函數同樣

不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤(Segmentation fault)。

注:很多文獻在闡述該標誌位時都認爲,如果設置了該標誌位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:自己實現

一個單一參數信號處理函數,並在程序中設置該標誌位,可以察看程序的運行結果。實際上,可以把該標誌位看成信號是否傳遞參數的開關,如果設置該位,

則傳遞參數;否則,不傳遞參數。


C:信號集及信號集操作函數:

信號集被定義爲一種數據類型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t




信號集用來描述信號的集合,linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是爲

信號集操作定義的相關函數:



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);

頭文件
#include <signal.h>

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指向的信號集中。



D:信號阻塞與信號未決:

每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程後都將被阻塞。下面是與信號阻塞

相關的幾個函數:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));

頭文件:
#include <signal.h>

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。


三, 示例程序

實例一:信號發送及處理,看看函數sigaction是怎麼使用的。
CODE

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void user_func(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=(void * )user_func;

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

while(1)
{
 sleep(2);
 printf("wait for the signal\n");
}
}
void user_func(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d\n\n\n", signum);
sleep(5);
}



在一終端執行cc -o act act.c
$./act 8&
[1] 992
$ wait for the signal
$

在另一終端執行

#kill -s 8 992


看看。。

$receive signal 8


實例二:信號阻塞及信號集操作

CODE

#include <signal.h>
#include <unistd.h>
void user_func(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*)user_func;

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(20);

printf("\n\nNow begin to get pending mask and unblock SIGRTMIN+10\n\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 ,ok ... ...\n\n\n");
}
void user_func(int signum)
{
printf("receive signal %d \n",signum);
}



$cc -o sus sus.c
$./sus&
[1] 997



another console
#kill -s 42 pid

看看...

$
Now begin to get pending mask and unblock SIGRTMIN+10

signal SIGRTMIN+10 is pending
receive signal 42
signal unblocked ,ok ... ...



[1]+ Exit 31 ./d
$



實例三:signal例子

CODE

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#define damo
void user_func(int no)
{
       switch (no)
       {
               case 1:
                       printf("Get SIGHUP.\n");
                       break;
               case 2:
                       printf("Get SIGINT\n");
                       break;
               case 3:
                       printf("Get SIGQUIT \n");
                       break;
               default:
                       printf("What wan yi a \n\n");
                       break;
       }

}
int main()
{
       printf("Process id is %d\n ",getpid());

#ifdef damo
       signal(SIGHUP, user_func);
       signal(SIGINT, user_func);
       signal(SIGQUIT, user_func);
       signal(SIGBUS, user_func);
#else
       signal(SIGHUP, SIG_IGN);
       signal(SIGINT, SIG_IGN);
       signal(SIGQUIT, SIG_IGN);
       signal(SIGBUS, SIG_DFL);
#endif

       while(1)
              ;
}



這個就是signal的用法集中展示,也是我經常用的一個方法。說實話,sco太古老了。。俺只能用這個函數了。
測試時你可以把#define damo這個註釋和不註釋看看。深刻體會signal的用法。

BTW:signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);

等等忽略了這些信號後,可以讓一個進程終端無關,即使你退出這個tty.當然kill信號還是不能屏蔽的。
這種方式多在守護進程中採用。

$ cc -o si si.c
$./si

Process id is 1501

在另一個tty上
#ps -ef
bank 1501 1465 51 04:07 pts/0 00:00:13 ./si
bank 1502 800 0 04:08 pts/1 00:00:00 ps -ef
#
注意觀察這時候是1456
你把./si這個tty關掉。
這時候你再
#ps -ef看看
bank 1501 1 50 04:07 ? 00:00:59 ./si
bank 1503 800 0 04:09 pts/1 00:00:00 ps -ef
注意這個1和?,知道這個進程成啥了吧?成精拉。哈哈~~




實例四:pause函數
CODE

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void user_func()
{
       printf("\n\nCatch a signal SIGINT \n");
}

int main()
{
       printf ("pid = %d \n\n ",getpid());
       signal(SIGINT, user_func);
       pause();
       printf("receive a signal \n\n");
}



在這個例子中,程序開始執行,就象進入了死循環一樣,這是因爲進程正在等待信號,
當我們按下Ctrl-C時,信號被捕捉,並且使得pause退出等待狀態。


實例五:下面是關於setitimer調用的一個簡單例子。

CODE

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>

void user_func(int sig)
{
 if ( sig ==  SIGALRM)
   printf("Catch a signal  SIGALRM \n");
 else if ( sig == SIGVTALRM)
   printf("Catch a signal  SIGVTALRM\n");
}

int main()
{
       struct itimerval value,ovalue,value2;

       printf("Process id is  =  %d \n",getpid());

       signal(SIGALRM, user_func);
       signal(SIGVTALRM, user_func);

       value.it_value.tv_sec = 1;
       value.it_value.tv_usec = 0;
       value.it_interval.tv_sec = 1;
       value.it_interval.tv_usec = 0;

       setitimer(ITIMER_REAL, &value, &ovalue);

       value2.it_value.tv_sec = 1;
       value2.it_value.tv_usec = 500000;
       value2.it_interval.tv_sec = 1;
       value2.it_interval.tv_usec = 500000;

       setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

       while(1);
}





在該例子中,每隔1秒發出一個SIGALRM,每隔1.5秒發出一個SIGVTALRM信號:

結果如下
$ ./ti
Process id is = 1734
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM


ctrl+c中斷。
....

開始喜歡setitimer函數了。。。

四, 補充

不得不介紹一下setjmp和longjmp的作用。

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

有時間再man 一下waitpid吧。。都是很有用的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章