【问题标题】:Keyboard interrupt handler not working in system iso键盘中断处理程序在系统 iso 中不起作用
【发布时间】:2018-06-30 10:28:49
【问题描述】:

我正在尝试使用 OSDev 和其他工具编写操作系统。现在,我一直在制作键盘中断处理程序。当我编译我的操作系统并使用qemu-system-i386 -kernel kernel/myos.kernel 运行内核时,一切正常。当我将所有内容放入 ISO 映像并尝试使用 qemu-system-i386 -cdrom myos.iso 运行它时,它会在我按下一个键时重新启动。我认为这是由我的中断处理程序中的一些问题或错误的 IDT 条目引起的。

我的键盘处理程序(AT&T 语法):

.globl   keyboard_handler
.align   4

keyboard_handler:

    pushal
    cld 
    call keyboard_handler_main
    popal
    iret

我在 C 中的主要处理程序:

void keyboard_handler_main(void) {
    unsigned char status;
  char keycode;
    /* write EOI */
    write_port(0x20, 0x20);

    status = read_port(KEYBOARD_STATUS_PORT);
    /* Lowest bit of status will be set if buffer is not empty */
    if (status & 0x01) {
        keycode = read_port(KEYBOARD_DATA_PORT);
        if(keycode < 0)
            return;

        if(keycode == ENTER_KEY_CODE) {
            printf("\n");
            return;
        }
        printf("%c", keyboard_map[(unsigned char) keycode]);
    }
}

C 函数我用来加载:

void idt_init(void)
{
    //unsigned long keyboard_address;
    unsigned long idt_address;
    unsigned long idt_ptr[2];

    auto keyboard_address = (*keyboard_handler);

    IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
    IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
    IDT[0x21].zero = 0;
    IDT[0x21].type_attr = INTERRUPT_GATE;
    IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;

    /*     Ports
    *    PIC1   PIC2
    *Command 0x20   0xA0
    *Data    0x21   0xA1
    */

    write_port(0x20 , 0x11);
    write_port(0xA0 , 0x11);

    write_port(0x21 , 0x20);
    write_port(0xA1 , 0x28);

    write_port(0x21 , 0x00);
    write_port(0xA1 , 0x00);

    write_port(0x21 , 0x01);
    write_port(0xA1 , 0x01);

    write_port(0x21 , 0xff);
    write_port(0xA1 , 0xff);

    idt_address = (unsigned long)IDT ;
    idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16);
    idt_ptr[1] = idt_address >> 16 ;

    load_idt(idt_ptr);

    printf("%s\n", "loadd");
}

文件的组织方式与OSDev's Meaty Skeleton 相同。我确实有不同的引导加载程序。

【问题讨论】:

  • 您在中断处理程序中使用 printf()?
  • 我有自己的 printf 实现。我不应该吗?
  • 应该吗?它似乎有点……奇怪。您的 printf 可以安全地中断并从中断状态重新进入吗?
  • 您没有向我们展示所有代码,但请考虑这一点 - Multiboot 规范要求您设置自己的 GDT。 Multibootloader 设置了自己的,但在规范中它说 GDTR 应该假定为无效,您应该在重新加载任何段寄存器之前建立自己的。使用中断需要更改段寄存器。在使用 QEMU(带有-kernel 选项)时会发生这种情况,GDTR 通常是有效的,但是当通过真正的 GRUB(在 ISO 上)完成时,它不是。
  • 看起来多肉的骨架没有设置 GDT。可能是因为他们不使用中断并且不设置任何段寄存器。如果您在多肉骨架上添加了 IDT 代码而没有添加代码来设置 GDT,那么很有可能就是这里出了问题。但是如果没有你所有的代码,我会做出最好的教育猜测

标签: c assembly x86 interrupt-handling osdev


【解决方案1】:

根据经验,我认为此问题与未设置 GDT 有关。通常,当有人说中断与 QEMU 的 -kernel 选项一起工作但不是 GRUB 的真实版本时,这通常与内核开发人员没有创建和加载自己的 GDT 有关。 Mulitboot Specification 说:

‘GDTR’ 即使段寄存器如上所述设置,“GDTR”可能无效,因此操作系统映像在设置自己的“GDT”之前不得加载任何段寄存器(即使只是重新加载相同的值!)。

当使用带有-kernel 选项的QEMU 时,GDTR 通常是有效的,但不能保证是这样。当使用真实版本的 GRUB(安装到硬盘驱动器、虚拟映像、ISO 等)启动时,您可能会发现 GDTR 实际上无效。第一次尝试使用选择器重新加载任何段寄存器时(即使它是相同的值),它可能会出错。使用中断时,代码段 (CS) 将被修改,这可能会导致三重故障并重新启动。

Multiboot 规范也没有说明哪些选择器指向代码或数据描述符。由于 Multiboot 规范不知道或保证 GDT 条目的布局,因此在填写 IDT 条目时会出现问题。每个 IDT 条目都需要指定一个特定的选择器,该选择器指向一个代码段。

OSDev 上的 Meaty Skeleton 教程没有设置中断,也没有修改任何段寄存器,因此代码很可能与 QEMU 的 -kernel 选项和真实版本的 GRUB 一起工作。在基础教程之上添加 IDT 和中断代码可能会导致您在使用 GRUB 引导时看到的失败。该教程可能会更清楚地说明您应该设置自己的 GDT,而不是依赖于 Multiboot loader 设置的 GDT。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 1970-01-01
    • 2015-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多