xv6, line by line
test yourself

Test yourself · category 2 of 20

RISC-V privilege and CSRs

Machine, supervisor and user mode, the control and status registers xv6 uses, what the hardware does on a trap, mret and sret, and why every CSR belongs to one hart.

1warm-upChoose one

start runs in machine mode and wants main to run in supervisor mode. Why does it write mstatus.MPP and mepc and then execute mret, instead of switching modes directly?

kernel/start.c
14void
17 // set M Previous Privilege mode to Supervisor, for mret.
18 unsigned long x = r_mstatus();
23 // set M Exception Program Counter to main, for mret.
24 // requires gcc -mcmodel=medany
2warm-upClick the line

Click the line in start where the hart actually stops running in machine mode.

kernel/start.c
14void
17 // set M Previous Privilege mode to Supervisor, for mret.
18 unsigned long x = r_mstatus();
23 // set M Exception Program Counter to main, for mret.
24 // requires gcc -mcmodel=medany
27 // disable paging for now.
30 // delegate all interrupts and exceptions to supervisor mode.
31 w_medeleg(0xffff);
32 w_mideleg(0xffff);
35 // configure Physical Memory Protection to give supervisor mode
36 // access to all of physical memory.
37 w_pmpaddr0(0x3fffffffffffffull);
40 // enable hardware updates of page table A and D bits
43 // ask for clock interrupts.
46 // keep each CPU's hartid in its tp register, for cpuid().
47 int id = r_mhartid();
50 // switch to supervisor mode and jump to main().
51 asm volatile("mret");

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

3warm-upTrue or false, and why

True or false: when a user program executes ecall, the hardware switches to the process’s kernel stack and the kernel page table as part of the trap.

kernel/trampoline.S
23 #
24 # trap.c sets stvec to point here, so
25 # traps from user space start here,
26 # in supervisor mode, but with a
27 # user page table.
28 #
30 # save user a0 in sscratch so
31 # a0 can be used to get at TRAPFRAME.
32 csrw sscratch, a0
34 # each process has a separate p->trapframe memory area,
35 # but it's mapped to the same virtual address
36 # (TRAPFRAME) in every process's user page table.
37 li a0, TRAPFRAME

Why?

4warm-upChoose one

Just before mret, start copies the hart ID into the ordinary register tp. From then on cpuid returns tp. Why not just read mhartid whenever the ID is needed?

kernel/start.c
46 // keep each CPU's hartid in its tp register, for cpuid().
47 int id = r_mhartid();
50 // switch to supervisor mode and jump to main().
51 asm volatile("mret");
5warm-upMatch the pairs

Match each supervisor CSR with what it holds in xv6.

6warm-upChoose all that apply

A user program executes ecall. Which of these does the hardware change as part of taking the trap? Choose all that apply.

7warm-upChoose one

Lines 31–32 write 0xffff to medeleg and mideleg. What would go wrong without them?

kernel/start.c
30 // delegate all interrupts and exceptions to supervisor mode.
31 w_medeleg(0xffff);
32 w_mideleg(0xffff);
9solidDecode the bits

After line 33 of start, gdb reads sie = 0x220 on every hart. Decode it.

kernel/start.c
30 // delegate all interrupts and exceptions to supervisor mode.
31 w_medeleg(0xffff);
32 w_mideleg(0xffff);

Value: 0x220

10solidDecode the bits

After kvminithart, gdb reads satp = 0x8000000000087fff on every hart. Decode it as an Sv39 satp (MODE in bits 63–60, ASID in bits 59–44, PPN in bits 43–0).

kernel/riscv.h
245// use riscv's sv39 page table scheme.
246#define SATP_SV39 (8L << 60)
248#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12))

Value: 0x8000000000087fff

11solidFill in the machine state

A user program has just executed ecall. The hart is about to run the first instruction of uservec, line 32. Fill in its state. (For the stack, count it only if the code running here may push onto it; see The stacks of xv6.)

kernel/trampoline.S
23 #
24 # trap.c sets stvec to point here, so
25 # traps from user space start here,
26 # in supervisor mode, but with a
27 # user page table.
28 #
30 # save user a0 in sscratch so
31 # a0 can be used to get at TRAPFRAME.
32 csrw sscratch, a0
34 # each process has a separate p->trapframe memory area,
35 # but it's mapped to the same virtual address
36 # (TRAPFRAME) in every process's user page table.
37 li a0, TRAPFRAME
12solidFill in the machine state

Hart 2 has just executed the mret at the end of start and is at the first instruction of main. Fill in its state.

kernel/main.c
9// start() jumps here in supervisor mode on all CPUs.
10void
13 if (cpuid() == 0) {
13solidType a number

On entry to start, gdb reads mstatus = 0xa00000000. What value is written to mstatus on line 21? Answer in hex.

kernel/riscv.h
14#define MSTATUS_MPP_MASK (3L << 11) // previous mode.
15#define MSTATUS_MPP_M (3L << 11)
16#define MSTATUS_MPP_S (1L << 11)
17#define MSTATUS_MPP_U (0L << 11)
decimal, 0x hex or 0b binary
14solidType a number

How many places in the kernel’s source write satp (count each w_satp(...) call and each csrw satp instruction, not the helper’s definition in riscv.h)?

decimal, 0x hex or 0b binary
15solidChoose all that apply

Hart 0 executes each of these. Which ones can affect what hart 1 sees or does? Choose all that apply.

16solidChoose one

usertrap panics on line 43 if the trap did not come from user mode. How does it know where the trap came from?

kernel/trap.c
40 int which_dev = 0;
42 if ((r_sstatus() & SSTATUS_SPP) != 0)
43 panic("usertrap: not from user mode");
45 // send interrupts and exceptions to kerneltrap(),
46 // since we're now in the kernel.
47 w_stvec((uint64)kernelvec); //DOC: kernelvec
17solidPut in order

Put the steps of a return to user mode in order, from prepare_return to the sret.

  1. stvec = uservec in the trampoline
  2. intr_off(): clear sstatus.SIE
  3. fill the trapframe’s kernel_satp, kernel_sp, kernel_trap, kernel_hartid
  4. write sstatus with SPP = 0 and SPIE = 1
  5. sepc = p->trapframe->epc
  6. ld sp, 48(a0): the user’s sp
  7. sret
  8. csrw satp, a0: user page table
18solidChoose one

timerinit asks for the first timer interrupt 0.1 s after boot. Hart 0 spends about 1.5 s in main building the kernel. When is hart 0’s first timer interrupt actually taken?

kernel/start.c
54// ask each hart to generate timer interrupts.
55void
58 // enable the sstc extension (i.e. stimecmp).
61 // allow supervisor to use stimecmp and time.
64 // ask for the very first timer interrupt.
65 w_stimecmp(r_time() + 1000000);
19solidChoose one

For a system call, usertrap adds 4 to the saved epc (line 62). For a page fault that vmfault fixes, it does not. Why the difference?

kernel/trap.c
51 // save user program counter.
54 if (r_scause() == 8) {
55 // system call
57 if (killed(p))
58 kexit(-1);
60 // sepc points to the ecall instruction,
61 // but we want to return to the next instruction.
62 p->trapframe->epc += 4;
64 // an interrupt will change sepc, scause, and sstatus,
65 // so enable only now that we're done with those registers.
69 } else if ((which_dev = devintr()) != 0) {
70 // ok
71 } else if ((r_scause() == 15 || r_scause() == 13) &&
73 (r_scause() == 13) ? 1 : 0) != 0) {
74 // page fault on lazily-allocated page
20deepChoose one

xv6 has no machine-mode trap handler. timerinit therefore enables the Sstc extension (menvcfg.STCE) and uses stimecmp. Why couldn’t xv6 just use the classic machine timer (mtimecmp) and rely on mideleg = 0xffff?

kernel/start.c
54// ask each hart to generate timer interrupts.
55void
58 // enable the sstc extension (i.e. stimecmp).
61 // allow supervisor to use stimecmp and time.
64 // ask for the very first timer interrupt.
65 w_stimecmp(r_time() + 1000000);
21deepChoose one

In a scratch copy of xv6, line 62 (w_mcounteren(r_mcounteren() | 2)) is deleted. The kernel finishes main on hart 0 and enters the scheduler. What happens next?

kernel/start.c
54// ask each hart to generate timer interrupts.
55void
58 // enable the sstc extension (i.e. stimecmp).
61 // allow supervisor to use stimecmp and time.
64 // ask for the very first timer interrupt.
65 w_stimecmp(r_time() + 1000000);
22deepChoose one

xv6’s PTEs never set the A (accessed) and D (dirty) bits. Line 41 sets menvcfg.ADUE (QEMU happens to set it already at reset). On a machine where it starts at 0, what would happen if line 41 were missing?

kernel/start.c
40 // enable hardware updates of page table A and D bits
23deepTrue or false, and why

True or false: if prepare_return left sstatus.SPIE at 0 (so that sret sets SIE to 0), a user program spinning in an infinite loop could never be preempted by the timer.

kernel/trap.c
124 // set S Previous Privilege mode to User.
125 unsigned long x = r_sstatus();
126 x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
127 x |= SSTATUS_SPIE; // enable interrupts in user mode

Why?

24deepChoose one

A trap from user mode always sets sstatus.SPP to 0. Yet prepare_return clears SPP explicitly on line 126 before every return to user mode. Why is that necessary?

kernel/trap.c
121 // set up the registers that trampoline.S's sret will use
122 // to get to user space.
124 // set S Previous Privilege mode to User.
125 unsigned long x = r_sstatus();
126 x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
127 x |= SSTATUS_SPIE; // enable interrupts in user mode
130 // set S Exception Program Counter to the saved user pc.
25deepChoose all that apply

A kernel thread is preempted by a timer interrupt (it calls yield from kerneltrap) and is later resumed by a scheduler, possibly on another hart. When it runs again, which of these may hold a different value from the moment before it yielded? Choose all that apply.