C 上的中断处理程序在一次中断后不起作用
Interrupt handler on C doesn't work after one interrupt
我正在尝试使用 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 bootloader 执行):
void kmain() {
setup_idt();
for (;;) {}
}
我认为问题出在 "iret" 指令中。没有它,我的内核至少会打印一些东西(只有一个字符,就像我之前说的)。但是当我执行 asm volatile("iret"); QEMU 打印一些垃圾,然后在每次击键后清除它 ("SeaBios ...")。我需要做什么?
谢谢!
如果您在没有优化的情况下编译,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
load/store,并用于复制大对象,除非您 使用 -mgeneral-regs-only
[=62= 编译]
另请参阅@MichaelPetch 对链接副本的回答: 了解更多有趣的观点,以及一些 non-GCC 信息。
这里有2种解决方案:
编写一个 pure-asm 包装器来保存 call-clobbered regs,调用你的 C 函数,然后 returns 和 iret
用 __attribute__((interrupt))
声明你的函数来告诉 GCC 它是一个中断处理程序 。 gcc 手册的 x86 function attributes 有一个例子 .
x86 对该属性的支持与 traditionally-embedded 像 ARM 这样的 ISA 相比有点新,但现代 GCC 确实知道如何发出保留所有 regs 并以 iret
结尾的函数。但是你还需要 -mgeneral-regs-only
.
另请参阅 https://wiki.osdev.org/Interrupt_Service_Routines#GCC_.2F_G.2B.2B,它告诉您与此答案相同的内容。
(它还暗示了一个带有 pushad
/ popad; leave; iret
的邪恶黑客,它只适用于禁用优化。我会 不 建议,如果你可能的话使用支持 interrupt
属性的较新 GCC。)
wiki 页面的前面部分涵盖了尝试使用您自己的 iret
时遇到的一般问题,因此您可以看到总的 asm(compiler-generated + 您的)对于您的尝试。
我正在尝试使用 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 bootloader 执行):
void kmain() {
setup_idt();
for (;;) {}
}
我认为问题出在 "iret" 指令中。没有它,我的内核至少会打印一些东西(只有一个字符,就像我之前说的)。但是当我执行 asm volatile("iret"); QEMU 打印一些垃圾,然后在每次击键后清除它 ("SeaBios ...")。我需要做什么? 谢谢!
如果您在没有优化的情况下编译,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
load/store,并用于复制大对象,除非您 使用 -mgeneral-regs-only
[=62= 编译]
另请参阅@MichaelPetch 对链接副本的回答:
这里有2种解决方案:
编写一个 pure-asm 包装器来保存 call-clobbered regs,调用你的 C 函数,然后 returns 和
iret
用
__attribute__((interrupt))
声明你的函数来告诉 GCC 它是一个中断处理程序 。 gcc 手册的 x86 function attributes 有一个例子 .x86 对该属性的支持与 traditionally-embedded 像 ARM 这样的 ISA 相比有点新,但现代 GCC 确实知道如何发出保留所有 regs 并以
iret
结尾的函数。但是你还需要-mgeneral-regs-only
.
另请参阅 https://wiki.osdev.org/Interrupt_Service_Routines#GCC_.2F_G.2B.2B,它告诉您与此答案相同的内容。
(它还暗示了一个带有 pushad
/ popad; leave; iret
的邪恶黑客,它只适用于禁用优化。我会 不 建议,如果你可能的话使用支持 interrupt
属性的较新 GCC。)
wiki 页面的前面部分涵盖了尝试使用您自己的 iret
时遇到的一般问题,因此您可以看到总的 asm(compiler-generated + 您的)对于您的尝试。