xv6, line by line
test yourself

Test yourself · category 1 of 20

Build, link and boot

How make, the linker script and mkfs produce kernel/kernel and fs.img, and how three harts get from QEMU’s boot ROM through _entry, start and main to the first process.

1warm-upChoose one

Every hart runs these lines of _entry at the same time. Why does the stack pointer use hartid + 1 instead of hartid?

kernel/entry.S
8 # set up a stack for C.
9 # stack0 is declared in start.c,
10 # with a 4096-byte stack per CPU.
11 # sp = stack0 + ((hartid + 1) * 4096)
12 la sp, stack0
13 li a0, 1024*4
14 csrr a1, mhartid
15 addi a1, a1, 1
16 mul a0, a0, a1
17 add sp, sp, a0
18 # jump to start() in start.c
19 call start
2warm-upType a number

In this build, stack0 is at 0x80007890. What value does sp hold on hart 2 right after line 17 (add sp, sp, a0)? Answer in hex.

kernel/entry.S
8 # set up a stack for C.
9 # stack0 is declared in start.c,
10 # with a 4096-byte stack per CPU.
11 # sp = stack0 + ((hartid + 1) * 4096)
12 la sp, stack0
13 li a0, 1024*4
14 csrr a1, mhartid
15 addi a1, a1, 1
16 mul a0, a0, a1
17 add sp, sp, a0
18 # jump to start() in start.c
19 call start
decimal, 0x hex or 0b binary
3warm-upChoose one

Every kernel file is compiled with $(CC), the RISC-V cross-compiler / toolchain. Why is mkfs compiled with plain gcc on line 124?

Makefile
124 gcc -Wno-unknown-attributes -I. -o mkfs/mkfs mkfs/mkfs.c
4warm-upMatch the pairs

Match each build input with its job.

5warm-upTrue or false, and why

True or false: when hart 0 calls kvminithart on line 21 of main, paging is turned on for all three harts.

kernel/main.c
19 kinit(); // physical page allocator
20 kvminit(); // create kernel page table
21 kvminithart(); // turn on paging
22 procinit(); // process table
23 trapinit(); // trap vectors
24 trapinithart(); // install kernel trap vector

Why?

6warm-upChoose one

userinit creates process 1 with no user memory at all (p->sz stays 0). How does process 1 get the code of /init?

kernel/proc.c
217// Set up first user process.
218void
221 struct proc *p;
226 p->cwd = namei("/");
7warm-upChoose all that apply

While hart 0 runs the if branch of main (lines 14–33), what are harts 1 and 2 doing? Choose all that apply.

kernel/main.c
9// start() jumps here in supervisor mode on all CPUs.
10void
13 if (cpuid() == 0) {
16 printk("\n");
17 printk("xv6 kernel is booting\n");
18 printk("\n");
19 kinit(); // physical page allocator
20 kvminit(); // create kernel page table
21 kvminithart(); // turn on paging
22 procinit(); // process table
23 trapinit(); // trap vectors
24 trapinithart(); // install kernel trap vector
25 plicinit(); // set up interrupt controller
26 plicinithart(); // ask PLIC for device interrupts
27 binit(); // buffer cache
28 iinit(); // inode table
29 fileinit(); // file table
30 virtio_disk_init(); // emulated hard disk
31 userinit(); // first user process
33 __atomic_store_n(&started, 1, __ATOMIC_RELEASE);
34 } else {
35 while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0)
36 ;
38 printk("hart %d starting\n", cpuid());
39 kvminithart(); // turn on paging
40 trapinithart(); // install kernel trap vector
41 plicinithart(); // ask PLIC for device interrupts
42 }
8warm-upChoose one

binit and iinit run in main, but fsinit, which reads the superblock from the disk, does not. Why must it wait until the first process runs?

kernel/proc.c
519 // Still holding p->lock from scheduler.
522 if (first) {
523 first = 0;
525 // File system initialization must be run in the context of a
526 // regular process (e.g., because it calls sleep), and thus cannot
527 // be run from main().
530 // We can invoke kexec() now that file system is initialized.
531 // Put the return value (argc) of kexec into a0.
532 p->trapframe->a0 = kexec("/init", (char *[]){"/init", 0});
533 if (p->trapframe->a0 == -1) {
534 panic("exec");
535 }
536 }
9solidClick the line

kvmmake maps everything below etext read+execute and everything above it read+write, page by page, so etext must be a multiple of 0x1000. Click the line that makes it so.

kernel/kernel.ld
12 .text : {
14 *(.text .text.*)
15 . = ALIGN(0x1000);
18 . = ALIGN(0x1000);
19 ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page");
20 PROVIDE(etext = .);
21 }

Your pick: none yet (click a line in the code)

10solidPut in order

Put these events on hart 0 in the order they happen, from power-on to the first moment it can take an interrupt.

  1. mret drops the hart to supervisor mode at main
  2. QEMU’s boot ROM at 0x1000 jumps to 0x80000000
  3. _entry points sp at the top of hart 0’s slice of stack0
  4. kvminithart writes satp: paging on
  5. started is set to 1 with a release store
  6. start sets mstatus.MPP to S and mepc to main
  7. the scheduler’s first intr_on()
  8. trapinithart sets stvec to kernelvec
11solidChoose all that apply

Which of these functions does hart 1 call during boot? Choose all that apply.

kernel/main.c
9// start() jumps here in supervisor mode on all CPUs.
10void
13 if (cpuid() == 0) {
16 printk("\n");
17 printk("xv6 kernel is booting\n");
18 printk("\n");
19 kinit(); // physical page allocator
20 kvminit(); // create kernel page table
21 kvminithart(); // turn on paging
22 procinit(); // process table
23 trapinit(); // trap vectors
24 trapinithart(); // install kernel trap vector
25 plicinit(); // set up interrupt controller
26 plicinithart(); // ask PLIC for device interrupts
27 binit(); // buffer cache
28 iinit(); // inode table
29 fileinit(); // file table
30 virtio_disk_init(); // emulated hard disk
31 userinit(); // first user process
33 __atomic_store_n(&started, 1, __ATOMIC_RELEASE);
34 } else {
35 while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0)
36 ;
38 printk("hart %d starting\n", cpuid());
39 kvminithart(); // turn on paging
40 trapinithart(); // install kernel trap vector
41 plicinithart(); // ask PLIC for device interrupts
42 }
12solidFill in the machine state

Hart 1 is spinning on line 35 of main, waiting for started. Hart 0 is somewhere in kinit. Fill in hart 1’s state.

kernel/main.c
34 } else {
35 while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0)
36 ;
38 printk("hart %d starting\n", cpuid());
39 kvminithart(); // turn on paging
40 trapinithart(); // install kernel trap vector
41 plicinithart(); // ask PLIC for device interrupts
13solidChoose one

The kernel’s .bss section is 0x19350 bytes and holds proc, stack0, cpus and the other zero-initialized globals. The C code assumes they start as zero. Who zeroes them?

14solidChoose all that apply

Which of these live in the kernel’s .bss section (0x80007860–0x80020bb0 in this build)? Choose all that apply.

15solidType a number

How many metadata blocks (nmeta) does mkfs compute? In this tree FSSIZE = 2000, BSIZE = 1024, LOGBLOCKS = 30, sizeof(struct dinode) = 64 (so IPB = 16) and BPB = 1024 × 8 = 8192.

mkfs/mkfs.c
23#define NINODES 200
25// Disk layout:
26// [ boot block | sb block | log | inode blocks | free bit map | data blocks ]
28int nbitmap = FSSIZE / BPB + 1;
30int nlog = LOGBLOCKS + 1; // Header followed by LOGBLOCKS data blocks.
31int nmeta; // Number of meta blocks (boot, sb, nlog, inode, bitmap)
32int nblocks; // Number of data blocks
decimal, 0x hex or 0b binary
16solidChoose one

The comment on line 24 says writing main’s address into mepc “requires gcc -mcmodel=medany”. Why?

kernel/start.c
23 // set M Exception Program Counter to main, for mret.
24 // requires gcc -mcmodel=medany
17solidType a number

make runs mkfs/mkfs fs.img README $(UPROGS). mkfs gives the root directory the first inode, then one inode to each file in command-line order. What is the inode number of /init?

decimal, 0x hex or 0b binary
18solidTrue or false, and why

True or false: right after kvminithart turns on paging, hart 0’s sp (still in its slice of stack0) points at usable memory, with no change to sp.

kernel/vm.c
38 // map kernel text executable and read-only.
41 // map kernel data and the physical RAM we'll make use of.

Why?

19solidChoose one

To leave for user mode the first time, forkret calls userret through the address TRAMPOLINE + (userret - trampoline) (0x3ffffff09c), not at its link address 0x8000609c. Why?

kernel/proc.c
538 // return to user space, mimicing usertrap()'s return.
21deepChoose one

You delete line 15 of kernel/kernel.ld (the ALIGN(0x1000) before _trampoline = .) and run make. In this build the ordinary kernel code ends at 0x80005bc0 and the trampoline code is 0x124 bytes long. What happens?

kernel/kernel.ld
12 .text : {
14 *(.text .text.*)
15 . = ALIGN(0x1000);
18 . = ALIGN(0x1000);
19 ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page");
20 PROVIDE(etext = .);
21 }
22deepChoose one

NCPU is 8, so stack0 has 8 slices and cpus has 8 entries. You run make qemu CPUS=10. What happens?

kernel/start.c
10// entry.S needs one stack per CPU.
11__attribute__((aligned(16))) char stack0[4096 * NCPU];
23deepChoose one

Process 1 runs fsinit with interrupts off on its hart (the scheduler’s acquire recorded intena = 0, so forkret's release leaves them off). Its first disk read sleeps waiting for the completion interrupt. Which hart can take that interrupt?

kernel/proc.c
512void
515 extern char userret[];
516 static int first = 1;
517 struct proc *p = myproc();
519 // Still holding p->lock from scheduler.
522 if (first) {
523 first = 0;
525 // File system initialization must be run in the context of a
526 // regular process (e.g., because it calls sleep), and thus cannot
527 // be run from main().
24deepChoose all that apply

Suppose harts 1 and 2, after seeing started == 1, also ran these functions themselves. Which would corrupt shared kernel state? Choose all that apply.

kernel/main.c
13 if (cpuid() == 0) {
16 printk("\n");
17 printk("xv6 kernel is booting\n");
18 printk("\n");
19 kinit(); // physical page allocator
20 kvminit(); // create kernel page table
21 kvminithart(); // turn on paging
22 procinit(); // process table
23 trapinit(); // trap vectors
24 trapinithart(); // install kernel trap vector
25 plicinit(); // set up interrupt controller
26 plicinithart(); // ask PLIC for device interrupts
27 binit(); // buffer cache
28 iinit(); // inode table
29 fileinit(); // file table
30 virtio_disk_init(); // emulated hard disk
31 userinit(); // first user process
33 __atomic_store_n(&started, 1, __ATOMIC_RELEASE);
34 } else {
35 while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0)
36 ;
38 printk("hart %d starting\n", cpuid());
39 kvminithart(); // turn on paging
40 trapinithart(); // install kernel trap vector
41 plicinithart(); // ask PLIC for device interrupts
42 }
25deepPut in order

Put the steps of process 1’s life in order, from its creation to its first user instruction.

  1. kexec("/init") loads the program and installs its new page table
  2. prepare_return points stvec at uservec and sets sepc to the entry point
  3. fsinit reads the superblock and recovers the log
  4. userret switches satp to the user page table and executes sret
  5. forkret releases p->lock
  6. a scheduler sets RUNNING and calls swtch, whose ret lands in forkret
  7. allocproc sets p->context.ra = forkret and p->context.sp to the top of its kernel stack
  8. userinit sets p->state = RUNNABLE