1.3.4 为在保护模式下执行head.s做准备
为了建立保护模式下的中断机制,setup程序将对可编程中断控制器8259A进行重新编程。
小贴士
8259A中断控制器:8259A是专门为了对8085A和8086/8088进行中断控制而设计的芯片,它是可以用程序控制的中断控制器。单个的8259A能管理8级向量优先级中断。在不增加其他电路的情况下,最多可以级联成64级的向量优先级中断系统。
具体代码如下:
- //代码路径:boot/setup.s
- 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
重新编程的结果在图1-20中有直观的表述。
![]() |
图1-20 对可编程中断控制器重新编程 |
在保护模式下,int 0x00~int 0x1F被Intel保留作为内部(不可屏蔽)中断和异常中断。如果不对8259A进行重新编程, int 0x00~int 0x1F中断将被覆盖。例如,IRQ0(时钟中断)为8号(int 0x08)中断,但在保护模式下此中断号是Intel保留的“Double Fault (双重故障)”。因此,必须通过8259A编程将原来的IRQ0x00~IRQ0x0F对应的中断号重新分布,即在保护模式下,IRQ0x00~IRQ0x0F的中断号是int 0x20~0x2F。
setup程序通过下面代码的前两行将CPU的工作方式设为保护模式。将CR0寄存器的第0位(PE)置1,即设定处理器的工作方式为保护模式。
小贴士
CR0寄存器:0号32位控制寄存器,存放系统控制标志。第0位为PE(Protected Mode Enable,保护模式使能)标志,置1时CPU工作在保护模式下,置0时为实模式。
- //代码路径:boot/setup.s
- mov ax,#0x0001 ! protected mode (PE) bit
- lmsw ax ! This is it!
- jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
图1-21对此做出了直观的标示。
![]() |
图1-21 打开保护模式 |
CPU工作方式转变为保护模式,一个重要的特征就是要根据GDT表来决定后续将执行哪里的程序。
注意看图1-18中对GDT表的设置,这些设置都是setup事先安排好了的默认设置。从setup程序跳转到head程序的方式,如图1-22所示。
![]() |
图1-22 程序段间跳转 |
![]() |
图1-23 保护模式开启前后的指令寻址方式对比示意图 |
![]() |
图1-23 (续) |
- //代码路径:boot/setup.s
- jmpi 0, 8
这一句中的“0”是段内偏移,“8”是保护模式下的段选择符,用于选择描述符表和描述符表项以及所要求的特权级。这里的8的解读方式很有意思,如果把8当作十进制的8来看待,这行程序的意思就很难理解了。必须把8看成二进制的1000,再把前后相关的代码联合起来当作一个整体看,并在头脑中形成类似图1-23所示的图,才能真正明白这行代码究竟在说什么。注意,这是一个以位为操作单位的数据使用方式,4个bit的每一位都有明确的意义,这是底层源代码的一个特色。
这里1000的最后两位00表示内核特权级,与之相对的用户特权级是11,第三位的0表示GDT表,如果是1,则表示LDT。1000的1表示所选的表(在此就是GDT表)的1项(GDT表项号排序为0项、1项、2项,也就是第2项)来确定代码段的段基址和段限长等信息,从图1-21中我们可以看到,代码是从段基址0x00000000、偏移为0处开始执行的,也就是head程序的开始位置,这意味着将执行head程序。
到这里为止,setup就执行完毕了,它为系统能够在保护模式下运行做了一系列的准备工作,但这些准备工作还不够,后续的准备工作将由head程序来完成。