Why can’t a spinlock be written as plain C, like this?
while (lk->locked)
;
lk->locked = 1;
Test yourself · category 12 of 20
The one atomic swap and the handful of fences xv6 relies on, what RVWMO lets other harts see, why the started flag needs release and acquire, what volatile does and does not do, and how locks carry data (and saved registers) from hart to hart.
Why can’t a spinlock be written as plain C, like this?
while (lk->locked)
;
lk->locked = 1;
Line 37 compiles to amoswap.w.aq a5,a5,(s1) with a5 = 1 and s1 = &lk->locked.
What does this one instruction do?
True or false: started is declared volatile on line 7, so the boot handoff would
still be correct on RISC-V if lines 33 and 35 used plain started = 1; and
while (started == 0) ;.
Why?
Hart 0 executes two stores to different addresses, first to x and then to y, with
no fence between them. Under RISC-V’s memory model (RVWMO), which statement is right?
Why does release free the lock with __atomic_store_n(&lk->locked, 0, __ATOMIC_RELEASE) rather than lk->locked = 0;? Choose all that apply.
Click the line of release that compiles to the fence rw,w at 0x80000c86.
Your pick: none yet (click a line in the code)
kalloc updates kmem.freelist with ordinary loads and stores and no fence of its
own, yet three harts call it. Why do the harts see each other’s updates correctly?
Hart 2 waits in acquire for a lock held by hart 1. Its swap on line 37 runs 1,000
times: the first 999 return 1, the last returns 0. How many times does hart 2 write
to lk->locked in total?
kernel.asm shows the spin instruction in acquire as
80000c04: 0cf4a7af amoswap.w.aq a5,a5,(s1). Decode the 32-bit word. (AMO format:
funct5 in bits 31–27, aq bit 26, rl bit 25, rs2 24–20, rs1 19–15, funct3 14–12, rd
11–7, opcode 6–0.)
Value: 0x0cf4a7af
kernel.asm contains 0310000f fence rw,w at 0x80000c86 and again at 0x80000f0a.
Decode the word. (FENCE format: fm in bits 31–28; the predecessor set PI PO PR PW in
bits 27–24; the successor set SI SO SR SW in bits 23–20; opcode in bits 6–0.)
Value: 0x0310000f
Click the line whose compiled code includes fence r,rw.
Your pick: none yet (click a line in the code)
Match each ordering instruction in this build’s kernel.asm with where it comes from.
A hart calls acquire(&kmem.lock) and, a few lines later, release(&kmem.lock). The
lock was free, so the swap succeeds at once. How many instructions whose mnemonic is
fence (not sfence.vma, not fence.i) does the hart execute inside these two calls?
How many atomic memory operation instructions (AMOs such as amoswap, amoadd, or
LR/SC pairs counted as one) does this build’s kernel/kernel.asm contain?
Compile static int started; ... while (started == 0) ; (a plain int, no volatile,
no atomics) with this toolchain and xv6’s flags (-O). What does GCC emit for the loop?
volatile is used on its own for the UART’s registers (Reg() in
kernel/uart.c:17) and for the panicked flag. Which of these does volatile
guarantee? Choose all that apply.
Hart 1 is spinning on line 35 of main, waiting for started, while hart 0 is still
building the kernel. What is hart 1’s state?
release clears lk->cpu on line 51 before the store on line 73 frees the lock.
What would go wrong if the order were reversed (free the lock first, then lk->cpu = 0)?
True or false: panicked (kernel/printk.c:19) is a plain volatile int with no
atomics or fences, and that is enough for what it is used for.
Why?
Hart 0’s store to started is a release (fence rw,w; sw). Why must harts 1 and 2 also
use an acquire load (lw; fence r,rw) instead of a plain lw in the loop?
Put these events in the order that makes hart 1’s first use of the kernel page table safe at boot.
fence r,rw (0x80000e76)sfence.vma at the start of kvminithartsw sets started to 1csrw satp turns paging onlw reads started == 1fence rw,w (0x80000f0a)kvminit's ordinary stores build the kernel page tableA timer interrupt makes sh yield on hart 1: swtch saves its 14 registers with
ordinary sd instructions into p->context, and hart 1’s scheduler releases sh’s
p->lock. Later hart 0’s scheduler acquires that lock, sees RUNNABLE, and
swtch loads p->context. What guarantees hart 0 loads the values hart 1 saved, not
older ones?
virtio_disk_rw holds disk.vdisk_lock, a spinlock with acquire and release
ordering. Why does it still need io_fence() between writing avail->idx and writing
the QUEUE_NOTIFY register?
An experiment built a copy of the kernel with __ATOMIC_RELAXED on release's line
73, so the compiled release has no fence at all. Run under QEMU on an x86-64 host,
it passed usertests -q three times and two stress runs. What does that show?
Consider the fence rw,w that release executes before sw zero,0(s1). Which of
these orderings does it guarantee, as other harts observe them? Choose all that apply.