1.3.5 head.s开始执行(3)
代码如下:
- //代码路径:boot/head.s
- setup_gdt
- ............
- setup_gdt:
- lgdt gdt_descr
- ret
- _gdt: .quad 0x0000000000000000 /* NULL descriptor */
- .quad 0x00c09a0000000fff /* 16Mb */
- .quad 0x00c0920000000fff /* 16Mb */
- .quad 0x0000000000000000 /* TEMPORARY-don't use */
- .fill 252,8,0 /* space for
LDT's and TSS's etc */
点评
为什么要废除原来的GDT而重新设置一套GDT呢?
原来GDT所在的位置是设计代码时在setup.s里面设置的,将来这个setup模块所在的内存位置会在设计缓冲区时被覆盖。如果不改变位置,GDT的内容将来肯定会被缓冲区覆盖掉,从而影响系统的运行。这样一来,将来整个内存中唯一安全的地方就是现在head.s所在的位置了。
那么有没有可能在执行setup程序时直接把GDT的内容拷贝到head.s所在的位置呢?肯定不能,如果先复制GDT的内容,后移动system模块,它就会被后者覆盖掉;如果先移动system模块,后复制GDT的内容,它又会把head.s对应的程序覆盖掉,而这时head.s还没有执行呢。所以,无论如何,都要重新建立GDT。
全局描述符表GDT的位置和内容发生了变化,特别要注意最后的三位是fff,说明段限长不是原来的8MB,而是现在的16MB。如果后面的代码第一次使用这几个段选择符就是访问8MB以后的地址空间,将会产生段限长超限报警,为了防止这类可能发生的情况,这里再次对一些段选择符进行重新设置,包括DS、ES、FS、GS和SS,方法与图1-26类似,主要是段限长增加了一倍,变为了16MB。上述过程如图1-30所示。
调整DS和ES等寄存器的对应代码如下:
- //代码路径:boot/head.s
- movl $0x10,%eax # reload all the segment registers
- mov %ax,%ds # after changing gdt. CS was already
- mov %ax,%es # reloaded in 'setup_gdt'
- mov %ax,%fs
- mov %ax,%gs
通过测试我们发现,这是一种舍近求远的方法,其实只要在setup中构建第一个GDT表时把控制段限长的7ff直接设置为fff就可以一步到位了,不需要在这里重新设置段选择符。
现在user_stack数据结构的起始位置就是内核栈的栈底,栈顶指针esp指向user_stack数据结构的外边缘,也就是内核栈的栈顶。这样,当后面的程序需要压栈时,就可以最大限度地使用栈空间。栈顶的增长方向是从高地址向低地址的,如图1-31所示。设置esp的代码如下:
图1-30 再一次调整DS、ES、FS、GS |
- //代码路径:boot/head.s
- lss _stack_start,%esp
图1-31 设置内核栈 |
图1-32 检验A20是否打开 |
- //代码路径:boot/head.s
- xorl %eax, %eax
- 1: incl %eax
- movl %eax, 0x000000
- cmpl %eax, 0x100000
- je 1b
点评
A20如果没有打开,则计算机处于实模式下,超过0xFFFFF寻址必然“回滚”。一个特例是0x100000就会回滚到0x000000,也就是说,地址0x100000处存储的值必然和地址0x000000处存储的值完全相同(参见图1-30的描述)。通过在内存0x000000位置写入一个数据,然后比较此处和1MB(即0x100000,注意,已超过实模式寻址范围)处数据是否一致,就可以检验A20地址线是否已打开。