bootmainis not supposed to return (it is supposed to load the kernel and jump into it). However, it may return if an error was observed during the loading process. In which case, the code following the
callinstruction executes some instructions which allow a user to debug the error.
bootmainfunction (on Sheet 85) will read the kernel image (stored in ELF format) from the disk, load its program segments into physical memory, and branch to its first instruction.
offset: the file offset at which the program segment is in the file (or on the disk).
filesz: the size of the program segment in the file
paddr: the physical address at which the segment should be loaded.
vaddr: the virtual address at which the segment should be loaded.
memsz: the size of the program segment in memory. This must be greater than
memszis not equal to
filesz, the remaining
(memsz - filesz)bytes in the segment are initialized to zero by the loader.
man elfto get full details about the ELF format.
bootmain.c. Line 8527 makes a call to
readseg()to read the first few bytes starting at disk sector 1 (notice the disk sector computed at line 8589 starts at sector 1) into a memory area named by the variable
elfvariable is treated as an ELF header and its values are read (e.g.,
phofffield to obtain the location of program headers,
phnumfield to obtain the number of program headers).
readseg()in line 8538 loads
ph.fileszbytes from disk at offset
ph.offand stores them at physical address
paddr) to load the corresponding segment.
ph.memszis greater than
ph.filesz, and if so, zeroes out the remaining bytes (as required).
entryfield, to which the bootloader makes a function call at line 8546. At this point the control has shifted from the bootloader to the first instruction in the kernel.
xv6$ objdump -p kernel Program Header: LOAD off 0x00001000 vaddr 0x80100000 paddr 0x00100000 align 2**12 filesz 0x0000b596 memsz 0x000126fc flags rwx STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2 filesz 0x00000000 memsz 0x00000000 flags rwxThe first segment is supposed to be loaded from offset
0x1000, at virtual address
0x80100000, physical address
0x100000. Its size in the file is
0xb596bytes, and its size in memory is
The second segment indicates a stack segment, and we can ignore it for now.
xv6$ objdump -f kernel kernel: file format elf32-i386 architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x0010000cNotice that the start address is a physical address (and deliberately so). This is correct because at the time of branching to the start address, we are still operating in the physical address space.
entry.S) execute from physical address space and initialize the page table and enable paging. After paging is enabled, the kernel will start executing from virtual address space.
entrypgdiris assigned a virtual address (like all other kernel variables). Thus the code converts it into a physical address (by subtracting KERNBASE from it using V2P_WO macro) before loading it into
cr3needs a physical address).
entrypgdirdefined at Sheet 13.
entrypgdiris an array of 1024
pde_tentries. In this case, all other entries are set to zero (which means the corresponding pages are not present), except the first entry (entry #0) and the
(KERNBASE >> PDXSHIFT)'th entry (entry #512) which both point to the same first 4MB page in the physical address space. Notice that these are the only two entries where the present bit (indicated by PTE_P) is set. Also, notice that both entries use large pages and allow writes.
EIPmay now point to a different byte. To avoid this, the kernel developer has carefully used an identity mapping for the first 4MB, i.e., the first 4MB of VA space (virtual address space) map to the first 4MB of PA space (physical address space). Through this, the developer ensures that the current execution addresses will remain valid even after paging is enabled (all these addresses are less than 4MB by design).
esp. Note that this will be a virtual address value (greater than KERNBASE), just like all other variables in the kernel. Because the address space in that region is mapped, this value will point to a valid address.
main, which is also a virtual address (greater than KERNBASE). After this instruction, the execution remains in virtual address mode.
entrypgdir) to switch from the physical address space to the virtual address space. This temporary page table had both physical-side mappings (0-4MB) and virtual-side mappings (KERNBASE to KERNBASE+4MB) for the same physical addresses (0-4MB). This allowed transitioning from one address space to another.
main()function at line 1217. This function initializes the physical memory, page tables, I/O devices, and other things, before initializing the first user process (line 1239) and going into an infinite scheduling loop thereafter (which schedules the available processes). Because xv6 is a multiprocessor OS, the first CPU that has booted also initializes the other CPUs in the system (line 1237).