《自己动手写操作系统》中断处理分析

    中断嵌套的过程发生时,那个中断先执行? 开始时没有理解清楚,《自己动手写操作系统》作者也没有明说,可能他自己理解了,觉得明白了。仔细看了代码后发现,中断的处理过程代码写的非常好,这个地方的处理方式总结之后,可以在以后的开发过程中借用。

    有几点知识需要了解:
    a. ret指令段跳转的时候只弹出栈里面的当前一个值,作为ip寄存器的值(CPU做的)。
        iretd有5个值,返回多少个值,与跳转的方式有关。段间,段外,不同特权级,这些都不一样,用到的命令也不一样。
    b. call指令发生时,cpu会自动将返回值压入到栈当中(cpu做的)。
    c. 最重要的一点,要理解汇编语言的精髓,不要把他和C混了。。  最重要的一点区别是:  汇编是单个单个的指令来运行的,没有一定要那两个指令配套使用。eg, 函数当中不一定要ret, 指令来返回,可以用jum指令直接跳到要返回的地方。但是当前栈的内容一定要非常熟悉, 才能这么做,不然就容易出错。而C语言中有很多是需要配套的语句使用的。

1. 将分析,在中断A发生之后,没有退出中断服务时,中断B又来了,这个时候哪个中断先执行,执行后又跳到哪,堆栈怎么变化的。

    整个流程相关代码如下(以时钟和键盘中断为例):
; ------------------------------------------------------------------------------------------------------------------------
//下面是中断向量表中指定的中断服务程序,只有两个例子。
ALIGN    16
hwint00:        ; Interrupt routine for irq 0 (the clock).
    hwint_master    0

ALIGN    16
hwint01:        ; Interrupt routine for irq 1 (keyboard)
    hwint_master    1

; ------------------------------------------------------------------------------------------------------------------------
//这是上面调用的宏的扩展。
%macro    hwint_master    1
    call    save
    in    al, INT_M_CTLMASK    ; ┓
    or    al, (1 << %1)        ; ┣ 屏蔽当前中断
    out    INT_M_CTLMASK, al    ; ┛

    mov    al, EOI            ; ┓置EOI位
    out    INT_M_CTL, al        ; ┛
    sti    ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断

    push    %1            ; ┓//这是用的时内核栈,不管重入不重入。
    call    [irq_table + 4 * %1]    ; ┣ 中断处理程序;当发生中断嵌套的时候,
    pop    ecx            ; ┛;第一个进来的中断还会发生,只是这时不在做任务切换,而是恢复到同一个进程。

    cli
    in    al, INT_M_CTLMASK    ; ┓
    and    al, ~(1 << %1)        ; ┣ 恢复接受当前中断
    out    INT_M_CTLMASK, al    ; ┛
    ret                ;根据非重入,重入跳到restart,restart_reenter函数里执行不同的过程
%endmacro

; ------------------------------------------------------------------------------------------------------------------------
//这是上面调用的save函数的扩展。
; ====================================================================================
;                                   save
; ====================================================================================
save:
    pushad        ; ┓
    push    ds    ; ┃
    push    es    ; ┣ 保存原寄存器值
    push    fs    ; ┃
    push    gs    ; ┛
    
    mov    dx, ss    ;也就是让字符拷贝发生在栈空间里面
    mov    ds, dx
    mov    es, dx

    mov    eax, esp            ; eax = 进程表起始地址

    inc    dword [k_reenter]        ; k_reenter++;
    cmp    dword [k_reenter], 0        ; if(k_reenter ==0)
    jne    .1                ; {
    mov    esp, StackTop            ;    mov esp, StackTop <-- 切换到内核栈
    push    restart                ;    push restart
    jmp    [eax + RETADR - P_STACKBASE]    ;    return;   ///跳到上面宏的 in al, INT_M_CTLMASK;这句。
.1:                        ; } else { 已经在内核栈,不需要再切换,中断重入的时候已经在内核栈了。
    push    restart_reenter            ;    push restart_reenter
    jmp    [eax + RETADR - P_STACKBASE]    ;    return;   ///跳到上面宏的 in al, INT_M_CTLMASK;这句。
                        ; }


; ====================================================================================
;                                   restart
; ====================================================================================
restart:
    mov    esp, [p_proc_ready]
    lldt    [esp + P_LDT_SEL]
    lea    eax, [esp + P_STACKTOP]    ;取后者在内存的偏移地址,送到前者
    ;lea    eax, [esp + 0x1000]
    mov    dword [tss + TSS3_S_SP0], eax   ;当着句执行完了后,会继续执行下面的restart_reenter:部分,因为没有指令改变当前ip值。

restart_reenter:
    dec    dword [k_reenter]
    ;在执行下面的命令前,esp指向regs的开始处,当pop时会将当前值
    ;递减的释放到各个相应寄存器,然后iretd之后,系统会开始执行
    ;TestA()函数,因为iretd会将regs.eip值放入到eip指针中。
    pop    gs
    pop    fs
    pop    es
    pop    ds
    popad
    add    esp, 4
    iretd    ;这个指令之后系统就开始执行testA函数了

上面怎么流程稍微有点乱,无非是两条线,一个是没有重入,一个是重入。这两个的最大区别就在于esp的变化。如下图:
1.  没有重入
 
上图中2应该执行regs的上面的顶部,不是底部,这是退出时,应该pop各个值到寄存器了。麻烦就没有重画了。
2. 发生重入
 
这就是嵌套的含义,像c语言的嵌套一样,先执行最新发生的中断,然后依次执行前一次发生的中断,直到最后一个中断发生处理完。都在内核栈完成。但是注意,这个时候clock中断是关了的。只有可能发生其它类型的中断重入。也就是说每一个同类型的中断是不可以两次重入的。发送重入只能是不同中断之间。

    上面中断处理函数为什么要写这么复杂?
1.  仔细看了之后其实结构还是很清晰的,逻辑调理清楚。不算复杂。
2.  之所以要写成这样,主要是为了迁就,最初的那个宏调用。宏虽然占代码空间,但是相对了函数来说,他节省时间。函数的压栈出栈,是很耗时的。
3. 这个地方写的比较难懂和精彩的地方,是宏当中的ret指令。这个指令是直接在栈中取当前值,为下一个执行的地址。也就是把esp的值,给ip使用。
以及对中断函数处理表(call    [irq_table + 4 * %1])的调用思想。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值