摘要:信号处理机制是Unix操作系统的一大特点。本文以Linux0.11信号处理相关源码为例,针对几个细节对信号处理的整个流程进行描述,力求简单明了,参考赵炯博士的《Linux内核完全剖析》和潘晓雷的《Linux0.11源码分析》。
一、信号(signal)机制概述
所谓信号,是一种软中断机制,是实现进程间异步通讯的手段。信号的发送和处理是异步的,进程并不知道什么时候会收到信号。信号同中断很像,不同的信号对应不同的信号类型,并且对应不同的信号类型有相应的信号处理函数和信号屏蔽位。
1.信号的来源
程序错误:除零,非法内存访问…
外部信号:终端Ctrl-C产生SGINT信号,定时器到期产生SIGALRM…
显式请求:kill函数允许进程发送任何信号给其他进程或进程组。
2.Linux下查看信号
在Linux下,可以通过以下命令查看系统所有的信号:
kill -l
可以通过类似下面的命令显式的给一个进程发送一个信号:
kill -2 pid
3.信号的处理
忽略信号:大部分信号可被忽略,除SIGSTOP和SIGKILL信号外(这是超级用户杀掉或停掉任意进程的手段)。
捕获信号:注册信号处理函数,它对产生的特定信号做处理。
让信号默认动作起作用:unix内核定义的默认动作,有5种情况:
a) abort:终止进程并产生core文件。
b) stop:终止进程但不生成core文件。
c) 忽略:忽略信号。
d) suspend:挂起进程。
e) continue:若进程是挂起的,则resume进程,否则忽略此信号。
二、深入信号机制内部
1.信号检测和响应的时机
当前进程由于系统调用、中断或异常而进入内核态以后,从内核态返回到用户态之前。
当前进程在内核中进入睡眠以后刚被唤醒的时候(必定是在系统调用中),或者由于不可忽略信号的存在而提前返回到用户空间。
2.Linux 0.11中用于信号处理的数据结构
struct sigaction {
void (*sa_handler)(int);//信号处理句柄
sigset_t sa_mask;//信号的屏蔽码,可以阻塞指定的信号
int sa_flags;//信号选项标志
void (*sa_restorer)(void);//信号恢复函数指针(系统内部使用)
};
进程结构体重关于信号的定义
long signal;
struct sigaction sigaction[32];
long blocked; /* bitmap of masked signals */
3.信号的预处理操作
void do_signal(long signr,long eax, long ebx, long ecx, long edx,
long fs, long es, long ds,
long eip, long cs, long eflags,
unsigned long * esp, long ss)
{
unsigned long sa_handler;
long old_eip=eip;
struct sigaction * sa = current->sigaction + signr - 1;
int longs;
unsigned long * tmp_esp;
sa_handler = (unsigned long) sa->sa_handler;
if (sa_handler==1)
return;
if (!sa_handler) {
if (signr==SIGCHLD)
return;
else
do_exit(1<<(signr-1));
}
if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;
*(&eip) = sa_handler;
longs = (sa->sa_flags & SA_NOMASK)?7:8;
*(&esp) -= longs;
verify_area(esp,longs*4);
tmp_esp=esp;
put_fs_long((long) sa->sa_restorer,tmp_esp++);
put_fs_long(signr,tmp_esp++);
if (!(sa->sa_flags & SA_NOMASK))
put_fs_long(current->blocked,tmp_esp++);
put_fs_long(eax,tmp_esp++);
put_fs_long(ecx,tmp_esp++);
put_fs_long(edx,tmp_esp++);
put_fs_long(eflags,tmp_esp++);
put_fs_long(old_eip,tmp_esp++);
current->blocked |= sa->sa_mask;
}
对内核态堆栈的修改