2.6 Code: starting xv6, the first process and system call
To make xv6 more concrete, we’ll outline how the kernel starts and runs the first process. The subsequent chapters will describe the mechanisms that show up in this overview in more detail. Please read kernel/entry.S, kernel/start.c, kernel/main.c, and user/init.c.
When the RISC-V computer powers on, it initializes itself and runs a boot loader which is stored in read-only memory. The boot loader copies the xv6 kernel into memory at physical address 0x80000000. The reason it places the kernel at 0x80000000 rather than 0x0 is because the address range 0x0:0x80000000 contains I/O devices.
Then the boot loader jumps to xv6 starting at
_entry
(1006).
The RISC-V starts with paging hardware disabled:
virtual addresses map directly to physical addresses.
The instructions at
_entry
set up a stack so that xv6 can run C code.
Xv6 declares space for this stack,
stack0,
in the file
start.c
(1060).
The code at
_entry
loads the stack pointer register
sp
with the address
stack0+4096,
the top of the stack, because the stack
on RISC-V grows down.
Now that the kernel has a stack,
_entry
calls into C code at
start
(1064).
The function
start
performs some setup that the CPU only allows in machine mode,
most crucially programming the clock chip to generate
timer interrupts.
Then start uses the RISC-V mret
instruction to switch to supervisor mode and
jump to
main
(1160).
mret requires
a bit of setup:
start sets the previous privilege mode to
supervisor in the register
mstatus,
sets the destination address to
main
by writing
main’s
address into
the register
mepc,
disables virtual address translation in supervisor mode
by writing
0
into the page-table register
satp,
and delegates all interrupts and exceptions
to supervisor mode.
After
main
(1160)
initializes several devices and subsystems,
it creates the first process by calling
userinit
(2327).
All newly created processes start executing in the kernel in
forkret
(2653).
As a special case for the first process, forkret
calls kexec to load the user program /init.
After calling
kexec,
forkret returns to user space in
the /init process.
init
(7764)
creates a new console device file
if needed
and then opens it as file descriptors 0, 1, and 2.
Then it starts a shell on the console.
The system is up.