如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
一、内核如何实现信号的捕捉,例如SIGQUIT函数:
1.用户注册了SIGQUIT信号的处理函数sighandler;
2.当前正在执行main函数,这时发生了中断或异常切换到内核态;
3.在中断处理完成后要返回到用户的main函数之前检查到有SIGQUIT信号递达;
4.内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler函数和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程;
5.sighandler函数返回后自动执行系统调用sigreturn再次进入内核态;
6.如果没有新的信号递达则返回用户态恢复main函数的上下文继续执行。
二、sigaction
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功返回0,出错则返回-1.
act 和oact指针指向struct sigaction结构体 :
struct sigaction {
void (*sa_handler)(int);
void (sa_sigaction)(int, siginfo_t , void *);
sigset_t sa_mask; // 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)
int sa_flags; //默认设置为零
void (*sa_restorer)(void);
};
sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL 表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册 了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信 号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main 函数调用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函 数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产 生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则 用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
三、pause
int pause(void); //pause函数使调用进程挂起直到有信号递达(只有出错返回值)
下面用alarm和pause函数实现一个mysleep函数:
1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数 sig_alrm。
2. 调用alarm(nsecs)设定闹钟。
3. 调用pause等待,内核切换到别的进程运行。
4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
6. 切换到用户态执⾏行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽, 从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用 sigreturn再次进入 内核,再返回用户态继续执行进程的主控制流程(main函数调用 的mysleep函数)。
7. pause函数返回-1,然后调⽤用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理 动作。
程序代码:
#include <stdio.h>
#include <signal.h>
void handler()
{
}
int mysleep(int time)
{
struct sigaction act, oact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM, &act, &oact);
//调用alarm设定闹钟
int ret = alarm(time);
//调用pause等待,内核运行别的进程
pause();
sigaction(SIGALRM, &oact, NULL);
//取消闹钟
ret = alarm(0);
//ret>0时表示休眠失败
return ret;
}
int main()
{
while(1)
{
mysleep(2);
printf("I am waking up!\n");
}
return 0;
}
运行结果:
现在重新审视一下mysleep程序,虽然alarm(time)紧接着的下一行就是pause,但是无法保证pause函数一定会在alarm被调用time秒后执行。由于异步事件在任何时候都有可能发生,所以如果我们写程序时不考虑周密,就可能因为时序问题而发生错误。这叫做竞态条件。
因此在上述程序中,可以采用sigsuspend函数将“解除信号屏蔽”和“挂起等待信号”这两步合为一个原子操作。四个suspend包含了pause的等待功能同时解决了竞态条件问题。
int sigsuspend(const sigset_t *sigmask); //执行了一个信号处理函数后才返回,返回值为-1。
调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时 解除对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原 来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。
改进后的程序代码:
#include <stdio.h>
#include <signal.h>
void handler()
{
}
int mysleep(int time)
{
struct sigaction act, oact;
sigset_t newmask, oldmask, susmask;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM, &act, &oact);
sigemptyset(&newmask);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
//调用alarm设定闹钟
int ret = alarm(time);
susmask = oldmask;
sigdelset(&susmask, SIGALRM);
//调用pause等待,内核运行别的进程
//pause();
sigsuspend(&susmask);
ret = alarm(0);
sigaction(SIGALRM, &oact, NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
//取消闹钟
//ret>0时表示休眠失败
return ret;
}
int main()
{
while(1)
{
mysleep(2);
printf("I am waking up!\n");
}
return 0;
}