I Can't Get Interrupts Working

来自osdev
跳到导航 跳到搜索

此页面是一种故障排除手册,可帮助您解决论坛来宾和成员在论坛上遇到的常见中断框架问题

请确保你收集了关于自己情况的足够信息(例如在Bochs中运行代码)。

ISR问题

我的处理程序没有被调用?!(汇编)

对于这个测试,你需要通过软件自己调用中断。 在确定IDT设置正确之前,不要尝试从一开始就处理IRQ。 你需要有:

  • 您的IDT已正确加载和填充。
  • 您的IDT线性地址与表的大小一起加载到结构中(以字节为单位,IIRC)。 如果您具有 Higher Half Kernel 设计或未设置 Identity Paging,请特别小心。
  • 描述符中的有效代码选择器(Selector)和偏移量(offset)、正确类型等。
  • 定义好偏移量处的处理代码。

参见下面的 汇编测试代码

我的处理程序没有被调用?!(C)

如果您正在用C语言编写IDT设置,请确保您的编译器已经正确理解了IDTR结构。 由于Intel的6个字节结构违反了大多数编译器的打包规则,因此您需要使用 “bitfields” 或 “packing pragmas”。 使用sizeof() and OFFSETOF()宏来确保使用了预期的定义(也可以运行时测试)

我的处理程序被调用,但没有返回!?

尝试在Bochs中运行它,看看是否得到任何异常报告。 对所有异常进行编程,使其具有与[#汇编示例|示例]]相同的行为,可以增加显示一个指示故障的字符。 发生在中断处理程序末尾的异常通常是由于处理程序内的错误堆栈操作造成的。

  • 不要尝试从异常中返回 (除非您解除了其成因)。例如,从零除法中返回根本没有任何意义
  • pop所有你曾push的内容,但不要多push
  • 确保保留CPU推送的错误码(至少异常8、10、14)
  • 确保您的处理程序不会意外丢弃寄存器值。对于异常和硬件IRQ处理程序,根本不需要修改任何寄存器。

在这一点上,另一个常见的错误源来自C语言中ISR(中断服务例程)的错误实现。 检查中断服务例程页面以获取启蒙.

IRQ问题

既然您确定可以调用中断并返回,那么就可以启用硬件中断了。 作为第一步,建议您只启用_keyboard处理程序_,因为它的功能范畴几乎可以被您完全控制。 使用PIC的掩码功能启用/禁用某些处理程序。

   outb(0x21,0xfd);
   outb(0xa1,0xff);
   enable(); // asm("sti");

我从第一次中断返回后立即收到一般保护故障中断

使用lgdt命令初始化GDT之后,请确保您正在执行跳远(long jump)。 例如:

init_gdt:
    lgdt [gdt_table]
    jmp 0x08:longjmp_after_gdt
longjmp_after_gdt:
    ; 下一步执行类似于重新指向段寄存器的操作

我在按键时收到的是EXC9而不是IRQ1?!

你漏过了PIC向量重新编程步骤。 检查我可以重新映射PIC吗?页 请注意,如果您将PIC向量重新映射到IDT之外,您将得到一个GPF异常,而不是任何中断。

启用中断后,我收到双重故障(double fault)

可能是上述相同错误的一种不同症状,这一次是由定时器中断调用向量8引起的。 如果您已经在保护模式下启用了中断,但是没有为您重新映射定时器到的任何向量定义中断处理程序,也可能会导致这种情况,因为定时器中断将在启用中断后不久出现并导致错误,除非您已经为其设置了处理程序或屏蔽了它。

我没有收到任何IRQ

首先参考这里,确保你[#ISR_问题|收到软件中断]]。 此外,请确保在PIC掩码上启用了您感兴趣的IRQ,并且如果您正在等待从IRQ,请确保您启用了PIC级联线路(主IRQ的第2位)。

我只能收到一个IRQ

每个IRQ都需要通过发送EOI手动向PIC返回确认。 你需要有

 outb(0x20,0x20)

在任何主处理程序和任何

 outb(0x20,0x20); outb(0xa0,0x20);

在任何从处理程序中

处理键盘IRQ时,请确保您读取了键盘发送给您的字节。 在读取之前,中断可能不会再次触发:

 unsigned char scan_code = inb(0x60);

此外,如果您正在学习基本教程,请确保您的主函数不会太快退出(因为当它退出时,它会禁用中断)。 确保它不会过早退出的常见解决方案是添加

 for(;;) {
    asm("hlt");
 }

到主内核函数的末尾。 for循环是必需的,因为在CPU接收到中断之后,执行仍在继续。

当我尝试启用PIT时,键盘不再起作用

一个常见的错误是,当人们想要添加定时器时,用0xFE重新加载掩码,但这样做实际上仅会启用定时器并禁用键盘(设置了0xFE的#1位!) 启用“”键盘和定时器“”的正确值是0xFC。

我一直没有明显的原因会得到IRQ7

这是一个无法避免的已知问题,尽管存在解决方法。 当收到任何IRQ7时,只需读取服务中寄存器(In-Service Register)

 outb(0x20, 0x0B); unsigned char irr = inb(0x20);

检查第7位

irr & 0x80

是否设为1。 如果不是,则在不发送EOI的情况下从中断返回。

有关更多信息,包括更详细的说明,请参阅 [Topic:11379|此论坛主题]]] 中的Brendan帖子。


“移位运算符只能应用于标量值”是什么意思?

您正在尝试加载一个16位字段(IDT描述符的一部分),其中引用了一个需要重新定位的32位标签。 尝试更换

 isr_label:
    iret
 bad_stuff dw isr_label & 0xFFFF
           dw 0xdead
           dw 0xbeef
           dw isr_label >> 16

通过从地址中提取“pure value”的东西 (例如,两个地址的差值是纯值,$$对NASM意味着section的开始)

 %define BASE_OF_SECTION SOME_CONSTANT_YOU_SHOULD_KNOW
isr_label:
   iret
 good_stuff dw (BASE_OF_SECTION + isr_label - $$) & 0xFFFF
            dw 0xcafe
            dw 0xbabe
            dw (BASE_OF_SECTION + isr_label - $$) >> 16
BASE_OF_SECTION

的作用是将pure offset调整到实际情况 (通常是在你的链接器脚本中定义的),例如如果你的内核加载到1MB,你会将其设置为0x100000以保持CPU满意。

汇编示例

NASM

此示例适用于在IA32模式(32位)下运行的x86 CPU。

 int_handler:
    mov ax, LINEAR_DATA_SELECTOR
    mov gs, ax
    mov dword [gs:0xB8000],') : '
    hlt

 idt:
    resd 50*2

 idtr:
    dw (50*8)-1
    dd LINEAR_ADDRESS(idt)

 test1:
    lidt [idtr]
    mov eax,int_handler
    mov [idt+49*8],ax
    mov word [idt+49*8+2],CODE_SELECTOR
    mov word [idt+49*8+4],0x8E00
    shr eax,16
    mov [idt+49*8+6],ax
    int 49

这应该会在左上角显示一个笑脸...然后,CPU无限期停止。

GNU汇编程序

此示例在长模式下设置中断处理程序。

.text
int_handler:
    movq $0x123abc, 0x0 // 这将魔术值 “0x123abc” 放置在内存的开头
    hlt

.p2align 4
idt:
    .skip 50*16

  idtr:
    .short (50*16)-1
    .quad idt

.globl do_test
do_test:
    lidt idtr
    movq$int_handler,%rax
    mov % ax,idt + 49*16
    movw $0x20, idt+49*16+2 // 用代码段选择器替换0x20
    movw $0x8e00, idt+49*16+4
    shr $16, %rax
    mov %ax, idt+49*16+6
    shr $16, %rax
    mov %rax, idt+49*16+8
    int $49

此示例与上一个示例不同: 它不会动屏幕,而是将值 “0x123abc” 写入0x0内存地址并停止。 当没有屏幕且BIOS可用时,它可能很有用。

IDT问题

我们中的许多人在操作系统开发时都会遇到IDT的问题。 以下是一些已解决的IDT问题

这是用来解决问题的。 未解决的问题可以在网站上找到 Forum

问题

请在这里发布 已完成的 问题。

首先,检查你的GDT。

键盘处理程序需要从端口0x60实际读取扫描码 - 仅仅让处理程序打印一些表示成功的东西,然后发送EOI是不够的。 出现的症状与忘记发送EOI相同。

汇编中的IDT问题

确保结构正确,并且您使用的是线性地址。

FASM 提醒

由于FASM不接受上述正常方式,我将对其进行描述。 但是,FASM确实支持shl和shr,因此要描述ISR地址的较高部分,我们只需使用label shl 0x10,其中label是ISR的名称。 要定义更高的部分,在编译之前,我们需要写更多一点,因为fasm使用64位。 这意味着,如果我们只是shl和shr,它将和以前一样。 我们应该这样做:(label shl 0x30) shr 0x30 这里有一个小例子,所以你可以看到它是如何工作的:

 idt:
  dw  ((isr1 shl 0x30) shr 0x30)        ; 地址的下半部分
  dw   0x8    ; selector
  db   0
  db   010001110b  ; type
  dw (isr1 shr 0x10) the high part of the address

 isr1:
  mov ax,0xdead

另见

相关文章