查看“I Can't Get Interrupts Working”的源代码
←
I Can't Get Interrupts Working
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
此页面是一种故障排除手册,可帮助您解决论坛来宾和成员在[[Forum:1|论坛]]上遇到的常见中断框架问题 请确保你收集了关于自己情况的足够信息(例如在Bochs中运行代码)。 == ISR问题 == === 我的处理程序没有被调用?!(汇编) === 对于这个测试,你需要通过软件自己调用中断。 在确定IDT设置正确之前,不要尝试从一开始就处理[[IRQ]]。 你需要有: * 您的IDT已正确加载和填充。 * 您的IDT''线性''地址与表的大小一起加载到结构中(以字节为单位,IIRC)。 如果您具有 [[Higher Half Kernel]] 设计或未设置 [[Identity Paging|Identity Paging]],请特别小心。 * [[Descriptors|描述符]]中的有效代码选择器(Selector)和偏移量(offset)、正确类型等。 * 定义好偏移量处的处理代码。 ''参见下面的 [[#汇编示例|汇编测试代码]]'' === 我的处理程序没有被调用?!(C)=== 如果您正在用C语言编写IDT设置,请确保您的编译器已经正确理解了IDTR结构。 由于Intel的6个字节结构违反了大多数编译器的打包规则,因此您需要使用 “bitfields” 或 “packing pragmas”。 使用<tt>sizeof()</tt> and <tt>OFFSETOF()</tt>宏来确保使用了预期的定义(也可以运行时测试) === 我的处理程序被调用,但没有返回!? === 尝试在Bochs中运行它,看看是否得到任何异常报告。 对所有异常进行编程,使其具有与[#汇编示例|示例]]相同的行为,可以增加显示一个指示故障的字符。 发生在中断处理程序末尾的异常通常是由于处理程序内的错误堆栈操作造成的。 * 不要尝试从异常中返回 (除非您解除了其成因)。例如,从零除法中返回根本没有任何意义 * pop所有你曾push的内容,但不要多push * 确保保留CPU推送的错误码(至少异常8、10、14) * 确保您的处理程序不会意外丢弃寄存器值。对于异常和硬件IRQ处理程序,根本不需要修改任何寄存器。 在这一点上,另一个常见的错误源来自C语言中ISR(中断服务例程)的错误实现。 检查[[Interrupt Service Routines|中断服务例程]]页面以获取启蒙. == IRQ问题 == 既然您确定可以调用中断并返回,那么就可以启用硬件中断了。 作为第一步,建议您只启用_keyboard处理程序_,因为它的功能范畴几乎可以被您完全控制。 使用PIC的掩码功能启用/禁用某些处理程序。 <source lang="c"> outb(0x21,0xfd); outb(0xa1,0xff); enable(); // asm("sti"); </source> === 我从第一次中断返回后立即收到一般保护故障中断 === 使用lgdt命令初始化GDT之后,请确保您正在执行跳远(long jump)。 例如: <source lang="asm"> init_gdt: lgdt [gdt_table] jmp 0x08:longjmp_after_gdt longjmp_after_gdt: ; 下一步执行类似于重新指向段寄存器的操作 </source> === 我在按键时收到的是EXC9而不是IRQ1?!=== 你漏过了PIC向量重新编程步骤。 检查[[PIC|我可以重新映射PIC吗?]]页 请注意,如果您将PIC向量重新映射到IDT之外,您将得到一个GPF异常,而不是任何中断。 === 启用中断后,我收到双重故障(double fault) === 可能是上述相同错误的一种不同症状,这一次是由定时器中断调用向量8引起的。 如果您已经在保护模式下启用了中断,但是没有为您重新映射定时器到的任何向量定义中断处理程序,也可能会导致这种情况,因为定时器中断将在启用中断后不久出现并导致错误,除非您已经为其设置了处理程序或屏蔽了它。 === 我没有收到任何IRQ === 首先参考这里,确保你[#ISR_问题|收到软件中断]]。 此外,请确保在PIC掩码上启用了您感兴趣的IRQ,并且如果您正在等待从IRQ,请确保您启用了PCI级联线路(主IRQ的第2位)。 === 我只能收到一个IRQ === 每个IRQ都需要通过发送EOI手动向PIC返回确认。 你需要有 <source lang="c"> outb(0x20,0x20) </source> 在任何主处理程序和任何 <source lang="c"> outb(0x20,0x20); outb(0xa0,0x20); </source> 在任何从处理程序中<br><br> 处理键盘IRQ时,请确保您读取了键盘发送给您的字节。 在读取之前,中断可能不会再次触发: <source lang="c"> unsigned char scan_code = inb(0x60); </source> 此外,如果您正在学习基本教程,请确保您的主函数不会太快退出(因为当它退出时,它会禁用中断)。 确保它不会过早退出的常见解决方案是添加 <source lang="c"> for(;;) { asm("hlt"); } </source> 到主内核函数的末尾。 ''for''循环是必需的,因为在CPU接收到中断之后,执行仍在继续。 === 当我尝试启用PIT时,键盘不再起作用 === 一个常见的错误是,当人们想要添加定时器时,用<tt>0xFE</tt>重新加载掩码,但这样做实际上''仅会''启用定时器并禁用键盘(设置了0xFE的#1位!) 启用“”键盘和定时器“”的正确值是0xFC。 === 我一直没有明显的原因会得到IRQ7 === 这是一个无法避免的已知问题,尽管存在解决方法。 当收到任何IRQ7时,只需读取服务中寄存器(In-Service Register) <source lang="c"> outb(0x20, 0x0B); unsigned char irr = inb(0x20);</source> 检查第7位 <source lang="c">irr & 0x80</source> 是否设为1。 如果不是,则在不发送EOI的情况下从中断返回。 有关更多信息,包括更详细的说明,请参阅 [Topic:11379|此论坛主题]]] 中的Brendan帖子。 === “移位运算符只能应用于标量值”是什么意思? === 您正在尝试加载一个16位字段(IDT描述符的一部分),其中引用了一个需要重新定位的32位标签。 尝试更换 <source lang="asm"> isr_label: iret bad_stuff dw isr_label & 0xFFFF dw 0xdead dw 0xbeef dw isr_label >> 16 </source> 通过从地址中提取“pure value”的东西 (例如,两个地址的差值是纯值,<tt>$$</tt>对NASM意味着section的开始) <source lang="asm"> %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 </source> <pre>BASE_OF_SECTION</pre> 的作用是将pure offset调整到实际情况 (通常是在你的链接器脚本中定义的),例如如果你的内核加载到1MB,你会将其设置为0x100000以保持CPU满意。 ==汇编示例== ===NASM=== 此示例适用于在IA32模式(32位)下运行的x86 CPU。 <source lang="asm"> 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 </source> 这应该会在左上角显示一个笑脸...然后,CPU无限期停止。 ===GNU汇编程序=== 此示例在长模式下设置中断处理程序。 <source lang="asm"> .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 </source> 此示例与上一个示例不同: 它不会动屏幕,而是将值 “0x123abc” 写入0x0内存地址并停止。 当没有屏幕且BIOS可用时,它可能很有用。 == IDT问题 == 我们中的许多人在操作系统开发时都会遇到IDT的问题。 以下是一些已解决的IDT问题 这是用来解决问题的。 未解决的问题可以在网站上找到 [http://forum.osdev.org/viewtopic.php?f=1&t=24805 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 这里有一个小例子,所以你可以看到它是如何工作的: <source lang="asm"> 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 </source> == 另见 == === 相关文章 === *[[IDT]] *[[IDT_problems|IDT常见问题]] [[Category:Troubleshooting]] [[Category:FAQ]] [[Category:Interrupts]]
返回至“
I Can't Get Interrupts Working
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息