4 min read

xv6 부팅

과거 ta 하며 적은 글

After working on xv6 about a month, you might be used to some parts of the kernel.
For example, now you know how to make a syscall. However, still some of you might wonder how kernel is loaded and run.

Here is a small article that walks through brief booting procedure and initializing virtual memory, which you didn't learn from lecture, but might be needed to complete p4.

From the booting button to the entry()

Loading the bootloader

Although we are using qemu in the project, let's assume if we are using baremetal machine.
If you want to boot your computer, you will press the power button of the computer.

This power button will tell motherboard to load BIOS to the RAM, and run the BIOS.

It will load MBR(Master Boot Record, 512 bytes) to designated memory region, 0x7c00. You can check it in makefile,

bootblock: bootasm.S bootmain.c
	$(CC) $(CFLAGS) -fno-pic -O -nostdinc -I. -c bootmain.c
	$(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S
	$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
	$(OBJDUMP) -S bootblock.o > bootblock.asm
	$(OBJCOPY) -S -O binary -j .text bootblock.o bootblock
	./sign.pl bootblock 
    # sign.pl will put a boot signature and make it 512 bytes.

Then our machine will start bootasm.S, with %cs=0 and %ip=7c00.

Currently, our CPU is on real mode(16 bit), for backward compatibility. However, xv6 is 32 bit operating system. We need to change to 32-bit protected mode.

Real Mode to 32-bit Protected Mode

Brief characteristics of real mode:

  • 16 bit
  • 20 bit memory address -> Max memory size 1MB
  • memory segmentation
    • addressing memory like [segment:offset]
    • segment registers(cs, ds, es, ss) are used for segment -> 16 bit segment number
    • Each segment will have 64KB region -> there exists an overlapping region

To switch to protected mode, there are several steps to do. You can find how xv6 does in bootasm.S.

  1. It should disable interrupts.
  2. It should enable A20 line.
  3. Load the Global Descriptor Table
    • It is done with lgdt instruction.
    • You can find out GDT contents at the bottom part of bootasm.S, labeled with gdt and gdtdesc

Then finally it is ready to enter the protected mode.

  movl    %cr0, %eax
  orl     $CR0_PE, %eax
  movl    %eax, %cr0

setting the CR0_PE bit in cr0 register enables it, and finally it calls bootmain().

Loading kernel into the memory

bootmain just loads kernel into memory. Since we can only load 512 bytes when we boot, that's why we are using a tiny bootloader to load kernel. You can read bootmain.c for the detail.

After successfully loading kernel into the memory, it will finally jump to the kernel by calling
entry(), which is defined in entry.S

entry() to main()

  • This paragraph might be related to p4, but not directly related.
  • Related documents

Currently we just called entry(). The main job of entry is to turn on paging and make a very first page table.

In xv6, the initial page consists of one 4MB huge page and one page directory.
This temporary page directory is defined in main.c as entrypgdir.

Here is the entrypgdir

__attribute__((__aligned__(PGSIZE)))
pde_t entrypgdir[NPDENTRIES] = {
  // Map VA's [0, 4MB) to PA's [0, 4MB)
  [0] = (0) | PTE_P | PTE_W | PTE_PS,
  // Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
  [KERNBASE>>PDXSHIFT] = (0) | PTE_P | PTE_W | PTE_PS,
};

As you can see, PTE_PS flag is enabled in PDE, so we are using one 4MB huge page.
This physical page is mapped to two regions, which starts from 0 and KERNBASE.

entry() will use this page directory by overwriting cr3 register.

  # Set page directory
  movl    $(V2P_WO(entrypgdir)), %eax
  movl    %eax, %cr3

From now, we have page directory so finally we can enable paging.

  # Turn on paging.
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
  movl    %eax, %cr0

After setting up the stack by loading %esp register, we are finally ready to jump main()

main()

  • I'll only cover kernel virtual memory initialization in main()
  • Here is the important part for p4!!

Until now, we have enabled paging and mapped a single 4MB hugepage.
To use 4MB region with kalloc and kfree, we will add the memory region from end(the end of kernel elf) to 4MB.

kinit1 is used for it. It will push pages in 4MB region to the free list by using freerange(). Since we don't have any other threads and also we didn't set up the interrupt controller, we don't need to use lock for modifying kmem data structure.

Then with these 4MB pages, we will set up the page table that is capable of all the physical memories.

kvmalloc() creates the page table for kernel. It will call setupkvm() which maps the virtual memory address to the physical memory address based on kmap. Now our page table is fully capable.

static struct kmap {
  void *virt;
  uint phys_start;
  uint phys_end;
  int perm;
} kmap[] = {
 { (void*)KERNBASE, 0,             EXTMEM,    PTE_W}, // I/O space
 { (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0},     // kern text+rodata
 { (void*)data,     V2P(data),     PHYSTOP,   PTE_W}, // kern data+memory
 { (void*)DEVSPACE, DEVSPACE,      0,         PTE_W}, // more devices
};

After initializing all the other components, we should add 4MB-PHYSTOP memory region to the free list. that's what kinit2() does.

Now, we have successfully mapped all the physical pages to the kernel virtual addresses. We can use all the memory from now.

Finally our xv6 will create the first user process, initcode, by calling userinit(). Then it calls mpmain(), which loads scheduler.

Then booting is finished.

Questions

    • In real system, we use 0xe820. But for xv6, I think they assume they will always have enough physical memory available.
    • Physical memory itself is defined as a part of QEMUOPTS. You can check it in the makefile.
    • In P4, we edited the options to have 2G memory to support huge page regions.

Q: How can we sure that 0-PHYSTOP memory is available?

QEMUOPTS = -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp $(CPUS) -m 512 $(QEMUEXTRA)
QEMUOPTS = -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp $(CPUS) -m 2048 $(QEMUEXTRA)