Tips: Linux Kernel Based on x86 Platform
1. 异常(也称同步中断)分类:
a) 处理器探测异常,根据eip寄存器值可进一步分为:
i. 故障(fault):当异常处理程序终止时,eip指示的引起故障的指令将被重新执行。
ii. 陷进(trap):主要用于调试程序(如断点)
iii. 异常中止(abort):严重错误。异常处理程序将强制受影响的进程终止。
b) 编程异常(也叫软中断software interrupt):有编程者请求发生,由int或int3指令触发。
2. 中断描述符(IDT):
a) idtr寄存器指示IDT的线性基地址及最大长度;
b) IDT包含三种类型的描述符:
i. 任务门:存放取代当前进程的那个进程的TSS选择符;
ii. 中断门:包含中断处理函数所在段的段选择符和段内偏移量;控制权转移时,处理器清IF标志;
iii. 陷进门:包含异常处理函数所在段的段选择符和段内偏移量;控制权转移时,处理器不清IF标志;
3. 异常的硬件处理:
a) 确定异常向量i,读取由idtr指向的IDT表的第i个异常(或中断)描述符;
b) 根据得到的中断描述符的段选择子和gdtr内容得到异常处理程序所在段的段描述符;
c) 特权级检测:将存放在cs寄存器中的当前特权级CPL与上述过程得到的段描述符特权级(DPL)做比较;
d) 如果特权级发生变化,控制单元必须开始使用与新特权级相关的栈(主要相关寄存器为ss、esp);
e) 保存eflags、cs、eip内容及可能的错误码;
f) 用IDT第i个描述符的段选择子和偏移量分别装载cs和eip寄存器,程序控制权转移给异常处理控制路径。
注:异常处理结束后,控制单元将执行上述过程的反操作。
4. 中断描述符的初始化(在系统的启动过程中):
a) setup()函数为内核程序的执行建立环境,将CPU从实模式切换到保护模式。在此过程中将建立起一个临时的中断描述符表;
b) startup_32()函数(包含在arch/i386/kernel/head.S)调用setup_idt()用空的中断处理程序填充IDT,并用IDT表的地址来填充idtr寄存器;
c) start_kernel()函数完成Linux内核的初始化工作。过程中将调用trap_init()函数和init_IRQ函数以完成IDT初始化。其中trap_init()完成非屏蔽中断和异常描述符的初始化(涉及到的设置函数:set_trap_gate() 、set_intr_gate()、set_system_gate()、set_system_intr_gate()、set_task_gate())。
——————————————————————上述大部分过程可推理至中断———————————————————————
5. 异常处理:
a) 异常发生时,内核(异常处理程序)会向引起异常的进程发送一个信号,这个进程便采取必要的步骤来恢复或中止运行。
b) Linux利用CPU异常管理硬件资源:
i. “Device not available”异常与cr0的TS标志一起用来把新值装入浮点寄存器;
ii. “Page Fault”异常推迟给进程分配新的页框,直到不能再推迟为止。
c) “Double fault”异常表示内核有严重的非法错误,该异常处理是通过任务门完成的,将导致:处理器在自己的私有栈上执行doublefault_fn()异常处理程序。
d) 异常处理程序流程:
Handler_name:
pushl $0/*only for some exceptions */
pushl$do_handler_name
jmp error_code/*会从栈中获取do_handler_name的地址*/
/*……
……*/
jmp ret_from_exception
6. 从异常返回:
a) 异常或中断返回以恢复某个程序的执行为目的,但是在这样做之前必须考虑几个问题:
i. 如果内核控制路径的并发执行数量为1,CPU必须切换到用户态;
ii. 如果有任何挂起进程的切换请求,内核就必须执行进程调度;否则,把控制权还给当前进程;
iii. 如果有信号(已被挂起)发送到当前进程,就必须处理它;
iv. 如果调试程序正在跟踪当前进程的执行,就必须在进程切换回到用户态之前恢复单步执行;
v. 如果CPU处于Virtual-8086模式,这种情况必须特殊处理。
b) 异常和中断返回的两个入口点:ret_from_intr()、ret_from_exception()。