您的位置:首页 > 运维架构 > Linux

从Linux 0.11内核看Linux信号处理机制

2012-12-26 09:40 323 查看
摘要:信号处理机制是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.信号的预处理操作



do_signal()函数 参数为内核态堆栈中的内容
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;
}
对内核态堆栈的修改





4.操作系统进入信号处理

通过上述内容,可以看到操作系统在检测到有信号传入时,首先把内核堆栈中存放返回执行点的eip(指令寄存器)保存为old_eip,然后将eip替换为信号处理函数的地址,然后将内核中保存的“原ESP”(即用户态栈地址)减去一定的值,目的是扩大用户态的栈,然后将内核栈上的内容保存到用户栈上。
之所以把EIP的值设置成信号处理函数的地址,是因为一旦进程返回用户态,就要去执行信号处理程序,所以EIP要指向信号处理程序而不是原来应该执行的地址。

5.操作系统退出信号处理

在前面介绍sigaction数据结构的时候出现了信号活动恢复函数指针sa_restroer,该指针主要用于用户态堆栈的清理,把系统调用后的返回值eax和寄存器ecx,edx以及标志寄存器eflags弹出,完全恢复系统调用后各寄存器和CPU的状态,最后通过ret指令弹出原用户程序的eip(即堆栈中的old_eip),返回执行用户程序。

但是在Linux内核代码中,并没有 给出此函数的具体定义,查看相关资料,在Linux的Libc函数库中定义有函数如下



编译程序在编译连接用户自定义的信号处理函数时,会将sa_restorer()函数插入到用户程序中。这样,用户在处理完自定义的信号处理函数后,就会继续执行用户代码了。、

注:文中图片来自《Linux内核完全剖析》。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: