当前位置: 首页 > 科技观察

Linux信号(信号)机制分析(二)

时间:2023-03-12 16:19:13 科技观察

接上5.信号发送发送信号的主要函数有:kill()、raise()、sigqueue()、alarm()、setitimer()和abort().5.1kill()#include#includeintkill(pid_tpid,intsigno)该系统调用可用于向任何进程或进程组发送任何信号。参数pid的值是接收信号的进程pid>0,进程ID为pid=0,同进程组pid!=-1的进程pid<0,所有进程组ID为-pid的进程都是pid=-1exceptsending除了进程本身,sinno是对所有进程ID大于1的进程的信号值,当为0(即空信号)时,实际上不发送信号,但错误检查是照常进行。因此,可以用来检查目标进程是否存在,以及当前进程是否有向目标发送信号的权限(具有root权限的进程可以向任何进程发送信号,而没有root权限的进程只能向属于同一会话或同一用户的进程发送信号)。Kill()最常用于在pid>0时发出信号。当调用执行成功时,返回值为0;失败时返回-1,并设置相应的错误码errno。以下是一些可能返回的错误代码:EINVAL:指定的信号sig无效。ESRCH:参数pid指定的进程或进程组不存在。注意,进程表项中存在的进程可能是僵尸进程,还没有被wait回收,但已经终止执行。EPERM:进程无权将此信号发送给指定接收信号的进程。因为,当一个进程被允许向进程pid发送信号时,它必须具有root权限,或者调用进程的UID或EUID与进程的UID或保存的用户ID(savedset-user-ID)相同指定的接收过程。如果参数pid小于-1,即向一个组发送信号,则该错误表示该组中的某个成员进程无法接收到该信号。5.2sigqueue()#include#includeintsigqueue(pid_tpid,intsig,constunionsigvalval)成功返回0;否则,返回-1。sigqueue()是一个相对较新的用于发送信号的系统调用。主要针对实时信号提出(当然也支持前32种)。它支持带参数的信号,并与函数sigaction()结合使用。sigqueue的第一个参数是指定接收信号的进程ID,第二个参数决定要发送的信号,第三个参数是一个联合数据结构unionsigval,指定发送信号的参数,也就是通常所说的to作为4个字的部分值。typedefunionsigval{intsival_int;void*sival_ptr;}sigval_t;sigqueue()比kill()传递了更多的附加信息,但是sigqueue()只能给进程发送信号,不能给进程组发送信号。如果signo=0,会进行错误检查,但实际上不会发送信号,一个0值的信号可以用来检查pid的有效性,以及当前进程是否有权限向目标进程发送信号。调用sigqueue时,会将sigval_t指定的信息复制到sig注册的3参数信号处理函数的siginfo_t结构体中,以便信号处理函数对这些信息进行处理。由于sigqueue系统调用支持发送带参数的信号,因此它比kill()系统调用灵活和强大得多。5.3alarm()#includeunsignedintalarm(unsignedintseconds)系统调用alarm安排内核在指定秒数后为调用进程发送一个SIGALRM信号。如果指定的参数seconds为0,则不会发送SIGALRM信号。后面的设置会取消前面的设置。此调用的返回值是上次预定调用与发送之间的剩余时间,或者为0,因为没有先前的预定调用。注意在使用的时候,闹钟只设置发送一次信号,如果要多次发送,需要多次使用闹钟呼叫。5.4setitimer()当前系统中的很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,getitimer来获取定时器的状态。这两个调用的声明格式如下:intgetitimer(intwhich,structitimerval*value);intsetitimer(intwhich,conststructitimerval*value,structitimerval*ovalue);使用这两个调用在进程中添加如下头文件:#include这个系统调用为进程提供了三个定时器,每个定时器都有自己独特的计时域,当其中任何一个到达时,一个相应的信号被发送到进程并重新启动定时器。这三个定时器是通过参数which来指定的,如下:TIMER_REAL:根据实际时间进行计时,当计时到达时,会向进程发送一个SIGALRM信号。ITIMER_VIRTUAL:仅在进程执行时计时。定时器到达将向进程发送SIGVTALRM信号。ITIMER_PROF:进程执行时间和系统为进程执行动作的时间。它与ITIMER_VIR-TUAL是一对。这个定时器经常被用来统计进程在用户态和内核态所花费的时间。当计时器到达时,将向进程发送一个SIGPROF信号。定时器中的参数值用来表示定时器的时间,其结构如下:structtimerval{structtimevalit_interval;/*下一次的值*/structtimevalit_value;/*本次的设置值*/};该结构体中timeval结构体定义如下:structtimeval{longtv_sec;/*seconds*/longtv_usec;/*microseconds,1秒=1000000微秒*/};在setitimer的调用中,如果参数ovalue不为空,则保留最后一次setsetvalue的调用。当定时器将it_value递减为0时,产生一个信号,将it_value的值设置为it_interval的值,然后重新开始计时,以此类推。当it_value设置为0时,或者定时器超时且it_interval为0时,定时器停止。调用成功时,返回0;失败时返回-1,并设置相应的错误码errno:EFAULT:Theparametervalueorovalueisaninvalidpointer。EINVAL:不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF之一的参数。下面是setitimer调用的简单演示。在此示例中,每秒发出一个SIGALRM,每0.5秒发出一个SIGVTALRM信号:#include#include#include#includeintsec;voidsigroutine(intsigno){switch(signo){caseSIGALRM:printf("Catchasignal--SIGALRM");break;caseSIGVTALRM:printf("Catchasignal--SIGVTALRM");break;}return;}intmain(){structitimervalvalue,ovalue,value2;sec=5;printf("processidis%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_testprocessidis579Catchasignal–SIGVTALRMCatchasignal–SIGALRMCatchasignal–SIGVTALRMCatchasignal–SIGVTALRMCatchasignal–SIGALRMCatchasignal–GVTALRM5.5abort()#includevoidabort(void);向进程发送SIGABORT信号,进程默认会异常退出,当然你可以定义自己的信号处理函数即使SIGABORT被进程处理设置为阻塞信号,在调用abort()后,仍然可以接收到SIGABORT通过这个过程。此函数不返回任何值。5.6raise()#includeinraise(intsigno)给进程本身发送信号,参数为要发送的信号值。调用成功返回0;否则,返回-1。6、信号集和信号集操作函数:信号集定义为一种数据类型:typedefstruct{unsignedlongsig[_NSIG_WORDS];}sigset_tsignalset用于描述信号集,每个信号占用一个bit。Linux支持的所有信号都可以全部或部分出现在信号集中,主要与信号阻塞相关函数结合使用。以下是为信号集操作定义的相关函数:#includeintsigemptyset(sigset_t*set);intsigfillset(sigset_t*set);intsigaddset(sigset_t*set,intsignum);intsigdelset(sigset_t*set,intsignum);intsigismember(constsigset_t*set,intsignum);sigemptyset(sigset_t*set)初始化set指定的信号集,清除信号集中的所有信号;sigfillset(sigset_t*set)调用该函数后,set指向的信号集将包含linux支持的64种信号;sigaddset(sigset_t*set,intsignum)在set指向的信号集中添加signum信号;sigdelset(sigset_t*set,intsignum)删除set指向的信号集中的signum信号;sigismember(constsigset_t*set,intsignum)判断信号signum是否在set指向的信号集中。7、信号阻塞和信号挂起:每个进程都有一个信号集,用来描述哪些信号传递给进程时会阻塞,信号集中的所有信号传递给进程后都会阻塞。下面是与信号阻塞相关的几个函数:函数可以根据参数how来实现对信号集的操作。主要有3个操作:SIG_BLOCK在进程的当前block信号集中添加set指向信号集的信号SIG_UNBLOCK如果进程的block信号集包含指向信号集的信号集,则释放blocksignalblockingSIG_SETMASK更新进程阻塞信号集到set指向的信号集中.sigsuspend(constsigset_t*mask))用于在接收到某个信号之前,将进程的信号掩码临时替换为mask,暂停进程的执行,直到接收到该信号。当sigsuspend返回时,将恢复调用之前的信号掩码。信号处理程序完成后,流程继续执行。该系统调用始终返回-1并将errno设置为EINTR。8.信号应用实例linux下的信号应用并没有想象中的那么可怕。程序员最多需要做三件事:安装信号(推荐使用sigaction());实现三参数信号处理函数,handler(intsignal,structsiginfo*info,void*);发送信号,推荐使用sigqueue()。事实上,对于某些信号,只需安装信号就足够了(默认或忽略信号处理)。其他的可能性无非是与信号集相关的几种操作。实例一:信号发送与处理实现一个信号接收程序sigreceive(信号由sigaction()安装)。#include#include#includevoidnew_op(int,siginfo_t*,void*);intmain(intargc,char**argv){structsigactionact;intsig;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("installsigalerror\n");}while(1){sleep(2);printf("waitforthesignal\n");}}voidnew_op(intsignum,siginfo_t*info,void*myact){printf("receivesignal%d",signum);sleep(5);}说明,命令行参数为信号值,在后台运行sigreceivesigno&,可以得到进程的ID,假设是pid,然后在另一个终端运行kill-ssignopid,验证发送,信号的接收和处理。同时可以验证信号的排队问题。例子2:信号传输附加信息主要包括两个例子:给进程本身发送信号,传递指针参数#include#include#includevoidnew_op(int,siginfo_t*,void*);intmain(intargc,char**argv){structsigactionact;unionsigvalmysigval;inti;intsig;pid_tpid;chardata[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_opif(sigaction(sig,&act,NULL)<0){printf("installsialerror\n");}while(1){sleep(2);printf("waitforthesignal\n");sigqueue(pid,sig,mysigval);//给这个进程发送一个信号并传递附加信息}}voidnew_op(intsignum,siginfo_t*info,void*myact)//三参数信号处理函数的实现{inti;for(i=0;i<10;i++){printf("%c\n",(*((char*)((*info).si_ptr)+i)));}printf("handlesignal%dover;",signum);}本例中信号实现了附加信息的传递,信号如何处理这些信息取决于具体的应用.不同进程之间传递整型参数:将1中的信号发送和接收放到两个程序中,在发送过程中传递整型参数。信号接收程序:#include#include#includevoidnew_op(int,siginfo_t*,void*);intmain(intargc,char**argv){structsigactionact;intsig;pid_tpid;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("installsigalerror\n");}while(1){sleep(2);printf("waitforthesignal\n");}}voidnew_op(intsignum,siginfo_t*info,void*myact){printf("theint值是%d\n",info->si_int);}信号发送程序:命令行第二个参数是信号值,第三个参数是接收进程ID。#include#include#include#includemain(intargc,char**argv){pid_tpid;intsignum;unionsigvalmysigval;signum=atoi(argv[1]);pid=(pid_t)atoi(argv[2]);mysigval.sival_int=8;//不代表具体含义,仅用于说明问题if(sigqueue(pid,signum,mysigval)==-1)printf("sender\n");sleep(2);}注意:例2中的两个例子重点是使用信号来传递信息。目前Linux下通过信号传递信息的例子非常少。Unix下也有,但是传递基本都是传递一个整数例3:信号阻塞和信号集操作#include"signal.h"#include"unistd.h"staticvoidmy_op(int);main(){sigset_tnew_mask,old_mask,pending_mask;structsigactionact;sigemptyset(&act.sa_mask);act.sa_flags=SA_SIGINFO;act.sa_sigaction=(void*)my_op;if(sigaction(SIGRTMIN+10,&act,NULL))printf("installsignalSIGRTMIN+10error\n");sigemptyset(&new_mask);sigaddset(&new_mask,SIGRTMIN+10);if(sigprocmask(SIG_BLOCK,&new_mask,&old_mask))printf("blocksignalSIGRTMIN+10error\n");sleep(10);printf("nowbegintogetpendingmaskandunblockSIGRTMIN+10\n");if(sigpending(&pending_mask)<0)printf("getpendingmaskerror\n");if(sigismember(&pending_mask,SIGRTMIN+10))printf("signalSIGRTMIN+10ispending\n");if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)printf("unblocksignalerror\n");printf("signalunblocked\n");sleep(10);}staticvoidmy_op(intsignum){printf("receivesignal%d\n",signum);}编译程序,在另一个终端后台运行,给进程发送信号(运行kill-s42pid,SIGRTMIN+10为42),查看结果,看几个运行机制按键功能,与信号设置相关的操作比较简单。