信号在内核中的表示
实际执行信号的处理动作称为信号递达(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;
}
当我们按下ctrl+c产生信号时,信号被阻塞,处于未决状态。接收到SIGQUIT信号时,解除阻塞,进入递达状态,但是只会对信号做出一次反应,即使你按了很多次ctrl+c,原因就在于,SIGINT是不可靠信号,不支持排队,只保留了一个。
如果我们采用实时信号的话,例如SIGRTMIN,那么对信号来说是支持排队的,不会发生丢失的情况,在解除阻塞后,会对每个信号做出处理。
Sigaction
前面我们讲过了使用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)sa_mask选项
{
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;
}
在执行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;
}
在响应SIGINT信号即handler处理时,SIGQUIT暂时被屏蔽,但是一旦handler函数处理完后,立即对SIGQUIT进行响应。
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) */
}
sigqueue
#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;
}