Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 11 Jun 2002 11:38:15 -0700 (PDT)
From:      Ross Lippert <ripper@eskimo.com>
To:        devnull@uptsoft.com
Cc:        freebsd-doc@FreeBSD.ORG
Subject:   Re: an addition to developer-handbook
Message-ID:  <200206111838.LAA12868@eskimo.com>
In-Reply-To: <20020611191338.A16901@oasis.uptsoft.com> (message from Sergey Lyubka on Tue, 11 Jun 2002 19:13:38 %2B0300)

next in thread | previous in thread | raw e-mail | index | archive | help


OK, I have improved on the english.

I am not sure if I have misread and hence perverted the meaning of some
of the section.  I know english, not booting.

<!--
Copyright (c) 2002 Sergey Lyubka <devnull@uptsoft.com>
All rights reserved
$FreeBSD$
-->

<sect1>
  <title>Overview</title>

  <para>An x86 computer running FreeBSD can boot by several methods,
  although the most common method, booting from a harddisk where the OS
  is installed will be discussed here.  The boot process can be divided 
  into several distinct
  steps:</para>

  <itemizedlist>
    <listitem><para>BIOS POST</para></listitem>
    <listitem><para>boot0 stage</para></listitem>
    <listitem><para>boot2 stage</para></listitem>
    <listitem><para>loader stage</para></listitem>
    <listitem><para>kernel initialization</para></listitem>
  </itemizedlist>

  <para>The boot0 and boot2 stages are also refered to as
  <emphasis>bootstrap stages 1 and 2</emphasis> in &man.boot.8; as
  the first steps in FreeBSD's 3-stage bootstrapping procedure. </para>
</sect1>

<sect1>
  <title>BIOS POST</title>

  <para>When the (x86) computer powers on, the processor's registers
  are set with some predefined values. One of the registers is the
  <emphasis>instruction pointer</emphasis> register, and its value
  after a power on is well defined: it is a 32-bit value of 0xffffff00.
  The instruction pointer register points to code to be executed by the
  processor. The value of 0xffffff00 is slightly less then 4Gb, so
  unless the machine has 4Gb RAM, it cannot point to a valid memory
  address.  The computer's hardware magicly translates this address so
  that it points to a BIOS memory block.</para>

  <para>BIOS stands for <emphasis>Basic Input Output
  System</emphasis>, and it is the chip on the motherboard with a
  relatively small amount of read-only memory (ROM).  This memory
  contains various low-level routines that are specific to the
  hardware supplied with the motherboard.  So, the processor will
  first jump to the address 0xffffff00, which really resides in BIOS's
  memory.  Usually this address contains another jump instruction to
  the BIOS's POST routines.</para>

  <para>POST stands for <emphasis>Power On Self Test</emphasis>.  This
  is a set of routines which includes a memory check, system bus check and
  other low-level stuff so that the CPU can initialize the computer
  properly.  One of the important steps in this stage is determining
  the boot device.  All modern BIOS's allow the boot device to be set
  manually, so you can boot from a floppy, CD-ROM, harddisk etc.</para>

  <para>The very last thing in the POST is the <literal>INT 0x19</literal> instruction.
  That instruction reads 512 bytes from the first sector of boot
  device to the memory at address 0x7c00.  The term <emphasis>first
  sector</emphasis> originates from harddrive architecture, where the
  magnetic plate is divided to a number of cylindrical tracks.  Tracks
  are numbered, and every track is divided by a number (usually 64) of
  sectors.  Track number 0 is the outermost on the magnetic plate, and
  the first sector, sector 1 (tracks, or, cylinders, are numbered
  starting from 0, but sectors - starting from 1), has a special
  meaning.  It is also called Master Boot Record, or MBR.  The remaining
  sectors on the first track are never used.</para>

</sect1>

<sect1>
  <title>boot0 stage</title>

  <para>Take a look at the file <filename>/boot/boot0</filename>.
  This is a small 512-byte file, and it is exactly what FreeBSD's
  installation procedure wrote to your harddisk's MBR if you chose
  the 'bootmanager' option.</para>

  <para>As I said before, the <literal>INT 0x19</literal> instruction loads an MBR block,
  i.e. the <filename>boot0</filename> block, into the memory at address
  0x7c00.  Taking a look at the file
  <filename>sys/boot/i386/boot0/boot0.s</filename> can give
  a guess what is happening there - this is the boot manager, which is an
  awesome piece of code written by Robert Nordier.</para>

  <para>The MBR, or, <filename>boot0</filename>, has a special structure,
  starting from offset 0x1be, called the <emphasis>partition table</emphasis>.
  It is 4 records of 16 bytes each, called <emphasis>partition
  records</emphasis>, which represent how the
  harddisk is partitioned, or, in FreeBSD's terminology, sliced.
  One byte of those 16 says whether a partition (slice)
  is bootable or not.  Exactly one record must have that flag set,
  otherwise <filename>boot0</filename>'s code will refuse to proceed.</para>

  <para>A partition record has the following fields:</para>

  <itemizedlist>
    <listitem><para>the 1 byte filesystem type</para></listitem>
    <listitem><para>the 1 byte bootable flag</para></listitem>
    <listitem><para>the 6 byte descriptor in CHS format</para></listitem>
    <listitem><para>the 8 byte descriptor in LBA format</para></listitem>
  </itemizedlist>

  <para>Partition record descriptors have the information about where
  exactly the partition resides on a drive.  Both descriptors, LBA and
  CHS, describe the same information, but in different ways: LBA
  (Logical Block Addressing) has the starting sector for the
  partition, and the partition's length, where CHS
  (Cylinder Head Sector) has coordinates for the first and last sector
  of the partition.
  </para>

  <para>The boot manager scans the partition table and prints a menu
  on the screen so the user can select what disk and what slice to boot.
  By pressing an appropriate key, <filename>boot0</filename>
  performs the following actions:</para>

  <itemizedlist>
    <listitem><para>modifies the bootable flag for the selected partition
    to make it bootable, and clears the previous</para></listitem>

    <listitem><para>saves itself to disk to remember what partition
    (slice) has been selected so to use it as the default on the next
    boot</para></listitem>

    <listitem><para>loads the first sector of the selected partition (slice)
    into memory and jumps there</para></listitem>
  </itemizedlist>

  <para>What kind of data should reside on the very first sector of a
  bootable partition (slice), in our case, a FreeBSD slice?  As you may
  have already guessed, it is <filename>boot2</filename>.</para>

</sect1>

<sect1>
  <title>boot2 stage</title>

  <para>You might wonder, why boot2 comes after boot0, and not
  boot1. Actually, there is a 512-byte file called
  <filename>boot1</filename> in the directory
  <filename>/boot</filename> as well.  It is used for booting from a
  floppy.  When booting from a floppy, <filename>boot1</filename>
  plays the same role as <filename>boot0</filename> for a harddisk: it
  locates boot2 and runs it.</para>

  <para>You may have realized that a file
  <filename>/boot/mbr</filename> exists as well.  It is a simplified
  version of boot0.  The code in <filename>mbr</filename> does not
  provide a menu for the user, it just blindly boots the partition
  marked active.</para>

  <para>The code implementing boot2 resides in
  <filename>sys/boot/i386/boot2/</filename>, and the executable itself
  is in <filename>/boot</filename>.  The files
  <filename>boot0</filename> and <filename>boot2</filename> that are
  in <filename>/boot</filename> are not used by the bootstrap, but by
  utilities such as <application>boot0cfg</application>.  The actual
  position for boot0 is in the MBR.  For boot2 it is the beginning of
  a bootable FreeBSD slice.  These locations are not under the
  filesystem's control, so they are invisible to commands like
  <application>ls</application>.</para>

  <para>The main task for boot2 is to
  load the file <filename>/boot/loader</filename>, which is the third
  stage in the bootstrapping procedure.  The code in boot2 cannot use any
  services like <function>open()</function> and <function>read()</function>,
  since the kernel is not yet loaded.  It must scan the harddisk, 
  knowing about the filesystem structure, find the file
  <filename>/boot/loader</filename>, read it into memory using a BIOS service,
  and then pass the execution to the loader's entry point.</para>

  <para>Besides that, boot2 prompts for user input so the loader can be booted
  from different disk, unit, slice and partition.</para>

  <para><emphasis>I'm a little confused here.  Is boot2 the program which
  does the twirly cursor and then gives me the boot prompt?? -Ross</emphasis>
  </para>

  <para>The boot2 binary is created in special way:</para>
  <programlisting><filename>sys/boot/i386/boot2/Makefile</filename>
boot2: boot2.ldr boot2.bin ${BTX}/btx/btx
	btxld -v -E ${ORG2} -f bin -b ${BTX}/btx/btx -l boot2.ldr \
		-o boot2.ld -P 1 boot2.bin
</programlisting>

  <para>This Makefile snippet shows that &man.btxld.8; is used to link
  the binary.  BTX, which stands for BooT eXtender, is a piece of code
  that provides a protected mode environment for the program, called
  the client, that it links with.  So boot2 is a BTX client, i.e. it
  uses the sevices provided by BTX.</para>

  <para>boot0 passes the execution to BTX's entry point.  BTX then switches
  processor to protected mode, and prepares a simple environment before
  calling the client. This includes:</para>

  <itemizedlist>
    <listitem><para>virtual v86 mode. That means, the BTX is a v86 monitor.
    Real mode instructions like pushf, popf,
    cli, sti, if called by the client, will work.</para></listitem>

    <listitem><para>Interrupt Descriptor Table (IDT) is set up so all hardware
    interrupts are routed to the default BIOS's handlers, and interrupt 0x30 is
    set up to be the syscall gate.</para></listitem>

    <listitem><para>Two system calls, <function>exec()</function> and
    <function>exit()</function>, are defined:</para>
    <programlisting><filename>sys/boot/i386/btx/lib/btxsys.s:</filename>
		.set INT_SYS,0x30		# Interrupt number
#
# System call: exit
#
__exit: 	xorl %eax,%eax			# BTX system
		int $INT_SYS			#  call 0x0
#
# System call: exec
#
__exec: 	movl $0x1,%eax			# BTX system
		int $INT_SYS			#  call 0x1</programlisting></listitem>
  </itemizedlist>

  <para>BTX creates a Global Descriptor Table (GDT):</para>
  <programlisting><filename>sys/boot/i386/btx/btx/btx.s</filename>
gdt:		.word 0x0,0x0,0x0,0x0		# Null entry
		.word 0xffff,0x0,0x9a00,0xcf	# SEL_SCODE
		.word 0xffff,0x0,0x9200,0xcf	# SEL_SDATA
		.word 0xffff,0x0,0x9a00,0x0	# SEL_RCODE
		.word 0xffff,0x0,0x9200,0x0	# SEL_RDATA
		.word 0xffff,MEM_USR,0xfa00,0xcf# SEL_UCODE
		.word 0xffff,MEM_USR,0xf200,0xcf# SEL_UDATA
		.word _TSSLM,MEM_TSS,0x8900,0x0 # SEL_TSS
</programlisting>

  <para>The client's code and data start from address MEM_USR (0xa000),
  and a selector, SEL_UCODE, points to client's code segment.  The SEL_UCODE
  descriptor has a Descriptor Privilege Level (DPL) 3, which is the lowest
  privilege level.  But the <literal>INT 0x30</literal> instruction handler resides in a segment
  pointed to by the SEL_SCODE (supervisor code) selector, as shown from
  the code that creates an IDT:</para>
  <programlisting><filename></filename>
		mov $SEL_SCODE,%dh		# Segment selector
init.2: 	shr %bx				# Handle this int?
		jnc init.3			# No
		mov %ax,(%di)			# Set handler offset
		mov %dh,0x2(%di)		#  and selector
		mov %dl,0x5(%di)		# Set P:DPL:type
		add $0x4,%ax			# Next handler
</programlisting>

  <para>So, when the client calls <function>__exec()</function>,
  the code will be executed with the highest
  privileges.  This allows kernel to change the protected mode data structures,
  such as page tables, GDT, IDT, etc later, if needed.</para>

  <para>boot2 defines an important structure,
  <literal>struct bootinfo</literal>.  This structure is initialized by the
  boot2 and passed to the loader, and then further to the
  kernel.  Some nodes of this structure are set by boot2,
  the rest by the loader.
  This structure, among other information, contains the kernel filename,
  BIOS harddisk geometry, BIOS drive number for boot device, physical memory
  available, <literal>envp</literal> pointer etc.
  The definition for it is:</para>
    <programlisting><filename>/usr/include/machine/bootinfo.h</filename>
struct bootinfo {
	u_int32_t	bi_version;
	u_int32_t	bi_kernelname;		/* represents a char * */
	u_int32_t	bi_nfs_diskless;	/* struct nfs_diskless * */
				/* End of fields that are always present. */
#define	bi_endcommon	bi_n_bios_used
	u_int32_t	bi_n_bios_used;
	u_int32_t	bi_bios_geom[N_BIOS_GEOM];
	u_int32_t	bi_size;
	u_int8_t	bi_memsizes_valid;
	u_int8_t	bi_bios_dev;		/* bootdev BIOS unit number */
	u_int8_t	bi_pad[2];
	u_int32_t	bi_basemem;
	u_int32_t	bi_extmem;
	u_int32_t	bi_symtab;		/* struct symtab * */
	u_int32_t	bi_esymtab;		/* struct symtab * */
				/* Items below only from advanced bootloader */
	u_int32_t	bi_kernend;		/* end of kernel space */
	u_int32_t	bi_envp;		/* environment */
	u_int32_t	bi_modulep;		/* preloaded modules */
};</programlisting>

  <para>boot2 enters into an infinite loop waiting for user input, and then
  calls <function>load()</function>.  If user does not press anything,
  loop brakes by timeout, so <function>load()</function> will load
  default filename. Functions
  <function>ino_t lookup(char *filename)</function> and
  <function>int xfsread(ino_t inode, void *buf, size_t nbyte)</function> are
  used to read the content of the file into memory.
  <filename>/boot/loader</filename> is an ELF binary, where the ELF header is
  prepended by a.out's <literal>struct exec</literal> structure.
  The <function>load()</function> scans the loader's ELF header, loading the
  content of <filename>/boot/loader</filename> into memory, and passes the
  execution to loader's entry:</para>
  <programlisting><filename>sys/boot/i386/boot2/boot2.c:</filename>
    __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
	   MAKEBOOTDEV(dev_maj[dsk.type], 0, dsk.slice, dsk.unit, dsk.part),
	   0, 0, 0, VTOP(&amp;bootinfo));</programlisting>

</sect1>

<sect1>
  <title>BTX loader stage</title>

  <para><application>loader</application> is a BTX client as well.  I will not
  describe it here in details, there is a comprehensive manual page written
  by Mike Smith, &man.loader.8;.  The underlying mechanisms and BTX where
  discussed above.</para>

  <para>The main task for the loader is to boot the kernel.  When the kernel is
  loaded into memory, it is being called by the loader:</para>
  <programlisting><filename>sys/boot/common/boot.c:</filename>
    /* Call the exec handler from the loader matching the kernel */
    module_formats[km->m_loader]->l_exec(km);
</programlisting>

<sect1>
  <title>Kernel initialization</title>

  <para>To where exactly is the execution passed by the loader,
  i.e. what is the kernel's actual entry point.  Let us take a look at
  the command that links the kernel, taken from the makefile:</para>

    <programlisting><filename>sys/conf/Makefile.i386:</filename>
ld -elf -Bdynamic -T /usr/src/sys/conf/ldscript.i386  -export-dynamic \
-dynamic-linker /red/herring -o kernel -X locore.o \
&lt;lots of kernel .o files&gt;</programlisting>

  <para>A few interesting things could be seen in this line.  First,
  the kernel is an ELF dynamically linked binary, but the dymamic
  linker for kernel is <filename>/red/herring</filename>, which is
  definitely a bogus file.  Second, taking a look at the file
  <filename>sys/conf/ldscript.i386</filename> may give an idea about
  what <application>ld</application> options are used when compiling
  a kernel.  Reading through the first few lines, the string</para>
  <programlisting><filename>sys/conf/ldscript.i386:</filename>
  ENTRY(btext)</programlisting>

  <para>says that a kernel's entry point is the symbol `btext'. This symbol is
  defined in <filename>locore.s</filename>:</para>

  <programlisting><filename>sys/i386/i386/locore.s:</filename>
	.text
/**********************************************************************
 *
 * This is where the bootblocks start us, set the ball rolling...
 *
 */
NON_GPROF_ENTRY(btext)</programlisting>

  <para>First what is done is register EFLAGS is set to a predefined value
  of 0x00000002, and then all the segment registers are initialized:</para>
  <programlisting><filename>sys/i386/i386/locore.s</filename>
/* Don't trust what the BIOS gives for eflags. */
	pushl	$PSL_KERNEL
	popfl

/*
 * Don't trust what the BIOS gives for %fs and %gs.  Trust the bootstrap
 * to set %cs, %ds, %es and %ss.
 */
	mov	%ds, %ax
	mov	%ax, %fs
	mov	%ax, %gs</programlisting>

  <para>btext calls the routines <function>recover_bootinfo()</function>,
  <function>identify_cpu()</function>,
  <function>create_pagetables()</function>, 
  which are also defined in <filename>locore.s</filename>. Here is a
  description of what they do:</para>

  <informaltable>
    <tgroup cols=2 align=left>
    <tbody>
      <row>
        <entry><function>recover_bootinfo</function></entry>
        <entry>This routine parses the parameters to the kernel passed from
        the bootstrapping program.  The Kernel may have been booted in
	3 ways: by the loader,
        described above, by the old disk boot blocks, and by the old diskless
        boot procedure.  This function determines the booting method, and
        stores the <literal>struct bootinfo</literal> structure into the
        kernel memory.</entry>
      </row>
      <row>
        <entry><function>identify_cpu</function></entry>
        <entry>This functions tries to find out what CPU it is running on,
        storing the value found in a variable <varname>_cpu</varname>.
        </entry>
      </row>
      <row>
        <entry><function>create_pagetables</function></entry>
        <entry>This function allocates and fills out a Page Table Directory
        on the top of the kernel memory area.</entry>
      </row>
    </tgroup>
  </informaltable>
   <para>The next steps are enabling VME, if the CPU supports it:</para>
  <programlisting>
	testl	$CPUID_VME, R(_cpu_feature)
	jz	1f
	movl	%cr4, %eax
	orl	$CR4_VME, %eax
	movl	%eax, %cr4</programlisting>

  <para>Then, enabling paging:</para>
  <programlisting>
/* Now enable paging */
	movl	R(_IdlePTD), %eax
	movl	%eax,%cr3			/* load ptd addr into mmu */
	movl	%cr0,%eax			/* get control word */
	orl	$CR0_PE|CR0_PG,%eax		/* enable paging */
	movl	%eax,%cr0			/* and let's page NOW! */</programlisting>

  <para>Up until now, the kernel was executing in protected mode,
  but at low memory
  addresses, where the loader has loaded the kernel. The next three
  lines of code orders FIXME</para>
  <programlisting>
	pushl	$begin				/* jump to high virtualized address */
	ret

/* now running relocated at KERNBASE where the system is linked to run */
begin:</programlisting>

  <para>The function <function>init386()</function> is called,
  with a pointer to the first free physical page, after that 
  <function>mi_startup()</function>.  init386 is an arch-dependent
  initialization function, and mi_startup is arch-independent one.  
  The kernel
  never returns from mi_startup, and by calling it, the kernel finishes
  booting:</para>

  <programlisting><filename>sys/i386/i386/locore.s:</filename>
	movl	physfree, %esi
	pushl	%esi				/* value of first for init386(first) */
	call	_init386			/* wire 386 chip for unix operation */
	call	_mi_startup			/* autoconfiguration, mountroot etc */
	hlt		/* never returns to here */</programlisting>

  <sect2>
    <title><function>init386()</function></title>

    <para><function>init386</function> is defined in
    <filename>sys/i386/i386/machdep.c</filename> and performs
    low-level initialization, specific to an
    i386 chip.  The switch to protected mode was performed
    by the loader.  The loader has created the very first task,
    in which the kernel continues to operate.  Before running straight
    away to the code, I will enumerate the tasks the processor must
    complete to initialize protected mode execution:</para>

    <itemizedlist>
      <listitem><para>Initialize the kernel tunable parameters, passed from
      bootstapping program.</para></listitem>
      <listitem><para>Prepare GDT.</para></listitem>
      <listitem><para>Prepare IDT.</para></listitem>
      <listitem><para>Initialize the system console.</para></listitem>
      <listitem><para>Initialize DDB, if it is compiled itnto kernel.
      </para></listitem>
      <listitem><para>Initialize TSS.</para></listitem>
      <listitem><para>Prepare LDT.</para></listitem>
      <listitem><para>Setup proc0's pcb</para></listitem>

    </itemizedlist>

    <para>What init386() first does is initialize tunable parameters
    passed from bootstrap.  This is done by setting the environment pointer
    (envp) and calling <function>init_param1()</function>.  The envp pointer
    has been passed from loader in the <literal>bootinfo</literal>
    structure:</para>
    <programlisting><filename>sys/i386/i386/machdep.c:</filename>
		kern_envp = (caddr_t)bootinfo.bi_envp + KERNBASE;

	/* Init basic tunables, hz etc */
	init_param1();</programlisting>

    <para><function>init_param1()</function> is defined in
    <filename>sys/kern/subr_param.c</filename>.  That file has a number of
    sysctls, and two functions, <function>init_param1()</function> and
    <function>init_param2()</function>, that are
    called from <function>init386()</function>:</para>
    <programlisting><filename>sys/kern/subr_param.c</filename>
	hz = HZ;
	TUNABLE_INT_FETCH("kern.hz", &amp;hz);</programlisting>

    <para>TUNABLE_&lt;typename&gt;_FETCH is used to fetch the value from
    the environment:</para>
    <programlisting><filename>/usr/src/sys/sys/kernel.h</filename>
#define	TUNABLE_INT_FETCH(path, var)	getenv_int((path), (var))
</programlisting>

    <para>Sysctl "kern.hz" is the system clock tick.  Along with this, the
    following sysctls are set by <function>init_param1()</function>:
    <literal>kern.maxswzone, kern.maxbcache, kern.maxtsiz, kern.dfldsiz,
    kern.dflssiz, kern.maxssiz, kern.sgrowsiz</literal>.</para>

    <para>Then <function>init386</function> prepares the Global
    Decriptor 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
    "absolute" virtual address for that instruction would be "the
    virtual address of the code segment beginning" + "the value of
    EIP".  For convenience, segments begin at virtual address 0 and
    end at a 4Gb boundary. Therefore, the absolute instruction's
    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 the GDT (to be more precise, the index is not a
    selector itself, but an INDEX field of a selector). FreeBSD's GDT
    holds descriptors for 15 selectors per CPU:</para>

    <programlisting><filename>sys/i386/i386/machdep.c:</filename>
union descriptor gdt[NGDT * MAXCPU];	/* global descriptor table */

<filename>sys/i386/include/segments.h:</filename>
/*
 * 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) */
</programlisting>

    <para>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 kernel code selector (GCODE_SEL)
    has the value 0x08.</para>

    <para>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, a user application does
    a <literal>INT 0x80</literal> 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 the NIDT is the maximum (256):</para>
    <programlisting><filename>sys/i386/i386/machdep.c:</filename>
static struct gate_descriptor idt0[NIDT];
struct gate_descriptor *idt = &amp;idt0[0];	/* interrupt descriptor table */
</programlisting>

    <para>For each interrupt, an appropriate handler is set. The syscall
    gate for <literal>INT 0x80</literal> is set as well:</para>

    <programlisting><filename>sys/i386/i386/machdep.c:</filename>
 	setidt(0x80, &amp;IDTVEC(int0x80_syscall),
			SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL));
</programlisting>

    <para>So when a userland application calls <literal>INT
    0x80</literal> instruction, control will transfer to the function
    <function>_Xint0x80_syscall</function>, which is in the kernel code
    segment and will be executed with supervisor privileges.</para>

    <para>Console and DDB are then initialized:</para>
    <programlisting><filename>sys/i386/i386/machdep.c:</filename>
	cninit();
/* skipped */
#ifdef DDB
	kdb_init();
	if (boothowto & RB_KDB)
		Debugger("Boot flags requested debugger");
#endif</programlisting>

    <para>The Task State Segment is another x86 protected mode structure,
    the TSS is used by the hardware to store task information when a task
    switch occurs.</para>

    <para>The Local Descriptors Table is used to reference userland code and
    data.  Several selectors are defined to point to the LDT, they are the
    system call gates and the user code and data selectors:</para>
      <programlisting><filename>/usr/include/machine/segments.h</filename>
#define	LSYS5CALLS_SEL	0	/* forced by intel BCS */
#define	LSYS5SIGR_SEL	1
#define	L43BSDCALLS_SEL	2	/* notyet */
#define	LUCODE_SEL	3
#define LSOL26CALLS_SEL	4	/* Solaris >= 2.6 system call gate */
#define	LUDATA_SEL	5
/* separate stack, es,fs,gs sels ? */
/* #define	LPOSIXCALLS_SEL	5*/	/* notyet */
#define LBSDICALLS_SEL	16	/* BSDI system call gate */
#define NLDT		(LBSDICALLS_SEL + 1)
</programlisting>

    <para>Next, proc0's Process Control Block
    (<literal>struct pcb</literal>) structure is initialized.
    proc0 is a <literal>struct proc</literal>
    structure that describes a kernel process.  It is always present while
    the kernel is running, therefore it is declared as a global:</para>
    <programlisting><filename>sys/kern/kern_init.c:</filename>
    struct	proc proc0;
</programlisting>

    <para>The structure <literal>struct pcb</literal> is a part of the
    proc structure. It is defined in
    <filename>/usr/include/machine/pcb.h</filename> and has a process's
    information specific to the i386 architecture, such as register values.
    </para>

  </sect2>

  <sect2>
    <title><function>mi_startup()</function></title>

    <para>This function performs a bubble sort of all the system
    initialization objects and then calls the entry of each object one
    by one.  The sysinit framework is described in the handbook:</para>
    <programlisting><filename>PUT FILE HERE</filename>
	for (sipp = sysinit; *sipp; sipp++) {

		/* ... skipped ... */

		/* Call function */
		(*((*sipp)->func))((*sipp)->udata);
		/* ... skipped ... */
	}</programlisting>

  <para>The last item on the list is the scheduler, which will not return
  and so we now have the system running.</para>

  </sect2>
</sect1>






To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-doc" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200206111838.LAA12868>