FreeBSD的Loader和内核初始化( 二 )


初始化内核的可调整参数,这些参数由引导程序传来准备GDT(全局描述符表)
准备IDT(中断描述符表)初始化系统控制台初始化DDB(内核的点调试器),如果它被编译进内核的话初始化TSS(任务状态段)准备LDT(局部描述符表)建立proc0(0号进程,即内核的进程)的pcb(进程控制块)init386()首先初始化内核的可调整参数,这些参数由引导程序传来 。先设置环境指针(environment pointer, envp)调用,再调用init_param1() 。
envp指针已由loader存放在结构bootinfo中:sys/i386/i386/machdep.c:
kern_envp = (CADdr_t)bootinfo.bi_envpKERNBASE;
/* 初始化基本可调整项,如hz等 */
init_param1();
init_param1()定义在sys/kern/subr_param.c之中 。这个文件里有一些sysctl项,两个函数,init_param1()和init_param2() 。这两个函数从init386()中调用:sys/kern/subr_param.c
hz = HZ;
TUNABLE_INT_FETCH("kern.hz", &hz);
TUNABLE__FETCH用来获取环境变量的值:/usr/src/sys/sys/kernel.h
#define TUNABLE_INT_FETCH(path, var)getenv_int((path), (var))
Sysctlkern.hz是系统时钟频率 。同时,这些sysctl项被init_param1()设定:
kern.maxswzone, kern.maxbcache, kern.maxtsiz, kern.dfldsiz, kern.dflssiz,
kern.maxssiz, kern.sgrowsiz 。然后init386() 准备全局描述符表(Global Descriptors Table, GDT) 。
在x86上每个任务都运行在自己的虚拟地址空间里,这个空间由"段址:偏移量"的数对指定 。
举个例子,当前将要由处理器执行的指令在 CS:EIP,那么这条指令的线性虚拟地址就是“代码段虚拟段地址CSEIP 。为了简便,段起始于虚拟地址0,终止于界限4G字节 。所以,在这个例子中,指令的线性虚拟地址正是EIP的值 。段寄存器,如CS、DS等是选择符,即全局描述符表中的索引(更精确的说,索引并非选择符的全部,而是选择符中的INDEX部分) 。译者注: 对于80386,选择符有16位,INDEX部分是其中的高13位 。
FreeBSD的全局描述符表为每个CPU保存着15个选择符:sys/i386/i386/Machdep.c:
union descriptor gdt[NGDT * MAXCPU];/* 全局描述符表 */
sys/i386/include/segments.h:
/*
* 全局描述符表(GDT)中的入口
*/
#define GNULL_SEL0/* 空描述符 */
#define GCODE_SEL1/* 内核代码描述符 */
#define GDATA_SEL2/* 内核数据描述符 */
#define GPRIV_SEL3/* 对称多处理(SMP)每处理器专有数据 */
#define GPROC0_SEL 4/* Task state process slot zero and up, 任务状态进程 */
#define GLDT_SEL5/* 每个进程的局部描述符表 */
#define GUSERLDT_SEL6/* 用户自定义的局部描述符表 */
#define GTGATE_SEL 7/* 进程任务切换关口 */
#define GBIOSLOWMEM_SEL 8/* BIOS低端内存访问(必须是这第8个入口) */
#define GPANIC_SEL 9/* 会导致全系统异常中止工作的任务状态 */
#define GBIOSCODE32_SEL 10 /* BIOS接口(32位代码) */
#define GBIOSCODE16_SEL 11 /* BIOS接口(16位代码) */
#define GBIOSDATA_SEL12 /* BIOS接口(数据) */
#define GBIOSUTIL_SEL13 /* BIOS接口(工具) */
#define GBIOSARGS_SEL14 /* BIOS接口(自变量,参数) */
请注意,这些#defines并非选择符本身,而只是选择符中的INDEX域,因此它们正是全局描述符表中的索引 。例如,内核代码的选择符(GCODE_SEL)的值为0x08 。
下一步是初始化中断描述符表(Interrupt Descriptor Table, IDT) 。
这张表在发生软件或硬件中断时会被处理器引用 。例如,执行系统调用时,用户应用程序提交INT 0x80 指令 。这是一个软件中断,处理器用索引值0x80在中断描述符表中查找记录 。这个记录指向处理这个中断的例程 。在这个特定情形中,这是内核的系统调用关口 。
译者注: Intel 80386支持“调用门,可以使得用户程序只通过一条call指令就调用内核中的例程 。可是FreeBSD并未采用这种机制,也许是因为使用软中断接口可免去动态链接的麻烦吧 。

推荐阅读