背景:内核刚刚处理完中断和异常.现在它要返回到@current进程了.然而,在返回以前,内核总会习惯性检查一下@current的TIF_SIGPENDING标志,以便确定是否有尚未处理的信号.很不幸,确实有信号正在挂起队列上排队.于是,内核开始启动do_signal()函数…


一. do_signal()函数

遍历@current的挂起信号队列:
1.如果信号的传递方式是SIG_IGN,则忽略这个信号;
2.如果信号的传递方式是SIG_DFL,则根据具体的信号执行相应的默认操作;
3.如果信号有一个处理函数,终止遍历,执行这个信号处理函数;
4.如果信号是0,则检查并处理系统调用的重新执行.
信号处理函数或系统调用的重新执行会返回到内核.紧接着,当内核尝试恢复原程序的执行时,又会陷入do_signal()函数.最终结果是,do_signal()将处理挂起信号队列中的每一个信号.

1.参数

  • regs 栈,current在用户态下寄存器的内容存于此处
  • oldset 阻塞信号的位掩码数组(已被删除)

2.说明

1. 通常只在CPU返回到用户态时才调用此函数:TIF_SIGPENDING标志的检查总是在内核准备返回到用户态时进行.
2. 反复调用dequeue_signal()直到pending和shared_pending队列为空
3. 调用栈

  • do_signal()
    在返回到用户态前,处理@current的每一个未阻塞的挂起信号.

    • get_signal_to_deliver()
      遍历挂起信号队列,自行忽略信号或为信号执行默认操作.
    • handle_signal()
      为执行信号处理程序做准备.

      • setup_rt_frame()
        复制,修改@current的硬件上下文.

3.复杂性

  • 竞争条件,冻结系统,产生内存信息转储,停止/杀死整个线程组
  • 中断处理程序调用此函数(可能性?)
  • 当current正受到其他进程监控的时候怎么办?
    do_notify_parent_cldstop()和schedule()
  • 待处理的信号是一个被忽略的信号(可能性?)
  • 待处理西信号需要被执行缺省操作
  • 待处理信号有一个信号处理函数

二.get_signal_to_deliver()函数

这个函数遍历挂起信号队列,处理被显式忽略的信号并并为具体信号执行相应的默认操作.如果遇到信号0(处理系统调用的重新执行)或者遇到注册了信号处理程序的信号,则终止遍历,返回到do_signal()

1.函数的执行过程

  • try_to_freeze()
    linux冻结系统在信号系统中的钩子函数.linux冻结系统利用信号系统完成自己的功能.
  • if(signal->flags & SIGNAL_CLD_MASK)
    每一个停止的进程在苏醒后都会运行这个检查.在这里,我们检查一下是不是需要通知@current的父进程
  • 陷入一个无限循环
    • 在循环的开始,检查是不是需要停止整个线程组.
    • 从挂起队列上摘下一个信号
    • 如果信号是0,则返回0
    • 如果信号被显式忽略,continue
    • 如果信号有一个处理程序,终止循环,返回信号ID
    • 执行默认操作.为具体的信号执行相应的信号处理程序

2.信号的缺省操作

  • 当接收进程是init()时,丢弃信号;
  • 当信号是SIGCONT, SIGCHLD, SIGWINCH, SIGURG时,忽略信号;
  • 当信号是SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU时,停止整个线程组;
  • 缺省操作:Dump
  • 缺省操作: Terminate: do_group_exit()(组退出)

do_signal_stop()
组停止

P440

三.handle_signal()函数

信号处理程序必须在用户态执行:

原程序– –信号处理程序— –信号处理程序– –原程序
|1(中断) |2 |3(系统调用) |4 |5 |
–内核态产生信号– –系统调用—- –内核–

多次状态切换引起复杂性:
- 内核态向用户态向用户态切换时,内核态堆栈被清空.因此,第2次特权级切换以后,原程序的硬件上下文丢失
- 信号处理程序调用系统调用后,第4次特却级切换时,内核必须返回到信号处理程序而非原程序

信号的处理过程回顾
一个非阻塞的信号被发送给一个进程.当中断或异常发生时,进程切换到内核态.正要返回到用户态前(return_from_intr),内核执行do_signal()函数,这个函数从get_signal_to_deliver()得到一个注册信号.于是,handle_signal()函数被调用来为这个注册信号的处理搭建环境.
当进程又切换回用户态时,因为信号处理程序的起始地址已经被放进程序计数器中, 因此开始执行信号处理程序.当信号处理程序终止时, setup_frame()函数放在用户态堆栈中的返回代码就被执行.这个代码调用sigreturn()系统调用,相应的服务例程把原程序的用户态堆栈的硬件上下文复制到内核态堆栈,并把用户态堆栈恢复到它原来的状态(restore_sigcontext()).当这个系统调用结束时,普通进程就因此能恢复自己的执行.

1.背景

CPU处在内核态,即将返回到用户态.但是,此时捕捉到了一个注册信号.内核必须做些什么,以保证:

  • 内核不会返回到原程序,而是返回到信号处理程序;
  • 信号处理程序执行结束后必须返回内核;
  • 从内核再返回到原程序的时候,原程序的硬件上下文不能丢失;

思考一: 在中断或异常发生的时候,程序是如何陷入内核的?
当执行了一条指令后,cs和eip这对寄存器包含下一条将要执行的指令的逻辑地址.在处理那条指令前,控制单元会检查是不是已经发生了一个中断或异常.如果有的话,控制器就会依次执行下列步骤:
1.确定中断向量;
2.访问IDT,找到与中断向量对应的中断门或陷阱门;
3.根据IDT中的门描述符,借助GDT,找到中断或异常处理程序的逻辑地址.
4.进行特权级检查.要求CPL大于等于中断处理程序逻辑地址的DPL,即引起中断程序的特权必须低于或等于中断处理程序的特权;
5.如果CPL与DPL不同,这通常意味着在用户态请求内核态的中断或用户处理.此时,利用TSS段把栈切换到内核态.
注意,上述过程是具体于Linux的.ULK145的叙述则不针对任何具体的操作系统,是单纯的描述Intel的硬件处理过程,因此叙述过程始终没有出现”内核态”和”用户态”等具体于操作系统的术语,十分严谨.

思考二: 中断或异常发生的时候,用户态进程的硬件上下文需要保存吗?保存于何处?
不管是中断还是异常,用户态进程的硬件上下文都会保存在当前的堆栈中.由于在保存以前已经进行了硬件处理,所当前的堆栈通常是内核栈.具体的:
@error_code的第一步就是”把高级C函数可能用到的寄存器保存在栈中”;
@common_interrupt的第一步就是”SAVE_ALL”

思考三: do_signal()结束以后,如何返回到信号处理程序而不是原程序?
只需要对保存的硬件上下文做一些修改即可.

思考四: 当信号处理程序恢复执行的时候,如何保证原程序的硬件上下文不会丢失?
原程序的硬件上下文会被复制两份.一份压入用户态堆栈中保存起来.一份稍作修改以后,用作信号处理程序的硬件上下文.

思考五: 信号处理程序结束以后如何返回到内核?
信号处理程序被调用的时候,它的返回地址(通常就是下一条指令的地址)会被压栈保存(是吗?).信号处理程序在它的返回地址之上建立自己的栈.
当信号处理程序执行结束以后,它的返回地址从栈中弹出.CPU开始从这个地址处继续执行因此,为了让信号处理程序结束以后返回到内核,只需要修改信号处理程序所使用的堆栈即可.

2.这个函数做了什么?

  • 将原程序的硬件上下文复制成两份:
setup_sigcontext(&frame->sc, fpstate, regs, set->sig[0])
  • 其中一份保存在帧中,并随帧一起压入用户态堆栈,以备将来恢复原程序的执行.恢复原程序执行的任务由sigreturn()系统调用完成,信号处理程序会通过pretcode返回到这个系统调用.
  • 另一份上下文(regs)作为信号处理程序的硬件上下文使用.handle_signal()会修改这份硬件上下文,将上下文中的返回地址改为信号处理程序的地址,并设置正确的栈顶地址;
  • 把帧放在信号处理程序的下面.帧的顶部存放着sigreturn()系统调用的入口地址,这样当信号处理程序返回时,这个入口地址会被当作返回地址使用,于是系统陷入sigreturn系统调用.
  • 检查信号标志.在执行信号的时候,新来的信号是要被阻塞的.哪些信号要被阻塞呢?
    1. 进程描述符里面规定要阻塞的信号是要被阻塞的: current->blocked
    2. 这个信号处理函数规定要阻塞的信号,是要被阻塞的: ka->->sa.sa_mask
    3. 当前信号,是要被阻塞的: sig

3.帧(sigframe)

pretcode
这个地址被放在帧的最顶部,由于内存中栈是倒着用的,所以以内存的角度叙述的话,这个地址也是帧的起始地址.帧被”悄悄地”放在信号处理程序的下面,因此pretcode会被当作信号处理程序的返回地址使用.
sig
信号编号,这是信号处理程序所需要的参数.
sc
用户态进程的上下文,这个上下文是原程序第一次陷入内核的时候从用户态堆栈复制而来的.现在,这个上下文又被内核放入帧中,随帧一起压入用户态堆栈.
fpstate
用户态进程的浮点寄存器内容.这个也算是硬件上下文的一部分把?
extramask
被阻塞的实时信号的位数组.这是位数组,这里面都是实时信号,这些实时信号都被阻塞了.问题是,这个帧域有什么用?
retcode
sigreturn()系统调用的8字节代码.已不再使用.

关于函数调用时栈状态的细节,可以百度一下x86上的函数调用.

4.set_frame()函数

计算帧在用户态堆栈的起始地址,然后认真填入帧的每一个字段.特别地,原程序的硬件上下文会在这个过程中被复制进用户态对战.
修改依然留在内核态堆栈中的硬件上下文.在这个过程中,esp和eip会被修改为正确的值.esp指向帧的起始地址,eip指向信号处理程序的起始地址.

参数
sig: 信号ID
ka: k_sigaction表
oldset: 阻塞信号掩码
regs: 内核态堆栈中的用户态硬件上下文的地址

至此,handle_signal()返回到do_signal(), do_signal()也立即返回.do_signal()返回时,当前进程恢复它在用户态的执行.而由于setup_frame()函数已经偷天换日,所以eip寄存器指向了信号处理程序的第一条指令,esp寄存器已压入用户栈顶.所以,信号处理程序开始执行.

当信号处理程序执行结束时,返回到栈顶地址,也就是pretcode.pretcode会稍作准备,然后陷入sigreturn系统调用,这个系统调用结束时,CPU控制权返回到原程序.


四.返回到原程序

信号处理程序结束时,返回栈顶地址.栈顶地址指向帧的pretcode字段所引用的vsyscall页中的代码,这段代码发出0x80中断,开始调用sigreturn()系统调用.

首先,sys_sigreturn()首先找到帧在用户态堆栈的地址,这可以通过esp字段轻易完成.

然后,恢复current->blocked字段.何为恢复?刚才,为了执行信号处理程序,我们修改了current->blocked字段,强迫它吞并了信号处理程序的阻塞位和所处理信号的阻塞位.现在,我们要将这个字段恢复到以前的状态.这样,为信号处理函数执行而屏蔽的所有信号被解除阻塞

接下来,我们重新调用recalc_sigpending()函数,.如果有新的阻塞信号,我们就传递它们.这里,我认为,所谓”阻塞信号”指的是,信号可以产生,但是不会被传递.解除阻塞意味着信号终于可以被传递了.

最后,我们要访问帧的sc字段,这个字段指向原程序在用户栈中的硬件上下文,我们把这个上下文拷贝到内核栈.所有这一切交给一个函数来完成:restore_sigcontext(),这个函数还会将用户栈中的硬件上下文删除.

五. 系统调用的重新执行

有的时候,进程通过系统调用向内核请求服务,比如读写一个文件.然而,内核并不能总是满足进程的请求,此时,内核将这个进程置为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE状态.

问题来了,如果进程由于不能完成系统调用而处在I或UI状态下,一个信号产生在这个进程上,会发生什么呢?

先来说明处在I状态的进程把.如果一个进程处在I状态,并且收到一个信号.那么内核不会等待系统调用完成,而是直接将进程置为R态.进程苏醒后,切换会用户态,此时信号被传递给进程.当这种情况发生的时候,系统调用没有完成它的工作,系统调用历程会向内核返回一个错误码.注意,用户进程并不会收到这个错误码,用户进程获得的唯一错误吗是EINTER,这个错误码告诉用户进程系统调用没有执行完.

让我们回到内核错误码上来.内核掌握着两点关键信息:

  1. 从系统调用服务历程返回的错误码
  2. 用户进程对信号的传递方式.

根据这两点信息,内核从如下动作中选择一个:

  • 不重新执行系统调用;
  • 重新执行系统调用;
  • 根据SA_RESTART标志的值决定是否重新执行系统调用.

上述这一切的前提是,进程因执行系统调用失败而挂起.那么,内核是怎么知道进程挂起的原因是系统调用执行失败的呢?这就是regs硬件上下文的orig_eax字段发挥作用的地方了.P446

系统调用的重新执行需要分情况讨论:

  • 系统调用被未捕获的信号中断
  • 系统调用被捕获的信号中断

这是自然的,因为前者不需要执行信号处理程序,后者则需要执行信号处理程序.

1.系统调用被未捕获的信号中断

在这种情况下,do_signal()修改regs硬件上下文,让eip指向int $0x80或者sysenter指令.这样,当原程重新开始执行的时候,它会直接重新开始执行系统调用.

有一种特殊情况,eax中存放的是restart_syscall()的系统调用号,系统调用服务例程返回RESTART_RESTARTBLOCK,这个错误代码仅仅用于与时间有关的系统调用.为什么?如果一个系统调用要求进程睡眠20ms,10ms后进程被信号中断,如果重新执行这个系统调用,那么进程最终会睡眠30ms.

怎么解决这个问题呢?这种情况发生时,内核不会忠实地完全重新执行系统调用.当这种情况发生时,正在执行系统调用服务历程的内核会将一个特别定制的系统调用服务历程的地值放在thread_info的restart_block字段,并在返回错误码-ERESTART_RESTARTBLOCK.这样,sys_restart_syscall()服务例程只执行这个特别定制的函数.

2.系统调用被捕获的信号中断.

梳理思路.进程因系统调用失败而挂起.此时捕获到一个信号,进程被唤醒,并从系统调用服务例程返回,注意,应该不会回到用户态,而是直接处理信号.

handle_signal()函数会根据内核收到的出错码和sigaction表的SA_RESTART标志来决定是否必须重新执行未完成的系统调用.

如果系统调用需要重新执行,那么,handle_signal()会修改eip:

regs->ip -= 2; //重新指向刚才的系统调用指令

然后继续完成handle_signal()剩余的工作,即更新内核栈与用户栈,为信号处理程序搭建环境.注意,在setup_frame()的时候,被复制进帧中保存起来的@ip是修改(-2)以后的ip,这个ip指向刚刚执行的系统调用指令.

这样do_signal()在handle_handle()后紧接着返回用户态执行信号处理程序.信号处理程序执行结束以后,通过sigreturn()系统调用陷入内核,当sigreturn()再次返回用户态时,原程序的系统调用会被重新执行.用户程序紧接着陷入内核执行系统调用,不会执行信号处理程序.

否则,系统调用不需要重新执行.这时候,handle_signal()会在@reg->ax中放入EINTR以向原程序返回错误代码.然后继续进行andle_signal()接下来的工作.即更新内核栈与用户栈,为信号处理程序的执行搭建环境.

[ULK11]信号(三):从信号传递到原程序恢复执行相关推荐

  1. 好的原程序做出好的软件

    好的原程序做出好的软件 有些人会想:只要程序运行结果好,就不管原程序编得怎样.但绝对不是这样的.软件不是一次性就作完的,有必要做修改,扩展等管理.所以原程序要尽量作成易看懂,管理方便. 这样做,第一是 ...

  2. 计算机5800计算道路标高程序,Casio fx-5800P计算器三个公路基本测量程序编写与应用...

    原标题:Casio fx-5800P计算器三个公路基本测量程序编写与应用 引论:casiofx-5800P计算器测量程序在计算机网络上多有流传,但有的测量主程序算法复杂,编程繁琐:有的程序内容表达错误 ...

  3. 三人抢答器逻辑电路图_三人抢答器plc程序图分享

    plc梯形图是使用得最多的图形编程语言,被称为PLC的第一编程语言.梯形图与电器控制系统的电路图很相似,具有直观易懂的优点,很容易被工厂电气人员掌握,特别适用于开关量逻辑控制.梯形图常被称为电路或程序 ...

  4. 基于VB算法+Picture+Timer控件制作的39种动画效果,类似屏保(完整原程序)

    基于VB算法+Picture+Timer控件制作的39种动画效果,类似屏保(完整原程序) 动画播放器程序,在WIN2003调试通过,详细请自行下载进行学习测试,程序大小13K 下载地址:http:// ...

  5. 会声会影2022最高版中文原程序新试用版

    会声会影2022是一款用来制作视频的软件,那么win系统能不能安装会声会影?一定的告诉人人,windows10系统可以安装会声会影.win10若何安装会声会影?首先我们先要找到这款软件的安装包!人人可 ...

  6. 基于VB算法+Picture+Timer控件制作的39种动画效果,类似屏保(完整原程序) (转)

    基于VB算法+Picture+Timer控件制作的39种动画效果,类似屏保(完整原程序) (转)[@more@] 基于VB算法+Picture+Timer控件制作的39种动画效果,类似屏保(完整原程序 ...

  7. matlab非线性方程组求解得到矩阵,非线性方程组求解——附Matlab原程序

    在科学与工程计算中,经常遇到求解非线性方程组的问题:非线性方程组在收敛速度及收敛性比线性方程组要差,特别对于非凸的非线性方程组,其求解更是困难.下面简要介绍非线性方程组的三种解法--牛顿法.拟牛顿法. ...

  8. 黯然微信小程序杂记(三):微信小程序实现倒计时功能 附讲解教学 附源码

    黯然微信小程序杂记(三):微信小程序实现倒计时功能 附超详细注释 附源码 一.功能描述 二.界面展示 三.test.wxml代码 四.test.js代码(注释很详细 很易懂) CSDN私信我,有关微信 ...

  9. 自己去年用intraweb写的模仿动网论坛的原程序,用的是动网论坛的数据库

    自己去年用intraweb写的模仿动网论坛的原程序,用的是动网论坛的数据库 动网个人服务器系统1.0 可以在不安装iis的情况下,在win98,win2000,winxp跑动网论坛 使用时,只需将动网 ...

最新文章

  1. hbase源码系列(一)Balancer 负载均衡
  2. HDU5248:序列变换(二分)
  3. python windows开发_windows 下 python 开发是一种什么样的体验?
  4. PHPCMS 核心代码与 www 分离部署
  5. 深度学习(七十二)tensorflow 集群训练
  6. 记录一次java.lang.ClassCastException的java类型转换异常解决方案-附最终解决方案
  7. HDU4417 线段树 + 离线处理
  8. django xadmin ForeignKey display
  9. 用VBA实现OUTLOOK接收新邮件后的自动转发
  10. 软考中级网络工程师学习笔记(知识点汇总)简略版
  11. IDE、SCSI、SATA、USB、并口和串口
  12. 动手了!限19天,大米云主机满10送2手慢无!
  13. 向量化回测系列2——全市场股票回测
  14. 西南交大大学生营养早餐优化食谱
  15. cv2.error: OpenCV(4.5.2) C:\Users\runneradmin\AppData\Local\Temp\pip-req-build-1y7gm6kn\opencv\modul
  16. Element组件框架
  17. 【BZOJ3503】【Cqoi2014】和谐矩阵 高斯消元,解异或方程组
  18. Vue3.0系列(一): VUE3.0的新特性
  19. TCP/UDP网络的通信
  20. VMware Network Adapter VMnet1和VMnet8 未识别的网络

热门文章

  1. 生病原来不是因为缺药,而是因为缺营养
  2. 小米11不再送充电头了吗?!
  3. 鼠标右键菜单跑到左边改回右边显示
  4. QQ浏览器彻底卸载方式
  5. 【大数据面试题】(二)Hive 相关面试题总结
  6. 解决The valid characters are defined in RFC 7230 and RFC 3986
  7. 剪印去水印-剪印怎么去除水印?
  8. 腾讯云GPU服务器NVIDIA P40 GPU、P4、T4和GPU自由卡详解
  9. 搜集网络上比较好的中文语料库
  10. 今日的质量,明日的市场--谈谈软件登记测试