天天看點

Linux信号(signal) 機制分析(2)

Linux信号(signal) 機制分析(2)

接上文

5. 信号的發送

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

5.1 kill()

#include <sys/types.h> 

#include <signal.h> 

int kill(pid_t pid,int signo)  

該系統調用可以用來向任何程序或程序組發送任何信号。參數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,并設定相應的錯誤代碼errno。下面是一些可能傳回的錯誤代碼:

einval:指定的信号sig無效。

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

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

5.2 sigqueue()

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

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

sigqueue()是比較新的發送信号系統調用,主要是針對實時信号提出的(當然也支援前32種),支援信号帶有參數,與函數sigaction()配合使用。

sigqueue的第一個參數是指定接收信号的程序id,第二個參數确定即将發送的信号,第三個參數是一個聯合資料結構union sigval,指定了信号傳遞的參數,即通常所說的4位元組值。

typedef union sigval { 

               int  sival_int; 

               void *sival_ptr; 

}sigval_t;  

sigqueue()比kill()傳遞了更多的附加資訊,但sigqueue()隻能向一個程序發送信号,而不能發送信号給一個程序組。如果signo=0,将會執行錯誤檢查,但實際上不發送任何信号,0值信号可用于檢查pid的有效性以及目前程序是否有權限向目标程序發送信号。

在調用sigqueue時,sigval_t指定的資訊會拷貝到對應sig 注冊的3參數信号處理函數的siginfo_t結構中,這樣信号處理函數就可以處理這些資訊了。由于sigqueue系統調用支援發送帶參數信号,是以比kill()系統調用的功能要靈活和強大得多。

5.3 alarm()

#include <unistd.h> 

unsigned int alarm(unsigned int seconds)  

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

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

5.4 setitimer()

現在的系統中很多程式不再使用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 <stdio.h> 

int sec; 

void sigroutine(int signo) { 

        switch (signo) { 

        case sigalrm: 

        printf("catch a signal -- sigalrm "); 

        break; 

        case sigvtalrm: 

        printf("catch a signal -- sigvtalrm "); 

        } 

        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 –gvtalrm  

5.5 abort()

#include <stdlib.h> 

void abort(void);  

向程序發送sigabort信号,預設情況下程序會異常退出,當然可定義自己的信号處理函數。即使sigabort被程序設定為阻塞信号,調用abort()後,sigabort仍然能被程序接收。該函數無傳回值。

5.6 raise()

int raise(int signo)  

向程序本身發送信号,參數為即将發送的信号值。調用成功傳回 0;否則,傳回 -1。

6. 信号集及信号集操作函數:

信号集被定義為一種資料類型:

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

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

7. 信号阻塞與信号未決:

每個程序都有一個用來描述哪些信号遞送到程序時将被阻塞的信号集,該信号集中的所有信号在遞送到程序後都将被阻塞。下面是與信号阻塞相關的幾個函數:

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

int sigpending(sigset_t *set)); 

int sigsuspend(const sigset_t *mask));  

sigprocmask()函數能夠根據參數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。

8. 信号應用執行個體

linux下的信号應用并沒有想象的那麼恐怖,程式員所要做的最多隻有三件事情:

安裝信号(推薦使用sigaction());

實作三參數信号處理函數,handler(int signal,struct siginfo *info, void *);

發送信号,推薦使用sigqueue()。

實際上,對有些信号來說,隻要安裝信号就足夠了(信号處理方式采用預設或忽略)。其他可能要做的無非是與信号集相關的幾種操作。

執行個體一:信号發送及處理

實作一個信号接收程式sigreceive(其中信号安裝由sigaction())。

void new_op(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=new_op; 

        if(sigaction(sig,&act,null) < 0) 

        { 

                printf("install sigal error\n"); 

        while(1) 

                sleep(2); 

                printf("wait for the signal\n"); 

void new_op(int signum,siginfo_t *info,void *myact) 

        printf("receive signal %d", signum); 

        sleep(5); 

說明,指令行參數為信号值,背景運作sigreceive signo &,可獲得該程序的id,假設為pid,然後再另一終端上運作kill -s signo pid驗證信号的發送接收及處理。同時,可驗證信号的排隊問題。

執行個體二:信号傳遞附加資訊

主要包括兩個執行個體:

向程序本身發送信号,并傳遞指針參數

        union sigval mysigval; 

        int i; 

        pid_t pid;          

        char data[10]; 

        memset(data,0,sizeof(data)); 

        for(i=0;i < 5;i++) 

                data[i]='2'; 

        mysigval.sival_ptr=data; 

        pid=getpid(); 

        act.sa_sigaction=new_op;//三參數信号處理函數 

        act.sa_flags=sa_siginfo;//資訊傳遞開關,允許傳說參數資訊給new_op 

                sigqueue(pid,sig,mysigval);//向本程序發送信号,并傳遞附加資訊 

void new_op(int signum,siginfo_t *info,void *myact)//三參數信号處理函數的實作 

        for(i=0;i<10;i++) 

                printf("%c\n ",(*( (char*)((*info).si_ptr)+i))); 

        printf("handle signal %d over;",signum); 

這個例子中,信号實作了附加資訊的傳遞,信号究竟如何對這些資訊進行處理則取決于具體的應用。

不同程序間傳遞整型參數:

把1中的信号發送和接收放在兩個程式中,并且在發送過程中傳遞整型參數。

信号接收程式:

        struct sigaction act; 

        sig=atoi(argv[1]);      

        if(sigaction(sig,&act,null)<0) 

        printf("the int value is %d \n",info->si_int); 

信号發送程式:

指令行第二個參數為信号值,第三個參數為接收程序id。

main(int argc,char**argv) 

        pid_t pid; 

        int signum; 

        signum=atoi(argv[1]); 

        pid=(pid_t)atoi(argv[2]); 

        mysigval.sival_int=8;//不代表具體含義,隻用于說明問題 

        if(sigqueue(pid,signum,mysigval)==-1) 

                printf("send error\n"); 

        sleep(2); 

注:執行個體2的兩個例子側重點在于用信号來傳遞資訊,目前關于在linux下通過信号傳遞資訊的執行個體非常少,倒是unix下有一些,但傳遞的基本上都是關于傳遞一個整數

執行個體三:信号阻塞及信号集操作

#include "signal.h" 

#include "unistd.h" 

static void my_op(int); 

main() 

        sigset_t new_mask,old_mask,pending_mask; 

        act.sa_sigaction=(void*)my_op; 

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

        printf("now begin to get pending mask and unblock sigrtmin+10\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\n"); 

static void my_op(int signum) 

        printf("receive signal %d \n",signum); 

編譯該程式,并以背景方式運作。在另一終端向該程序發送信号(運作kill -s 42 pid,sigrtmin+10為42),檢視結果可以看出幾個關鍵函數的運作機制,信号集相關操作比較簡單。

本文作者:佚名

來源:51cto

繼續閱讀