一、了解中断
在学习中断之前需要了解中断的概念:中断是指CPU 在执行程序的过程中,出现了某些突发事件急待处理, CPU 必须暂停当前程序的执行,转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。需要注意的就是中断不仅仅是打断当前的执行任务,还包括了后续的还原的工作
二、中断的分类
根据中断来源可以将中断分为硬中断和软中断:
硬中断:由电脑主机的硬件中断控制芯片(如8259A芯片)或ARM中断控制器发出的中断
软中断:CPU自行保留的中断、系统调用异常
三、中断流程
在看代码前要先初步了解中断的流程,后续再结合中断流程代码分析,整个流程就更加清晰明了:
- CPU切换工作模式
- 寄存器的拷贝与压栈
- 设置中断异常向量表
- 保存正常运行的函数返回值
- 跳转到对应的中断服务函数上运行
- 进行模式的复原和寄存器的复原
- 跳转回起初工作的函数上继续运行
可以看到1、2步属于中断前的处理,3、4、5步属于中断的执行过程,6、7步就属于中断后的还原过程。
具体kernel代码结构如下:
中断的中断前处理和中断还原处理 | 中断的执行过程 | |
硬件中断处理过程 | asm.s | trap.c |
软件及系统调用处理过程 | system_call.s | fork.c signal.c exit.c sys.c |
.s后缀代表的是汇编文件,这些文件之所以按表格中的顺序摆放,是因为第一列控制着中断的中断前处理和中断还原处理,第二列控制着中断的执行过程;第一行表示硬件中断处理过程,第二行表示软件及系统调用处理过程,接下来看代码的具体实现:
在看汇编代码的时候可能看不懂,我们可以将其画成一幅部分栈的内存结构图(由低位向高位压入栈,表格右侧并不是代表真实地址,仅为了便于观看和下文说明):
SS地址 | 18 |
ESP的值 | 17 |
EFLAGS | 16 |
CS | 15 |
EIP发生中断的地址 | 14 |
有出错码就压栈,无出错码就不做 | 13 |
EIP函数的返回值寄存器值 | 12 |
中断处理函数的调用C的函数入口的地址(EAX) | 11 |
EBX | 10 |
ECX | 9 |
EDX | 8 |
EDI | 7 |
ESI | 6 |
EBP | 5 |
DS | 4 |
ES | 3 |
FS | 2 |
ERROR CODE(有错误码则入栈,无错误码压0也要占位) | 1 |
ESP(发生中断的初始地址,实际存储的也就是EIP) | 0 |
Linux中在中断前会先将所有的寄存器的值入栈(8086处理器为例),包括SS、EFLAGS、ESP、CS、EIP、错误码,这步操作对应着上图地址17~13。
先将C函数do_divide_error地址压入栈,接下来判断有无错误码,上图均表示的是无错误码的情况。首先把发生中断的函数的地址交换给EAX寄存器:
然后地址10~2都是寄存器压栈的过程:
地址1表示将错误码压入栈,没有错误码就压0:
然后代码的意思是又将原来的ESP压入栈:
接下来压入EDX寄存器并为它和DS、ES、FS寄存器赋值:
接下来代码中call了EAX寄存器,那么EAX在上面栈的结构图中已经写到它存储的是“中断处理函数的调用C的函数入口的地址”,所以这一步是在调用EAX中存储的地址所对应的C函数:
接下来将指针从栈顶向栈底方向移动8个字节,也就是两个地址,此时指针指向栈结构图中的地址2,然后以FS寄存器为开始,将栈中数据弹出最后返回:
四、总结
以上就是asm.s文件中对于中断的前期处理和还原处理过程,执行过程主要位于trap.c文件中,将上面的流程再次总结如下:
先将所有的寄存器的值入栈,将错误码入栈,将当前函数返回值入栈为了在中断执行过后可以复原,调用对应中断服务函数,出栈函数返回值,返回所有入栈的寄存器值
本文仅描述了无错误码的情况,针对有错误码的情况,linux会根据具体的错误码从而调用不同的中断服务函数,例如do_init3、do_nmi函数。