中断处理 - 练习题

练习 1:理解中断向量

回答以下问题:

  1. x86 CPU预定义了多少个异常向量?
  2. 为什么PIC的IRQ需要重映射?原始映射会导致什么问题?
  3. 向量14(页错误)的错误码包含哪些信息?

练习 2:注册自定义中断处理器

在 kernel.c 中注册一个断点(INT 3)处理器:

void breakpoint_handler(registers_t *regs) {
    screen_set_color(VGA_COLOR_YELLOW, VGA_COLOR_BLACK);
    screen_printf("[BREAKPOINT] at EIP: 0x%x\n", regs->eip);
    screen_set_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);
}
 
// 在kernel_main中注册并测试
register_interrupt_handler(3, breakpoint_handler);
__asm__ volatile("int $3");  // 触发断点

验证处理器被正确调用并打印了EIP。

练习 3:实现定时器中断

时钟中断(IRQ0)是向量32。实现一个简单的计时器:

static uint32_t tick = 0;
 
void timer_handler(registers_t *regs) {
    tick++;
    if (tick % 100 == 0) {  // 约每秒一次
        screen_printf("[TIMER] %d seconds\n", tick / 100);
    }
}
 
// 注册
register_interrupt_handler(32, timer_handler);

练习 4:PIC中断掩码

学习使用中断掩码控制IRQ:

// 禁用某个IRQ
void irq_set_mask(uint8_t irq) {
    uint16_t port;
    if (irq < 8) {
        port = 0x21;
    } else {
        port = 0xA1;
        irq -= 8;
    }
    uint8_t value = inb(port) | (1 << irq);
    outb(port, value);
}
 
// 启用某个IRQ
void irq_clear_mask(uint8_t irq) {
    // 提示:与上面相反,使用 & ~(1 << irq)
}

实现 irq_clear_mask,并测试只启用键盘中断(IRQ1)。

练习 5:异常信息增强

修改 isr_handler 函数,在异常发生时打印更多寄存器信息:

要求显示:

  • 所有通用寄存器(EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP)
  • 段寄存器(DS, CS)
  • 控制寄存器(EFLAGS)
  • 错误码

格式参考:

=== REGISTER DUMP ===
EAX=0x00000000  EBX=0x00000000  ECX=0x00000000  EDX=0x00000000
ESI=0x00000000  EDI=0x00000000  EBP=0x00000000  ESP=0x00000000
EIP=0x00001234  CS=0x0008  DS=0x0010  EFLAGS=0x00000202
INT=0x0E  ERR=0x00000002

思考题

  1. 为什么中断处理程序中需要切换到内核数据段(0x10)? 如果不切换会发生什么?

  2. IRET和RET有什么区别? 为什么中断处理程序不能用RET返回?

  3. 如果在中断处理程序中再次发生中断(中断嵌套),会发生什么? 32位中断门(0x8E)是如何处理这个问题的?

  4. 假设系统频繁发生伪中断(IRQ7/IRQ15),你应该如何处理? 提示:查阅ISR(In-Service Register)。