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()
- This paragraph is not really needed to do projects. Just read it for fun.
- Related documents
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
- Related documents
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.
- It should disable interrupts.
- It should enable A20 line.
- Load the Global Descriptor Table
- It is done with
lgdtinstruction. - You can find out GDT contents at the bottom part of
bootasm.S, labeled withgdtandgdtdesc
- It is done with
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 callingentry(), which is defined in entry.S
entry() to main()
- This paragraph might be related to p4, but not directly related.
- Related documents
- x86 cpu registers - see what cr0, cr3, cr4 register do.
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)