在保护模式下设置中断(x86)

为保护模式设置中断的过程是什么?

这个链接说应该:

  • 为中断描述符表腾出空间
  • 告诉CPU该空间在哪里(参见GDT教程:lidt的工作方式与lgdt完全相同)
  • 告诉PIC您不再需要使用BIOS默认值(请参阅编程PIC芯片)
  • 为IRQ和exception编写几个ISR处理程序(请参阅中断服务程序)
  • 将ISR处理程序的地址放在适当的描述符中
  • 启用IRQ掩码中所有支持的中断(PIC)

第三步对我没有任何意义(我查看了这个链接,但没有任何关于告诉PIC的事情)所以我忽略了它并完成了接下来的两个步骤,当我到达最后一步时再次无能为力。 但是,根据我对中断的理解,我不理解的两个步骤都与PIC控制器的硬件中断有关,不应该影响PIT在IRQ 0上引发的中断。因此我也忽略了这一步骤。

当我运行我的代码时,它编译得很好,甚至在虚拟机中运行,但中断似乎只发射一次。 然后我意识到我没有向EOI发送EOI,以防止它再引发任何中断。 但是,在iret指令使虚拟机崩溃之前,添加mov al, 0x20out 0x20, al

这是我的IDT:

 ; idt idt_start : dw 0x00 ; The interrupt handler is located at absolute address 0x00 dw CODE_SEG ; CODE_SEG points to the GDT entry for code db 0x0 ; The unused byte db 0b11101001 ; 1110 Defines a 32 bit Interrupt gate, 0 is mandatory, privilege level = 0 (0b00), the last bit is one so that the CPU knows that the interrupt will be used dw 0x00 ; The higher part of the offset (0x00) is 0x00 idt_end: idt_descriptor : dw idt_end - idt_start - 1 ; Size of our idt, always one less than the actual size dd idt_start ; Start address of our idt 

这是我的中断处理程序(位于内存中的绝对位置0x00):

 ISR_0: push eax add [0x300], byte mov al, 0x20 out 0x20, al pop eax iret times 512-($-$$) db 0 

这是我用来进入保护模式并将GDT和IDT加载到内存中的代码:

 [bits 16] switch_to_pm: cli lgdt [gdt_descriptor] lidt [idt_descriptor] mov eax, cr0 or eax, 1 mov cr0,eax jmp CODE_SEG:init_pm [bits 32] init_pm : mov ax, DATA_SEG mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax mov ebp, 0x90000 mov esp, ebp sti call BEGIN_PM 

我的主要function(检查0x300的值)如下:

 void main() { char iii[15]; int * aa = (int *)0x300; for (;;) { setCursor(0, 0); print(itoab(*aa, iii)); } } 

顺便说一句,我已经validation使用内存转储,所有内容都加载到正确的地址,一切都正是预期的位置。 例如,0x300是一个免费的内存部分,仅用于简化我的代码。

让我们来看看一些相对较小的内核,即Linux 0.01如何做到这一点!

  • 为中断描述符表腾出空间

这样做了两次(好吧,技术上只有一次):首先,bootloader(路径为/boot/boot.s )初始化IDTR,因此CPU在进入保护模式时很开心。 IDTR内容如下:

 idt_48: .word 0 | idt limit=0 .word 0,0 | idt base=0L 

IDTR加载如下:

 lidt idt_48 | load idt with 0,0 

现在,可以执行跳转。
请注意,这里没有IDT。 它只是一个虚拟的,所以内核中的任何地方都不会发生错误。

然后,初始化实际IDT(路径为/boot/head.s )。 空间分配如下:

 _idt: .fill 256,8,0 # idt is uninitialized 
  • 告诉CPU该空间在哪里(参见GDT教程: lidt工作方式与lgdt

lidt需要一个包含IDTR内容的线性地址。 该内容如下所示:

 idt_descr: .word 256*8-1 # idt contains 256 entries .long _idt 

IDTR初始化如下:

 lidt idt_descr 
  • 告诉PIC您不再需要使用BIOS默认值(请参阅编程PIC芯片)

正如@RossRidge在您的问题的评论中提到的那样,这意味着重新映射IRQ中断向量(IV)。
由于PIC IV与Intel x86exception地址重叠,我们必须重新映射其中一个。 exception地址是硬连线的,因此我们需要重新映射PIC矢量。
另请参阅Linus相应代码上方的此评论:

 | well, that went ok, I hope. Now we have to reprogram the interrupts :-( | we put them right after the intel-reserved hardware interrupts, at | int 0x20-0x2F. There they won't mess up anything. Sadly IBM really | messed this up with the original PC, and they haven't been able to | rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, | which is used for the internal hardware interrupts as well. We just | have to reprogram the 8259's, and it isn't fun. 

现在,这是真正的代码。 其间的jmp用于同步CPU和PIC,因此CPU不会发送PIC无法接收的数据。 这与写入内存时的等待状态相当:当CPU比内存/内存仲裁器快时,下次访问内存之前需要等待一段时间。

 mov al,#0x11 | initialization sequence out #0x20,al | send it to 8259A-1 .word 0x00eb,0x00eb | jmp $+2, jmp $+2 out #0xA0,al | and to 8259A-2 .word 0x00eb,0x00eb mov al,#0x20 | start of hardware int's (0x20) out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 | start of hardware int's 2 (0x28) out #0xA1,al .word 0x00eb,0x00eb mov al,#0x04 | 8259-1 is master out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 | 8259-2 is slave out #0xA1,al .word 0x00eb,0x00eb mov al,#0x01 | 8086 mode for both out #0x21,al .word 0x00eb,0x00eb out #0xA1,al .word 0x00eb,0x00eb mov al,#0xFF | mask off all interrupts for now out #0x21,al .word 0x00eb,0x00eb out #0xA1,al 
  • 为IRQ和exception编写几个ISR处理程序(请参阅中断服务程序)

对于exception,您可以在/kernel/traps.c/kernel/asm.s找到处理程序代码。
一些例外在跳转到处理程序之前在堆栈上推送错误代码,您必须弹出或iret指令将失败。 页面错误还会将相应的虚拟地址写入cr2
IRQ处理程序遍布整个系统。 -.-定时器和磁盘中断处理程序位于/kernel/system_call.s ,键盘中断处理程序位于/kernel/keyboard.s

  • 将ISR处理程序的地址放在适当的描述符中

exception的初始化在trap_init函数的/kernel/traps.c中完成:

 void trap_init(void) { int i; set_trap_gate(0,&divide_error); set_trap_gate(1,&debug); set_trap_gate(2,&nmi); set_system_gate(3,&int3); /* int3-5 can be called from all */ set_system_gate(4,&overflow); set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op); set_trap_gate(7,&device_not_available); set_trap_gate(8,&double_fault); set_trap_gate(9,&coprocessor_segment_overrun); set_trap_gate(10,&invalid_TSS); set_trap_gate(11,&segment_not_present); set_trap_gate(12,&stack_segment); set_trap_gate(13,&general_protection); set_trap_gate(14,&page_fault); set_trap_gate(15,&reserved); set_trap_gate(16,&coprocessor_error); for (i=17;i<32;i++) set_trap_gate(i,&reserved); /* __asm__("movl $0x3ff000,%%eax\n\t" "movl %%eax,%%db0\n\t" "movl $0x000d0303,%%eax\n\t" "movl %%eax,%%db7" :::"ax");*/ } 

IRQ处理程序条目初始化再次分布在多个文件中。 sched_init来自/kernel/sched.c sched_init初始化定时器中断处理程序的地址。

  • 启用IRQ掩码中所有支持的中断(PIC)

这是在main函数的/init/main.c中用宏sti 。 它在/asm/system.h定义如下:

 #define sti() __asm__ ("sti"::)