前言
学习笔记,基于 Linux0.11 简单介绍下 Linux 信号处理流程
以前看完 Linux0.11 的时候,感觉没什么用,到后面看的书多了才有了感觉,这本书写的都是内核的骨的感觉啊。。。
因为信号会用内核态调用到用户态中,与硬件高度相关,这大概就是 《Linux 内核设计与实现》中没有介绍他的原因吧,可惜了。
参考资料:
x86 堆栈切换相关知识
《Linux 内核完全注释》
《 Unix 环境高级编程》
应用编程
表 10-1 UNIX 系统信号
例子程序
signal() 与 sigaction() 区别
内核流程
信号处理位置
信号处理是在系统调用完成后,返回用户空间前检查信号处理的
处理处理函数:
地址空间与堆栈
下面简单介绍下 Linux0.11 中程序的地址空间,在 Linux0.11 上,内核将 32 总线寻址的 4G 地址空间以 64M 为单位分给各个进程使用,
每个进程使用不同的地址空间
针对每个进程,其内部划分又如下所示,分为代码段,数据段,bss, 堆栈,环境参数块
其中堆栈用于保存函数调用帧,比如函数 1 调用到函数 2 时,会将相关参数与返回地址保存在栈中,结构如下:
所以函数调用后,不想返回之前的调用函数,则可以通过修改调用栈实现。但这里有个问题,就是针对信号处理,
因为信号是在内核态检查的,内核态与用户态的堆栈是不一样的,所以还涉及到一个堆栈切换的问题。
但核心思想不变,如果想让用户态程序调用到我们自己定义的信号处理函数,那就要修改用户态的调用栈
内核态与用户态
硬件特权级示意图:
软件上权限体现在哪里呢?体现在代码段数据段的 DPL 字段中
再细一点,就是体现在 GDT 表中各程序的代码段数据段中,表示不同的特权级:
那会在哪里用到中转呢?毕竟切换前后会不会特权级变化,起码需要保存当前的特权级,然后跟要换入的任务比较,
这里就是通过 TR 指向的 TSS 保存当前程序的代码段,数据段的,里面有当前进程物特权级:
这也就引出了堆栈段,因为不同的特权级执行的代码是需要隔离的,所以堆栈也是使用各自的,不然有些用户程序,修改特权级堆栈瞎搞,
这不是会出事嘛。
所以特权级切换也会有堆栈切换,堆栈切换时,会传入参数及原堆栈指针位置:
用户栈与内核栈
用户栈位置前面已经有图描述过了,在用户地址空间中位置如下:
而内核堆栈则在分配 task_struct 结构体所在一页内存的高端地址中,所以堆栈大小是有限的,需要注意调用栈不能嵌套太深
信号调用流程
前面巴拉巴拉那么多,就是引出两个概念:
1. 用户态跟内核态使用的堆栈是不一样的
2. 函数调用过程有调用栈,可以通过修改调用栈来改变返回时的函数调用路径
所对针对信号调用处理流程就是类似这样的:
1. 系统调用,在内核态检查信号,如果有,就修改:
1.1 内核态堆栈,让其返回用户态时,直接去调用信号处理程序
1.2 用户态堆栈,让其执行完信号处理程序后,调用 sa_restorer 恢复用户堆栈至没调用信号处理函数一样
2. 信号处理程序执行完成后,因为用户态堆栈也会修改了,会执行 sa_restorer 恢复信号调用前堆栈,从而继续执行系统调用下一条
相关堆栈修改如下:
插一句,这个 sa_restorer() 是标志 C 库里面的。