转自:http://itindex.net/detail/16418-linux-%E4%BF%A1%E5%8F%B7-%E5%A0%86%E6%A0%88

此文只简单分析发送信号给用户程序后,用户堆栈和内核堆栈的变化。没有分析实时信号,当然整个过程基本一致。很多参考了<情景分析>,所以有些代码和现在的内核可能不同,比如RESTORE_ALL,但大体的机制是类似的。

. 一个信号小例子

hex@Gentoo ~/signal $ cat sigint.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h> void sig_int(int signo)
{
printf("hello\n");
} int main()
{
if(signal(SIGINT, sig_int) == SIG_ERR){
printf("can't catch SIGINT\n");
exit(-);
} for(;;)
; return ;
} . 用户堆栈里发生的故事 2.1 编译运行该程序,并设置断点在sig_int函数开头(0x80482e8),并设置SIGINT信号的处理方式
hex@Gentoo ~/signal $ gdb ./sigint
(gdb) b *0x80482e8
Breakpoint at 0x80482e8: file sigint.c, line .
(gdb) handle SIGINT noprint pass
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal Stop Print Pass to program Description
SIGINT No No Yes Interrupt
(gdb) r
Starting program: /home/gj/signal/sigint 2.2 向该程序发送信号: kill -INT 此程序的pid号
hex@Gentoo ~/signal $ kill -INT 2.3 该程序收到信号后停在断点处
Breakpoint , sig_int (signo=) at sigint.c:
{
(gdb) i r esp
esp 0xbfffe7ec 0xbfffe7ec
(gdb) x/40a 0xbfffe7ec
0xbfffe7ec: 0xb7fff400 0x2 0x33 0x0
0xbfffe7fc: 0x7b 0x7b 0x8048930 <__libc_csu_init> 0x80488f0 <__libc_csu_fini>
0xbfffe80c: 0xbfffed58 0xbfffed40 0x0 0x0
0xbfffe81c: 0xbfffec18 0x0 0x0 0x0
0xbfffe82c: 0x8048336 <main+> 0x73 0x213 0xbfffed40
0xbfffe83c: 0x7b 0xbfffead0 0x0 0x0
0xbfffe84c: 0x0 0x0 0x0 0x0
0xbfffe85c: 0x0 0x0 0x0 0x0
0xbfffe86c: 0x0 0x0 0x0 0x0
0xbfffe87c: 0x0 0x0 0x0 0x0
栈上的内容为信号栈sigframe:
根据此结构可以知道:
). 返回地址0xb7fff400,它指向vdso里的sigreturn
(gdb) x/10i 0xb7fff400
0xb7fff400 <__kernel_sigreturn>: pop %eax
0xb7fff401 <__kernel_sigreturn+>: mov $0x77,%eax
0xb7fff406 <__kernel_sigreturn+>: int $0x80
这个地址根据内核的不同而不同,我的内核版本是2.6.38。
). 信号处理程序完成后,会回到 eip = 0x8048336 的地址继续执行。 2.4 执行完sig_int函数后,进入了__kernel_sigreturn,接着回到了代码0x8048336处,一切恢复了正常。
(gdb) x/5i $pc
=> 0x8048336 <main+>: jmp 0x8048336 <main+>
(gdb) i r esp
esp 0xbfffed40 0xbfffed40 在用户层我们能看到的只有上面这么多信息了,可能有一个地方不能理解:在上面过程c中 从0xbfffe7ec起那一块栈上的内容从哪来的?(正常情况下堆栈esp应该一直指向在过程d中显示的esp值0xbfffed40) 现在来看看在上面这些现象之下,内核的堆栈发生了怎样的变化。 . 内核堆栈里发生的故事
3.1 发信号时
在 2.2 里当执行kill -INT 4639后,pid为4639的程序(也就是我们运行的 ./sigint)会收到一个信号,但是信号实际都是在内核里实现的。每个进程(这里只讲进程的情况,线程类似,线程有一个tid)都有一个pid,与此pid对应有一个结构 task_struct ,在task_struct里有一个变量 struct sigpending pending,当该进程收到信号时,并不会立即作出反应,只是让内核把这个信号记在了此变量里(它里面是一个链表结构)。当然,此时与内核堆栈还没有多大关系。 3.2 检测信号
如果只记录了信号,但没有相应反应,那有什么用啊。一个进程在什么 情况下会检测信号的存在呢?在<情景分析>里说到了:“在中断机制中,处理器的硬件在每条指令结束时都要检测是否有中断请求的存在。信号机制是纯软件的,当然不能依靠硬件来检测信号的到来。同时,要在每条指令结束时都来检测显然是不现实的,甚至是不可能的。所以对信号的检测机制是:每当从系统调用,中断处理或异常处理返回到用户空间的前夕;还有就是当进程被从睡眠中唤醒(必定是在系统调用中)的时候,此时若发现有信号在等待就要提前从系统调用返回。总而言之,不管是正常返回还是提前返回,在返回到用户空间的前夕总是要检测信号的存在并作出反应。” 因此,对收到的信号做出反应的时间是 从内核返回用户空间的前夕,那么有那些情况会让程序进入内核呢?答案是中断,异常和系统调用。简单了解一下它们发生时内核堆栈的变化。 //-----中断,异常,系统调用 : 开始
)在用户空间发生中断时,CPU会自动在内核空间保存用户堆栈的SS, 用户堆栈的ESP, EFLAGS, 用户空间的CS, EIP, 中断号 -
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 -
进入内核后,会进行一个SAVE_ALL,这样内核栈上的内容为:
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 - | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX 好了,一切都处理完时,内核jmp到RESTORE_ALL(它是一个宏,例:在x86_32体系结构下,/usr/src/kernel/arch//kernel/entry_32.S文件里包含该宏的定义) RESTORE做的工作,从它的代码里就可以看出来了:
首先把栈上的 ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX pop到对应的寄存器里
然后将esp + 把 “中断号 - ” pop掉
此时内核栈上的内容为:
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP
最后执行iret指令,此时CPU会从内核栈上取出SS, ESP, ELFGAS, CS, EIP,然后接着运行。 ) 在用户空间发生异常时,CPU自动保存在内核栈的内容为:
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 出错代码 error_code
(注:CPU只是在进入异常时才知道是否应该把出错代码压入堆栈(为什么?),而从异常处理通过iret指令返回时已经时过境迁,CPU已经无从知当初发生异常的原因,因此不会自动跳过这一项,而要靠相应的异常处程序对堆栈加以调整,使得在CPU开始执行iret指令时堆栈顶部是返回地址) 进入内核后,没有进行SAVE_ALL,而是进入相应的异常处理函数(这个函数是包装后的,真正的处理函数在后面)(在此函数里会把真正的处理函数的地址push到栈上),然后jmp到各种异常处理所共用的程序入口error_code,它会像SAVE_ALL那样保存相应的寄存器(没有保存ES),此时内核空间上的内容为:
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 出错代码 error_code | 相应异常处理函数入口 | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX
(注:如果没有出错代码,则此值为0) 最后结束时与中断类似(RESTORE_ALL)。 ) 发生系统调用时,CPU自动保存在内核栈的内容为:
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP
为了与中断和异常的栈一致,在进入系统调用入口(ENTRY(system_call))后会首先push %eax,然后进行SAVE_ALL,此时内核栈上的内容为
| 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | EAX | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX 最后结束时与中断类似(RESTORE_ALL)。
//-----中断,异常,系统调用 : 结束 中断,异常,系统调用这部分有一点遗漏的地方:检测信号的时机就是紧挨着RESTORE_ALL之前发生的。 3.3 对检测到的信号做出反应
如果检测到有要处理的信号时,就要开始做一些准备工作了,此时内核里的内容为(进入内核现场时的内容)
| 用户堆栈的SS1 | 用户堆栈的ESP1 | EFLAGS1 | 用户空间的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
(注:?的值有三个选择:中断号 - /出错代码 error_code/出错代码 error_code)
假设将要处理的信号对应的信号处理程序是用户自己设置的,即本文中SIGINT对应的信号处理程序sig_int。
现在要做的事情是让cpu去执行信号处理程序sig_int,但是执行前需要做好准备工作:
3.3. setup_frame
在用户空间设置好信号栈(struct sigframe)(假设设置好栈后esp的值为sigframe_esp,在本文中其值为0xbfffe7ec),即在2.3里看到的栈内容。
注:struct sigframe里至少包含以下内容:
用户堆栈的SS1, 用户堆栈的ESP1, EFLAGS1, 用户空间的CS1, EIP1, ES1, DS1, EAX1, EBP1, EDI1, ESI1, EDX1, ECX1, EBX1 3.3. 设置即将运行的eip的值为信号处理函数sig_int的地址(为0x80482e8),并设置用户ESP的值为sigframe_esp(为0xbfffe7ec),这是通过修改内核栈里的EIP和ESP的值实现的,因为在从系统调用里iret时,会从内核栈里取EIP,ESP。
这时内核栈的内核为:
| 用户堆栈的SS1 | 0xbfffe7ec | EFLAGS1 | 用户空间的CS1 | 0x80482e8 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1 最后,进行RESTORE_ALL,内核栈上的内容为:
| 用户堆栈的SS1 | 0xbfffe7ec | EFLAGS1 | 用户空间的CS1 | 0x80482e8 RESTORE_ALL里执行完iret后,寄存器内容为: EIP为0x80482e8(即sig_int),esp为0xbfffe7ec 。 于是用户空间到了步骤 2.3 3.4 信号处理程序完成以后
2.3 -> 2.4,进入了sig_return系统调用,在sig_return里,内核栈的内容为(每个名字后面加一个2以便与前面的1区分)
| 用户堆栈的SS2 | 用户堆栈的ESP2 | EFLAGS2 | 用户空间的CS2 | EIP2 | ? | ES2 | DS2 | EAX2 | EBP2 | EDI2 | ESI2 | EDX2 | ECX2 | EBX2
sig_return要做的主要工作就是根据用户栈里sigframe的值修改内核栈里的内容,使内核栈变为:
| 用户堆栈的SS1 | 用户堆栈的ESP1 | EFLAGS1 | 用户空间的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1 至此内核栈里的内容和进行信号处理前一样了。经过RESTORE_ALL后,用户堆栈里的内容也和以前一样(主要指ESP的值一样)。 "kill -INT 4639" 只是一段小插曲。程序从原处开始运行。

linux信号机制 - 用户堆栈和内核堆栈的变化【转】的更多相关文章

  1. Linux信号机制

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  2. Linux环境下用户空间与内核空间数据的交换方式

    在linux环境开发过程中,经常会需要在用户空间和内核空间之间进行数据交换. 介绍了 Linux 系统下用户空间与内核空间数据交换的几种方式 第一节:使用procfs实现内核交互简明教程(1) 第二节 ...

  3. 利用linux信号机制调试段错误(Segment fault)

    在实际开发过程中,大家可能会遇到段错误的问题,虽然是个老问题,但是其带来的隐患是极大的,只要出现一次,程序立即崩溃中止.如果程序运行在PC中,segment fault的调试相对比较方便,因为可以通过 ...

  4. 利用linux信号机制调试段错误(Segment fault)【转】

    转自:http://blog.csdn.net/ab198604/article/details/6164517 版权声明:本文为博主原创文章,未经博主允许不得转载. 在实际开发过程中,大家可能会遇到 ...

  5. xenomai内核解析之信号signal(一)---Linux信号机制

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1. Linux信号 1.1注册信号处理函数 ...

  6. linux信号机制与python信号量

    1.信号本质 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件.在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是进程间 ...

  7. Linux探秘之用户态与内核态

    一. Unix/Linux的体系架构 如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核).内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程 ...

  8. 【Linux 系统】Linux探秘之用户态与内核态

    一. Unix/Linux的体系架构 如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核).内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程 ...

  9. linux 信号机制

    文章目录 1. 实时信号非实时信号 2. 信号状态: 3. 信号生命周期: 4. 信号的执行和注销 信号掩码和信号处理函数的继承 信号处理函数的继承 信号掩码的继承 sigwait 与多线程 sigw ...

随机推荐

  1. POJ1326问题描述

    Description Mileage program of ACM (Airline of Charming Merlion) is really nice for the travelers fl ...

  2. UVA 10127题目描述

    Given any integer 0 ≤ n ≤ 10000 not divisibleby 2 or 5, some multiple of n is a number whichin decim ...

  3. GT-n8000平板开机密码忘记 解决办法

    开机密码忘记了 1: 关机 (不需要连接电脑) 2: 音量上 开机键 一起按住 别松手 直至 进入 英文界面 Android system recovery <3e> 备注:音量键方向错误 ...

  4. 淘宝杨志丰:OceanBase--淘宝结构化大数据解决之道

    时至今日,“Big data”(大数据)时代的来临已经毋庸置疑,尤其是在电信.金融等行业,几乎已经到了“数据就是业务本身”的地步.这种趋势已经让很多相信数据之力量的企业做出改变.恰逢此时,为了让更多的 ...

  5. RouterOS软路由常用命令

    修改用户密码 [admin@MikroTik]>/user                               #进入操作路径 [admin@MikroTik]/user>prin ...

  6. C读取文件

    C读取文件,这种写法不会多一行. #include "stdafx.h" #include <vector> using namespace std; struct P ...

  7. 01.Box2dWeb入门教程

    1.下载box2dweb.直接在页面中引用即可. -Box2D.js是未压缩版 -Box2d.min.js是压缩版 2.编写HelloWorld代码. <!DOCTYPE HTML> &l ...

  8. QString 和 TCHAR 的相互转换

    参考资料: http://www.cnblogs.com/fuyanwen/p/3200536.htmlhttp://www.cnblogs.com/wendao/archive/2012/07/27 ...

  9. gradle编译andoroid

    下载sdk ,api, build tool 1. 取消离线模式 2. 取消代理模式

  10. unresolved external symbol __report_rangecheckfailure 解决思路

    __report_rangecheckfailure  是用来检查堆栈缓存溢出的,如果编译的时候打开GS(project property-->Configuration properties- ...