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


另外还有一个附带的好处:在仿真Linux时,当遇到FreeBSD内核不支持的而又并非关键性的系统调用时,内核只会显示一些出错信息,这使得程序能够继续运行;而不是在真正执行程序之前的初始化过程中就因为动态链接失败而不允许程序运行 。中断描述符表最多可以有256 (0x100)条记录 。内核分配NIDT条记录的内存给中断描述符表,这里NIDT=256,是最大值:sys/i386/i386/Machdep.c:
static struct gate_descriptor idt0[NIDT];
struct gate_descriptor *idt = &idt0[0]; /* 中断描述符表 */
每个中断都被设置一个合适的中断处理程序 。系统调用关口INT 0x80也是如此:sys/i386/i386/machdep.c:
setidt(0x80, &IDTVEC(int0x80_syscall),
SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL));
所以当一个用户应用程序提交INT 0x80指令时,全系统的控制权会传递给函数_Xint0x80_syscall,这个函数在内核代码段中,将被以管理员权限执行 。然后,控制台和DDB(调试器)被初始化:sys/i386/i386/machdep.c:
cninit();
/* 以下代码可能因为未定义宏DDB而被跳过 */
#ifdef DDB
kdb_init();
if (boothowto & RB_KDB)
Debugger("Boot flags requested debugger");
#endif
任务状态段(TSS)是另一个x86保护模式中的数据结构 。当发生任务切换时,任务状态段用来让硬件存储任务现场信息 。局部描述符表(LDT)用来指向用户代码和数据 。系统定义了几个选择符,指向局部描述符表,它们是系统调用关口和用户代码、用户数据选择符:/usr/include/Machine/segments.h
#define LSYS5CALLS_SEL 0/* Intel BCS强制要求的 */
#define LSYS5SIGR_SEL1
#define L43BSDCALLS_SEL 2/* 尚无 */
#define LUCODE_SEL 3
#define LSOL26CALLS_SEL 4/* Solaris >=2.6版系统调用关口 */
#define LUDATA_SEL 5
/* separate stack, es,fs,gs sels ? 分别的栈、es、fs、gs选择符? */
/* #define LPOSIXCALLS_SEL 5*/ /* notyet, 尚无 */
#define LBSDICALLS_SEL 16 /* BSDI system call gate, BSDI系统调用关口 */
#define NLDT(LBSDICALLS_SEL1)
然后,proc0(0号进程,即内核所处的进程)的进程控制块(Process Control Block)(struct pcb)结构被初始化 。proc0是一个struct proc 结构,描述了一个内核进程 。内核运行时,该进程总是存在,所以这个结构在内核中被定义为全局变量:sys/kern/kern_init.c:
struct proc proc0;
结构struct pcb是proc结构的一部分,它定义在/usr/include/machine/pcb.h之中,内含针对i386硬件结构专有的信息,如寄存器的值 。1.7.2 mi_startup()这个函数用冒泡排序算法,将所有系统初始化对象,然后逐个调用每个对象的入口:sys/kern/init_main.c:
for (sipp = sysinit; *sipp; sipp) {
/* ... 省略 ... */
/* 调用函数 */
(*((*sipp)->func))((*sipp)->udata);
/* ... 省略 ... */
}
尽管sysinit框架已经在《FreeBSD开发者手册》中有所描述,我还是在这里讨论一下其内部原理 。每个系统初始化对象(sysinit对象)通过调用宏建立 。让我们以announce sysinit对象为例 。这个对象打印版权信息:sys/kern/init_main.c:
static void
print_CADdr_t(void *data __unused)
{
printf("%s", (char *)data);
}
SYSINIT(announce, SI_SUB_COPYRIGHT, SI_ORDER_FIRST, print_caddr_t, copyright)
这个对象的子系统标识是SI_SUB_COPYRIGHT(0x0800001),数值刚好排在SI_SUB_CONSOLE(0x0800000)后面 。所以,版权信息将在控制台初始化之后就被很早的打印出来 。
让我们看一看宏SYSINIT()到底做了些什么 。它展开成宏C_SYSINIT() 。宏C_SYSINIT()然后展开成一个静态结构struct sysinit 。结构里申明里调用了另一个宏DATA_SET:/usr/include/sys/kernel.h:
 #define C_SYSINIT(uniquifIEr, subsystem, order, func, ident)

推荐阅读