Linux信號(signal) 機制分析(2)
接上文
5. 信號的發(fā)送
發(fā)送信號的主要函數(shù)有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
5.1 kill()
- #include <sys/types.h>
- #include <signal.h>
- int kill(pid_t pid,int signo)
該系統(tǒng)調(diào)用可以用來向任何進程或進程組發(fā)送任何信號。參數(shù)pid的值為信號的接收進程
pid>0 進程ID為pid的進程
pid=0 同一個進程組的進程
pid<0 pid!=-1 進程組ID為 -pid的所有進程
pid=-1 除發(fā)送進程自身外,所有進程ID大于1的進程
Sinno是信號值,當為0時(即空信號),實際不發(fā)送任何信號,但照常進行錯誤檢查,因此,可用于檢查目標進程是否存在,以及當前進程是否具有向目標發(fā)送信號的權限(root權限的進程可以向任何進程發(fā)送信號,非root權限的進程只能向?qū)儆谕粋€session或者同一個用戶的進程發(fā)送信號)。
Kill()最常用于pid>0時的信號發(fā)送。該調(diào)用執(zhí)行成功時,返回值為0;錯誤時,返回-1,并設置相應的錯誤代碼errno。下面是一些可能返回的錯誤代碼:
EINVAL:指定的信號sig無效。
ESRCH:參數(shù)pid指定的進程或進程組不存在。注意,在進程表項中存在的進程,可能是一個還沒有被wait收回,但已經(jīng)終止執(zhí)行的僵死進程。
EPERM: 進程沒有權力將這個信號發(fā)送到指定接收信號的進程。因為,一個進程被允許將信號發(fā)送到進程pid時,必須擁有root權力,或者是發(fā)出調(diào)用的進程的UID 或EUID與指定接收的進程的UID或保存用戶ID(savedset-user-ID)相同。如果參數(shù)pid小于-1,即該信號發(fā)送給一個組,則該錯誤表示組中有成員進程不能接收該信號。
5.2 sigqueue()
- #include <sys/types.h>
- #include <signal.h>
- int sigqueue(pid_t pid, int sig, const union sigval val)
調(diào)用成功返回 0;否則,返回 -1。
sigqueue()是比較新的發(fā)送信號系統(tǒng)調(diào)用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數(shù),與函數(shù)sigaction()配合使用。
sigqueue的第一個參數(shù)是指定接收信號的進程ID,第二個參數(shù)確定即將發(fā)送的信號,第三個參數(shù)是一個聯(lián)合數(shù)據(jù)結構union sigval,指定了信號傳遞的參數(shù),即通常所說的4字節(jié)值。
- typedef union sigval {
- int sival_int;
- void *sival_ptr;
- }sigval_t;
sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發(fā)送信號,而不能發(fā)送信號給一個進程組。如果signo=0,將會執(zhí)行錯誤檢查,但實際上不發(fā)送任何信號,0值信號可用于檢查pid的有效性以及當前進程是否有權限向目標進程發(fā)送信號。
在調(diào)用sigqueue時,sigval_t指定的信息會拷貝到對應sig 注冊的3參數(shù)信號處理函數(shù)的siginfo_t結構中,這樣信號處理函數(shù)就可以處理這些信息了。由于sigqueue系統(tǒng)調(diào)用支持發(fā)送帶參數(shù)信號,所以比kill()系統(tǒng)調(diào)用的功能要靈活和強大得多。
5.3 alarm()
- #include <unistd.h>
- unsigned int alarm(unsigned int seconds)
系統(tǒng)調(diào)用alarm安排內(nèi)核為調(diào)用進程在指定的seconds秒后發(fā)出一個SIGALRM的信號。如果指定的參數(shù)seconds為0,則不再發(fā)送 SIGALRM信號。后一次設定將取消前一次的設定。該調(diào)用返回值為上次定時調(diào)用到發(fā)送之間剩余的時間,或者因為沒有前一次定時調(diào)用而返回0。
注意,在使用時,alarm只設定為發(fā)送一次信號,如果要多次發(fā)送,就要多次使用alarm調(diào)用。
5.4 setitimer()
現(xiàn)在的系統(tǒng)中很多程序不再使用alarm調(diào)用,而是使用setitimer調(diào)用來設置定時器,用getitimer來得到定時器的狀態(tài),這兩個調(diào)用的聲明格式如下:
- int getitimer(int which, struct itimerval *value);
- int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
在使用這兩個調(diào)用的進程中加入以下頭文件:
- #include <sys/time.h>
該系統(tǒng)調(diào)用給進程提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就發(fā)送一個相應的信號給進程,并使得計時器重新開始。三個計時器由參數(shù)which指定,如下所示:
TIMER_REAL:按實際時間計時,計時到達將給進程發(fā)送SIGALRM信號。
ITIMER_VIRTUAL:僅當進程執(zhí)行時才進行計時。計時到達將發(fā)送SIGVTALRM信號給進程。
ITIMER_PROF:當進程執(zhí)行時和系統(tǒng)為該進程執(zhí)行動作時都計時。與ITIMER_VIR-TUAL是一對,該定時器經(jīng)常用來統(tǒng)計進程在用戶態(tài)和內(nèi)核態(tài)花費的時間。計時到達將發(fā)送SIGPROF信號給進程。
定時器中的參數(shù)value用來指明定時器的時間,其結構如下:
- struct itimerval {
- struct timeval it_interval; /* 下一次的取值 */
- struct timeval it_value; /* 本次的設定值 */
- };
該結構中timeval結構定義如下:
- struct timeval {
- long tv_sec; /* 秒 */
- long tv_usec; /* 微秒,1秒 = 1000000 微秒*/
- };
在setitimer 調(diào)用中,參數(shù)ovalue如果不為空,則其中保留的是上次調(diào)用設定的值。定時器將it_value遞減到0時,產(chǎn)生一個信號,并將it_value的值設定為it_interval的值,然后重新開始計時,如此往復。當it_value設定為0時,計時器停止,或者當它計時到期,而it_interval 為0時停止。調(diào)用成功時,返回0;錯誤時,返回-1,并設置相應的錯誤代碼errno:
EFAULT:參數(shù)value或ovalue是無效的指針。
EINVAL:參數(shù)which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。
下面是關于setitimer調(diào)用的一個簡單示范,在該例子中,每隔一秒發(fā)出一個SIGALRM,每隔0.5秒發(fā)出一個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);
向進程發(fā)送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數(shù)。即使SIGABORT被進程設置為阻塞信號,調(diào)用abort()后,SIGABORT仍然能被進程接收。該函數(shù)無返回值。
5.6 raise()
- #include <signal.h>
- int raise(int signo)
向進程本身發(fā)送信號,參數(shù)為即將發(fā)送的信號值。調(diào)用成功返回 0;否則,返回 -1。
6. 信號集及信號集操作函數(shù):
信號集被定義為一種數(shù)據(jù)類型:
- typedef struct {
- unsigned long sig[_NSIG_WORDS];
- } sigset_t
信號集用來描述信號的集合,每個信號占用一位。Linux所支持的所有信號可以全部或部分的出現(xiàn)在信號集中,主要與信號阻塞相關函數(shù)配合使用。下面是為信號集操作定義的相關函數(shù):
- #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)調(diào)用該函數(shù)后,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. 信號阻塞與信號未決:
每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程后都將被阻塞。下面是與信號阻塞相關的幾個函數(shù):
- #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()函數(shù)能夠根據(jù)參數(shù)how來實現(xiàn)對信號集的操作,操作主要有三種:
SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號
SIG_UNBLOCK 如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
SIG_SETMASK 更新進程阻塞信號集為set指向的信號集
sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的所有信號,在set指向的信號集中返回結果。
sigsuspend(const sigset_t *mask))用于在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 并暫停進程執(zhí)行,直到收到信號為止。sigsuspend 返回后將恢復調(diào)用之前的信號掩碼。信號處理函數(shù)完成后,進程將繼續(xù)執(zhí)行。該系統(tǒng)調(diào)用始終返回-1,并將errno設置為EINTR。
8. 信號應用實例
linux下的信號應用并沒有想象的那么恐怖,程序員所要做的最多只有三件事情:
- 安裝信號(推薦使用sigaction());
- 實現(xiàn)三參數(shù)信號處理函數(shù),handler(int signal,struct siginfo *info, void *);
- 發(fā)送信號,推薦使用sigqueue()。
實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式采用缺省或忽略)。其他可能要做的無非是與信號集相關的幾種操作。
實例一:信號發(fā)送及處理
實現(xiàn)一個信號接收程序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);
- }
說明,命令行參數(shù)為信號值,后臺運行sigreceive signo &,可獲得該進程的ID,假設為pid,然后再另一終端上運行kill -s signo pid驗證信號的發(fā)送接收及處理。同時,可驗證信號的排隊問題。
實例二:信號傳遞附加信息
主要包括兩個實例:
向進程本身發(fā)送信號,并傳遞指針參數(shù)
- #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;//三參數(shù)信號處理函數(shù)
- act.sa_flags=SA_SIGINFO;//信息傳遞開關,允許傳說參數(shù)信息給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);//向本進程發(fā)送信號,并傳遞附加信息
- }
- }
- void new_op(int signum,siginfo_t *info,void *myact)//三參數(shù)信號處理函數(shù)的實現(xiàn)
- {
- int i;
- for(i=0;i<10;i++)
- {
- printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
- }
- printf("handle signal %d over;",signum);
- }
這個例子中,信號實現(xiàn)了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決于具體的應用。
不同進程間傳遞整型參數(shù):
把1中的信號發(fā)送和接收放在兩個程序中,并且在發(fā)送過程中傳遞整型參數(shù)。
信號接收程序:
- #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);
- }
信號發(fā)送程序:
命令行第二個參數(shù)為信號值,第三個參數(shù)為接收進程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下有一些,但傳遞的基本上都是關于傳遞一個整數(shù)
實例三:信號阻塞及信號集操作
- #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);
- }
編譯該程序,并以后臺方式運行。在另一終端向該進程發(fā)送信號(運行kill -s 42 pid,SIGRTMIN+10為42),查看結果可以看出幾個關鍵函數(shù)的運行機制,信號集相關操作比較簡單。