天天看點

linux signal 用法和注意事項

http://blog.chinaunix.net/uid-9354-id-2425031.html

是以希望能用相同方式處理信号的多次出現,最好用sigaction.信号隻出現并處理一次,可以用signal.

signal函數每次設定具體的信号處理函數(非SIG_IGN)隻能生效一次,每次在程序響應處理信号時,随即将信号處理函數恢複為預設處理方式.是以如果想多次相同方式處理某個信号,通常的做法是,在響應函數開始,再次調用signal設定,如下圖:

int sig_int(); //My signal handler

    ...

    signal(SIGINT, sig_int);

int sig_int()

{

    ....

}

這種代碼段的一個問題是:在信号發生之後到信号處理程式中調用s i g n a l函數之間有一個

時間視窗。在此段時間中,可能發生另一次中斷信号。第二個信号會造成執行預設動作,而對

中斷信号則是終止該程序。這種類型的程式段在大多數情況下會正常工作,使得我們認為它們

正确,而實際上卻并不是如此。

另一個問題是:在程序不希望某種信号發生時,它不能關閉該信号。

sigaction: 

1.在信号處理程式被調用時,系統建立的新信号屏蔽字會自動包括正被遞送的信号。是以保證了在處理一個

給定的信号時,如果這種信号再次發生,那麼它會被阻塞到對前一個信号的處理結束為止

2.響應函數設定後就一直有效,不會重置

3.對除S I G A L R M以外的所有信号都企圖設定S A _ R E S TA RT标志,于是被這些信号中斷

的系統調用(read,write)都能自動再起動。不希望再起動由S I G A L R M信号中斷的系統調用的原因是希望對I / O操作可以設定時間限制。

是以希望能用相同方式處理信号的多次出現,最好用sigaction.信号隻出現并處理一次,可以用signal

//////////////////////////////////////////////////////////////////////////////////////

信号是Linux程式設計中非常重要的部分,本文将詳細介紹信号機制的基本概念、Linux對信号機制的大緻實作方法、如何使用信号,以及有關信号的幾個系統調用。 

信号機制是程序之間互相傳遞消息的一種方法,信号全稱為軟中斷信号,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。是以,信号可以說是程序控制的一部分。 

一、信号的基本概念 

本節先介紹信号的一些基本概念,然後給出一些基本的信号類型和信号對應的事件。基本概念對于了解和使用信号,對于了解信号機制都特别重要。下面就來看看什麼是信号。 

1、基本概念 

軟中斷信号(signal,又簡稱為信号)用來通知程序發生了異步事件。程序之間可以互相通過系統調用kill發送軟中斷信号。核心也可以因為内部事件而給程序發送信号,通知程序發生了某個事件。注意,信号隻是用來通知某程序發生了什麼事件,并不給該程序傳遞任何資料。 

收 到信号的程序對各種信号有不同的處理方法。處理方法可以分為三類:第一種是類似中斷的處理程式,對于需要處理的信号,程序可以指定處理函數,由該函數來處 理。第二種方法是,忽略某個信号,對該信号不做任何處理,就象未發生過一樣。第三種方法是,對該信号的處理保留系統的預設值,這種預設操作,對大部分的信 号的預設操作是使得程序終止。程序通過系統調用signal來指定程序對某個信号的處理行為。 

在程序表的表項中有一個軟中斷信号域,該域中每一位對應一個信号,當有信号發送給程序時,對應位置位。由此可以看出,程序對不同的信号可以同時保留,但對于同一個信号,程序并不知道在處理之前來過多少個。 

2、信号的類型 

發出信号的原因很多,這裡按發出信号的原因簡單分類,以了解各種信号: 

(1) 與程序終止相關的信号。當程序退出,或者子程序終止時,發出這類信号。 

(2) 與程序例外事件相關的信号。如程序越界,或企圖寫一個隻讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。 

(3) 與在系統調用期間遇到不可恢複條件相關的信号。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。 

(4) 與執行系統調用時遇到非預測錯誤條件相關的信号。如執行一個并不存在的系統調用。 

(5) 在使用者态下的程序發出的信号。如程序調用系統調用kill向其他程序發送信号。 

(6) 與終端互動相關的信号。如使用者關閉一個終端,或按下break鍵等情況。 

(7) 跟蹤程序執行的信号。 

Linux支援的信号清單如下。很多信号是與機器的體系結構相關的,首先列出的是POSIX.1中列出的信号: 

信号 值 處理動作 發出信号的原因 

---------------------------------------------------------------------- 

SIGHUP 1 A 終端挂起或者控制程序終止 

SIGINT 2 A 鍵盤中斷(如break鍵被按下) 

SIGQUIT 3 C 鍵盤的退出鍵被按下 

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 背景程序企圖從控制終端寫 

下面的信号沒在POSIX.1中列出,而在SUSv2列出 

-------------------------------------------------------------------- 

SIGBUS 10,7,10 C 總線錯誤(錯誤的記憶體通路) 

SIGPOLL A Sys V定義的Pollable事件,與SIGIO同義 

SIGPROF 27,27,29 A Profiling定時器到 

SIGSYS 12,-,12 C 無效的系統調用 (SVID) 

SIGTRAP 5 C 跟蹤/斷點捕獲 

SIGURG 16,23,21 B Socket出現緊急條件(4.2 BSD) 

SIGVTALRM 26,26,28 A 實際時間報警時鐘信号(4.2 BSD) 

SIGXCPU 24,24,30 C 超出設定的CPU時間限制(4.2 BSD) 

SIGXFSZ 25,25,31 C 超出設定的檔案大小限制(4.2 BSD) 

(對于SIGSYS,SIGXCPU,SIGXFSZ,以及某些機器體系結構下的SIGBUS,Linux預設的動作是A (terminate),SUSv2 是C (terminate and dump core))。 

下面是其它的一些信号 

SIGIOT 6 C IO捕獲指令,與SIGABRT同義 

SIGEMT 7,-,7 

SIGSTKFLT -,16,- A 協處理器堆棧錯誤 

SIGIO 23,29,22 A 某I/O操作現在可以進行了(4.2 BSD) 

SIGCLD -,-,18 A 與SIGCHLD同義 

SIGPWR 29,30,19 A 電源故障(System V) 

SIGINFO 29,-,- A 與SIGPWR同義 

SIGLOST -,-,- A 檔案鎖丢失 

SIGWINCH 28,28,20 B 視窗大小改變(4.3 BSD, Sun) 

SIGUNUSED -,31,- A 未使用的信号(will be SIGSYS) 

(在這裡,- 表示信号沒有實作;有三個值給出的含義為,第一個值通常在Alpha和Sparc上有效,中間的值對應i386和ppc以及sh,最後一個值對應mips。信号29在Alpha上為SIGINFO / SIGPWR ,在Sparc上為SIGLOST。) 

處理動作一項中的字母含義如下 

A 預設的動作是終止程序 

B 預設的動作是忽略此信号 

C 預設的動作是終止程序并進行核心映像轉儲(dump core) 

D 預設的動作是停止程序 

E 信号不能被捕獲 

F 信号不能被忽略 

上 面介紹的信号是常見系統所支援的。以表格的形式介紹了各種信号的名稱、作用及其在預設情況下的處理動作。各種預設處理動作的含義是:終止程式是指程序退 出;忽略該信号是将該信号丢棄,不做處理;停止程式是指程式挂起,進入停止狀況以後還能重新進行下去,一般是在調試的過程中(例如ptrace系統調 用);核心映像轉儲是指将程序資料在記憶體的映像和程序在核心結構中存儲的部分内容以一定格式轉儲到檔案系統,并且程序退出執行,這樣做的好處是為程式員提 供了友善,使得他們可以得到程序當時執行時的資料值,允許他們确定轉儲的原因,并且可以調試他們的程式。 

注意 信号SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信号SIGIOT與SIGABRT是一個信号。可以看出,同一個信号在不同的系統中值可能不一樣,是以建議最好使用為信号定義的名字,而不要直接使用信号的值。 

二、信 号 機 制 

上 一節中介紹了信号的基本概念,在這一節中,我們将介紹核心如何實作信号機制。即核心如何向一個程序發送信号、程序如何接收一個信号、程序怎樣控制自己對信 号的反應、核心在什麼時機處理和怎樣處理程序收到的信号。還要介紹一下setjmp和longjmp在信号中起到的作用。 

1、核心對信号的基本處理方法 

内 核給一個程序發送軟中斷信号的方法,是在程序所在的程序表項的信号域設定對應于該信号的位。這裡要補充的是,如果信号發送給一個正在睡眠的程序,那麼要看 該程序進入睡眠的優先級,如果程序睡眠在可被中斷的優先級上,則喚醒程序;否則僅設定程序表中信号域相應的位,而不喚醒程序。這一點比較重要,因為程序檢 查是否收到信号的時機是:一個程序在即将從核心态傳回到使用者态時;或者,在一個程序要進入或離開一個适當的低排程優先級睡眠狀态時。 

核心處理一個程序收到的信号的時機是在一個程序從核心态傳回使用者态時。是以,當一個程序在核心态下運作時,軟中斷信号并不立即起作用,要等到将傳回使用者态時才處理。程序隻有處理完信号才會傳回使用者态,程序在使用者态下不會有未處理完的信号。 

内 核處理一個程序收到的軟中斷信号是在該程序的上下文中,是以,程序必須處于運作狀态。前面介紹概念的時候講過,處理信号有三種類型:程序接收到信号後退 出;程序忽略該信号;程序收到信号後執行使用者設定用系統調用signal的函數。當程序接收到一個它忽略的信号時,程序丢棄該信号,就象沒有收到該信号似 的繼續運作。如果程序收到一個要捕捉的信号,那麼程序從核心态傳回使用者态時執行使用者定義的函數。而且執行使用者定義的函數的方法很巧妙,核心是在使用者棧上創 建一個新的層,該層中将傳回位址的值設定成使用者定義的處理函數的位址,這樣程序從核心傳回彈出棧頂時就傳回到使用者定義的函數處,從函數傳回再彈出棧頂時, 才傳回原先進入核心的地方。這樣做的原因是使用者定義的處理函數不能且不允許在核心态下執行(如果使用者定義的函數在核心态下運作的話,使用者就可以獲得任何權 限)。 

在信号的處理方法中有幾點特别要引起注意。第一,在一些系統中,當一個程序處理完中斷信号傳回使用者态之前,核心清除使用者區中設 定的對該信号的處理例程的位址,即下一次程序對該信号的處理方法又改為預設值,除非在下一次信号到來之前再次使用signal系統調用。這可能會使得程序 在調用signal之前又得到該信号而導緻退出。在BSD中,核心不再清除該位址。但不清除該位址可能使得程序因為過多過快的得到某個信号而導緻堆棧溢 出。為了避免出現上述情況。在BSD系統中,核心模拟了對硬體中斷的處理方法,即在處理某個中斷時,阻止接收新的該類中斷。 

第二個要 引起注意的是,如果要捕捉的信号發生于程序正在一個系統調用中時,并且該程序睡眠在可中斷的優先級上,這時該信号引起程序作一次longjmp,跳出睡眠 狀态,傳回使用者态并執行信号處理例程。當從信号處理例程傳回時,程序就象從系統調用傳回一樣,但傳回了一個錯誤代碼,指出該次系統調用曾經被中斷。這要注 意的是,BSD系統中核心可以自動地重新開始系統調用。 

第三個要注意的地方:若程序睡眠在可中斷的優先級上,則當它收到一個要忽略的信号時,該程序被喚醒,但不做longjmp,一般是繼續睡眠。但使用者感覺不到程序曾經被喚醒,而是象沒有發生過該信号一樣。 

第 四個要注意的地方:核心對子程序終止(SIGCLD)信号的處理方法與其他信号有所差別。當程序檢查出收到了一個子程序終止的信号時,預設情況下,該程序 就象沒有收到該信号似的,如果父程序執行了系統調用wait,程序将從系統調用wait中醒來并傳回wait調用,執行一系列wait調用的後續操作(找 出僵死的子程序,釋放子程序的程序表項),然後從wait中傳回。SIGCLD信号的作用是喚醒一個睡眠在可被中斷優先級上的程序。如果該程序捕捉了這個 信号,就象普通信号處理一樣轉到處理例程。如果程序忽略該信号,那麼系統調用wait的動作就有所不同,因為SIGCLD的作用僅僅是喚醒一個睡眠在可被 中斷優先級上的程序,那麼執行wait調用的父程序被喚醒繼續執行wait調用的後續操作,然後等待其他的子程序。 

如果一個程序調用signal系統調用,并設定了SIGCLD的處理方法,并且該程序有子程序處于僵死狀态,則核心将向該程序發一個SIGCLD信号。 

2、setjmp和longjmp的作用 

前面在介紹信号處理機制時,多次提到了setjmp和longjmp,但沒有仔細說明它們的作用和實作方法。這裡就此作一個簡單的介紹。 

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

三、有關信号的系統調用 

前面兩節已經介紹了有關信号的大部分知 識。這一節我們來了解一下這些系統調用。其中,系統調用signal是程序用來設定某個信号的處理方法,系統調用kill是用來發送信号給指定程序的。這 兩個調用可以形成信号的基本操作。後兩個調用pause和alarm是通過信号實作的程序暫停和定時器,調用alarm是通過信号通知程序定時器到時。所 以在這裡,我們還要介紹這兩個調用。 

1、signal 系統調用 

系統調用signal用來設定某個信号的處理方法。該調用聲明的格式如下: 

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

在使用該調用的程序中加入以下頭檔案: 

#include <signal.h> 

上述聲明格式比較複雜,如果不清楚如何使用,也可以通過下面這種類型定義的格式來使用(POSIX的定義): 

typedef void (*sighandler_t)(int); 

sighandler_t signal(int signum, sighandler_t handler); 

但這種格式在不同的系統中有不同的類型定義,是以要使用這種格式,最好還是參考一下聯機手冊。 

在調用中,參數signum指出要設定處理方法的信号。第二個參數handler是一個處理函數,或者是 

SIG_IGN:忽略參數signum所指的信号。 

SIG_DFL:恢複參數signum所指信号的處理方法為預設值。 

傳遞給信号處理例程的整數參數是信号值,這樣可以使得一個信号處理例程處理多個信号。系統調用signal傳回值是指定信号signum前一次的處理例程或者錯誤時傳回錯誤代碼SIG_ERR。下面來看一個簡單的例子: 

#include <unistd.h> 

#include <stdio.h> 

void sigroutine(int dunno) { /* 信号處理例程,其中dunno将會得到信号的值 */ 

switch (dunno) { 

case 1: 

printf("Get a signal -- SIGHUP "); 

break; 

case 2: 

printf("Get a signal -- SIGINT "); 

case 3: 

printf("Get a signal -- SIGQUIT "); 

return; 

int main() { 

printf("process id is %d ",getpid()); 

signal(SIGHUP, sigroutine); //* 下面設定三個信号的處理方法 

signal(SIGINT, sigroutine); 

signal(SIGQUIT, sigroutine); 

for (;;) ; 

其中信号SIGINT由按下Ctrl-C發出,信号SIGQUIT由按下Ctrl-發出。該程式執行的結果如下: 

localhost:~$ ./sig_test 

process id is 463 

Get a signal -SIGINT //按下Ctrl-C得到的結果 

Get a signal -SIGQUIT //按下Ctrl-得到的結果 

//按下Ctrl-z将程序置于背景 

[1]+ Stopped ./sig_test 

localhost:~$ bg 

[1]+ ./sig_test & 

localhost:~$ kill -HUP 463 //向程序發送SIGHUP信号 

localhost:~$ Get a signal – SIGHUP 

kill -9 463 //向程序發送SIGKILL信号,終止程序 

localhost:~$ 

2、kill 系統調用 

系統調用kill用來向程序發送一個信号。該調用聲明的格式如下: 

int kill(pid_t pid, int sig); 

#include <sys/types.h> 

該 系統調用可以用來向任何程序或程序組發送任何信号。如果參數pid是正數,那麼該調用将信号sig發送到程序号為pid的程序。如果pid等于0,那麼信 号sig将發送給目前程序所屬程序組裡的所有程序。如果參數pid等于-1,信号sig将發送給除了程序1和自身以外的所有程序。如果參數pid小于- 1,信号sig将發送給屬于程序組-pid的所有程序。如果參數sig為0,将不發送信号。該調用執行成功時,傳回值為0;錯誤時,傳回-1,并設定相應 的錯誤代碼errno。下面是一些可能傳回的錯誤代碼: 

EINVAL:指定的信号sig無效。 

ESRCH:參數pid指定的程序或程序組不存在。注意,在程序表項中存在的程序,可能是一個還沒有被wait收回,但已經終止執行的僵死程序。 

EPERM: 程序沒有權力将這個信号發送到指定接收信号的程序。因為,一個程序被允許将信号發送到程序pid時,必須擁有root權力,或者是發出調用的程序的UID 或EUID與指定接收的程序的UID或儲存使用者ID(savedset-user-ID)相同。如果參數pid小于-1,即該信号發送給一個組,則該錯誤 表示組中有成員程序不能接收該信号。 

3、pause系統調用 

系統調用pause的作用是等待一個信号。該調用的聲明格式如下: 

int pause(void); 

該調用使得發出調用的程序進入睡眠,直到接收到一個信号為止。該調用總是傳回-1,并設定錯誤代碼為EINTR(接收到一個信号)。下面是一個簡單的範例: 

void sigroutine(int unused) { 

printf("Catch a signal SIGINT "); 

pause(); 

printf("receive a signal "); 

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

4、alarm和 setitimer系統調用 

系統調用alarm的功能是設定一個定時器,當定時器計時到達時,将發出一個信号給程序。該調用的聲明格式如下: 

unsigned int alarm(unsigned int seconds); 

系 統調用alarm安排核心為調用程序在指定的seconds秒後發出一個SIGALRM的信号。如果指定的參數seconds為0,則不再發送 SIGALRM信号。後一次設定将取消前一次的設定。該調用傳回值為上次定時調用到發送之間剩餘的時間,或者因為沒有前一次定時調用而傳回0。 

注意,在使用時,alarm隻設定為發送一次信号,如果要多次發送,就要多次使用alarm調用。 

對于alarm,這裡不再舉例。現在的系統中很多程式不再使用alarm調用,而是使用setitimer調用來設定定時器,用getitimer來得到定時器的狀态,這兩個調用的聲明格式如下: 

int getitimer(int which, struct itimerval *value); 

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

在使用這兩個調用的程序中加入以下頭檔案: 

#include <sys/time.h> 

該系統調用給程序提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信号給程序,并使得計時器重新開始。三個計時器由參數which指定,如下所示: 

TIMER_REAL:按實際時間計時,計時到達将給程序發送SIGALRM信号。 

ITIMER_VIRTUAL:僅當程序執行時才進行計時。計時到達将發送SIGVTALRM信号給程序。 

ITIMER_PROF:當程序執行時和系統為該程序執行動作時都計時。與ITIMER_VIR-TUAL是一對,該定時器經常用來統計程序在使用者态和核心态花費的時間。計時到達将發送SIGPROF信号給程序。 

定時器中的參數value用來指明定時器的時間,其結構如下: 

struct itimerval { 

struct timeval it_interval; /* 下一次的取值 */ 

struct timeval it_value; /* 本次的設定值 */ 

}; 

該結構中timeval結構定義如下: 

struct timeval { 

long tv_sec; /* 秒 */ 

long tv_usec; /* 微秒,1秒 = 1000000 微秒*/ 

在setitimer 調用中,參數ovalue如果不為空,則其中保留的是上次調用設定的值。定時器将it_value遞減到0時,産生一個信号,并将it_value的值設 定為it_interval的值,然後重新開始計時,如此往複。當it_value設定為0時,計時器停止,或者當它計時到期,而it_interval 為0時停止。調用成功時,傳回0;錯誤時,傳回-1,并設定相應的錯誤代碼errno: 

EFAULT:參數value或ovalue是無效的指針。 

EINVAL:參數which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。 

下面是關于setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM信号: 

int sec; 

void sigroutine(int signo) { 

switch (signo) { 

case SIGALRM: 

printf("Catch a signal -- SIGALRM "); 

case SIGVTALRM: 

printf("Catch a signal -- SIGVTALRM "); 

struct itimerval value,ovalue,value2; 

sec = 5; 

signal(SIGALRM, sigroutine); 

signal(SIGVTALRM, sigroutine); 

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 = 0; 

value2.it_value.tv_usec = 500000; 

value2.it_interval.tv_sec = 0; 

value2.it_interval.tv_usec = 500000; 

setitimer(ITIMER_VIRTUAL, &value2, &ovalue); 

該例子的螢幕拷貝如下: 

localhost:~$ ./timer_test 

process id is 579 

Catch a signal – SIGVTALRM 

Catch a signal – SIGALRM 

Catch a signal –GVTALRM 

本文簡單介紹了Linux下的信号,如果希望了解其他調用,請參考聯機手冊或其他文檔。

繼續閱讀