什么是信号
信号本质上是在软件层次上对中断机制的一种模拟,其主要有以下几种来源:
-
程序错误:除零,非法内存访问等。
-
外部信号:终端
Ctrl-C
产生SGINT
信号,定时器到期产生SIGALRM等。 -
显式请求:kill函数允许进程发送任何信号给其他进程或进程组。
目前 Linux 支持64种信号。信号分为非实时信号(不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64。
信号是异步的,一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。一般来说,我们只需要在进程中设置信号相应的处理函数,当有信号到达的时候,由系统异步触发相应的处理函数即可。如下代码:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void sigcb(int signo) {
switch (signo) {
case SIGHUP:
printf("Get a signal -- SIGHUP\n");
break;
case SIGINT:
printf("Get a signal -- SIGINT\n");
break;
case SIGQUIT:
printf("Get a signal -- SIGQUIT\n");
break;
}
return;
}
int main() {
signal(SIGHUP, sigcb);
signal(SIGINT, sigcb);
signal(SIGQUIT, sigcb);
for (;;) {
sleep(1);
}
}
运行程序后,当我们按下 Ctrl+C
后,屏幕上将会打印 Get a signal -- SIGINT
。当然我们可以使用 kill -s SIGINT pid
命令来发送一个信号给进程,屏幕同样打印出 Get a signal -- SIGINT
的信息。
信号实现原理
接下来我们分析一下Linux对信号处理机制的实现原理。
在进程管理结构 task_struct
中有几个与信号处理相关的字段,如下:
struct task_struct {
...
int sigpending;
...
struct signal_struct *sig;
sigset_t blocked;
struct sigpending pending;
...
}
成员 sigpending
表示进程是否有信号需要处理(1表示有,0表示没有)。成员 blocked
表示被屏蔽的信息,每个位代表一个被屏蔽的信号。成员 sig
表示信号相应的处理方法,其类型是 struct signal_struct
,定义如下:
#define _NSIG 64
struct signal_struct {
atomic_t count;
struct k_sigaction action[_NSIG];
spinlock_t siglock;
};
typedef void (*__sighandler_t)(int);
struct sigaction {
__sighandler_t sa_handler;
unsigned long sa