Chapter 1 Bootstrapping and kernel initialization
TUNABLE_INT_FETCH("kern.hz", &hz);
TUNABLE_<typename>_FETCH is used to fetch the value from the environment:
/usr/src/sys/sys/kernel.h
#define TUNABLE_INT_FETCH(path, var) getenv_int((path), (var))
Sysctl kern.hz is the system clock tick. Along with this, the following sysctls are set by init_param1():
kern.maxswzone, kern.maxbcache, kern.maxtsiz, kern.dfldsiz, kern.dflssiz,
kern.maxssiz, kern.sgrowsiz.
Then init386() prepares the Global Descriptors Table (GDT). Every task on an x86 is running in its own virtual
address space, and this space is addressed by a segment:offset pair. Say, for instance, the current instruction to be
executed by the processor lies at CS:EIP, then the linear virtual address for that instruction would be “the virtual
address of code segment CS” + EIP. For convenience, segments begin at virtual address 0 and end at a 4Gb boundary.
Therefore, the instruction’s linear virtual address for this example would just be the value of EIP. Segment registers
such as CS, DS etc are the selectors, i.e. indexes, into GDT (to be more precise, an index is not a selector itself, but
the INDEX field of a selector). FreeBSD’s GDT holds descriptors for 15 selectors per CPU:
sys/i386/i386/machdep.c:
union descriptor gdt[NGDT * MAXCPU]; /* global descriptor table */
sys/i386/include/segments.h:
/*
* Entries in the Global Descriptor Table (GDT)
*/
#define GNULL_SEL 0 /* Null Descriptor */
#define GCODE_SEL 1 /* Kernel Code Descriptor */
#define GDATA_SEL 2 /* Kernel Data Descriptor */
#define GPRIV_SEL 3 /* SMP Per-Processor Private Data */
#define GPROC0_SEL 4 /* Task state process slot zero and up */
#define GLDT_SEL 5 /* LDT - eventually one per process */
#define GUSERLDT_SEL 6 /* User LDT */
#define GTGATE_SEL 7 /* Process task switch gate */
#define GBIOSLOWMEM_SEL 8 /* BIOS low memory access (must be entry 8) */
#define GPANIC_SEL 9 /* Task state to consider panic from */
#define GBIOSCODE32_SEL 10 /* BIOS interface (32bit Code) */
#define GBIOSCODE16_SEL 11 /* BIOS interface (16bit Code) */
#define GBIOSDATA_SEL 12 /* BIOS interface (Data) */
#define GBIOSUTIL_SEL 13 /* BIOS interface (Utility) */
#define GBIOSARGS_SEL 14 /* BIOS interface (Arguments) */
Note that those #defines are not selectors themselves, but just a field INDEX of a selector, so they are exactly the
indices of the GDT. for example, an actual selector for the kernel code (GCODE_SEL) has the value 0x08.
The next step is to initialize the Interrupt Descriptor Table (IDT). This table is to be referenced by the processor
when a software or hardware interrupt occurs. For example, to make a system call, user application issues the INT
0x80 instruction. This is a software interrupt, so the processor’s hardware looks up a record with index 0x80 in the
IDT. This record points to the routine that handles this interrupt, in this particular case, this will be the kernel’s
syscall gate. The IDT may have a maximum of 256 (0x100) records. The kernel allocates NIDT records for the IDT,
where NIDT is the maximum (256):
sys/i386/i386/machdep.c:
9