环境:Linux 0.11 / Linux 3.4.2
参考书籍:Linux内核完全剖析基于0.11内核-赵炯
一、Linux信号是什么,都有什么信号
在系统中,信号是一种"软件中断"处理机制,为异步事件的处理提供了一种有效的方法。例如:① 如果一个进程设置的报警时钟到期后,系统就会向当前进程发送SIGALRM信号。② 进程可以通过kill函数向另一个进程发送终止信号,如在shell终端上输入kill -9 pid,向pid进程发送进程终止信号。
在Linux 0.11内核版本中定义了一共22中信号,具体可在文件/sys/signal.h中查阅,在内核代码中使用一个无符号长整数(32位比特)表示各种不同的信号。因此在当前版本内核中最多定义32个信号。
//定义信号量总共22个
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGUNUSED 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
二、进程收到信号的处理方式
对于一个进程在接收到信号后有三种处理方式:
① 忽略该信号,但是SIGKILL和SIGSTOP信号忽略不了。
② 捕获信号,执行默认信号处理函数。
③ 捕获信号,执行自定义信号处理函数。
进程修改原信号处理句柄的两种方式
① signal()
函数原型如下:
void (*signal(int _sig, void (*_func)(int)))(int);
signal函数有两个参数:
_sig为要捕获的信号
另外一个参数_func是新的信号处理句柄,参数_func即可以是用户自定义的信号的信号处理句柄,也可以是内核定义的函数指针SIG_IGN或SIG_DFL,SIG_IGN代表忽略该信号,SIG_DFL代表接收到信号后执行默认的操作。
注意:新的信号处理句柄在执行完一次以后,会恢复到默认的SIG_DFL,因此要想重复捕获该信号执行自定义处理句柄,代码如下:
void handler(int sign)
{
signal(SIGINT, handler);
........ //处理函数代码
}
main()
{
signal(SIGINT, handler);
}
② sigaction
signal函数具有一定的不可靠性质,因为在自定义信号处理句柄中,在重新设置自己的句柄之前,该信号的处理句柄为默认操作,如果此时再来这个信号,就会造成信号的丢失。
因此通常采用可靠的sigaction()函数执行此机制,函数原型如下:
int sigaction(int sig, struct sigaction *act, struct sigaction *oldact);
//结构体定义
struct sigaction {
void (*sa_handler)(int);//信号处理函数
sigset_t sa_mask;//是否屏蔽
int sa_flags;
void (*sa_restorer)(void);
};
a. sig为要捕获的信号
b. 函数根据结构体指针act的内容指定信号的行为
c. 函数会将sig信号原来的行为保存到okdact指针中
d.结构体内容sa_mask是一个需要加入到当前进程的信号屏蔽图集,作用是在执行自定义信号句柄sa_handler的中途阻塞当前信号的再次出现,并在信号句柄返回时会恢复到进程原来的信号屏蔽图集。
注意:在阻塞解除前的多个同一信号,只会调用一次任务句柄。
e.结构体内容sa_flags用于指定其他的一些处理事项,选项如下:
/* Ok, I haven't implemented sigactions, but trying to keep headers POSIX */
#define SA_NOCLDSTOP 1
#define SA_NOMASK 0x40000000
#define SA_ONESHOT 0x80000000
三、信号预处理函数do_signal()
do_signal函数是在发生内核调用系统调用(0x80)中断或者时钟中断时将信号处函数句柄插入到用户堆栈中。具体流程如下图所示:
信号处理流程如下:
在系统调用中断或时钟中断中调用
->call _sys_call_table(,%eax,4)(在系统调用表中找到sys_signal函数)
->sys_signal()(实现信号的预处理)
->do_signal() (将信号处函数句柄插入到用户堆栈中,使系统调用或时钟中断返回时可以直接执行信号处理句柄)
sys_signal函数如下:
//进行一些信号的预处理设置
int sys_signal(int signum, long handler, long restorer)
{
struct sigaction tmp;//设置一个信号结构体
if (signum<1 || signum>32 || signum==SIGKILL)//检索信号范围在1-32之间且不是终止信号
return -1;
//指定信号处理句柄
tmp.sa_handler = (void (*)(int)) handler;
//设置屏蔽码
tmp.sa_mask = 0;
//设置信号的状态为只可执行一次就恢复到默认值
tmp.sa_flags = SA_ONESHOT | SA_NOMASK;
//保存恢复处理程序指针
tmp.sa_restorer = (void (*)(void)) restorer;
//更新当前标识指针的信号信息
handler = (long) current->sigaction[signum-1].sa_handler;
current->sigaction[signum-1] = tmp;
return handler;
}
do_signal函数具体分析可在pdf P304找到