軟中斷信號(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 <signal.h> 
#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 "); 
break; 
case 3: 
printf("Get a signal -- SIGQUIT "); 
break; 

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> 
#include <signal.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); 
在使用該調用的進程中加入以下頭文件: 
#include <unistd.h> 

該調用使得發出調用的進程進入睡眠,直到接收到一個信號爲止。該調用總是返回-1,並設置錯誤代碼爲EINTR(接收到一個信號)。下面是一個簡單的範例: 

#include <unistd.h> 
#include <stdio.h> 
#include <signal.h> 
void sigroutine(int unused) { 
printf("Catch a signal SIGINT "); 


int main() { 
signal(SIGINT, sigroutine); 
pause(); 
printf("receive a signal "); 


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

4、alarm和 setitimer系統調用 

系統調用alarm的功能是設置一個定時器,當定時器計時到達時,將發出一個信號給進程。該調用的聲明格式如下: 
unsigned int alarm(unsigned int seconds); 
在使用該調用的進程中加入以下頭文件: 
#include <unistd.h> 

系 統調用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信號: 

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

void sigroutine(int signo) { 
switch (signo) { 
case SIGALRM: 
printf("Catch a signal -- SIGALRM "); 
break; 
case SIGVTALRM: 
printf("Catch a signal -- SIGVTALRM "); 
break; 

return; 


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

printf("process id is %d ",getpid()); 
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); 

for (;;) ; 


該例子的屏幕拷貝如下: 

localhost:~$ ./timer_test 
process id is 579 
Catch a signal – SIGVTALRM 
Catch a signal – SIGALRM 
Catch a signal – SIGVTALRM 
Catch a signal – SIGVTALRM 
Catch a signal – SIGALRM 
Catch a signal –GVTALRM 

alarm(設置信號傳送鬧鐘)
相關函數 signal,sleep

表頭文件 #include<unistd.h>

定義函數 unsigned int alarm(unsigned int seconds);

函數說明 alarm()用來設置信號SIGALRM在經過參數seconds指定的秒數後傳送給目前的進程。如果參數seconds 爲0,則之前設置的鬧鐘會被取消,並將剩下的時間返回。

返回值返回之前鬧鐘的剩餘秒數,如果之前未設鬧鐘則返回0。

範例 #include<unistd.h>
#include<signal.h>
void handler() {
printf("hello\n");
}
main()
{
int i;
signal(SIGALRM,handler);
alarm(5);
for(i=1;i<7;i++){
printf("sleep %d ...\n",i);
sleep(1);
}
}

執行 sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
sleep 5 ...
hello
sleep 6 ...

 

 


kill(傳送信號給指定的進程)
相關函數 raise,signal

表頭文件 #include<sys/types.h>
#include<signal.h>

定義函數 int kill(pid_t pid,int sig);

函數說明 kill()可以用來送參數sig指定的信號給參數pid指定的進程。參數pid有幾種情況:
pid>0 將信號傳給進程識別碼爲pid 的進程。
pid=0 將信號傳給和目前進程相同進程組的所有進程
pid=-1 將信號廣播傳送給系統內所有的進程
pid<0 將信號傳給進程組識別碼爲pid絕對值的所有進程
參數sig代表的信號編號可參考附錄D

返回值 執行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EINVAL 參數sig 不合法
ESRCH 參數pid 所指定的進程或進程組不存在
EPERM 權限不夠無法傳送信號給指定進程

範例 #include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
main()
{
pid_t pid;
int status;
if(!(pid= fork())){
printf("Hi I am child process!\n");
sleep(10);
return;
}
else{
printf("send signal to child process (%d) \n",pid);
sleep(1);
kill(pid ,SIGABRT);
wait(&status);
if(WIFSIGNALED(status))
printf("chile process receive signal %d\n",WTERMSIG(status));
}
}

執行 sen signal to child process(3170)
Hi I am child process!
child process receive signal 6

 

 


pause(讓進程暫停直到信號出現)
相關函數 kill,signal,sleep

表頭文件 #include<unistd.h>

定義函數 int pause(void);

函數說明 pause()會令目前的進程暫停(進入睡眠狀態),直到被信號(signal)所中斷。

返回值 只返回-1。

錯誤代碼 EINTR 有信號到達中斷了此函數。

 

 


sigaction(查詢或設置信號處理方式)
相關函數 signal,sigprocmask,sigpending,sigsuspend

表頭文件 #include<signal.h>

定義函數 int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);

函數說明 sigaction()會依參數signum指定的信號編號來設置該信號的處理函數。參數signum可以指定SIGKILL和SIGSTOP以外的所有信號。
如參數結構sigaction定義如下
struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
sa_handler此參數和signal()的參數handler相同,代表新的信號處理函數,其他意義請參考signal()。
sa_mask 用來設置在處理該信號時暫時將sa_mask 指定的信號擱置。
sa_restorer 此參數沒有使用。
sa_flags 用來設置信號處理的其他相關操作,下列的數值可用。
OR 運算(|)組合
A_NOCLDSTOP : 如果參數signum爲SIGCHLD,則當子進程暫停時並不會通知父進程
SA_ONESHOT/SA_RESETHAND:當調用新的信號處理函數前,將此信號處理方式改爲系統預設的方式。
SA_RESTART:被信號中斷的系統調用會自行重啓
SA_NOMASK/SA_NODEFER:在處理此信號未結束前不理會此信號的再次到來。
如果參數oldact不是NULL指針,則原來的信號處理方式會由此結構sigaction 返回。

返回值 執行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EINVAL 參數signum 不合法,或是企圖攔截SIGKILL/SIGSTOPSIGKILL信號
EFAULT 參數act,oldact指針地址無法存取。
EINTR 此調用被中斷

範例 #include<unistd.h>
#include<signal.h>
void show_handler(struct sigaction * act)
{
switch (act->sa_flags)
{
case SIG_DFL:printf("Default action\n");break;
case SIG_IGN:printf("Ignore the signal\n");break;
default: printf("0x%x\n",act->sa_handler);
}
}
main()
{
int i;
struct sigaction act,oldact;
act.sa_handler = show_handler;
act.sa_flags = SA_ONESHOT|SA_NOMASK;
sigaction(SIGUSR1,&act,&oldact);
for(i=5;i<15;i++)
{
printf("sa_handler of signal %2d =".i);
sigaction(i,NULL,&oldact);
}
}

執行 sa_handler of signal 5 = Default action
sa_handler of signal 6= Default action
sa_handler of signal 7 = Default action
sa_handler of signal 8 = Default action
sa_handler of signal 9 = Default action
sa_handler of signal 10 = 0x8048400
sa_handler of signal 11 = Default action
sa_handler of signal 12 = Default action
sa_handler of signal 13 = Default action
sa_handler of signal 14 = Default action

sigaddset(增加一個信號至信號集)
相關函數 sigemptyset,sigfillset,sigdelset,sigismember

表頭文件 #include<signal.h>

定義函數 int sigaddset(sigset_t *set,int signum);

函數說明 sigaddset()用來將參數signum 代表的信號加入至參數set 信號集裏。

返回值執行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EFAULT 參數set指針地址無法存取
EINVAL 參數signum非合法的信號編號

 

 


sigdelset(從信號集裏刪除一個信號)
相關函數 sigemptyset,sigfillset,sigaddset,sigismember

表頭文件 #include<signal.h>

定義函數 int sigdelset(sigset_t * set,int signum);

函數說明 sigdelset()用來將參數signum代表的信號從參數set信號集裏刪除。

返回值 執行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EFAULT 參數set指針地址無法存取
EINVAL 參數signum非合法的信號編號

 

 


sigemptyset(初始化信號集)
相關函數 sigaddset,sigfillset,sigdelset,sigismember

表頭文件 #include<signal.h>

定義函數 int sigemptyset(sigset_t *set);

函數說明 sigemptyset()用來將參數set信號集初始化並清空。

返回值 執行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EFAULT 參數set指針地址無法存取

 

 


sigfillset(將所有信號加入至信號集)
相關函數 sigempty,sigaddset,sigdelset,sigismember

表頭文件 #include<signal.h>

定義函數 int sigfillset(sigset_t * set);

函數說明 sigfillset()用來將參數set信號集初始化,然後把所有的信號加入到此信號集裏。

返回值 執行成功則返回0,如果有錯誤則返回-1。

附加說明 EFAULT 參數set指針地址無法存取

 

 


sigismember(測試某個信號是否已加入至信號集裏)
相關函數 sigemptyset,sigfillset,sigaddset,sigdelset

表頭文件 #include<signal.h>

定義函數 int sigismember(const sigset_t *set,int signum);

函數說明 sigismember()用來測試參數signum 代表的信號是否已加入至參數set信號集裏。如果信號集裏已有該信號則返回1,否則返回0。

返回值信號集已有該信號則返回1,沒有則返回0。如果有錯誤則返回-1。

錯誤代碼 EFAULT 參數set指針地址無法存取
EINVAL 參數signum 非合法的信號編號

 

 


signal(設置信號處理方式)
相關函數 sigaction,kill,raise

表頭文件 #include<signal.h>

定義函數 void (*signal(int signum,void(* handler)(int)))(int);

函數說明 signal()會依參數signum 指定的信號編號來設置該信號的處理函數。當指定的信號到達時就會跳轉到參數handler指定的函數執行。如果參數handler不是函數指針,則必須是下列兩個常數之一:
SIG_IGN 忽略參數signum指定的信號。
SIG_DFL 將參數signum 指定的信號重設爲核心預設的信號處理方式。
關於信號的編號和說明,請參考附錄D

返回值返回先前的信號處理函數指針,如果有錯誤則返回SIG_ERR(-1)。

附加說明在信號發生跳轉到自定的handler處理函數執行後,系統會自動將此處理函數換回原來系統預設的處理方式,如果要改變此操作請改用sigaction()。

範例 參考alarm()或raise()。

 

 


sigpending(查詢被擱置的信號)
相關函數 signal,sigaction,sigprocmask,sigsuspend

表頭文件 #include<signal.h>

定義函數 int sigpending(sigset_t *set);

函數說明 sigpending()會將被擱置的信號集合由參數set指針返回。

返回值執 行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EFAULT 參數set指針地址無法存取
EINTR 此調用被中斷。

 

 


sigprocmask(查詢或設置信號遮罩)
相關函數 signal,sigaction,sigpending,sigsuspend

表頭文件 #include<signal.h>

定義函數 int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);

函數說明 sigprocmask()可以用來改變目前的信號遮罩,其操作依參數how來決定
SIG_BLOCK 新的信號遮罩由目前的信號遮罩和參數set 指定的信號遮罩作聯集
SIG_UNBLOCK 將目前的信號遮罩刪除掉參數set指定的信號遮罩
SIG_SETMASK 將目前的信號遮罩設成參數set指定的信號遮罩。
如果參數oldset不是NULL指針,那麼目前的信號遮罩會由此指針返回。

返回值 執行成功則返回0,如果有錯誤則返回-1。

錯誤代碼 EFAULT 參數set,oldset指針地址無法存取。
EINTR 此調用被中斷

 

 


sleep(讓進程暫停執行一段時間)
相關函數 signal,alarm

表頭文件 #include<unistd.h>

定義函數 unsigned int sleep(unsigned int seconds);

函數說明 sleep()會令目前的進程暫停,直到達到參數seconds 所指定的時間,或是被信號所中斷。

返回值 若進程暫停到參數seconds 所指定的時間則返回0,若有信號中斷則返回剩餘秒數。



linux 信號通信應用實例

    在本例中,創建兩個進程。在父進程中執行文件拷貝操作,接收到SIGUSR1信號就打印出當前拷貝的進度。因此在父進程中安裝了SIGUSR1信號。而在子進程中每隔一個固定的時間向父進程發送一個SIGUSR1信號。因此,子進程需要安裝SIGALRM信號。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
int count;//當前拷貝大小
int file_size;// 每次拷貝文件大小
void sig_alarm(int arg);//處理alarm信號
void sig_usr(int sig);//處理普通信號SIGUSR1
int main(int argc,char *argv[])
{
       pid_t pid;
       int i;
       int fd_src,fd_des;//定義用於表示源文件和目的文件的句柄
       char buf[128];//拷貝時的臨時空間
/////////////////////////////可見很多代碼都在處理特殊的情況,增強代碼的可靠性/////////////////////////////////
       if(argc!=3)//檢查輸入命令的參數是不是正確
       {
              printf("請檢查命令的格式:\n");
              return -1;
       }
       if((fd_src=open(argv[1],O_RDONLY))==-1)//以只讀模式打開源文件
       {
              perror("打開源文件失敗!");
              return -1;
       }
       file_size = lseek(fd_src,0,SEEK_END);//獲取源文件大小
       lseek(fd_src,0,SEEK_SET);//重新設置讀寫位置爲文件頭
       if((fd_des = open(argv[2],O_RDWR | O_CREAT,0644))==-1)
       {
              perror("打開目的文件失敗!");
              return -1;
       }
       if((pid = fork()) == -1)
       {
              perror("創建進程失敗!");
              return -1;
       }
///////////////////////////////////////////////////////////////////////////
       else if(pid > 0)//在父進程中
       {
              signal(SIGUSR1,sig_usr);//安裝信號SIGUSR1
              do
               {
                        memset(buf,'\0',128);
                        if((i=read(fd_src,buf,1))==-1)//拷貝數據,可以調整每次拷貝的大小
                        {
                              perror("讀取文件失敗!");
                              return -1;
                        }
                        else if(i==0)//如果拷貝完畢,則向子進程發送sigint信號,使子進程終止
                        {
                              kill(pid,SIGINT);
                              break;
                        }
                        else
                        {
                               if(write(fd_des,buf,i)==-1)//否則執行拷貝操作
                                {
                                         perror("寫入數據失敗!");
                                         return -1;
                                }
                                count+=i;//更新已拷貝的大小
                        }
               }
               while(i!=0);
               wait(pid,NULL,0); //等待子進程推出
               exit(EXIT_SUCCESS);      
       }
       else if(pid == 0)//在子進程中,請求父進程打印進程拷貝的速度
       {
               usleep(1);
               signal(SIGALRM,sig_alarm);//安裝SIGALRM
               ualarm(1,1);
               while(1)
               {
                             ;
               }
               exit(EXIT_SUCCESS);                   
       }
}

void sig_alarm(int arg)
{
      kill(getppid(),SIGUSR1);//向父進程發送SIGUSR1信號
}

void sig_usr(int sig)
{
      float i;
      i=(float)count/(float)file_size;//求拷貝進程
      printf("拷貝完成:%0.0f%%\n",i*100);
}


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