Linux下Signal訊號系統呼叫
前面兩節已經介紹了有關訊號的大部分知 識。這一節我們來了解一下這些系統呼叫。其中,系統呼叫signal是程序用來設定某個訊號的處理方法,系統呼叫kill是用來發送訊號給指定程序的。這 兩個呼叫可以形成訊號的基本操作。後兩個呼叫pause和alarm是通過訊號實現的程序暫停和定時器,呼叫alarm是通過訊號通知程序定時器到時。所 以在這裡,我們還要介紹這兩個呼叫。
1、signal 系統呼叫
系統呼叫signal用來設定某個訊號的處理方法。該呼叫宣告的格式如下:
void (*signal(int signum, void (*handler)(int)))(int);
在使用該呼叫的程序中加入以下標頭檔案:
#include
上述宣告格式比較複雜,如果不清楚如何使用,也可以通過下面這種型別定義的格式來使用(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 #include #include void sigroutine(int dunno) { /* 訊號處理例程,其中dunno將會得到訊號的值 */ switch (dunno) { case 1: printf("Get a signal -- SIGHUP "); brea在使用這兩個呼叫的程序中加入以下標頭檔案: #include 在使用這兩個呼叫的程序中加入以下標頭檔案: #include k; 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
#include
該 系統呼叫可以用來向任何程序或程序組傳送任何訊號。如果引數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
該呼叫使得發出呼叫的程序進入睡眠,直到接收到一個訊號為止。該呼叫總是返回-1,並設定錯誤程式碼為EINTR(接收到一個訊號)。下面是一個簡單的範例:
#include
#include
#include
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
系 統呼叫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
該系統呼叫給程序提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就傳送一個相應的訊號給程序,並使得計時器重新開始。三個計時器由引數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
#include
#include
#include
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