本章已经详细地介绍了IDT和中断描述符(ID)的结构,进入到保护模式以后,需要在内核里为CPU内核设置一个包含256个中断描述符的IDT,具体实现如代码清单2-10所示。
1 .code32
2 .text
3 .globl startup_32
4 startup_32:
5 movl $0x10,%eax
6 movw %ax, %ds
7 movw %ax, %es
8 movw %ax, %fs
9 movl $0x18,%eax 10movw %ax,%gs
11
12 call setup_idt
13 int $0x80
14
15 movl $0x18, %eax
16 movw %ax,%gs
17 movl $0x0,%edi
18 movb $0xf,%ah
19 movb $0x42,%al
20 movw %ax,%gs:(%edi)
21
22 loop:
23 jmploop
24
25 setup_idt:
26 leal ignore_int,%edx
27 movl $0x00080000, %eax
28 movw %dx,%ax
29 movw $0x8e00,%dx
30 leal idt,%edi
31 movl $256,%ecx
32 rp_sidt:
33 movl %eax,(%edi)
34 movl%edx,4(%edi)
35 addl $8,%edi
36 decl%ecx
37 jnerp_sidt
38 lidt idt_descr
39 ret
40
41 ignore_int:
42 /* 我们现在还没有printk函数,通过向显存直接写入字符来模拟它 */
43 pushl %eax
44 pushl %ecx
45 pushl %edx
46 pushw %ds
47 pushw %es
48 pushw %fs
49 movl $0x10, %eax
50 movw %ax,%ds
51 movw %ax,%es
52 movw %ax,%fs
53 /* 调用printk函数 */
54 movl $0x96,%edi
55 movb $'I', %al
56 movb $0x0c, %ah
57 movw %ax, %gs:(%edi)
58 popw %fs
59 popw %es
60 popw %ds
61 popl %edx
62 popl %ecx
63 popl %eax
64 iret
65
66 .align 4
67 .word 0
68 idt_descr:
69 .word 256 *8-1
70 .long idt
71
72 idt:
73 .fill 256,8,0
首先,第73行声明了一个包含256个元素,每个元素的宽度都是8B,且其值为0的数组,这就是内核所要使用的IDT。接下来,就是初始化IDT的过程,第12行调用了setup_idt这一段子程序,从第25行到39行是setup_idt子程序的定义。
setup_idt子程序先初始化中断描述符,并将它保存到edx、eax寄存器中,其中描述符中的中断服务程序都指向了ignore_int。接下来通过rp_sidt循环了256次,将IDT的256项都设置成相同的中断描述符(读者可以根据图2-10自己分析中断描述符的结构)。这样一来,不管发生什么中断,CPU都会转而调用ignore_int这个中断服务程序。
而ignore_int的实现则是在屏幕第1行的靠右位置(0x96/2=0x4b)处打印红色的字母I。这里要注意的是,所有的中断服务程序要遵循开始时保存上下文,结束后恢复上下文的规定。中断服务程序的最后,一定是iret指令,用于恢复到中断发生之前的程序继续执行。
程序的第13行使用了int $0x80语句进行了实验。这条语句使用了软中断来触发一次CPU中断,看上去它和之前使用的BIOS中断的指令是一样的,但实际上现在已经是保护模式了,它背后的原理和BIOS中断已经大相径庭了。如果一切正常的话,当0x80号中断被触发时,屏幕上就可以正确地显示红色的I了,如图2-13所示。
图2-13 中断服务程序