我的常见isr存根定义为:
isr_common_stub:
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; load the kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call isr_handler
;call saySomething
pop ebx ; reload the original data segment descriptor
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
;call saySomething
popa ; Pops edi,esi,ebp...
;add esp, 8 ; Cleans up the pushed error code and pushed ISR number
;sti
;call saySomething
;iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
ret ;Return to the caller. Iret and interrupt enabling is handled within caller.
从每个中断调用此代码如下:
%macro ISR_NOERRCODE 1
global isr%1
isr%1:
cli ; Disable interrupts firstly.
push byte 0 ; Push a dummy error code.
push byte %1 ; Push the interrupt number.
jmp isr_common_stub ; Go to our common handler code.
sub esp, 2 ;Removes 2 bytes from stack
;sti ;Iret enables interrupts again
iret ;Return from interrupt
%endmacro
此代码适当地调用外部处理方法,但在从外部处理方法返回后导致gpf中断。
此外,我已经将我的gdt映射到覆盖整个4G地址空间,其中包含0个特权级别数据和代码描述符(以及空描述符)。任何帮助表示赞赏。
答案 0 :(得分:2)
我们走了......
a)在宏中,做cli
是愚蠢的。使用"中断门" (而不是"陷阱门")并且CPU将自动禁用IRQ而不存在竞争条件的风险(例如,在中断处理程序启动之后但在完成执行cli
之前的IRQ)。 / p>
b)在宏中,jmp
之后的代码永远不会执行,因此毫无意义。
c)如果你将2个字节压入堆栈,那么你就搞乱了堆栈对齐,并且应该得到你将要获得的性能损失。推两个双字来避免这种情况。
d)CPU为某些异常提供32位错误代码;因此,您的虚拟错误代码也应该是32位。
是的,你没有向ISR传递任何东西。大多数异常处理程序需要在异常发生时知道通用寄存器的状态等。 f)您不能指望ret
从中断返回并需要取消注释iret
。
g)删除所有段寄存器(DS,ES,FS,GS)可能是一个坏主意。操作系统将它们视为常量,您没有理由在从ISR返回之前加载它们,或者必须保存它们然后重新加载。
h)不同的异常有不同的要求,所以有一个常见的异常处理程序"很傻。为不同的异常提供不同的异常处理程序会更好,这就是IDT提供的内容(在这种情况下,没有必要在堆栈上推送"中断号")。
j)IRQ与例外情况不同。对于IRQ,中断处理程序永远不需要无意义的错误代码(或中断代码的状态)。请注意,处理/隐藏PIC芯片"虚假IRQ"在汇编存根中,这意味着检查服务寄存器中的PIC""告诉真正的IRQ7和虚假IRQ7之间的区别,以及真实IRQ15和虚假IRQ15之间的区别。对于虚假IRQ15,您需要将EOI发送给主设备而不是从设备,对于虚假的IRQ7,您根本不能发送任何EOI。如果您正在使用IO APIC,那么没有理智的方法来禁用/屏蔽PIC芯片的虚假IRQ,因此您仍然需要为两个PIC芯片提供伪IRQ处理程序(除了处理器之外) APIC自己的虚假中断)。