【问题标题】:Setting up interrupts in protected mode (x86)在保护模式下设置中断 (x86)
【发布时间】:2016-04-06 07:15:10
【问题描述】:

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

This 链接说应该:

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

第三步对我来说毫无意义(我查看了this 链接,但没有告诉 PIC 任何事情)所以我忽略了它并完成了接下来的两个步骤,只是当我再次无能为力时到达了最后一步。但是,根据我对中断的理解,我不理解的两个步骤都与来自 PIC 控制器的硬件中断有关,不应影响 PIT 在 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 是内存的空闲部分,仅用于简化我的代码。

【问题讨论】:

  • 第三步是指从标准 BIOS 映射重新映射中断的通常过程。通常 IRQ 0-7 映射到 INT 8-15,IRQ 8-15 映射到 INT 0x70-0x77。前一种映射会带来问题,因为许多 CPU 异常都在 INT 8-15 到范围内,因此大多数操作系统将至少 IRQ 0-7 重新映射到为 CPU 异常保留的范围之外的东西
  • 注意,这意味着连接到IRQ 0的PIT生成INT 8。INT 0是整数除法溢出(被零除)异常。
  • 强烈建议执行第 3 步,将 IRQ 移到前 32 个 IDT 条目之上,因为它们用于陷阱。我还强烈建议为前 32 个处理程序放置一些处理程序,以便您查看是否遇到问题。 [并为您的 aa 变量使用 volatiled!
  • @RossRidge 所以如果我要在我的代码中引发 PIT 中断,我应该引发 int 0x08?为什么 IRQ 线重叠?
  • 是的,如果您不将 PIC IRQ 更改为 INT 映射,那么 INT 0x08 将调用与 IRQ 0 使用的处理程序相同的处理程序。它也是在 CPU 双重故障时调用的处理程序。如果你的代码真的和你展示的一样,并且 IDT 中只有一个条目,那么当 PIT 发出未屏蔽中断信号时,它应该会导致三重错误。如果 INT 8 的条目超出 IDT 的限制,则 CPU 将生成一般保护故障。 GP故障入口也超出了限制,所以产生了双重故障,也超出了限制。这会导致三重故障。

标签: c assembly x86 operating-system interrupt


【解决方案1】:

让我们看看一些相对较小的内核,即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 教程:lidt 的工作方式与 lgdt 完全相同)

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

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

IDTR初始化如下:

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

正如 @RossRidge 在 cmets 中提到的问题,这意味着重新映射 IRQ 中断向量 (IV)。
由于 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.

现在,这是真正的代码。中间的jmps 用于同步 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 和异常编写几个 ISR 处理程序(请参阅中断服务例程)

对于异常,您可以在/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 掩码中启用所有支持的中断

这是在 /init/main.cmain 函数中使用宏 sti 完成的。在/asm/system.h中定义如下:

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

【讨论】:

  • 感谢您的详细解答!现在似乎一切正常,只是硬件中断没有触发 - 调用 int 32 会触发 PIT 中断处理程序,但是即使在 sti 指令之后,PIT 中断也不会触发..
猜你喜欢
  • 2014-12-14
  • 1970-01-01
  • 1970-01-01
  • 2016-02-04
  • 2015-09-25
  • 2016-11-05
  • 2021-10-01
  • 1970-01-01
  • 2018-08-30
相关资源
最近更新 更多