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