http://myblog-maurice.blogspot.tw/2011/12/linux-signal.html
Linux 信號signal處理機制
信號機制是進程之間相互傳遞消息的一種方法,信號全稱為軟中斷信號,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。所以,信號可以說是進程式控制制的一部分。
一、信號的基本概念
1、基本概念
軟中斷信號(signal,又簡稱為信號)用來通知進程發生了非同步事件。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因為內部事件而給進程發送信號,通知進程發生了某個事件。注意,信號只是用來通知某進程發生了什麼事件,並不給該進程傳遞任何資料。
收 到信號的進程對各種信號有不同的處理方法。處理方法可以分為三類:第一種是類似中斷的處理常式,對於需要處理的信號,進程可以指定處理函數,由該函數來處 理。第二種方法是,忽略某個信號,對該信號不做任何處理,就象未發生過一樣。第三種方法是,對該信號的處理保留系統的預設值,這種缺省操作,對大部分的信 號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行為。
在進程表的表項中有一個軟中斷信號域,該域中每一位元對應一個信號,當有信號發送給進程時,對應位置位元。由此可以看出,進程對不同的信號可以同時保留,但對於同一個信號,進程並不知道在處理之前來過多少個。
2、信號的類型
發出信號的原因很多,這裡按發出信號的原因簡單分類,以瞭解各種信號:
(1) 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。
(2) 與進程例外事件相關的信號。如進程越界,或企圖寫一個唯讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。
(3) 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。
(4) 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。
(5) 在使用者態下的進程發出的信號。如進程調用系統調用kill向其他進程發送信號。
(6) 與終端交互相關的信號。如使用者關閉一個終端,或按下break鍵等情況。
(7) 跟蹤進程執行的信號。
值 |
名 字 |
說明 |
01 |
SIGHUP |
掛起(hangup) |
02 |
SIGINT |
中斷,當使用者從鍵盤按ctrl+c鍵 |
03 |
SIGQUIT |
退出,當使用者從鍵盤按quit鍵時 |
04 |
SIGILL |
非法指令 |
05 |
SIGTRAP |
跟蹤陷阱(trace trap),啟動進程,跟蹤代碼的執行 |
06 |
SIGIOT |
IOT指令 |
07 |
SIGEMT |
EMT指令 |
08 |
SIGFPE |
浮點運算溢出 |
09 |
SIGKILL |
殺死、終止進程 |
10 |
SIGBUS |
匯流排錯誤 |
11 |
SIGSEGV |
段違例(segmentation? violation),進程試圖去訪問其虛位址空間以外的位置 |
12 |
SIGSYS |
系統調用中參數錯,如系統調用號非法 |
13 |
SIGPIPE |
向某個非讀管道中寫入資料 |
14 |
SIGALRM |
鬧鐘。當某進程希望在某時間後接收信號時發此信號 |
15 |
SIGTERM |
軟體終止(software? termination) |
16 |
SIGUSR1 |
使用者自訂信號1 |
17 |
SIGUSR2 |
使用者自訂信號2 |
18 |
SIGCLD |
某個子進程死 |
19 |
SIGPWR |
電源故障 |
注意 信號SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信號SIGIOT與SIGABRT是一個信號。可以看出,同一個信號在不同的系統中值可能不一樣,所以建議最好使用為信號定義的名字,而不要直接使用信號的值。
二、有關信號的系統調用
系統調用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
三、參考代碼
參考 1.
#include<stdio.h>
char buf[]={"check lock!\n"};
main()
{
int i,p1,p2,fd;
fd=creat("lock.dat",0644);
write(fd,buf,20);
while((p1=fork())==-1);
if(p1==0)
{
lockf(fd,1,0);
for (i=1;i<=3;i++)
printf("child1!\n");
lockf(fd,0,0);
}
else{while((p2=fork())==-1);
if (p2==0)
{
lockf(fd,1,0);
for (i=1;i<=4;i++)
printf("child2!\n");
lockf(fd,0,0);
}
else printf("parrent!\n");
}
close(fd);
}
參考 2
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int waite;
static void start(){
waite=0;
}
//自訂中斷調用函數
static void waiting(){
while(waite==1);
}
main(){
int pid1,pid2;
while((pid1=fork())==-1);
if(pid1>0){
printf("chilld process 1 is %d\n",pid1);
while((pid2=fork())==-1);
if(pid2>0){
printf("child process 2 is %d\n",pid2);
printf("please press 'delete'\n");
waite=1;
if(signal(SIGUSR1,start)==SIG_ERR);
else{
alarm(5);
signal(SIGALRM,start);//alarm函數使用的信號
}
waiting();
kill(pid1,16);//向child 1 子進程發送16號中斷
kill(pid2,17);//向child 2 子進程發送17號中斷
wait(0);//等待兩個子進程結束
wait(0);
printf("parent process is killed\n");
exit(0);
}
else{
waite=1;
signal(17,start);//接受父進程發送的17號中斷,調用信號中斷函數start()
waiting();
printf("child 2 is killed\n");
exit(0);
}
}
else{
waite=1;
signal(16,start);//接受父進程發送的16號中斷,調用信號中斷函數start()
waiting();
printf("child 1 is killed\n");
exit(0);
}
}
參考三
#include <stdio.h>
#include <signal.h>
int main()
{
int i,j,stop();
signal(SIGINT,stop);
if(i=fork())
{
if(j=fork())
{
//signal(SIGINT,SIG_IGN);
sleep(10);
kill(i,15);
kill(j,16);
wait(0);
wait(0);
printf("Parent process is killed!\n");
}
else {
signal(16,SIG_IGN);
sleep(10);
//signal(16,stop);
printf("Child process 3 is killed!\n");
exit(0);
}
}
else {
signal(15,SIG_IGN);
sleep(10);
//signal(15,stop);
printf("Child process 1 is killed!\n");
exit(0);
}
}
stop()
{
//printf("del key is got!\n");
}
參考四
#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
int pid1,pid2;
main()
{
int fd[2];
char OutPipe[100],InPipe[100];
pipe(fd);
for(;;)
{
while((pid1=fork())==-1);
if(pid1 == 0)
{
lockf(fd[1],1,0);
sprintf(OutPipe,"Child process 1 is sending message!\n");
write(fd[1],OutPipe,50);
sleep(3);
lockf(fd[1],0,0);
exit(0);
}
else {
while((pid2=fork())==-1);
if(pid2 == 0)
{
lockf(fd[1],1,0);
sprintf(OutPipe,"Child process 2 is sending message!\n");
write(fd[1],OutPipe,50);
sleep(3);
lockf(fd[1],0,0);
}
else {
wait(0);
read(fd[0],InPipe,50);
printf("%s\n",InPipe);
}
}
}
}
sigaction(查詢或設置信號處理方式)
相關函數 |
signal,sigprocmask,sigpending,sigsuspend |
表標頭檔 |
#include<signal.h> |
定義函數 |
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact); |
函數說明 |
sigaction()會依參數signum指定的信號編號來設置該信號的處理函數。參數signum可以指定SIGKILL和SIGSTOP以外的所有信號。
|
返回值 |
執行成功則返回0,如果有錯誤則返回-1。 |
錯誤代碼 |
EINVAL 參數signum 不合法, 或是企圖攔截SIGKILL/SIGSTOPSIGKILL信號 |
1. #include <stdio.h>
2. #include <signal.h>
3. #include <unistd.h>
4.
5. void show_handler(int sig)
6. {
7. printf("I got signal %d\n", sig);
8. int i;
9. for(i = 0; i < 5; i++) {
10. printf("i = %d\n", i);
11. sleep(1);
12. }
13. }
14.
15. int main(void)
16. {
17. int i = 0;
18. struct sigaction act, oldact;
19. act.sa_handler = show_handler;
20. sigaddset(&act.sa_mask, SIGQUIT); //見注(1)
21. act.sa_flags = SA_RESETHAND | SA_NODEFER; //見注(2)
22. //act.sa_flags = 0; //見注(3)
23.
24. sigaction(SIGINT, &act, &oldact);
25. while(1) {
26. sleep(1);
27. printf("sleeping %d\n", i);
28. i++;
29. }
30. }
sigaddset(增加一個信號至信號集)
相關函數 |
sigemptyset,sigfillset,sigdelset,sigismember |
表標頭檔 |
#include<signal.h> |
定義函數 |
int sigaddset(sigset_t *set,int signum); |
函數說明 |
sigaddset()用來將參數signum 代表的信號加入至參數set 信號集裡。 |
返回值 |
執行成功則返回0,如果有錯誤則返回-1。 |
錯誤代碼 |
EFAULT 參數set指針地址無法存取 |
sigprocmask(查詢或設置信號遮罩)
相關函數 |
signal,sigaction,sigpending,sigsuspend |
表標頭檔 |
#include<signal.h> |
定義函數 |
int sigprocmask(int how,const sigset_t *set,sigset_t * oldset); |
函數說明 |
sigprocmask()可以用來改變目前的信號遮罩,其操作依參數how來決定 |
返回值 |
執行成功則返回0,如果有錯誤則返回-1。 |
錯誤代碼 |
EFAULT 參數set,oldset指針地址無法存取。 |
實例:
1. #include <signal.h>
2. #include <stdio.h>
3. #include <string.h>
4. #include <errno.h>
5. #include <unistd.h>
6. #define PROMPT "你想終止程式嗎?"
7. char *prompt=PROMPT;
8. void ctrl_c_op(int signo)
9. {
10. write(STDERR_FILENO,prompt,strlen(prompt));
11. }
12. int main()
13. {
14. struct sigaction act;
15. act.sa_handler=ctrl_c_op;
16. sigemptyset(&act.sa_mask);
17. act.sa_flags=0;
18. if(sigaction(SIGINT,&act,NULL)<0)
19. {
20. fprintf(stderr,"Install Signal Action Error:%s\n\a",strerror(errno));
21. exit(1);
22. }
23. while(1);
24. }
留言列表