信号的基本属性:软中断,由内核发送,内核处理。某个进程通过内核向另一个进程发送信号时(引起信号产生的五个因素),另一个进程将会陷入内核进行中断处理,未决信号集中相应信号置1,当递达后,置0。如果阻塞信号集相应信号为1,则该信号处于未决状态。处于未决状态中的信号,多次发送时,只是执行一次,因为在未决信号集中只是记录了该信号的状态,没有记录发送的次数。信号抵达后,内核进行处理。处理方式有三:默认处理方式(5种);忽略(丢弃)和捕捉。下面说明捕捉机制。
signal和sigaction函数只是完成对一个信号进行注册的功能,而对信号的捕捉的处理都是由内核完成的。当对一个信号进行注册后,内核对其捕捉同时调用其注册时对应的用户处理函数。
(1)signal函数
typedef void (*sighandler_t)(int); //定义一个函数类型 sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
作用:注册一个信号捕捉函数
返回值:成功返回sighandler_t类型的函数(或函数首地址);失败则返回一个宏:SIG_ERR。注意判断该函数的返回值: sighandler ret = signal(·······);if(ret==SIG_ERR)
第一个参数为信号;第二个参数为sighandler_t类型函数(即返回值为void,形参为int)。
注意:该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>typedef void (*sighandler_t) (int); //定义sighandler_t类型void catchsigint(int signo)
{printf("-----------------catch\n");
}int main(void)
{sighandler_t handler;handler = signal(SIGINT, catchsigint); //注册2号信号if (handler == SIG_ERR) {perror("signal error");exit(1);} //判断返回值while (1);return 0;
}
[root@localhost 01_signal_test]# ./signal2
^C-----------------catch //Ctrl+C
^C-----------------catch //Ctrl+C
^\Quit (core dumped) //Ctrl+\
只要一发送2号信号,就会执行相应函数。
(2)sigaction函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
作用:对某个信号进行注册(同signal),即对某个信号之前对应的处理方式(函数)进行修改。
返回值:成功0;失败-1,设置errno。
参数:
act:传入参数,新的处理方式。
oldact:传出参数,旧的处理方式。
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表忽略 或 SIG_DFL表执行默认动作;
sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置;sa_mask也是一个字(64位),只是在执行相应的用户处理函数期间生效。即在执行用户处理函数期间, sa_mask屏蔽的信号也不能递达,处于未决状态。如果sa_mask未屏蔽,则响应信号,中断嵌套。相当于此期间,sa_mask代替了mask。
sa_flags:通常设置为0,表示用默认属性。默认属性即为:sa_mask中将自己屏蔽,即该信号的注册函数执行期间,再次向进程发送该信号,该信号不能递达,处于未决状态。
最后一个参数如果不关心之前的处理方式,可以为NULL。
(3)信号捕捉机制
进程正常运行时,默认PCB中有一个信号屏蔽字,假定为mask,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由mask来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为mask。
sa_flags为0时,XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
阻塞的常规信号(1-31)不支持排队,产生多次只记录一次。(后32个实时信号支持排队)
内核实现信号捕捉的过程如下:
首先,处于用户态(user mode)的某个进程在执行到某个指令时突然接收某个信号(软中断,终端按键产生;硬件异常产生;命令产生;系统调用产生或者软件条件产生),会暂停执行下一条指令而陷入内核进入内核态。
内核在处理这一异常后,在准备会用户态之前先处理可以递达该进程的信号。
如果该信号的处理方式为捕捉,则内核对该信号进行捕捉,同时调用相应的用户处理函数,回到用户态执行相应的用户处理函数(注意不是回到主控制流程)。
在用户处理函数执行完返回时,再次执行系统调用sigretum再次进入内核。因为函数执行完需要返回到该函数的调用点,而该函数是内核调用的,因此需要再次返回到内核。
最后,从内核再次返回到用户模式,从上次中断处继续执行下一条指令。
//练习1:为某个信号设置捕捉函数;验证在信号处理函数执行期间,该信号多次递送,那么只在处理函数之行结束后,处理一次;验证sa_mask在捕捉函数执行期间的屏蔽作用。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>void docatch(int signo) //用户处理函数
{printf("the %dth signal is catched\n", signo);sleep(10); printf("-------finish------\n");
}
int main(void)
{int ret;struct sigaction act;act.sa_handler = docatch;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); //sa_mask屏蔽字中,3号信号置1act.sa_flags = 0; //默认属性 信号捕捉函数执行期间,自动屏蔽本信号ret = sigaction(SIGINT, &act, NULL); //注册2号信号if (ret == -1) {perror("sigaction error");exit(1);}while (1);return 0;
}
[root@localhost 01_signal_test]# ./test_sigac
^Cthe 2th signal is catched // 发2号信号 Ctrl +C
-------finish------
^Cthe 2th signal is catched // 发2号信号 Ctrl +C
^C^C^C^C^C^C^C^C^C^C^C^C^C-------finish------ // 执行期间,发多个2号信号
the 2th signal is catched
-------finish------ //但是只是执行了一次
^Cthe 2th signal is catched
^\^\^\^\^\^\^\^\^\^\^\^\-------finish------ // 执行期间,发多个3号信号
Quit (core dumped) //2号信号处理完后,处理2号,则退出进程,结束。