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

7

如何设置保护模式下的中断?

这个链接提供了以下步骤:

  • 为中断描述符表腾出空间
  • 告诉CPU该空间在哪里(参见GDT教程:lidt和lgdt运作方式相同)
  • 告诉PIC不再使用BIOS默认值(参见编程PIC芯片)
  • 编写一些ISR处理程序(参见中断服务例程),包括IRQ和异常处理
  • 将ISR处理程序的地址放入适当的描述符中
  • 在IRQ掩码(PIC)中启用所有支持的中断
第三步对我来说没有意义(我查看了this链接,但里面没有关于告诉PIC任何事情的内容),因此我忽略了它并完成了接下来的两个步骤,但是当我到达最后一步时再次感到困惑。然而,从我对中断的理解来看,我不理解的这两个步骤都涉及来自PIC控制器的硬件中断,不应影响由IRQ 0引发的中断。因此,我也忽略了这一步。
当我运行代码时,它编译得很好,甚至在虚拟机中运行,但中断似乎只触发了一次。然后我意识到我没有向PIC发送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

我的主要函数(用于检查0x300的值)如下所示:
void main() {
    char iii[15];
    int * aa = (int *)0x300;
    for (;;)
    {
        setCursor(0, 0);
        print(itoab(*aa, iii));
    }
}

顺便说一下,我已经使用内存转储进行了验证,确保所有内容都加载在正确的地址,并且一切都在预期的位置。例如,0x300是内存的一个空闲部分,仅用于简化我的代码。

1
第三步是指将中断从标准BIOS映射重新映射的常规过程。通常IRQ 0-7被映射到INT 8-15,而IRQ 8-15被映射到INT 0x70-0x77。前一种映射方式存在问题,因为许多CPU异常在INT 8-15范围内,所以大多数操作系统会将至少IRQ 0-7重新映射到保留给CPU异常之外的范围。 - Ross Ridge
强烈建议执行第3步将IRQ移动到前32个IDT条目之上,因为这些用于陷阱。我还强烈建议为这前32个设置一些处理程序,以便您可以查看是否遇到问题。[并且对于您的aa变量使用volatile!] - Mats Petersson
哦,它们重叠是因为IBM PC中使用的原始8088仅使用INT 0-7用于异常。整个范围从8-31被保留,但IBM决定将这些保留的中断用于硬件中断。当80286添加了保护模式时,英特尔需要更多的异常来处理诸如一般保护故障和双重错误之类的问题,因此将它们分配到先前为自己保留的范围内。 - Ross Ridge
@RossRidge 感谢您的所有帮助!只有一个问题,如果我创建一个IDT,并填充空字节(当然除了我计划处理的中断),这样做是否可以解决我的问题?显然,一旦我弄清楚如何重新映射中断,我将立即进行重新映射。 - DividedByZero
如果这些条目从未被使用过,那么它们包含什么并不重要,因此零字节或随机数据都可以。 - Ross Ridge
显示剩余3条评论
1个回答

9
让我们来看一下一些相对较小的内核,比如Linux 0.01是如何做到的!
  • 为中断描述符表腾出空间
这个动作被执行了两次(实际上只有一次):首先,在引导加载程序(路径为/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 教程:lidtlgdt 的工作方式完全相同)。

lidt 需要一个包含 IDTR 内容的线性地址。该内容看起来像这样:

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

IDTR 的初始化如下所示:
lidt idt_descr
  • 告诉PIC您不再想使用BIOS默认值(请参阅编程PIC芯片)

正如@RossRidge在您的问题的评论中提到的那样,这意味着重新映射IRQ中断向量(IVs)。
由于PIC IV与Intel x86异常地址重叠,因此我们需要重新映射其中一个。异常地址是硬连接的,因此我们需要重新映射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
  • 编写一些ISR处理程序(参见中断服务例程),用于处理IRQ和异常

对于异常,您可以在/kernel/traps.c/kernel/asm.s中找到处理程序代码。
某些异常在跳转到处理程序之前会将错误代码推入堆栈,您需要弹出该代码,否则iret指令将失败。页面故障还会将相应的虚拟地址写入cr2
IRQ处理程序分布在整个系统中。例如,计时器和磁盘中断处理程序位于/kernel/system_call.s中,键盘中断处理程序位于/kernel/keyboard.s中。

  • 将ISR处理程序的地址放入相应的描述符中

异常的初始化在/kernel/traps.ctrap_init函数中完成:

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处理程序的入口初始化再次分散在几个文件中。例如,来自/kernel/sched.c的sched_init初始化定时器中断处理程序的地址。 在PIC的IRQ掩码中启用所有支持的中断。 这是在main函数中通过宏sti在/init/main.c中完成的。它在/asm/system.h中定义如下:
#define sti() __asm__ ("sti"::) 

1
感谢您提供详细的答案!现在一切似乎都正常了,只是硬件中断没有触发 - 调用 int 32 触发了 PIT 中断处理程序,但即使在 sti 指令之后,PIT 中断也从未触发。 - DividedByZero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接