信号在内核中的表示
实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
1)block集(阻塞集、屏蔽集):一个进程所要屏蔽的信号,在对应要屏蔽的信号位置1
2)pending集(未决信号集):如果某个信号在进程的阻塞集中,则也在未决集中对应位置1,表示该信号不能被递达,不会被处理3)handler(信号处理函数集):表示每个信号所对应的信号处理函数,当信号不在未决集中时,将被调用。
4)block状态字、pending状态字均64位(bit);
5)block状态字用户可以读写,pending状态字用户只能读;这是信号设计机制。
那么我们该如何对信号的屏蔽字状态进行改变和读取呢?接下来我们介绍一组信号集操作函数:
#include <signal.h> int sigemptyset(sigset_t *set); //把信号集清零;(64bit/8=8字节) int sigfillset(sigset_t *set); //把信号集64bit全部置为1 int sigaddset(sigset_t *set, int signo); //根据signo,把信号集中的对应位置成1 int sigdelset(sigset_t *set, int signo); //根据signo,把信号集中的对应位置成0 int sigismember(const sigset_t *set, int signo); //判断signo是否在信号集中
sigprocmask 功能:读取或者更改进程的信号屏蔽字(Block)
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
读取:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
更改:如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
sigpending获取信号未决状态字(pending)信息,保存至set态,NSIG信号的最大值=64。
#include <signal.h> int sigpending(sigset_t *set);
sigismember函数
用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1,否则返回0。如果有错误则返回-1。出错的情况及其错误代码见下:
EFAULT 参数set指针地址无法存取
EINVAL 参数signum 非合法的信号编号
int sigismember(const sigset_t *set,int signum);我们注册一个SIGINT信号,打印出pending的状态,结果如下:
void handler(int sig) { printf("recv a sig=%d\n",sig); } void printsigset(sigset_t *set) { int i; for(i=1;i<NSIG;i++) { if(sigismember(set,i)) putchar('1'); //打印出未决态 else putchar('0'); } printf("\n"); } int main() { sigset_t pset; if(signal(SIGINT,handler)==SIG_ERR) ERR_EXIT("signal error!"); while(1) { sigpending(&pset); printsigset(&pset); sleep(1); } return 0; }
在接下来的例子中,我们先屏蔽SIGINT信号, 但是如果该进程接收到了SIGQUIT信号, 则将对SIGINT信号的屏蔽节解除,当然,我们需要先注册SIGINT和SIGQUIT信号。
/*开始阻塞信号的程序,产生未决状态*/ void handler(int sig) { if(sig==SIGINT) printf("recv a sig=%d\n",sig); else if(sig==SIGQUIT) //解除SIGINT的屏蔽 { sigset_t uset; sigemptyset(&uset); sigaddset(&uset,SIGINT); sigprocmask(SIG_UNBLOCK,&uset,NULL); } // printf("recv a sig=%d\n",sig); } void printsigset(sigset_t *set) { int i; for(i=1;i<NSIG;i++) { if(sigismember(set,i)) putchar('1'); else putchar('0'); } printf("\n"); } int main() { sigset_t pset; sigset_t bset; sigemptyset(&bset); sigaddset(&bset,SIGINT); if(signal(SIGINT,handler)==SIG_ERR) ERR_EXIT("signal error!"); if(signal(SIGQUIT,handler)==SIG_ERR) ERR_EXIT("signal error!"); sigprocmask(SIG_BLOCK,&bset,NULL);//屏蔽SIGINT信号 while(1) { sigpending(&pset); printsigset(&pset); sleep(1); } return 0; }
如果我们采用实时信号的话,例如SIGRTMIN,那么对信号来说是支持排队的,不会发生丢失的情况,在解除阻塞后,会对每个信号做出处理。
前面我们讲过了使用signal安装不可靠信号,虽然signal不如sigaction功能丰富,但是也可以安装可靠信号;
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
sigaction函数用于改变进程接收到特定信号后的行为。
简而言之参数就是(信号,指针,原行为)
关于sigaction结构体
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等
struct sigaction { //信号处理程序 不接受额外数据(比较过时) void (*sa_handler)(int); //信号处理程序能接受额外数据,和sigqueue配合使用(支持信号排队,信号传送其他信息),推荐使用 void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; //屏蔽 int sa_flags; //表示信号的行为:SA_SIGINFO表示能接受数据 void (*sa_restorer)(void); //废弃不用了 };
sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;
sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。
sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。
sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。
sa_restorer已过时,POSIX不支持它,不应再使用。
注意:回调函数sa_handler和sa_sigaction只能选一个
因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,
实例1: 利用 sigaction 实现了 signal 函数的功能
__sighandler_t my_signal(int sig,__sighandler_t handler) { struct sigaction act; struct sigaction oldact; act.sa_handler=handler; sigemptyset(&act.sa_mask); act.sa_flags=0; if(sigaction(sig,&act,&oldact)<0) return SIG_ERR; return oldact.sa_handler; } void handler(int sig) { printf("recv a sig=%d\n",sig); } int main() { /* struct sigaction act; act.sa_handler=handler; sigemptyset(&act.sa_mask); act.sa_flags=0; if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); */ my_signal(SIGINT,handler); while(1) pause(); return 0; }sa_mask选项
在执行handler 的时候, 如果此时进程收到了sa_mask所包含的信号, 则这些信号将不会被响应, 直到handler函数执行完毕。
sigprocmask使其即使发生了也不能递达,但是sa_mask 仅是在处理handler是屏蔽外来的信号;两者的区别还是要好好搞一搞的。
void handler(int sig) { printf("recv a sig=%d\n",sig); sleep(5); } int main() { struct sigaction act; act.sa_handler=handler; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask,SIGQUIT);//屏蔽SIGQUIT信号 act.sa_flags=0; if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); while(1) pause(); return 0; }
siginfo_t结构:
siginfo_t{ int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since Linux 2.6.32) */ }
#include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval value);
功能
sigqueue是新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
和kill函数相比多了一个参数:const union sigval value(int kill(pid_t pid, int sig)),因此sigqueue()可以比kill()传递更多的信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
参数
参数1是指定接收信号的进程id,参数2确定即将发送的信号;
参数3是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
注意:要想在进程间通信的话,sa_flags要置为 SA_SIGINFOsigval联合体
typedef union sigval{ int sival_int; void *sival_ptr; } sigval_t;接下来我们模拟一下进程间通信的实例:
先运行hello开启接收,然后使用send发送信号;通过这种方式可以达到进程见通信的目的。
Hello.c void handler(int sig,siginfo_t *info,void *ctx) { printf("recv a sig=%d data=%d\n",sig,info->si_value.sival_int); } int main() { struct sigaction act; act.sa_sigaction=handler; sigemptyset(&act.sa_mask); act.sa_flags=SA_SIGINFO; if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); while(1) pause(); return 0; } Send int main(int argc,char *argv[]) { if(argc!=2) { fprintf(stderr,"Usage %s pid\n",argv[0]); exit(EXIT_FAILURE); } pid_t pid=atoi(argv[1]); union sigval v; v.sival_int=100; sigqueue(pid,SIGINT,v); return 0; }