信号的基本概念
硬件中的 “中断” 是为了立即响应,而 “信号” 是中断的软件实现。在程序中不需要通过通过 “轮询” 的方式来判断是否有事件发生,只要进程收到信号就会立即响应。“信号” 的发生完全是不可预测的,但是一旦信号发生了,却会有相应的处理程序。比如当程序卡死,在键盘上按下组合键 Cltr + C ,向该程序发送 SIGINT 信号,程序就会终止。“信号” 的特点使得它很容易来实现 “异步消息响应机制”, 这使得我们程序处理更加灵活。UNIX中有很多产生信号的方式,比如cltr+c组合键产生中断信号,又或者使用 kill
命令向进程发送任意信号。信号的产生最终是为了作出快速响应。内核对信号的处理方法就是以下的三种:
- 忽略信号:内核对信号不作出任何响应,忽略该信号。但是SIGKILL, SIGSTOP 是不能忽略的,原因在于它们向内核和超级用户提供了使进程终止或者停止的可靠方法。
- 捕捉信号:内核可以通过用户设置好的捕捉函数来作出相应的处理。下面会说到如何设置捕捉函数。
- 默认动作:对所有的信号内核都有一个默认动作,如果用户未设置捕捉函数,那么这是内核的默认动作。大部分的默认动作为终止进程。
常见信号
不同的unix平台下支持的信号种类各不相同,可以使用 kill -l 来查看。这里只列举出几个常见的,重要的信号来进行说明还有解释。见下表:
名字 | 说明 | 默认动作 |
---|---|---|
SIGABRT | 异常终止(abort) | 终止+core |
SIGALRM | 定时器超时(alram) | 终止 |
SIGINT | 终端中断符 | 终止 |
SIGKILL | 终止 | 终止 |
SIGQUIT | 终端退出符 | 终止+core |
SIGSEGV | 无效内存引用 | 终止+core |
SIGUSR1 | 用户定义信号 | 终止 |
signal函数
如果用户想对某个信号定义自己的处理函数,那么就需要用到这个 signal 接口函数进行信号捕捉函数的注册了。下面是这个函数的定义:
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR
这个复杂的函数声明我还是第一次见到,在没有理解之前着实让我费解。这里先讲这个函数怎么用,然后再解释这个函数声明。
signal
函数需要传入两个参数,signo为我们程序想要捕捉的信号名。比如 SIGINT。第二个参数是一个函数地址。该函数需要传入一个int型,无返回值。unix系统也我们提供了三个处理函数,
- SIG_ERR 表示signal函数出错
- SIG_DEL 表示对信号采用默认动作
- SIG_IGN 表示忽略该信号
signal
函数的一般编程写法如下:
static void sig_usr1(int); if ((signal(SIG_USR1, sig_usr1)) == SIG_ERR) { /* error handler code */ ... } ... static void sig_usr1(int signo) { /* the signal SIGUSR1 handler code*/ ... }
现在知道了 signal
函数该怎么用了,再后头看看这个函数的声明。直接看上面这个复杂的声明确实很难看懂,书上将其转换成下面这个形似:
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc*)
说实话我只能看懂 signal
函数的参数,一个是 int
, 一个是函数地址。我总是在纠结函数声明中最后的一个 int
,实在看不懂它的作用是什么。后来我把 Sigfunc先替换成我们常见的类型,比如: int *signal(int, Sigfunc*);
一下子就豁然开朗了。就是个返回值嘛! signal
函数奇怪就奇怪在它的返回值,它返回的是一个函数指针,这个函数指针对应的函数形参为一个 int
型,无返回值。这一下就理解了这个 signal
函数了。
下面是一个 signal
函数的实例,抄自书上,并无太多新意:
# include "apue.h" static void sig_usr(int); int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can‘t catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can‘t catch SIGUSR2); for ( ; ; ) pause(); } static void sig_usr(int signo) { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_sdump("received signal %d\n", signo); }
这里再补充说明一下 signal 函数的返回值。即 "以前信号的处理配置" 的含义。当调用 signal 函数注册信号处理函数,如果成功,则返回此信号的上一个注册的信号处理函数的地址。要理解这一点,可以看看下面的这段测试代码:
#include "apue.h" static void sig_usr(int); static void sig_usr2(int); int main(void) { Sigfunc *pfunc; pfunc = signal(SIGUSR1, sig_usr); printf("the pfunc addr is %p\n", pfunc); /* 打印0,因为SIGUSR1信号并未注册*/ pfunc = signal(SIGUSR1, sig_usr2); if (pfunc == SIG_ERR) printf("can not use sig_usr2 to handle SIGUSR1\n"); // print the sig_usr addr printf("the sig_usr addr is %p\n", sig_usr); printf("the pfunc addr is %p\n", pfunc); /* 两者的地址一样 */ while (1) pause(); }