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()
- #include <sys/types.h>
- #include <signal.h>
- 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 <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
5.5 abort()
- #include <stdlib.h>
- void abort(void);
向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程設置為阻塞信號,調用abort()后,SIGABORT仍然能被進程接收。該函數無返回值。
5.6 raise()
- #include <signal.h>
- int raise(int signo)
向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1。
6. 信號集及信號集操作函數:
信號集被定義為一種數據類型:
- typedef struct {
- unsigned long sig[_NSIG_WORDS];
- } sigset_t
信號集用來描述信號的集合,每個信號占用一位。Linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是為信號集操作定義的相關函數:
- #include <signal.h>
- 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. 信號阻塞與信號未決:
每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程后都將被阻塞。下面是與信號阻塞相關的幾個函數:
- #include <signal.h>
- 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())。
- #include <signal.h>
- #include <sys/types.h>
- #include <unistd.h>
- 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驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。
實例二:信號傳遞附加信息
主要包括兩個實例:
向進程本身發送信號,并傳遞指針參數
- #include <signal.h>
- #include <sys/types.h>
- #include <unistd.h>
- void new_op(int,siginfo_t*,void*);
- int main(int argc,char**argv)
- {
- struct sigaction act;
- union sigval mysigval;
- int i;
- int sig;
- pid_t pid;
- char data[10];
- memset(data,0,sizeof(data));
- for(i=0;i < 5;i++)
- data[i]='2';
- mysigval.sival_ptr=data;
- sig=atoi(argv[1]);
- pid=getpid();
- sigemptyset(&act.sa_mask);
- act.sa_sigaction=new_op;//三參數信號處理函數
- act.sa_flags=SA_SIGINFO;//信息傳遞開關,允許傳說參數信息給new_op
- if(sigaction(sig,&act,NULL) < 0)
- {
- printf("install sigal error\n");
- }
- while(1)
- {
- sleep(2);
- printf("wait for the signal\n");
- sigqueue(pid,sig,mysigval);//向本進程發送信號,并傳遞附加信息
- }
- }
- void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現
- {
- int i;
- for(i=0;i<10;i++)
- {
- printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
- }
- printf("handle signal %d over;",signum);
- }
這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決于具體的應用。
不同進程間傳遞整型參數:
把1中的信號發送和接收放在兩個程序中,并且在發送過程中傳遞整型參數。
信號接收程序:
- #include <signal.h>
- #include <sys/types.h>
- #include <unistd.h>
- void new_op(int,siginfo_t*,void*);
- int main(int argc,char**argv)
- {
- struct sigaction act;
- int sig;
- pid_t pid;
- pid=getpid();
- sig=atoi(argv[1]);
- sigemptyset(&act.sa_mask);
- act.sa_sigaction=new_op;
- act.sa_flags=SA_SIGINFO;
- 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("the int value is %d \n",info->si_int);
- }
信號發送程序:
命令行第二個參數為信號值,第三個參數為接收進程ID。
- #include <signal.h>
- #include <sys/time.h>
- #include <unistd.h>
- #include <sys/types.h>
- main(int argc,char**argv)
- {
- pid_t pid;
- int signum;
- union sigval mysigval;
- 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;
- struct sigaction act;
- sigemptyset(&act.sa_mask);
- act.sa_flags=SA_SIGINFO;
- 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");
- sleep(10);
- }
- static void my_op(int signum)
- {
- printf("receive signal %d \n",signum);
- }
編譯該程序,并以后臺方式運行。在另一終端向該進程發送信號(運行kill -s 42 pid,SIGRTMIN+10為42),查看結果可以看出幾個關鍵函數的運行機制,信號集相關操作比較簡單。