2 Star 6 Fork 3

稀风/KOS

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
中断编程实验.md 9.84 KB
一键复制 编辑 原始数据 按行查看 历史
稀风 提交于 2022-12-11 19:19 . 中断编程实验:优化笔记

引言

  • 本章节我们将会通过 2 个实验来深入理解保护模式下的中断

实验一

  • 实验目标:自定义一个保护模式下的软中断(int 0x80),使得在使用 int 0x80 中断后在屏幕上打印字符串 “int 0x80”

  • 实现思路

    软中断int0x80实现思路

  • 直接开始吧,让我们先找个以前实现过的基础代码:loader.asm

  • 回顾代码的框架

    boot.asm 跳转到 loader.asm,并打印 “Welcome to KOS.” 
          |
    CODE16_START   ; 实模式,打印 “Loader...”
          |
    CODE32_START   ; 进入保护模式,打印 “Enter protection”  
  • 老规矩,先把最终实现代码给你们,再来一步一步讲解过程.最终代码:loader.asm

  • 先准备好 2 个中断服务程序,注意,中断服务程序必须用 iret 返回

  • DefaultHandler 函数用于默认中断处理函数,仅提供函数地址作用,可以不用实现函数内部功能;Int0x80Handler 函数就是我们本次实验的目标函数,内部实现打印 “int 0x80” 字符串

    DefaultHandler:
        iret
    DefaultHandler_Offset   equ     DefaultHandler-CODE32_START
    
    Int0x80Handler:
        mov ebp, msg_int0x80_offset
        mov bl, 0x0F        ; 打印属性,黑底白字
        ; 坐标 (0, 3)
        mov dl, 0x00
        mov dh, 0x03
        call print_str_32
        iret
    Int0x80Handler_Offset   equ     Int0x80Handler-CODE32_START
  • 接下来构建一个 IDT 中断描述符表,可以借用 【调用门】代码中门描述符的定义。共 256 个中断描述符

    ; 中断描述符表定义
    ;                选择子,          偏移地址,               参数个数,属性    
    IDT_BASE : Gate  CODE32_SELECTOR,  DefaultHandler_Offset,  0,  DA_INTR_GATE
               Gate  CODE32_SELECTOR,  DefaultHandler_Offset,  0,  DA_INTR_GATE
               Gate  CODE32_SELECTOR,  DefaultHandler_Offset,  0,  DA_INTR_GATE
               ...(重复 256 次) 
    IDT_LEN     equ     $ - IDT_BASE   
  • 我们看到中断描述符表中有大量重复的代码,我们可以使用 rep 指令处理重复操作,rep 使用格式如下:

    ; 重复 n 次 xxx
    %rep n
    xxx
    %endrep
  • 改动一下

    IDT_BASE:
    %rep 256
        Gate   CODE32_SELECTOR,  DefaultHandler_Offset,  0,       DA_INTR_GATE
        %endrep
    IDT_LEN     equ     $ - IDT_BASE 
  • int 0x80 中断在整个中断描述符表中排第 129 位,于是再次改动一下:

    ; IDT 中断描述符表定义
    ;          选择子,           偏移地址,             参数个数, 属性 
    IDT_BASE: 
        %rep 128
        Gate   CODE32_SELECTOR,  DefaultHandler_Offset,  0,       DA_INTR_GATE
        %endrep
        Gate   CODE32_SELECTOR,  Int0x80Handler_Offset,  0,       DA_INTR_GATE
        %rep 127
        Gate   CODE32_SELECTOR,  DefaultHandler_Offset,  0,       DA_INTR_GATE
        %endrep
    IDT_LEN     equ     $ - IDT_BASE 
  • 中段描述符表 IDT 我们已经构建好了,参考全局描述符表加载(lgdt),我们需要告诉 CPU 哪片内存是 IDT,即 lidt 指令,其格式是 lidt [6 个字节的内存数据首地址]

  • 我们只需要先定义这 6 个字节的数据

    IDT_PTR :
      dw   IDT_LEN - 1
      dd   IDT_BASE
  • 然后再使用 lidt 就可以了,注意:要放在实模式下调用

    lidt [IDT_PTR]
  • 准备工作全部完成啦,下面就是在 32 位保护模式下调用 int 0x80 触发中断

  • 运行一下,看看最终效果

    软中断int0x80

实验二

  • 实验目标:处理外部时钟中断(接主 8259A IRQ0 引脚),接收到时钟中断后,在屏幕上循环打印 '0'-'9'

  • 先提供实验完整代码:loader.asm

  • 实现思路跟实验一一样,都是套路

  • 由于本实验依赖 8259A,所以先把 8259A驱动编写 中实现的驱动代码复制过来并初始化 8259A

    call pic_init
  • 准备中断服务函数,注意程序最后需要调用 write_m_EOI 手动结束中断,如果不手动结束中断,那么该中断只会触发一次

    TimerHandler:
        cmp al, '9'         ; 判断 al 是否为 '9'
        je .to_0            ; 如果 al=9,则跳转到 .to_0 处执行
        inc al
        jmp .display        ; 跳转到 .display 处执行
    
    .to_0:
        mov al, '0'         ; al = '0'
    
    .display:               ; 使用现存方式打印,al 为要打印的字符,ah 为打印属性
        mov ah, 0x0F        ; 打印属性,黑底白字
        mov [gs:(80*4+0)*2], ax ; (80 * 4 + 0)*2 ; 坐标 (0, 4)
    
        call write_m_EOI    ; 手动结束中断
        iret
    TimerHandler_Offset   equ     TimerHandler-CODE32_START
  • 回想一下8259A驱动编写 中,pic_init 将主 8259A IRQ0 引脚(外部时钟)的中断向量号设置为了 0x20,于是我们在中段描述符表 IDT 中添加对应的中断描述符(第 33 个)

    ...
    Gate   CODE32_SELECTOR,  TimerHandler_Offset,  0,  DA_INTR_GATE
    ...
  • 编译运行一下,理论是应看到实验现象,但是并没有

  • 思考一下,8259A 的 IMR 寄存器起到放行作用,是不是 IMR 寄存器默认不放行呢?

  • 于是,我们把 IMR 寄存器的 bit0 (对应IRQ0,即外部时钟引脚)清零

    EnableTimer:
        push ax
      
        call read_m_IMR
        and al, 0xFE
        call write_m_IMR
      
        pop ax
        ret
  • 再次编译运行一下,发现还是不行,又是什么原因导致没有出现我们想要的现象呢?

  • 我们又想到, 在讲 8259A 的时候我们就说过,不过 8259A 能屏蔽中断,CPU 也可以屏蔽中断,是不是 CPU 把外部中断屏蔽了呢?

  • 实验一下不就知道了吗,sti 指令就可以开启 CPU 中断(cli:关中断)

  • 果然,加上 sti 指令后,屏幕上果然循环打印出 '0'-'9'。这说明 CPU 默认是屏蔽外部中断的

  • 再改动一下,不执行 EnableTimer 函数,即不手动放行 8259A IRQ 中断

    ; call EnableTimer
    sti
  • 发现依然能循环打印出 '0'-'9',这说明 8259A 的 IMR 寄存器是默认放行的

  • 最后贴上实验截图,虽然无法看到 '0'-'9' 跳动过程,但至少能证明我们的代码是实实在在运行成功的

    外部时钟中断实验

中断嵌套

  • 提到中断,那么我们就得思考一个问题,那就是中断嵌套。什么是中断嵌套呢?
  • 比如我们上面实验二所做的外部时钟中断,假设时钟中断引脚 IRQ0 每 5ms 触发一次,而对应的中断服务程序执行实时间是 20ms。这时候就会遇到一个问题,在第一次中断服务程序正在执行的过程中,又触发了一次中断,这种情况我们就称之为中断嵌套
  • 从 pic_init 初始化主 8259A 函数中 ICW4 寄存器的设置中,我们知道,主 8259A 被设置成了特殊全嵌套模式,即中断服务程序执行过程中,仍然响应同级中断
  • 对于外部中断,不光 8259A 的 IMR 寄存器提供了中断屏蔽机制, CPU 也有个总开关(eflags 的 IF 位),也可以屏蔽中断
  • 就拿实验二的代码,让我们反汇编后断点调试,看一看中断执行前后 IF 位的情况吧。IF=1:可以响应外部中断;IF=0:屏蔽外部中断
  • make 之后反汇编一下
    ndisasm -o 0x900 loader.bin  > loader.txt
  • 查看 loader.txt 文件,找到开启中断 sti 指令地址:0x1214,TimerHandler 函数入口处(cmp al, '9')地址:0x125B,TimerHandler 函数最后返回指令 iret 地址:0x1273,使用 reg 指令,看一下各状态下 eflags 中 IF 位的变化(大写表示值为 1, 小写表示值为 0)
    <bochs:1> b 0x1214
    <bochs:2> b 0x125B
    <bochs:3> b 0x1273
    <bochs:4> c
    ...
    (0) Breakpoint 1, 0x00001214 in ?? ()
    Next at t=16768062
    (0) [0x00001214] 0008:00000032 (unk. ctxt): sti                       ; fb
    <bochs:5> reg
    eax: 0x00000030 48
    ecx: 0x00000009 9
    edx: 0x00000300 768
    ebx: 0x0000000f 15
    esp: 0x00000fff 4095
    ebp: 0x00000011 17
    esi: 0x0000130f 4879
    edi: 0x00000923 2339
    eip: 0x00000032
    eflags 0x00000046: id vip vif ac vm rf nt IOPL=0 of df if tf sf ZF af PF cf
    <bochs:6> s
    Next at t=16768063
    (0) [0x00001215] 0008:00000033 (unk. ctxt): jmp .-2 (0x00001215)      ; ebfe
    <bochs:7> reg
    eax: 0x00000030 48
    ecx: 0x00000009 9
    edx: 0x00000300 768
    ebx: 0x0000000f 15
    esp: 0x00000fff 4095
    ebp: 0x00000011 17
    esi: 0x0000130f 4879
    edi: 0x00000923 2339
    eip: 0x00000033
    eflags 0x00000246: id vip vif ac vm rf nt IOPL=0 of df IF tf sf ZF af PF cf
    <bochs:8> c
    (0) Breakpoint 3, 0x00001273 in ?? ()
    Next at t=16921763
    (0) [0x00001273] 0008:00000091 (unk. ctxt): iretd                     ; cf
    <bochs:9> reg
    eax: 0x00000f31 3889
    ecx: 0x00000009 9
    edx: 0x00000300 768
    ebx: 0x0000000f 15
    esp: 0x00000ff3 4083
    ebp: 0x00000011 17
    esi: 0x0000130f 4879
    edi: 0x00000923 2339
    eip: 0x00000091
    eflags 0x00000003: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf CF
    <bochs:10> s
    Next at t=16921764
    (0) [0x00001215] 0008:00000033 (unk. ctxt): jmp .-2 (0x00001215)      ; ebfe
    <bochs:11> reg
    eax: 0x00000f31 3889
    ecx: 0x00000009 9
    edx: 0x00000300 768
    ebx: 0x0000000f 15
    esp: 0x00000fff 4095
    ebp: 0x00000011 17
    esi: 0x0000130f 4879
    edi: 0x00000923 2339
    eip: 0x00000033
    eflags 0x00000246: id vip vif ac vm rf nt IOPL=0 of df IF tf sf ZF af PF cf
    <bochs:12>
  • 从调试信息上看
    • sti 指令的本质就是将 eflags IF 位置 1
    • 在中断服务程序 TimerHandler 执行期间, IF 位为 0,说明 CPU 在中断服务程序执行过程中本身是不响应其它中断的
  • 如果你想实现在中断服务程序执行过程中依旧能响应其它中断,可以在中断服务程序的开始处手动加上 sti 指令
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/thin-wind/KOS.git
git@gitee.com:thin-wind/KOS.git
thin-wind
KOS
KOS
main

搜索帮助