标签:
中断或异常发生之前
当 CPU 执行了当前指令之后,CS 和 EIP 这对寄存器中所包含的内容就是下一条将要执行 指令的逻辑地址。在对下一条指令执行前,CPU 先要判断在执行当前指令的过程中是否发生 了中断或异常。
如果发生了一个中断或异常
那么 CPU 将做以下事情
• 确定所发生中断或异常的向量i(在 0~255 之间)。
• 通过 IDTR 寄存器找到 IDT 表,读取 IDT 表第i项(或叫第i个门)。
• 分两步进行有效性检查:首先是“段”级检查,将 CPU 的当前特权级 CPL(存放在 CS 寄存器的最低两位)与 IDT 中第i项段选择符中的 DPL 相比较,如果 DPL(3)大于 CPL(0), 就产生一个“通用保护”异常(中断向量 13),因为中断处理程序的特权级不能低于引起中 断的程序的特权级。这种情况发生的可能性不大,因为中断处理程序一般运行在内核态,其 特权级为 0。然后是“门”级检查,把 CPL 与 IDT 中第 i个门的 DPL 相比较,如果 CPL 大于 DPL,也就是当前特权级(3)小于这个门的特权级(0),CPU 就不能“穿过”这个门,于是 产生一个“通用保护”异常,这是为了避免用户应用程序访问特殊的陷阱门或中断门。但是 请注意,这种“门”级检查是针对一般的用户程序,而不包括外部 I/O 产生的中断或因 CPU 内部异常而产生的异常,也就是说,如果产生了中断或异常,就免去了“门”级检查。
(这里的免去没大明白,是不进行有效性检查了?直接跳过这一步?)
• 检查是否发生了特权级的变化。当中断发生在用户态(特权级为 3),而中断处理程 序运行在内核态(特权级为0),特权级发生了变化,所以会引起堆栈的更换。也就是说,从 用户堆栈切换到内核堆栈。而当中断发生在内核态时,即 CPU 在内核中运行时,则不会更换堆栈。
找到对应的门
异常处理没说怎么在idt_table中找到对应的门的,中断倒是因为有中断号,可以根据这个中断号去idt_table中找到门,反正cpu硬件干的
堆栈变化
如果堆栈变化则将当前的(SS,ESP)压入栈中,此时的栈为内核栈,因为一旦出现中断或异常,堆栈就切换到了内核堆栈,上面这些操作是由硬件完成的,至于具体怎么操作的也没大明白,压内核栈不是得先更新成内核的SS和ESP吗?ESP更新了,那压的ESP不就是内核的?看见书中有提到此时的内核堆栈是空的,也许压的固定位置吧。看到后面搞懂了再更新。
更新:有看到说从TSS中取到的内核堆栈,难道是用movl实现的?
处理程序格式对于异常和中断是不同的
异常处理
handler_name: |
处理程序名称如:debug,nmi,int3,overflow,bounds等异常名,与异常类型相对应 |
pushl $0 /* only for some exceptions */ |
没有错误码的需要压一个0,使内核堆栈保持一致性 |
pushl $do_handler_name |
将真正的处理函数地址压栈 |
jmp error_code |
跳至公共异常处理 |
此时堆栈状态
error_code: |
公共异常处理 |
pushl %ds |
ds入栈 |
pushl %eax |
eax入栈 |
xorl %eax,%eax |
eax清零 |
pushl %ebp |
ebp入栈 |
pushl %edi |
edi入栈 |
pushl %esi |
esi入栈 |
pushl %edx |
edx入栈 |
decl %eax |
# eax = -1 |
pushl %ecx |
ecx入栈 |
pushl %ebx |
ebx入栈 |
cld |
清eflag中的DF标志,使EIP朝向增长方向 |
movl %es,%ecx |
es移入ecx |
movl ORIG_EAX(%esp), %esi |
# get the error code,移入esi |
movl ES(%esp), %edi |
# get the function address,上面压栈的do_handler_name函数的地址,移入edi |
movl %eax, ORIG_EAX(%esp) |
-1移入esp+ORIG_EAX的位置,对应原来的错误码的位置 |
movl %ecx, ES(%esp) |
ecx中的值(es)存入esp+ES的位置,即是ES本该存的地方 |
movl %esp,%edx |
将当前的堆栈地址存入edx |
pushl %esi |
# push the error code,esi中错误码入栈 |
pushl %edx |
# push the pt_regs pointer,将现场信息的起始地址压栈,类似于pt_reg的作用,使异常处理程序可以按照统一规则去访问出错现场的数据 |
movl $(__KERNEL_DS),%edx |
读取内核数据段 |
movl %edx,%ds |
加载内核数据段 |
movl %edx,%es |
加载内核ES段 |
GET_CURRENT(%ebx) |
将当前进程的task_struct存入ebx,中断返回进程调度和信号处理需要task_struct中的信息,而且只能在内核态操作,因为task_struct存在内核底部 |
call *%edi |
call执行真正的异常处理程序 |
addl $8,%esp |
真正的异常处理程序返回后丢弃错误码和异常处理程序地址 |
jmp ret_from_exception |
跳转异常返回,还需对进程调度,信号处理,vm模式和是否返回用户态进行处理,恢复现场 |
中断处理
预处理后的结果
IRQn_interrupt: |
中断号为n的中断处理程序 |
pushl $n-256 |
将中断号入栈,与异常处理的硬件自动压栈错误码或者手动压0相对应 |
jmp common_interrupt |
跳至公共中断处理 |
预处理后的结果(因为加了asmlinkage标识,所以do_IRQ会在栈中寻找参数,即pt_regs就是ESP)
common_interrupt: |
公共异常处理 |
SAVE_ALL |
保存现场到栈中,作为pt_regs参数,与公共异常处理前半截压栈操作相对应,异常处理程序还需将错误码和真正的异常处理程序地址取出来,将es存入正确位置,然后才调用真正的异常处理程序进行处理,同样的采用栈传参数的方式,传入的是*pt_regs和错误码两个参数,只是第一个参数是ESP地址,而中断的第一个参数取到的就是ESP |
call do_IRQ |
call调用中断处理程序 |
jmp ret_from_intr |
跳至从中断返回 |
标签:
原文地址:http://www.cnblogs.com/hmxb/p/4903208.html