为保护模式设置中断的过程是什么?
This链接说应该:
- 为中断描述符表创建空间
- 告诉CPU该空间在哪里(参见GDT教程:lidt的工作方式与lgdt完全相同)
- 告诉PIC您不再需要使用BIOS默认值(请参阅编程PIC芯片)
- 为IRQ和异常编写几个ISR处理程序(请参阅中断服务程序)
- 将ISR处理程序的地址放在适当的描述符中
- 启用(PIC的)IRQ掩码中所有支持的中断
第三步对我没有意义(我查看了this链接,但没有任何关于告诉PIC的事情)所以我忽略了它并完成了接下来的两个步骤,只是再次无能为力当我到达最后一步。但是,根据我对中断的理解,我不理解的两个步骤都与PIC控制器的硬件中断有关,不应该影响PIT在IRQ 0上引发的中断。因此,我也忽略了这一步骤。 p>
当我运行我的代码时,它编译得很好,甚至在虚拟机中运行,但中断似乎只发射一次。然后我意识到我没有向EOI发送EOI,以防止它再引发任何中断。但是,在mov al, 0x20
指令之前添加out 0x20, al
和iret
会导致虚拟机崩溃。
这是我的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是一个免费的内存部分,仅用于简化我的代码。
答案 0 :(得分:7)
让我们看看一些相对较小的内核,即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 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
- 为IRQ和异常编写几个ISR处理程序(请参阅中断服务程序)
对于例外情况,您可以在/kernel/traps.c
和/kernel/asm.s
中找到处理程序代码
在跳转到处理程序之前,有些异常会在堆栈上推送错误代码,您必须将其弹出或iret
指令失败。页面错误还会将相应的虚拟地址写入cr2
IRQ处理程序遍布整个系统。 -.-定时器和磁盘中断处理程序位于/kernel/system_call.s
,键盘中断处理程序位于/kernel/keyboard.s
中。
- 将ISR处理程序的地址放在适当的描述符中
异常初始化在/kernel/traps.c
函数的trap_init
中完成:
void trap_init(void)
{
int i;
set_trap_gate(0,÷_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
的{{1}}初始化定时器中断处理程序的地址。
- 启用(PIC的)IRQ掩码中所有支持的中断
这是使用宏/kernel/sched.c
在/init/main.c
函数的main
中完成的。它在sti
中定义如下:
/asm/system.h