3.4 实模式代码go_to_proteced_mode函数
main函数走到go_to_protected_mode,就是将告别实模式环境,进入保护模式了。保护模式(Protected Mode,或有时简写为 pmode)是一种 80286 系列和之后的 x86 兼容 CPU 操作模式。保护模式有一些新的特色,设计用来增强多功能和系统稳定度,像是内存保护,分页系统,以及硬件支援的虚拟内存。大部分的现今x86操作系统都在保护模式下运行,包含Linux、FreeBSD、以及微软 Windows 2.0 和之后版本。
注意,这里还是在实模式下,只不过是进入保护模式相关的代码。有关保护模式编程的详细介绍,请参考博客“保护模式编程”
看到main函数中的最后一行代码,注释上写的是激活保护模式,调用
go_to_protected_mode()函数,在linux/arch/x86/boot/pm.c中:
101/* 102 * Actual invocation sequence 103 */ 104void go_to_protected_mode(void) 105{ 106 /* Hook before leaving real mode, also disables interrupts */ 107 realmode_switch_hook(); 108 109 /* Enable the A20 gate */ 110 if (enable_a20()) { 111 puts("A20 gate not responding, unable to boot.../n"); 112 die(); 113 } 114 115 /* Reset coprocessor (IGNNE#) */ 116 reset_coprocessor(); 117 118 /* Mask all interrupts in the PIC */ 119 mask_all_interrupts(); 120 121 /* Actual transition to protected mode... */ 122 setup_idt(); 123 setup_gdt(); 124 protected_mode_jump(boot_params.hdr.code32_start, 125 (u32)&boot_params + (ds() << 4)); 126} |
3.4.1 禁止可屏蔽和不可屏蔽中断
107行,调用realmode_switch_hook()函数。这个函数位于同一个文件中,内容如下:
18/* 19 * Invoke the realmode switch hook if present; otherwise 20 * disable all interrupts. 21 */ 22static void realmode_switch_hook(void) 23{ 24 if (boot_params.hdr.realmode_swtch) { 25 asm volatile("lcallw *%0" 26 : : "m" (boot_params.hdr.realmode_swtch) 27 : "eax", "ebx", "ecx", "edx"); 28 } else { 29 asm volatile("cli"); 30 outb(0x80, 0x70); /* Disable NMI */ 31 io_delay(); 32 } 33} |
注释中写得很清楚了,这个函数的目的在于如果hdr参数的realmode_swtch字段被设置,则直接跳到对应的子程序中,一些bootloader程序用该子程序来抓住实模式下最后的执行代码的机会。否则关中断。我们看到header.S的第122行,realmode_swtch字段为0,即没有设置,所以函数直接到29行,执行cli指令关中断。有关x86相关的指令请参考博客“Intel 8086/8088 指令系统”
30行是全内核代码中第一次出现跟I/O低级函数,在博文“Linux I/O体系结构”有这些低级函数的介绍,这里讲这些函数的原型:
static inline void outb(u8 v, u16 port) { asm volatile("outb %0,%1" : : "a" (v), "dN" (port)); } |
其实这些低级I/O函数都在arch/x86/boot/Boot.h中定义,以后全代码的所有对这些函数的扩展,最后也都会走到这里,我们就只看outb的源代码,其他的类似。outb()函数就是对outb指令进行封装,将参数v的内容传输到参数port对应的端口中。那么30行的outb(0x80, 0x70)意思是将0x70端口对应接口内部寄存器的值置为0x80。
注意,Linux启动程序运行到此,还没有建立起内核I/O子系统,所有这里的I/O端口跟我们平常在/proc/ioports文件中查的I/O端口是不一样的。系统上电以后,BIOS程序把0x70号端口置为的不可屏蔽中断寄存器,NMI。
NMI (Non Maskable Interrupt)——不可屏蔽中断(即CPU不能屏蔽),无论状态寄存器中 IF 位的状态如何,CPU收到有效的NMI必须进行响应。NMI中断类型号固定为2,它在被响应时无中断响应周期。不可屏蔽中断通常用于故障处理(如:协处理器运算出错,存储器校验出错,I/O通道校验出错等)。这里执行30行代码就是紧接着把这个NMI也给禁止掉。随后31行io_delay()函数,是对outb()函数的扩展:
static inline void io_delay(void) { const u16 DELAY_PORT = 0x80; asm volatile("outb %%al,%0" : : "dN" (DELAY_PORT)); } |
是把0x80端口对应接口内部寄存器的值也置为0x80。我们知道,0x80其实是个空端口,所以这个io_delay函数的意义仅仅是对I/O端口执行一个空操作,以达到对CPU的I/O访问周期的延时工作。