zoukankan      html  css  js  c++  java
  • 中断与异常详解(二)

    中断或异常发生之前

    CPU 执行了当前指令之后,CS EIP 这对寄存器中所包含的内容就是下一条将要执行 指令的逻辑地址。在对下一条指令执行前,CPU 先要判断在执行当前指令的过程中是否发生 了中断或异常

    如果发生了一个中断或异常

    那么 CPU 将做以下事情

    • 确定所发生中断或异常的向量i(在 0~255 之间)。

    • 通过 IDTR 寄存器找到 IDT 表,读取 IDT 表第i项(或叫第i个门)。

    分两步进行有效性检查:首先是“段”级检查,将 CPU 的当前特权级 CPL(存放在 CS 寄存器的最低两位)与 IDT 中第i项段选择符中的 DPL 相比较,如果 DPL3)大于 CPL0), 就产生一个“通用保护”异常(中断向量 13),因为中断处理程序的特权级不能低于引起中 断的程序的特权级。这种情况发生的可能性不大,因为中断处理程序一般运行在内核态,其 特权级为 0。然后是“门”级检查,把 CPL IDT 中第 i个门的 DPL 相比较,如果 CPL 大于 DPL,也就是当前特权级(3)小于这个门的特权级(0),CPU 不能“穿过”这个门,于是 产生一个“通用保护”异常,这是为了避免用户应用程序访问特殊的陷阱门或中断门。但是 请注意,这种“门”级检查是针对一般的用户程序,而不包括外部 I/O 产生的中断或因 CPU 内部异常而产生的异常,也就是说,如果产生了中断或异常,就免去了“门”级检查

    (这里的免去没大明白,是不进行有效性检查了?直接跳过这一步?)

    检查是否发生了特权级的变化。当中断发生在用户态(特权级为 3),而中断处理程 序运行在内核态(特权级为0),特权级发生了变化,所以会引起堆栈的更换。也就是说,从 用户堆栈切换到内核堆栈。而当中断发生在内核态时,即 CPU 在内核中运行时,则不会更换堆栈

    找到对应的门

    异常处理没说怎么在idt_table中找到对应的门的,中断倒是因为有中断号,可以根据这个中断号去idt_table中找到门,反正cpu硬件干的

    堆栈变化

    如果堆栈变化则将当前的(SS,ESP)压入栈中,此时的栈为内核栈,因为一旦出现中断或异常,堆栈就切换到了内核堆栈,上面这些操作是由硬件完成的,至于具体怎么操作的也没大明白,压内核栈不是得先更新成内核的SSESP吗?ESP更新了,那压的ESP不就是内核的?看见书中有提到此时的内核堆栈是空的,也许压的固定位置吧。看到后面搞懂了再更新。

    更新:有看到说从TSS中取到的内核堆栈,难道是用movl实现的?

     10/29/2015更新:看到后面有些理解了,因为每个进程有task_struct记录其所有信息,也就是常说的pcb,而这个task_struct与该进程的内核堆栈共用8KB的存储空间,所以中断或异常发生的时候完全可以从tss_struct取出内核esp指针,再得到内核堆栈,因为内核堆栈开始与页首部,而且以8KB为单位,所以esp&~8191UL就可以取到内核堆栈起始地址了,压栈操作的目的地就是这儿,压完了再进行esp切换。tss_struct是任务状态段,是intel设计的cpu任务切换硬件支持,linux为了保证灵活性和对出错恢复的可操作性以及性能考虑,未完全使用这种切换方式。

    处理程序格式对于异常和中断是不同的

    异常处理

    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 codeesi中错误码入栈

    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

    跳至从中断返回

  • 相关阅读:
    Vue系列:.sync 修饰符的作用及使用范例
    Vue系列:Websocket 使用配置
    Vue系列:Slot 插槽的使用范例
    Vue系列:滚动页面到指定位置实现
    Vue系列:为不同页面设置body背景颜色
    Element UI系列:Upload图片自定义上传
    Vue系列:wangEditor富文本编辑器简单例子
    Element UI系列:Select下拉框实现默认选择
    sublime text 3 15个常用插件介绍
    基于iis下 wcf接口发布
  • 原文地址:https://www.cnblogs.com/hmxb/p/4903208.html
Copyright © 2011-2022 走看看