【问题标题】:Interrupt handler on C doesn't work after one interrupt [duplicate]一个中断后C上的中断处理程序不起作用[重复]
【发布时间】:2024-04-28 11:05:03
【问题描述】:

我正在尝试使用 C 和 QEMU 实现键盘中断处理程序。但是当我执行程序时,我的处理程序只打印一个字符。之后,处理程序根本不起作用。

我的 IDT 设置:

struct IDT_entry {
    unsigned short int offset_lowerbits;
    unsigned short int selector;
    unsigned char zero;
    unsigned char type_attr;
    unsigned short int offset_higherbits;
};

void setup_idt() {
    struct IDT_entry IDT[256];
    unsigned long keyboard_address;
    unsigned long idt_address;
    unsigned long idt_ptr[2];

    keyboard_address = (unsigned long) keyboard_handler;
    IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
    IDT[0x21].selector = 0x8;
    IDT[0x21].zero = 0;
    IDT[0x21].type_attr = 0x8e;
    IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;

    /*
                PIC1   PIC2
    Commands    0x20   0xA0
    Data        0x21   0xA1

    */

    // ICW1 - init
    outb(0x20, 0x11);
    outb(0xA0, 0x11);

    // ICW2 - reset offset address if IDT
    // first 32 interrpts are reserved
    outb(0x21, 0x20);
    outb(0xA1, 0x28);

    // ICW3 - setup cascading
    outb(0x21, 0b0);
    outb(0xA1, 0b0);

    // ICW4 - env info
    outb(0x21, 0b00000011);
    outb(0xA1, 0b00000011);
    // init finished

    // disable IRQs except IRQ1
    outb(0x21, 0xFD);
    outb(0xA1, 0xff);

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

    __asm__ __volatile__("lidt %0" :: "m" (*idt_ptr));
    __asm__ __volatile__("sti");
}

我的键盘处理程序:

// Variables for printing ==
unsigned int location = 0;
char* vga = (char*)0xb8000;
char letter;
// =========================

void keyboard_handler() {
    if (inb(0x64) & 0x01 && (letter = inb(0x60)) > 0) {
        vga[location] = keyboard_map[letter];
        vga[location+1] = 0x4;
        location += 2;
    }

    outb(0x20, 0x20);

    // __asm__ __volatile__("iret");
}

主函数(它是从我的 asm 引导加载程序中执行的):

void kmain() {
    setup_idt();

    for (;;) {}
}

我认为问题出在“iret”指令中。没有它,我的内核至少会打印一些东西(只有一个字符,就像我之前说的那样)。但是当我执行 asm volatile("iret"); QEMU 打印一些垃圾,然后在每次击键后将其清除(“SeaBios ...”)。我需要做什么? 谢谢!

【问题讨论】:

  • 在纯汇编中编写一个包装器,根据 C 调用约定调用您的处理程序。
  • @Jester,有什么区别?
  • 与您的问题无关,但中断处理程序的更改 - inb(0x64) &amp; 0x01 不需要从中断处理程序进行检查。您只需要在轮询键盘的中断处理程序之外执行此操作。

标签: c gcc assembly interrupt-handling osdev


【解决方案1】:

如果您在没有优化的情况下进行编译,asm("iret") 可能会在堆栈指针仍指向保存的 EBP 值时运行,因为-fno-omit-frame-pointer 是默认值,而清理尾声发生在最后一个之后函数的C语句。

或者它可能指向其他保存的寄存器。无论如何,欺骗编译器并跳出内联 asm 语句永远不会安全(除非您使用 asm goto 可能会跳转到函数内的 C 标签,但这并不能解决你的问题)。


此外,C 调用约定允许函数破坏 EAX、ECX、EDX 和 FPU 状态。即使您确实设法将iret 破解到您的函数中,它也会破坏被中断代码的状态。 GCC 将使用 SSE/x87 以 32 位模式实现 _Atomic int64_t 加载/存储,并用于复制大型对象,除非您使用 -mgeneral-regs-only 编译

另请参阅@MichaelPetch 对链接副本的回答:Creating a C function without compiler generated prologue/epilogue & RET instruction? 了解更多有趣的观点和一些非 GCC 信息。


这里有两种解决方案:

  • 编写一个纯 asm 包装器来保存被调用破坏的 regs,调用您的 C 函数,然后返回 iret

  • __attribute__((interrupt)) 声明你的函数,告诉GCC 它是一个中断处理程序。 gcc手册的x86 function attributes有一个例子

    与传统的嵌入式 ISA(如 ARM)相比,x86 对该属性的支持是最近才出现的,但现代 GCC 确实知道如何发出保留所有 reg 并以 iret 结尾的函数。但是你仍然需要-mgeneral-regs-only

另请参阅https://wiki.osdev.org/Interrupt_Service_Routines#GCC_.2F_G.2B.2B,它告诉您与此答案相同的内容。

(它还建议使用 pushad / popad; leave; iret 进行邪恶黑客攻击,它仅适用于禁用优化。如果您可以使用支持 @987654336 的较新 GCC,我会建议@ 属性。)

wiki 页面的前面部分介绍了尝试使用您自己的 iret 时遇到的一般问题,因此您可以看到总 asm(编译器生成的 + 您的)在您的尝试中会是什么样子。

【讨论】:

    最近更新 更多