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

摘要:信号处理机制是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内核完全剖析》。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值