Head.S也要先做一些屏蔽中断一类的准备工作,然后,它会对中断向量表做一定的处理:用一个默认的表项把所有的256个中断向量填满。这个默认表项指向一个特殊的中断服务程序,事实上,该程序什么都不作。为什么这样呢?这是因为,在 Linux系统初始化完成后,BIOS的中断服务程序是不会再被使用了。Linux采用了很完善的设备驱动程序使用机制,该机制使特定硬件设备的中断服务程序很容易被系统本身或用户直接调用,而且,调用时所需的参数通常都要比BIOS调用来得简单。由于设备驱动程序是专门针对一个设备的,所以,它所包含的中断服务程序在功能上通常也比BIOS中断要完善,所以,BIOS的中断向量在这里就被覆盖了。启动处在这个阶段,第一,还不需要开启中断,第二,相应的设备驱动程序还未被加载,所以把中断向量表置空显然是个合理的选择。事实上,直到初始化到了最后的阶段(start_kernel()被调用后),中断向量表才被各个中断服务程序重新填充。
Boot Loader读入内存中的启动参数和命令行参数,这些参数在启动过程中是必须的数据资料,他们不但在启动的过程中要使用,在启动后也是必须的,Head.S把它们保存在empty_zero_page 页中,这就涉及到了页机制,之所以先映射这个页,是因为以后运行的程序可能会占用启动参数和命令行参数的内存区,所以要先保存它们。
Head.S此后会检查CPU的类型:虽然Intel系列CPU保持兼容,但无疑后继产品会有很多新的特性和功能(如奔腾芯片支持
下面是一个非常重要的初始化步骤—页初始化。
Head.S调用了Setup_paging这个子函数,这个函数只对4MB大小的内存空间进行了映射,剩下的部分在start_kernel()中分配,这个函数并不复杂,它先把从线性地址0xC0000000处开始的两个大小为4KB的内存空间映射为两个页:一个为页目录表—swap_page_dir,另一个为零页—pg0,他们分别指向物理地址的0x1000和0x2000。此后设置页目录表的属性(可读写、存在、任意特权级可访问),并使它的表项指向为内核分配的4MB空间上的各个页。在Head.S里定义了几个比较特殊的页,除了上面提到的swap_page_dir,pg0 以外,还有empty_bad_page、empty_bad_page_table、empty_zero_page等等。刚才提到了empty_zero_page,这里详细介绍一下这个页,以便对页机制及页初始化有更进一步的认识。
empty_zero_page这个页存放的是系统启动参数和命令行参数,他们各占2KB大小,即各占半页。该页的具体内容如下:
偏移量 数据类型 描述
------ ---- -----------
0 32 bytes 一段屏幕显示信息
2 unsigned
short EXT_MEM_K, extended memory size in Kb (BIOS的15号
中断得到的内存大小)
0x20 unsigned
short CL_MAGIC, 命令行标志数 (=0xA
0x22 unsigned
short CL_OFFSET, 经计算所得的命令行程序的偏移地址,
0x90000 + contents of CL_OFFSET
(只在CL_MAGIC = 0xA
0x40 20
bytes APM_BIOS_INFO结构体
0x80 16
bytes hd0-disk-parameter
(硬盘1参数,由BIOS的40号中断得到)
0x90 16
bytes hd1-disk-parameter
(硬盘1参数,由BIOS的46号中断得到)
0xa0 16
bytes 系统描述符表截为16字节的信息
(
sys_desc_table_struct结构)
0xb0 - 0x1df 未占用
0x1e0 unsigned long ALT_MEM_K,可变化的内存大小
0x
0x
0x
0x
0x
0x1fa unsigned short VGA-Mode
0x1fc unsigned short ORIG_ROOT_DEV (high=Major, low=minor)(根设备的从
次设备号)
0x1ff char AUX_DEVICE_INFO
0x200 short jump to start of setup code aka
"reserved" field.
0x202 4 bytes SETUP-header的标志, ="HdrS"
目前的版本是0x0241...
0x206 unsigned short header的标志
0x208 8 bytes setup.S 获得boot loaders信息的内存区
0x210 char LOADER_TYPE = 0, 老式引导程序,
否则由boot loader自己设置
0xTV:
T=0 : LILO
1
: Loadlin
2
: bootsect-loader
3
: SYSLINUX
4
: ETHERBOOT
V
= 引导程序版本号
0x211 char loadflags(调入标志):
bit0
= 1: 读入高内存区 (bzImage)
bit7
= 1: boot loader设置了堆和指针。
0x212 unsigned short (setup.S)
0x214 unsigned long KERNEL_START, loader从何处调入内核
0x218 unsigned long INITRD_START, 调入内存的image的位置
0x
0x220 4 bytes (setup.S)
0x224 unsigned short setup.S 的堆和指针
0x226 - 0x7ff setup.S 的程序体
0x800 string, 2K max 命令行参数
这个页中的内容是从启动的多个程序中总结出来的,从中我们可以看出,页的大小为0x800—4K,页内的数据按照一定的偏移量顺序排列,对页的读写操作也要通过这些偏移量来完成。在你分析Linux的启动时,通过和这张表的对照,希望你能对在这种汇编语言环境中准确的定位读写及对页与段的安排,有一个更明确的认识。
此外,你应该发现,物理地址的0x0000没有被页映射,这部分空间保存着中断向量表,全局描述符表等等系统的比较重要的数据结构,他们不必要进行页交换,而且,对线性空间的访问页机制也完全不能察觉,所以,没有必要用页来映射这个空间。这样做还有另外一层用意:系统中凡是无效的指针(NULL),都可以自动对应到这个空间。
关于页初始化更详细的内容请参见第六章。那么,段机制在Head.S中有没有变化呢?我们知道,一旦系统进入保护模式,那么,系统的多任务特性就完全可以体现了。这里,段机制的多任务特性,当然要进一步地体现出来了。下面是Head.S中定义的全局描述符表:
ENTRY(gdt_table)
.quad 0x0000000000000000 /*
NULL descriptor */
.quad 0x0000000000000000 /*
not used */
.quad 0x00cf
.quad 0x00cf92000000ffff /*
0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /*
0x23 user 4GB code at
0x00000000 */
.quad 0x00cff2000000ffff /*
0x2b user 4GB data at
0x00000000 */
.quad 0x0000000000000000 /*
not used */
.quad 0x0000000000000000 /*
not used */
/*
* The APM segments have byte granularity and their bases
* and limits are set at run time.
*/
.quad 0x0040920000000000 /*
0x40 APM set up for bad BIOS's */
.quad
0x
.quad 0x
.quad 0x0040920000000000 /*
0x58 APM DS data */
.fill NR_CPUS*4,8,0
/* space for TSS's and LDT's */
关于这部分内容的详细解释请参见第二章
到这一步,保护机制下内存管理,中断管理的框架已经建好了,下一步,就是怎么样具体实现操作系统的功能了,于是,Head.S调用/init/main.C中的start_kernel函数,把控制权交给启动的下一部分代码。