Every hart runs these lines of _entry at the same time. Why does the stack pointer
use hartid + 1 instead of hartid?
Quick 10
Ten questions from all twenty categories, favouring ones you have not answered yet or got wrong.
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.
Every kernel file is compiled with $(CC), the RISC-V cross-compiler / toolchain. Why is
mkfs compiled with plain gcc on line 124?
Match each build input with its job.
True or false: when hart 0 calls kvminithart on line 21 of main, paging is
turned on for all three harts.
Why?
userinit creates process 1 with no user memory at all (p->sz stays 0). How
does process 1 get the code of /init?
While hart 0 runs the if branch of main (lines 14–33), what are harts 1
and 2 doing? Choose all that apply.
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?
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.
Your pick: none yet (click a line in the code)
Put these events on hart 0 in the order they happen, from power-on to the first moment it can take an interrupt.
mretdrops the hart to supervisor mode atmain- QEMU’s boot ROM at 0x1000 jumps to 0x80000000
_entrypointsspat the top of hart 0’s slice ofstack0kvminithartwritessatp: paging onstartedis set to 1 with a release storestartsetsmstatus.MPPto S andmepctomain- the scheduler’s first
intr_on() trapinithartsetsstvectokernelvec
Which of these functions does hart 1 call during boot? Choose all that apply.
Hart 1 is spinning on line 35 of main, waiting for started. Hart 0 is
somewhere in kinit. Fill in hart 1’s state.
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?
Which of these live in the kernel’s .bss section (0x80007860–0x80020bb0 in this
build)? Choose all that apply.
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.
The comment on line 24 says writing main’s address into mepc “requires gcc
-mcmodel=medany”. Why?
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?
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.
Why?
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?
In this build end is 0x80020bb0 and PHYSTOP is 0x88000000. How many pages
does kinit hand to the page allocator?
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?
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?
Suppose harts 1 and 2, after seeing started == 1, also ran these functions themselves.
Which would corrupt shared kernel state? Choose all that apply.
Put the steps of process 1’s life in order, from its creation to its first user instruction.
kexec("/init")loads the program and installs its new page tableprepare_returnpointsstvecatuservecand setssepcto the entry pointfsinitreads the superblock and recovers the loguserretswitchessatpto the user page table and executessretforkretreleasesp->lock- a scheduler sets
RUNNINGand callsswtch, whoseretlands inforkret allocprocsetsp->context.ra = forkretandp->context.spto the top of its kernel stackuserinitsetsp->state = RUNNABLE
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?
Click the line in start where the hart actually stops running in machine mode.
Your pick: none yet (click a line in the code)
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.
Why?
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?
Match each supervisor CSR with what it holds in xv6.
A user program executes ecall. Which of these does the hardware change as part of
taking the trap? Choose all that apply.
Lines 31–32 write 0xffff to medeleg and mideleg. What would go wrong without them?
usertrap recognizes a system call by the value of scause. What value is it?
After line 33 of start, gdb reads sie = 0x220 on every hart. Decode it.
Value: 0x220
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).
Value: 0x8000000000087fff
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.)
Hart 2 has just executed the mret at the end of start and is at the first
instruction of main. Fill in its state.
On entry to start, gdb reads mstatus = 0xa00000000. What value is written
to mstatus on line 21? Answer in hex.
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)?
Hart 0 executes each of these. Which ones can affect what hart 1 sees or does? Choose all that apply.
usertrap panics on line 43 if the trap did not come from user mode. How does it know
where the trap came from?
Put the steps of a return to user mode in order, from prepare_return to the sret.
stvec=uservecin the trampolineintr_off(): clearsstatus.SIE- fill the trapframe’s
kernel_satp,kernel_sp,kernel_trap,kernel_hartid - write
sstatuswithSPP= 0 andSPIE= 1 sepc=p->trapframe->epcld sp, 48(a0): the user’sspsretcsrw satp, a0: user page table
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?
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?
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?
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?
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?
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.
Why?
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?
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.
echo calls write(1, "hi", 2). What is write in user space?
How does the kernel know which system call the program asked for, and where does
syscall read that information?
In our gdb run, echo’s ecall for write sits at user address 0x354. When the system
call finishes, at what address does echo continue? (Answer in hex.)
True or false: when a user program executes ecall, the hardware switches sp to the
process’s kernel stack.
Why?
Why does usertrap add 4 to p->trapframe->epc for a system call?
Match each scause value with what it means. These are the values usertrap,
kerneltrap and devintr compare against.
Which of these does the hardware itself change when a user program executes ecall?
For a system call, usertrap turns interrupts on at line 66, but only after line 52
and the scause test. Why not earlier?
Click the line that delivers sys_write’s result to the user program.
Your pick: none yet (click a line in the code)
A buggy program puts 99 in a7 and executes ecall. What happens in this kernel?
What is NELEM(syscalls), the number of elements in the table?
A timer interrupt arrives while cat runs in user mode (it has not been killed). Which of
these does usertrap do for this trap?
A user program stores to a lazily allocated heap page and takes a store page fault
(scause 15). usertrap calls vmfault. What is the state of the hart at line 463,
before kalloc has been called?
Inside usertrap, scause reads 0x8000000000000009. Decode it.
Value: 0x8000000000000009
True or false: if a user program sets sp to 0 just before its ecall, it can crash the
kernel.
Why?
Put these steps of one system call (one that does not sleep or yield) in the order they happen.
syscallruns the handler and stores its result intrapframe->a0uservecsaves the user registers into the trapframeuservecloads the kernel stack and switchessatpto the kernel page tableuserretswitches to the user page table, restores the registers and executessretusertrapturns interrupts onecall: mode U to S,sepc= theecall’s address,pc=stvecprepare_returnturns interrupts off and pointsstvecatuservecusertrapcopiessepcintop->trapframe->epc
Why must kerneltrap write sepc and sstatus back (lines 162–163) before returning
to kernelvec?
kernelvec restores every register it saved except tp; it does not even save it. Why?
Suppose usertrap called intr_on() right after line 47 (so before line 52), and a timer interrupt was
already pending when echo executed its ecall for write. What would happen?
In this kernel, which of these traps from user mode end with usertrap killing the
process?
cat is inside copyout (in a system call, interrupts on) when hart 2’s timer fires.
Put the steps in order.
kerneltrapcopiessepcandsstatusinto local variables- The hardware sets
sepcto the interrupted instruction,scauseto the timer code, clearsSIE, and jumps tostvec, which iskernelvec kernelvecpushes a 256-byte frame oncat’s kernel stack and saves the caller-saved registersdevintrcallsclockintr, which writes a newstimecmpkernelvecreloads the registers, pops its frame and executessretyieldswitches away; latercatresumes, perhaps on another hartkerneltrapwrites the savedsepcandsstatusback
Hart 1 has nothing to run. Its scheduler briefly enables interrupts at line 441, and a
timer interrupt is taken there. What does kerneltrap do?
During one system call that neither sleeps nor yields, how many times is stvec written,
from the ecall to the sret?
proc_pagetable maps the trampoline page at TRAMPOLINE in every user page table,
and kvmmake maps the same page at the same address in the kernel page table. Why must
the address be the same in both?
The first instruction of uservec is csrw sscratch, a0. Why does it start there?
What is the virtual address TRAPFRAME in this kernel? (Answer in hex.)
True or false: every process has its own trampoline page.
Why?
How many general-purpose registers does uservec save into the trapframe?
In userret, one register must be restored last. Click the line that restores it.
Your pick: none yet (click a line in the code)
Put the steps of userret in the order it executes them.
fence.i: make this hart’s instruction fetches see what is now in memorysret: drop to user mode atsepcli a0, TRAPFRAME- load
spand the other user registers, excepta0 ld a0, 112(a0): the user’sa0csrw satp, a0between twosfence.vma: install the user page table
Neither the TRAMPOLINE nor the TRAPFRAME mapping has PTE_U. What does that achieve?
Match each load in the trampoline with what it fetches from the trapframe.
Line 76 puts the kernel-stack address into sp, but at that moment the address is not
mapped. Click the line that installs the page table in which it maps the kernel stack.
Your pick: none yet (click a line in the code)
A process has just made a system call. Hart 0 has executed line 76 of uservec,
ld sp, 8(a0), and nothing after it. What is its state? (For stack, give the stack the
hart could safely push to right now; see The stacks of xv6.)
gdb shows sstatus = 0x8000000200006020 on hart 0 at the sret in userret. Decode
the bits xv6 cares about.
Value: 0x8000000200006020
forkret computes MAKE_SATP(p->pagetable) for init and gets 0x8000000000087f52.
Decode it.
Value: 0x8000000000087f52
prepare_return calls intr_off() (line 108) before it points stvec at
uservec (line 112). What would go wrong if an interrupt could arrive after line 112?
Which trapframe fields does prepare_return write?
Which of these are sret’s own doing, done by the hardware when userret executes it?
Which of these are the same for every process in this kernel?
Hart 0 (running cat) and hart 1 (running grep) both execute sd ra, 40(a0) in
uservec at the same instant, both with a0 = 0x3fffffe000. Why does neither
overwrite the other’s saved ra?
In this tree, what does sscratch hold while a process runs in user mode?
usertrap computes the user satp at the very end and hands it to userret in a0.
Why not reuse the satp value that was installed when the trap arrived?
A process lives in proc[2]. What value does prepare_return store in its
trapframe->kernel_sp? (Answer in hex.)
Imagine a bug that leaves the TRAPFRAME mapping out of a process’s page table. What
happens the first time that process traps into the kernel?
True or false: in this kernel, the twelve loads ld s0 … ld s11 in userret are
redundant on every return to user mode, because the s registers already hold the user’s
values when usertrap returns.
Why?
uservec sets tp from trapframe->kernel_hartid, a value written by
prepare_return the last time this process left the kernel. Why is it guaranteed to be
the ID of the hart now executing uservec?
A supervisor timer interrupt is pending on hart 1. In this kernel, what makes it stop being pending?
In devintr, the external-interrupt branch asks the PLIC who interrupted with
plic_claim, but the timer branch does not. Why?
plicinithart writes one 32-bit value into this hart’s S-mode enable register. What is
that value? (Hex like 0x10 or decimal are both accepted.)
What is the address of hart 2’s S-mode claim/complete register, the one
plic_claim reads on hart 2? Give it in hex.
The machine has three harts, and QEMU’s time counter runs at 10,000,000 per second.
Approximately how many times per second does the kernel’s global ticks counter increase?
(Give the nearest whole number.)
A hart is running kernel code in supervisor mode. Which of these does the hardware check before it takes a supervisor timer interrupt?
kerneltrap reads scause = 0x8000000000000009. Decode it.
Value: 0x8000000000000009
Under gdb, hart 0 at the first instruction of scheduler shows sie = 0x220. Decode
it.
Value: 0x220
Hart 2 is idle in its scheduler when the disk finishes a read. Put the events in order, from the device to the moment the PLIC may forward the disk’s interrupts again.
- Hart 2 executes
intr_on()in its scheduler and takes the trap atstvec, which is kernelvec - plic_claim reads hart 2’s claim register and gets 1
- virtio_disk_intr acknowledges the device, sets
b->disk = 0and calls wakeup - kerneltrap calls devintr, which sees scause
0x8000000000000009 - plic_complete(1) writes 1 back to the claim register
- The disk raises interrupt source 1; the PLIC marks it pending and signals the harts (SEIP)
In clockintr, click the line that acknowledges the timer interrupt, so that it stops
being pending.
Your pick: none yet (click a line in the code)
Two harts trap for the same disk interrupt. Hart 2 claims it; hart 1’s claim returns 0.
In devintr, click the line that keeps hart 1 from sending a completion to the PLIC.
Your pick: none yet (click a line in the code)
Hart 1 is idle in its scheduler and traps for a disk interrupt that hart 2 has already
claimed, so hart 1’s plic_claim returns 0. What happens next on hart 1?
True or false: while a hart is executing user code, a pending supervisor timer interrupt
(with sie.STIE set) is taken even if sstatus.SIE happened to be 0.
Why?
clockintr increments ticks only when cpuid() == 0. What is the reason?
Suppose clockintr were changed so that only hart 0 executes line 179
(w_stimecmp(...)), on the theory that only hart 0’s ticks matter. What happens to
hart 1 after its first timer interrupt?
Hart 2 was idle in its scheduler when a disk interrupt arrived. It is now in
virtio_disk_intr and has just executed line 303, acquire(&disk.vdisk_lock).
What is the state of hart 2?
Match each register with its role in taking an interrupt.
When its scan finds nothing to run, the scheduler executes wfi at line 467 with
interrupts off (line 442 turned them off). Why doesn’t the hart sleep through the
interrupt that should wake it?
The scheduler turns interrupts on at line 441 and straight back off at line 442, and then
scans the table and possibly executes wfi with interrupts off. What would go wrong if
line 442 were removed, so interrupts stayed on during the scan and wfi?
A process calls pause(5) just after a tick. Assume nothing else wakes or kills it, and
that after each wakeup some scheduler runs it again before the next tick. How many times
does sys_pause call sleep() (line 85)?
Hart 0 started a disk read and its process went to sleep. When the disk finishes, which
hart runs virtio_disk_intr?
virtio_disk_intr writes the device’s INTERRUPT_ACK register (line 311), and later
devintr calls plic_complete. What would happen if xv6 called plic_complete
without acknowledging the device?
True or false: if hart 1 has interrupts off when its timer fires, hart 0 may handle that timer interrupt instead.
Why?
Which of these always run with interrupts off (sstatus.SIE = 0) on the hart executing
them?
Hypothetically, hart 0 runs for 0.35 seconds with interrupts off (say, a buggy driver spinning). Harts 1 and 2 run normally. Which statements are true?
Where does a hart’s scheduler stack come from?
How many kernel-stack pages does proc_mapstacks allocate at boot in this kernel?
True or false: a child created by fork starts with a copy of its parent’s kernel stack.
Why?
Match each instruction with what it does to the hart’s active stack.
In this build stack0 is at 0x80007890. What value does sp hold on hart 2 right
after line 17 of entry.S? Give it in hex.
In swtch, click the line at which the hart stops running on the old thread’s stack
and starts running on the new one’s.
Your pick: none yet (click a line in the code)
A process is returning to user mode. The hart has just executed line 111 of userret,
csrw satp, a0, and has not reached line 118 yet. What is its state?
Hart 0’s scheduler has just switched to init (pid 1) for the first time. The hart is at
the first instruction of forkret, before line 520. What is its state?
Which of these are save areas (fixed slots for registers) rather than stacks?
A process is in the middle of a system call, with interrupts on, when a timer interrupt
arrives. Where does kernelvec save the interrupted registers?
kernelvec saves most caller-saved registers in its frame and restores them, but it
deliberately neither saves nor restores tp (lines 20 and 44). Why?
A timer interrupt arrives while hart 2 is in its scheduler’s interrupt window (between
lines 441 and 442 of proc.c). Which stack does kerneltrap run on, and does it call
yield?
Which of these stacks have a guard page below them in this kernel?
How many registers does kernelvec store into its 256-byte frame?
TRAMPOLINE is 0x3ffffff000 and PGSIZE is 0x1000. What address is the top of
slot 4’s kernel stack, the value uservec loads into sp for the process in proc[4]?
Give it in hex.
gdb shows sp = 0x3fffff9ee0 on a hart in supervisor mode. Decode this address using
KSTACK(i) = 0x3ffffff000 - (i + 1) * 0x2000.
Value: 0x3fffff9ee0
While kexec copies the argument strings onto the new program’s user stack
(exec.c lines 101–119), which stack is the hart running on?
Process A, running in user mode on hart 1, is interrupted by the timer. Hart 1 then runs
process B, which had been preempted the same way earlier. Put hart 1’s stacks in the
order sp visits them.
- B’s user stack, loaded by
ld sp, 48(a0)in userret - hart 1’s scheduler stack, loaded by
ld sp, 8(a1)in swtch called from sched - A’s user stack
- A’s kernel stack, loaded by
ld sp, 8(a0)in uservec - B’s kernel stack, at the point inside sched where B stopped, loaded by swtch called from scheduler
kexit marks the process ZOMBIE and calls sched, never to return. What happens to
the kernel stack it was running on?
A scheduler switches to a fork child for the very first time. What does swtch’s
ld sp, 8(a1) load, and what is on that stack?
When process A gives up hart 1, why does sched switch to the scheduler stack
instead of picking process B and switching directly from A’s kernel stack to B’s?
True or false: while a process is running in user mode, its kernel stack still holds the frames of its last system call.
Why?
Hypothetically, hart 0’s scheduler stack overflows by 32 bytes: some code on it pushes
32 bytes below the bottom of hart 0’s slice of stack0 (0x80007890). Which of these
would be overwritten? (In this build kernel_pagetable is at 0x80007870, initproc at
0x80007878, ticks at 0x80007880, and cpus at 0x8000f9d0.)
In prepare_return, click the line that decides where sp will point when this process
next traps in from user mode.
Your pick: none yet (click a line in the code)
swtch saves the registers of the thread that is stopping into a struct context.
Which registers are they?
Hart 1 is running process A. A’s timer tick makes it yield, and hart 1 next runs
process B. How many calls to swtch does hart 1 make between “running A” and
“running B”?
When sched switches away from a process, hart 1 continues in scheduler. Which
memory is hart 1’s stack pointer pointing into while the scheduler scans proc[]?
Which of these functions call sched directly?
In swtch, click the instruction at which the hart stops using the old thread’s stack
and starts using the new thread’s stack.
Your pick: none yet (click a line in the code)
A process created by fork has never run. When a scheduler first calls
swtch(&c->context, &p->context) for it, where does swtch’s ret jump?
Lines 10–23 of swtch store the old thread’s registers into its context. How many
bytes do they write in total?
True or false: swtch ought to save and restore tp as well, and leaving it out
means a process resumed on a different hart will compute mycpu wrongly.
Why?
yield acquires p->lock, sets p->state = RUNNABLE, and calls sched still
holding the lock. The lock is released by the scheduler only after swtch. Why must
it stay held across the switch?
Each pass of scheduler's outer loop executes intr_on() immediately followed by
intr_off(). What is the point of turning interrupts on for a single instruction?
Hart 2 is idle. A timer interrupt has been pending, and it is taken right after
intr_on() at line 441. What does kerneltrap do with it?
An idle hart runs one full pass of scheduler's inner for loop and finds nothing
RUNNABLE. How many times does it call acquire during that pass?
Pid 5 was spinning in user mode on hart 1 when a timer interrupt arrived. usertrap
called yield, which called sched, which called swtch. Hart 1 has just
executed line 26, ld sp, 8(a1). What is hart 1’s state?
Hart 0’s scheduler has just returned from acquire(&p->lock) at line 446, during a
scan. What is hart 0’s state?
Which of these are true at the instant scheduler executes the call
swtch(&c->context, &p->context) on line 453?
Pid 5 is preempted by a timer tick on hart 1 and later resumed by hart 0. Put these events in the order they must happen.
- Hart 1’s scheduler releases pid 5’s
p->lock(line 463) - Hart 0’s
swtchloads pid 5’s context, andyieldreleases the lock usertrapseeswhich_dev == 2and callsyieldschedchecks its four rules and copiesintenainto a local- Hart 0’s scheduler acquires pid 5’s
p->lock, seesRUNNABLEand setsRUNNING swtchsaves pid 5’s registers and loads hart 1’s scheduler contextyieldacquires pid 5’sp->lockand setsRUNNABLE
Hart 1’s scheduler switched to the process in proc[4]. A tick later that process
yields, and the scheduler returns from the swtch on line 453. Which slot does it
examine next?
Each release(&p->lock) below releases a lock that was acquired somewhere else. Match
each release with the code that acquired that lock.
Once a process is running, its p->lock is free: any scheduler can lock the slot and
look at it. Click the line that makes those schedulers leave the process alone.
Your pick: none yet (click a line in the code)
Why does scheduler turn interrupts off (line 442) before scanning, instead of
leaving them on for the scan?
Right after its swtch returns, scheduler executes mycpu()->intena = 0 (line
456). What would go wrong without that line?
Process P stops on hart 1 through sched and is later resumed on hart 2. Which of
these are carried from hart 1 to hart 2 with P, so that P finds its own values after
the switch?
True or false: when an idle hart executes wfi on line 467, its timer interrupt can
still end the wait, even though sstatus.SIE is 0 at that point.
Why?
Suppose release(&p->lock) on line 520 were moved to become the first statement inside
the if (first) block, so that init still releases it but no later fork child does.
A new child, pid 9, reaches user mode on hart 1. Which of these would then happen?
cat called sleep from inside read, with interrupts on, on hart 1. It is woken
and resumed by hart 2’s scheduler. Suppose line 496 of sched,
mycpu()->intena = intena;, were deleted (line 494 kept). What changes for cat?
After fork, parent and child run the same code from the same instruction. How does
fork() come to return 0 in the child but the child’s pid in the parent?
True or false: a fork child’s kernel stack starts out as a copy of its parent’s kernel
stack, so that the child can return through sys_fork, syscall and usertrap just
like the parent.
Why?
A child process has called exit and is now a ZOMBIE; its parent has not called
wait yet. Which of these does the zombie still hold?
ls (on hart 2) calls exit(0) while its parent, the shell, sleeps in kwait. Put
these events in the order they must happen.
- Hart 2’s scheduler releases
ls’sp->lock kexitcallswakeup(p->parent)kexitclosesls’s open files and drops its current directorykexitreleaseswait_lockand callssched- The shell’s
kwaitacquiresls’sp->lock, seesZOMBIEand callsfreeproc kexitacquires its ownp->lockand setsstate = ZOMBIEkexitacquireswait_lockand callsreparent
Process A is running in user mode on hart 1. On hart 0, another process calls
kill(A's pid), and kkill finds A’s slot. What does kkill do to A?
True or false: after a successful exec, the process still has the same pid and the
same open file descriptors it had before.
Why?
After loading the program’s segments, kexec calls uvmalloc on line 91. How many
pages does that call map in the new page table?
In kfork, click the line at which the child first becomes eligible to be run by a
scheduler on any hart.
Your pick: none yet (click a line in the code)
Near its end, kfork releases the child’s lock (line 294), takes wait_lock to set
np->parent, releases it, and only then re-acquires the child’s lock. Why not keep the
child’s lock and take wait_lock inside it?
Line 279 copies the parent’s whole trapframe into the child’s. What is in the child’s
trapframe->kernel_sp right after that line, and why is it not a problem?
In kexit, wakeup(p->parent) (line 353) comes before acquire(&p->lock) (line
355). Why can’t kexit take its own lock first?
kwait holds wait_lock from its scan until after sleep_prepare, and releases it
only just before sleep. Why does it register before releasing wait_lock?
A process whose user memory is exactly 5 pages, all present, at virtual addresses
0x0–0x4fff, calls fork, and it succeeds. How many pages does the whole kfork
take from kalloc (directly or through the functions it calls)?
allocproc claims proc[4]. What value does line 147 store in p->context.sp?
Use KSTACK(p) = TRAMPOLINE - ((p) + 1) * 2 * PGSIZE, TRAMPOLINE = MAXVA - PGSIZE,
MAXVA = 1 << 38 and PGSIZE = 4096. Answer in hex.
Process P is asleep in piperead on an empty pipe whose write end is still open
(it slept at line 126). Another process calls kill(P). The pipe stays empty. What
happens to P?
Some sleep loops check killed each time round, so that a killed process does not
wait forever. Which of these do?
Click the line at which the process’s user address space becomes the new program’s,
the commit point of kexec.
Your pick: none yet (click a line in the code)
Match each process state with the code that sets it in the situation described.
ls called exit(0) (a system call). Its kexit has just executed line 358,
p->state = ZOMBIE. What is the state of the hart running it?
True or false: as soon as a process’s state is ZOMBIE, no hart is executing on its
kernel stack any more.
Why?
Process A is spinning in user mode on hart 1, making no system calls. On hart 0, another
process calls kill(A's pid), and it returns 0. Which of these are true?
kexec's argument loop writes ustack[argc] with no check that argc < MAXARG
(ustack has MAXARG = 32 entries, on the kernel stack). What stops a user program
from overflowing ustack by calling exec with 40 arguments?
Process P in piperead has passed the killed check on line 120 and called
sleep_prepare on line 124, but has not yet called sleep(). On another hart, kill(P)
runs to completion. Then the pipe’s writer does nothing for an hour, keeping its end open.
What happens to P during that hour?
True or false: because first in forkret is a plain static int, read and cleared
with no lock and no atomic instruction, two harts could both run the if (first) block
and both call fsinit.
Why?
Process P (a child of the shell) has a child Z that has already exited and is a
ZOMBIE; P never called wait. Now P itself calls exit. Who eventually calls
freeproc on Z?
acquire calls push_off before it tries to take the lock. Why must
interrupts be off before the lock is taken, rather than just after?
Which of these locks are acquired inside an interrupt handler (from devintr or
code it calls) in this kernel?
A process is in kwait holding wait_lock and a zombie child’s p->lock. It calls
freeproc, which calls kfree, which is now inside acquire(&kmem.lock) with the
lock held. What is mycpu()->noff at that moment?
Put the steps of acquire in the order they happen.
holding(lk)check, panic if this hart already holds the lockamoswap.w.aqin a loop until the old value is 0push_off(): interrupts off, noff + 1lk->cpu = mycpu()
In pop_off, click the line that can turn interrupts back on.
Your pick: none yet (click a line in the code)
Hart 1 is running cat’s consoleread (a read system call). It has just executed
line 95, acquire(&cons.lock), and the lock is now held. What is the state of hart 1?
gdb shows sstatus = 0x200000122 on a hart running kernel code. Decode it.
Value: 0x200000122
Match each panic message with the mistake that triggers it.
True or false: holding tells you whether the current process holds the lock.
Why?
Hart 0 holds a spinlock. Hart 1, inside acquire, executes the swap below with
a5 = 1 and s1 pointing at the lock’s locked word. What happens?
80000c02: mv a5,a4
80000c04: amoswap.w.aq a5,a5,(s1)
80000c08: sext.w a5,a5
80000c0a: bnez a5,80000c02
Hart 0 holds kmem.lock. A process on hart 1 calls acquire(&kmem.lock). Until hart 0
releases it, what is hart 1 doing?
True or false: push_off saves the current value of SIE into intena every time it is
called.
Why?
A spinlock is free (locked is 0). Three harts execute their amoswap.w.aq on its
locked word at the same instant. How many of the three swaps return 0?
Why does xv6 hold spinlocks only for short stretches of code, never across a disk read or a wait for input?
Why does xv6 count interrupt-disabling with push_off/pop_off instead of having
acquire call intr_off and release call intr_on?
release frees the lock with __atomic_store_n(&lk->locked, 0, __ATOMIC_RELEASE),
which compiles to fence rw,w followed by sw zero,0(s1). What does the fence
prevent?
release clears lk->cpu on line 51 before it stores 0 into lk->locked on line
73. What could go wrong if the two were the other way round?
Your pick: none yet (click a line in the code)
A system call on hart 0 is in sys_uptime, holding tickslock, when hart 0’s timer
deadline passes. Put the events in order.
- the pending timer interrupt is taken, and clockintr acquires tickslock, which hart 0 no longer holds
- pop_off takes noff to 0, sees intena 1, and sets SIE
- release stores 0 into tickslock.locked
- the amoswap takes tickslock
- the timer deadline passes: the interrupt becomes pending but is not taken
- acquire’s push_off clears SIE on hart 0 and records intena = 1
The comment above holding says “Interrupts must be off.” What could go wrong if
holding(lk) ran with interrupts on?
sh went to sleep in consoleread (a system call) on hart 0, so the p->lock it
took in sleep was acquired with SIE on and hart 0’s intena is 1. Suppose line 456
(mycpu()->intena = 0) were deleted. What goes wrong when hart 0’s scheduler
continues after its swtch?
Which of these can change sstatus.SIE from 0 to 1 on a hart in this kernel?
A process called exit(0). kexit has just executed line 360,
release(&wait_lock), while still holding its own p->lock (taken on line 355). What
is the state of its hart?
iput drops the last reference to an unlinked file. Holding itable.lock, it calls
acquiresleep(&ip->lock) on line 362. Inside acquiresleep, line 32 calls
myproc. Right after myproc’s push_off, what is mycpu()->noff?
wc finds the pipe empty, and piperead calls sleep_prepare(&pi->nread) on line
124. Right after that call returns, what has changed?
True or false: in this kernel, the caller passes its condition lock to sleep, and
sleep releases it before the process goes to sleep.
Why?
wakeup finds a process that is SLEEPING with p->chan equal to the channel. What
state does it give that process?
piperead sleeps on the channel &pi->nread. When pipewrite calls
wakeup(&pi->nread), what does wakeup do with the integer stored at that address?
Which of these are true of a sleep-lock but not of a spinlock, in this kernel?
Match each place that sleeps with the channel it registers on.
Line 119 of piperead is a while, not an if. Why?
wc reads from an empty pipe whose writer is still open and goes to sleep. Put the
steps in order.
- release(&pi->lock)
- sched() switches to this hart’s scheduler
- see nread == nwrite with writeopen set, and killed() returning 0
- acquire(&pi->lock)
- the scheduler releases wc’s p->lock
- sleep_prepare(&pi->nread): p->chan is set under p->lock
- sleep(): under p->lock, p->chan is still set, so p->state = SLEEPING
A process has called sleep_prepare but not yet sleep; it is still RUNNING.
Another hart calls wakeup on its channel. Click the line of wakeup that makes the
process’s coming sleep() return without sleeping.
Your pick: none yet (click a line in the code)
wc is between lines 125 and 126 of pipe.c: registered on &pi->nread, pi->lock
released, sleep() not yet called. On another hart, cat takes pi->lock, writes 512
bytes and calls wakeup(&pi->nread). What happens to wc?
When pipewrite finds the pipe full, it calls wakeup(&pi->nread) on line 89 before
it registers on &pi->nwrite and sleeps. What could happen without line 89?
A reader is SLEEPING in piperead, registered on &pi->nread. Which of these
events can make it RUNNABLE?
One call to wakeup runs on a machine where exactly one process is registered on the
channel. How many times does that call execute acquire(&p->lock)?
ls holds an inode’s sleep-lock and a buffer’s sleep-lock, and goes to sleep in
virtio_disk_rw waiting for the disk (line 290). When sched checks
mycpu()->noff on line 487, what value does it find?
A process is writing to the console. In uartwrite it holds tx_lock (a sleep-lock)
and has just returned from sleep_prepare(&tx_chan) on line 86; it is about to read
LSR on line 87. What is the state of its hart?
holding checks a spinlock’s owner by hart (lk->cpu), but holdingsleep checks
a sleep-lock’s owner by process ID (lk->pid). Why the difference?
ls holds a buffer’s sleep-lock. grep calls acquiresleep on the same lock, and
ls calls releasesleep only after grep is fully asleep. No third process wants the
lock. Put the events in order.
- ls: wakeup(lk) makes grep RUNNABLE
- grep: re-acquires lk->lk, sees locked == 0, sets locked = 1 and pid
- grep: sleep_prepare(lk) sets grep’s p->chan = lk
- grep: acquire(&lk->lk), sees locked == 1
- grep: sleep() marks grep SLEEPING and switches away
- ls: under lk->lk, locked = 0 and pid = 0
- grep: release(&lk->lk)
pipewrite is about to go to sleep because the pipe is full (line 88 was true). At
that moment, what is pi->nwrite - pi->nread?
Hart 0 is idle in its scheduler. At the top of the loop, line 441 turns interrupts on
and the pending timer interrupt is taken at once. clockintr holds tickslock and is
inside wakeup(&ticks), holding one process’s p->lock. What is the state of hart 0?
True or false: once kkill has set wc’s killed flag, wc cannot go to sleep in
piperead's wait loop until it has noticed the kill.
Why?
uartwrite sleeps on &tx_chan without holding any spinlock: its condition is the
LSR_TX_IDLE bit of a device register, and the waker is uartintr. Why can’t the
“transmitter is idle” wakeup be lost?
iput calls acquiresleep(&ip->lock) on line 362 while holding the spinlock
itable.lock. Why doesn’t this break the rule “never sleep while holding a spinlock”?
True or false: in kexit, swapping lines 353 and 355, so that the process takes its
own p->lock before calling wakeup(p->parent), would be harmless.
Why?
A process P has p->chan != 0. Which of these could be true at that moment?
Hart 1 holds spinlock A and calls acquire(&B). At the same moment hart 2 holds B and
calls acquire(&A). Nothing else holds A or B. What happens in this kernel?
Two processes deadlock on two sleep-locks (each holds one inode lock and waits for the other’s). Compared with a deadlock on two spinlocks, what does the rest of the machine see?
Which of these mistakes does xv6 turn into a panic at run time?
kexit and kwait each need wait_lock and some p->lock at the same time. Read
the comment. Which nesting does this kernel use?
A leaf lock is one that no code acquires anything else while holding (so it can never be part of a cycle). Which of these are leaves in this kernel?
True or false: kexit acquires wait_lock (line 347) and then its own p->lock
(line 355), but releases wait_lock first (line 360). Releasing in that order breaks
the lock-ordering discipline and could deadlock.
Why?
bget finds the cached block on line 66. Why does it release bcache.lock (line 68)
before waiting for the buffer’s sleep-lock (line 69)?
kexit calls wakeup(p->parent) on line 353 while holding wait_lock. How many
different p->lock spinlocks does that single wakeup call acquire (one after
another)?
allocproc returned np with np->lock held. Click the line that keeps kfork
from holding a p->lock while it waits for wait_lock.
Your pick: none yet (click a line in the code)
Between line 294 and line 300, kfork holds no lock on the new child at all. Why is
that safe?
Put the steps of kexit (from line 347 on) in the order they happen.
release(&wait_lock), thensched()acquire(&wait_lock)reparent(p): hand any children toinitp->state = ZOMBIE(withxstate)wakeup(p->parent)acquire(&p->lock)
Suppose someone moved acquire(&p->lock) (line 355) up to just before
wakeup(p->parent) (line 353), so the exiting process holds its own lock during the
wakeup. What happens?
sys_unlink holds the parent directory dp locked and may then lock the entry ip
inside it. Click the line that stops it from ever locking a parent while holding its
child.
Your pick: none yet (click a line in the code)
In create, when the name already exists, line 281 unlocks the parent dp before
line 282 locks the existing ip. Which input would hang the process if the two lines
were swapped (lock ip while still holding dp)?
cat called exit(0) (a system call). kexit has just executed line 360,
release(&wait_lock), and is about to call sched on line 363. What is the state of
its hart?
Each place below avoids a deadlock in a different way. Match each one with its technique.
namex locks a directory, looks up the next name, and then (line 720) unlocks it
before the next iteration locks the entry it found. Why not keep the directory locked
while locking the next one?
True or false: if a process calls ilock on an inode whose lock it already holds,
xv6 panics, just as acquire does for a spinlock.
Why?
In this kernel, which of these locks are ever acquired while the acquiring hart holds a
p->lock?
Suppose bget's lines 68 and 69 were swapped, so it calls acquiresleep(&b->lock)
while still holding bcache.lock, and the buffer is held by another process. Inside
acquiresleep, the process reaches sleep and then sched. What is
mycpu()->noff when sched checks it on line 487?
rm drops the last reference to an unlinked inode, so iput calls
acquiresleep(&ip->lock) on line 362 while holding itable.lock. Inside
acquiresleep, at line 32 the call to myproc runs its own push_off. What is
noff at that moment (inside myproc, before its pop_off)?
Imagine filewrite called ilock before begin_op. The log already holds
15 blocks from this group (LOGBLOCKS is 30, MAXOPBLOCKS 10), and rm x on another
hart is inside its own transaction (outstanding 1) and about to lock x. The writer
locks x, then calls begin_op. What happens?
A modified kernel deletes kfork's lines 294 and 300, so it takes wait_lock while
holding the half-built child’s np->lock. Under a fork/exit stress test it froze. The
other hart in the cycle never names that child’s lock in its code. Which code took the
child’s lock on the other side?
True or false: if you draw a graph of lock classes (an edge A → B whenever some path holds a lock of class A while acquiring one of class B), this kernel’s graph has no cycle.
Why?
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.
- Hart 1:
fence r,rw(0x80000e76) - Hart 1:
sfence.vmaat the start ofkvminithart - Hart 0:
swsetsstartedto 1 - Hart 1:
csrw satpturns paging on - Hart 1:
lwreadsstarted == 1 - Hart 0:
fence rw,w(0x80000f0a) - Hart 0:
kvminit's ordinary stores build the kernel page table
A 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.
In kvminithart, line 80 turns paging on with csrw satp. Nothing jumps to a new
address afterwards: the program counter simply moves on to the next instruction. Why
does that next instruction (the second sfence.vma) still get fetched correctly?
A page-table page in Sv39 is one 4096-byte page. How many page-table entries (PTEs) does it hold?
Right after exec, the level-0 PTE for sh’s data page (virtual 0x2000) read
0x21fce817 in our run. Decode it using the macros in kernel/riscv.h.
Value: 0x21fce817
TRAMPOLINE is 0x3ffffff000. What is its index in the root (level-2)
page-table page, PX(2, TRAMPOLINE)?
Sv39 can translate 39-bit virtual addresses, 512 GiB. Why does xv6 set MAXVA to
1 << 38 (256 GiB) instead?
True or false: every xv6 user page table maps the whole kernel, protected from the
program only by leaving PTE_U clear.
Why?
Click the line in walk without which a freshly allocated page-table page would be
full of entries that look valid, so that walk would follow garbage pointers or
mappages would panic with “remap”.
Your pick: none yet (click a line in the code)
sh’s address space has these pages. Which of them have PTE_U set, so that sh
itself, in user mode, may touch them?
Line 47 of kvmmake maps the trampoline page at TRAMPOLINE, although the
direct map (line 39) already covers its physical page 0x80006000. Why is this second
mapping necessary?
A buggy program passes its trapframe’s address, 0x3fffffe000, as a buffer to a system
call, and the kernel calls copyout(p->pagetable, p->sz, 0x3fffffe000, src, 8). The
page is mapped in the user table (R W, no U). What happens?
sh runs lb a5, 0(a0) with a0 = 0x2020 in user mode, and the translation is not in
the TLB. Put the hardware’s steps in order.
- Read the level-2 PTE at index bits 38…30 (0); it has V and no R/W/X, so follow it
- Take the root page-table page’s physical address from satp’s PPN field
- Add the offset 0x020 to the leaf’s page address and load the byte
- Read the level-1 PTE at index bits 29…21 (0) and follow it
- Look up virtual page 0x2 in this hart’s TLB, and miss
- Read the level-0 PTE at index bits 20…12 (2) and check V, U and R
Match each macro from kernel/riscv.h with what it computes.
Hart 0 runs sh and hart 2 runs ls. At the same instant both execute a load from
virtual address 0x0. Why don’t they interfere?
Line 36 of kvmmake maps the PLIC: 64 MiB (0x4000000) starting at 0x0c000000.
The level-2 and level-1 tables for the first GiB already exist. How many level-0
page-table pages does walk allocate for this one call?
gdb shows satp = 0x8000000000087f41 on a hart. In this build the kernel’s root
page is at 0x87fff000. Decode it.
Value: 0x8000000000087f41
Hart 1 has left its spin loop in main, printed hart 1 starting, and has just
executed the csrw satp on line 80 of kvminithart (called from main line 39). What
is hart 1’s state?
Hart 0 executed sfence.vma around its csrw satp at boot. Why must harts 1 and 2 run
kvminithart, with its own two sfence.vma, for themselves?
True or false: because three harts translate through kernel_pagetable at the same
time, xv6 must protect it with a lock.
Why?
Suppose a buggy kernel function, handling sh’s read(0, buf, 100), did
*(char *)0x2020 = 'l' with the user’s address (buf is at 0x2020). What would
happen?
With KSTACK(p) = TRAMPOLINE - (p+1) × 2 × PGSIZE, what is mapped at virtual
address 0x3fffffc000 in the kernel page table?
Why does proc_freepagetable unmap TRAMPOLINE and TRAPFRAME (with do_free = 0)
before calling uvmfree?
Which of these mappings in the kernel page table have PTE_X (executable) set?
sh’s layout: code 0x0–0x1fff (R X U), data 0x2000 (R W U), guard 0x3000 (R W),
stack 0x4000 (R W U), sz = 0x5000, trapframe at 0x3fffffe000 (R W). Which of
these, executed by sh in user mode, raise a page fault?
What does the satp register give the hardware?
sh’s page table uses 5 page-table pages (root; a level-1 and a level-0 page for the
bottom; a level-1 and a level-0 page for the top). If its heap grew eagerly from
0x5000 by 4 MiB (to sz = 0x405000), how many page-table pages would the table use
in total?
In this build the kernel image ends at end = 0x80020bb0, and PHYSTOP is
0x88000000. How many pages does freerange put on the free list at boot?
Right after kinit, which physical page does the first kalloc return (it becomes
the root of the kernel page table)?
kalloc fills each page it hands out with 0x05 bytes instead of zeros, even though
many callers then zero it themselves. Why?
A program calls sbrklazy(4096). Click the line in sys_sbrk that is the entire
allocation work the kernel does for it.
Your pick: none yet (click a line in the code)
kalloc returns pages full of 0x05 junk. Which of these functions zero the page
they get from kalloc before using it?
Click the line in vmfault that makes a user-mode store to the stack’s
guard page fatal instead of quietly supplying a new page.
Your pick: none yet (click a line in the code)
After sbrklazy(1 << 30), usertests stores to 0x13000, a page that is not mapped
yet. Put the events in order.
- usertrap sees scause 15 and calls vmfault with p->sz and stval
- The store finds a level-0 PTE with V clear and raises a store page fault: scause 15, stval 0x13000
- userret installs the user page table between two sfence.vma and executes sret
- vmfault checks 0x13000 < sz and that the page is not mapped, then kallocs, zeroes and maps it R W U
- uservec saves the user registers in the trapframe and switches satp to the kernel page table
- The same store executes again, since sepc was not advanced, and succeeds
A process takes a page fault on a lazily allocated heap page. usertrap calls
vmfault, which calls kalloc, which has just acquired kmem.lock. What is the
state of this hart?
True or false: after sbrk(-8192) frees two pages, xv6 must flush them from the TLB,
but it forgets to, so the program could still reach the freed pages through stale TLB
entries.
Why?
In our run, the usertests child running lazy_alloc had p->sz = 0x12000 when it
called sbrklazy(1 << 30). It then stores to 0x13000, 0x53000, … , every 64 pages,
4096 stores in all. Besides the 4096 data pages, how many page-table pages does
walk allocate during these faults?
A process with p->sz = 0x5000 calls sbrk(65536) and it succeeds. What does
sbrk return?
kexec maps the user stack’s guard page and then clears its PTE_U, instead of
simply leaving the page unmapped. In this kernel, why does that difference matter?
A process has p->sz = 0x5000 (page-aligned, nothing mapped above it). It calls
sbrk(1) and then sbrk(1) again, both eager. How many physical pages does
uvmalloc allocate in total across the two calls?
A process has p->sz = 0x5000 (page-aligned, nothing mapped above it). It calls
sbrklazy(1), then, as its very next memory access above 0x5000, stores a byte at
0x5800. What happens?
Which of these functions can call vmfault to allocate a lazily promised page?
A program grows its memory with sbrklazy, writes machine code into the new region
(which faults the page in), and then jumps to it. What happens at the jump?
True or false: kalloc returns a page filled with zeros.
Why?
Match each function with what it writes into a physical page it has just freed or obtained.
Imagine kmem.lock were removed. Hart 1 in kalloc reads the head (A); then hart 2
runs a complete kfree(P); then hart 1 stores A->next (B) as the new head. What is
the result?
Which of these calls make kfree panic? (In this build end = 0x80020bb0 and
PHYSTOP = 0x88000000.)
A program calls sbrklazy(-8192) to give back two pages. What does sys_sbrk do?
The shell’s child has p->sz = 0x5000 and its whole memory is mapped through a single
level-0 page-table page. malloc calls sbrk(65536) (eager). How many times is
kalloc called during that system call?
Physical memory is nearly exhausted. How does running out show up to a program that grew
with eager sbrk, compared with one that used sbrklazy?
Right after exec, the level-0 PTE for sh’s page at 0x3000 (just below its one
stack page) read 0x21fce407 in our run. Decode it.
Value: 0x21fce407
cat calls open("README", 0), and in the kernel the saved a0 is 0x3fe0, the
address of the string in cat’s memory. Why can’t sys_open simply cast that number
to char * and read the name through it?
A user program passes garbage in every argument register. Which of these functions reject a bad user-supplied value themselves, by returning -1?
cat’s page table maps the trapframe page and the stack guard page, both without
PTE_U. Click the line in walkaddr that stops the kernel from copying into or out of
such pages on a user’s behalf.
Your pick: none yet (click a line in the code)
sys_open calls argstr(0, path, MAXPATH) with char path[MAXPATH] and MAXPATH =
128. What is the longest path, in characters not counting the NUL, that this call
accepts?
cat has p->sz = 0x4000, laid out as in Tour 6: System-call arguments and user pointers: code and data at 0x0000-0x1fff,
the guard page at 0x2000 (mapped, no PTE_U), and the stack page at 0x3000, which
holds "README\0" at 0x3fe0 and "cat\0" at 0x3ff0. For which of these addresses,
passed as the open path, does copyinstr return -1?
True or false: a user program can make read(fd, (char *)main, 100) overwrite its own
code with file data, because the code page has PTE_U and lies below p->sz.
Why?
sys_open fetches its path with argstr(0, path, MAXPATH); the user passed 0x3fe0.
Put these steps in the order they happen.
- fetchstr returns
strlen(path), which is 6 - copyinstr rounds the address down to its page,
va0 = 0x3000 - bytes are copied from the physical page until the NUL
walkaddrchecks the leaf PTE forPTE_VandPTE_Uand returns the physical pageargaddrcopies the saveda0(0x3fe0) out of the trapframefetchstrcallscopyinstrwithp->pagetableandp->sz
argraw returns p->trapframe->a0 and so on. Why does it read the trapframe instead of
the hart’s real a0-a5 registers?
cat is in open, and its hart is inside the byte-copying loop of copyinstr (no
lazy page is involved). What is the state of that hart?
A write system call on a pipe reaches pipewrite, which holds pi->lock and calls
copyin on a never-touched lazy sbrk page. copyin calls vmfault, which calls
kalloc. What is the hart’s state at the moment kalloc holds kmem.lock?
copyin is called with srcva = 0x1ff0 and len = 48. How many bytes does the
first iteration of its while loop copy?
sys_exec collects the user’s argv into char *argv[MAXARG], with MAXARG = 32.
What is the largest number of argument strings (not counting the terminating 0 pointer)
that exec can accept?
Match each function with what it does.
lazy_copyinstr grows the heap lazily by two pages with sbrklazy(2 * PGSIZE),
writes '/' to the last byte of the first page (p[4095]), and never touches the second
page. Then it calls open(&p[4095], O_RDONLY). The string’s NUL would be at p[4096], on
the untouched page. What happens?
fetchaddr tests addr >= p->sz || addr + sizeof(uint64) > p->sz. With
p->sz = 0x4000, which addr would pass the second test, even though it is outside the
process, so that only the first test rejects it?
A program passes copyout a buffer in heap memory it grew with lazy sbrk and has never
touched. Click the line that makes the copy succeed anyway.
Your pick: none yet (click a line in the code)
A program calls read(fd, buf, 2048) on a regular file at offset 0 whose size is at
least 2048. buf is exactly 1024 bytes below p->sz (which is page-aligned), on a page the
program has already used. Which statements are true?
True or false: walkaddr's test if (va >= MAXVA) return 0; is redundant, because
vmfault would refuse such an address anyway (it is above p->sz).
Why?
When copyin translates a user address, which page table does it walk?
walkaddr returns a physical address pa0, and copyin then calls
memmove(dst, (void *)(pa0 + (srcva - va0)), n) while the hart uses the kernel page
table. Why does using a physical address as a pointer work?
A buggy program passes 0x2f00, inside its stack guard page, as the buffer for fstat.
In copyout, walkaddr returns 0, so vmfault gets its chance. The address is below
p->sz. Why doesn’t vmfault give the program a fresh page there?
In one run, gdb shows the leaf PTE for cat’s guard page at 0x2000 as 0x21fc7407.
Decode it.
Value: 0x21fc7407
sys_open copies its path into char path[MAXPATH] on the kernel stack, but
sys_exec copies each argument string into a whole page from kalloc. Why the
difference?
A program whose file descriptor 1 is the console calls
write(1, (char *)0x80000000, 8). What does write return?
cat needs file-system block 48, so virtio_disk_rw builds a request for it. Which
sector number does it put in the request header?
The driver has NUM = 8 descriptors, and every request uses exactly three. At most how
many disk requests can be in flight (handed to the device and not yet freed) at once?
Why is each buffer’s lock (b->lock) a sleep-lock rather than a spinlock?
True or false: virtio_disk_rw holds disk.vdisk_lock while it sleeps waiting for the
disk to finish.
Why?
Put the steps of a disk read in virtio_disk_rw in order.
- fill in the header, data and status descriptors
- get three free descriptors with alloc3_desc
- compute the sector and acquire disk.vdisk_lock
- record b in disk.info[idx[0]] and set b->disk = 1
- write idx[0] into the avail ring, fence, then increment avail->idx
- store to the QUEUE_NOTIFY register
- sleep until b->disk == 0, then free the chain
Most of the driver talks to the device through ordinary RAM that both share. Click the one line in this excerpt that writes to a device register (memory-mapped I/O): the doorbell.
Your pick: none yet (click a line in the code)
While a request is in flight, gdb shows one of its three descriptors with flags = 0x3.
Decode it (VRING_DESC_F_NEXT = 1, VRING_DESC_F_WRITE = 2).
Value: 0x3
Hart 1 is idle in its scheduler loop when the disk interrupt arrives. It traps through
kernelvec and kerneltrap into devintr, which calls virtio_disk_intr. What
is hart 1’s state just after acquire(&disk.vdisk_lock) returns?
cat’s read system call has reached virtio_disk_rw (through fileread,
readi and bread). It holds README’s inode lock and the buffer’s sleep-lock, and is
at line 284, about to ring the doorbell. What is the state of its hart?
Right after boot, binit has built the LRU list and every buffer has refcnt == 0. The
first bget miss scans from bcache.head.prev. Which index i of bcache.buf[i] does
it recycle?
Which fields of a struct buf are protected by bcache.lock (the spinlock), rather than
by the buffer’s own sleep-lock or by the disk lock?
Match each lock with what it protects.
On a cache hit, bget increments b->refcnt before releasing bcache.lock and
calling acquiresleep. What could go wrong if it released bcache.lock first and
incremented refcnt afterwards?
When bget recycles a buffer, it overwrites dev, blockno and valid without ever
writing the old data back to disk. Why can’t that lose a modification?
Child A (hart 1) and child B (hart 2) both need block 46 at once. A’s bget misses,
relabels a buffer as 46 with valid = 0, and releases bcache.lock; then B’s bget runs
while A is reading the block from disk. Which statements are true?
When alloc3_desc gets only one or two descriptors, it gives them back before
virtio_disk_rw sleeps. Why not keep them and wait only for the missing ones?
cat issued a request on hart 0 and is asleep. The completion interrupt is taken on
hart 1. How does virtio_disk_intr know which buffer, and so which sleeper, the
completion is for?
virtio_disk_rw waits with while (b->disk == 1) { sleep_prepare(b); release; sleep(); acquire; } instead of sleeping once. When can sleep() return while the disk is still
working on the request?
During a disk read, who copies the 1024 bytes of the block into b->data?
Process B is asleep in acquiresleep, waiting for a buffer that process A holds. Click
the line of A’s brelse that lets B continue.
Your pick: none yet (click a line in the code)
True or false: panic("bget: no buffers") can only happen while a log transaction has
pinned buffers; with no transaction running, every buffer is eventually free.
Why?
Which of these are done by the hart that takes the disk completion interrupt (in
devintr and virtio_disk_intr), rather than by the process that issued the request?
Why is there an io_fence between writing avail->ring[...] and incrementing
avail->idx?
balloc has just set a bit in the bitmap buffer and calls log_write on it.
What has happened on the disk by the time log_write returns?
In commit, click the line whose disk write is the moment the transaction becomes
durable: if the power fails before this write completes, the transaction never
happened; after it, the next boot will finish it.
Your pick: none yet (click a line in the code)
The last end_op of a group commits it. Put these events in the order they
happen.
end_opclearscommitting, incrementsncommit, and wakes sleepers on&logend_opsetslog.committing = 1underlog.lock, then releases the lockwrite_headagain, withn = 0: erase the transactionwrite_log: copy each logged block from the cache into the log areawrite_head: write the header withnand the block list (the commit point)install_trans(0): copy each block to its home location and unpin it
The log is empty (log.lh.n = 0) and no commit is running. Processes keep calling
begin_op and none of them has reached end_op yet. How many of them are
admitted before the next one has to sleep?
The machine crashed with a committed transaction still in the log. Put the steps of the next boot’s file-system start-up in order.
write_headwritesn = 0, clearing the logireclaimscans the inodes for orphans (allocated,nlink == 0) and frees themfsinitreads the superblock (block 1) and checks its magic numberread_headcopiesnand the block list from the header block intolog.lhinstall_trans(1)copies each log block to its home block, printingrecovering tail …
Inside one transaction, writei changes block 1006 and calls log_write; a moment
later the same transaction changes block 1006 again and calls log_write a second time.
What does the second call do?
Three processes are inside transactions (log.outstanding = 3). They call
end_op one after another. Which call runs commit?
echo hi > f is creating f (blocks 34, 47 and 33 are logged). Match each moment of
power failure with what the disk shows after the next boot.
end_op releases log.lock before calling commit (line 172, then 177). What
would happen if it called commit() while still holding log.lock?
One transaction is running (log.outstanding = 1, log.committing = 0). A second
process calls begin_op. What is the largest value of log.lh.n for which it is
admitted without sleeping?
A process writes 2000 bytes at offset 0 to an empty regular file (no data blocks yet).
The log was empty and no other transaction runs. The filewrite chunk is one
transaction. How many distinct blocks are in log.lh.block[] when end_op
commits?
Which of these situations make begin_op put the caller to sleep?
True or false: in this kernel, once a write() to a regular file has returned to
user space, its data is on the disk.
Why?
install_trans calls bunpin only when recovering == 0. Why does it skip the
unpin during crash recovery?
At the end of a commit, end_op calls wakeup(&log) (line 181). Which of these
sleeping processes can it wake?
After a crash, gdb shows the first 16 bytes of block 2 (the log header,
struct logheader, little-endian 32-bit ints) as
03 00 00 00 22 00 00 00 2f 00 00 00 21 00 00 00. sb.logstart is 2. Decode it.
Value: 03000000 22000000 2f000000 21000000
While a commit is running, log.lock is not held. Click the line in end_op that
keeps new transactions from starting during the commit.
Your pick: none yet (click a line in the code)
The machine is booting after a crash. pid 1 is in install_trans during recovery,
executing line 78 (the memmove from the log block’s buffer to the home block’s
buffer). Both buffers are held. What is the state of the hart running it?
commit reads log.lh.n and log.lh.block[] without holding log.lock, although
log_write changes them under that lock. Why is this not a race?
Suppose commit omitted line 210 (it still sets log.lh.n = 0 in memory, but no
longer writes the empty header). The disk header would keep saying n = 3: 34, 47, 33
after this commit. What could go wrong?
A process calls sync() while log.committing = 0, log.outstanding = 2 and
log.ncommit = 5. When does sys_sync return?
A commit finds log.lh.n = 4. How many disk writes does commit issue in
total? (Count writes only, not reads.)
A struct inode in memory has both ref and nlink. What does each one count?
How many on-disk inodes (dinode) fit in one disk block in this file system
(the value of IPB)?
What does the content of a directory (its data blocks) consist of in this file system?
True or false: when rm removes the only name of a file that another process still
has open, the file’s data blocks are freed immediately.
Why?
iget returns an inode table entry with valid = 0 and does not read the inode
from disk; ilock does that later. Why not read it in iget?
Match each function with its job.
In namex, click the line that gives up the current directory (its lock and its
reference) after finding the next element, before the loop locks that element.
Your pick: none yet (click a line in the code)
What is the largest number of data blocks one file can have in this file system
(MAXFILE)?
In this image the superblock says inodestart = 33. Which disk block holds inode 24?
Why does namex unlock the current directory before it locks the next path
element, instead of locking the child first and then letting go of the parent?
open("newfile", O_CREATE|O_WRONLY) calls create for a name that does not exist
yet. Put the steps of create in order.
iunlockput(dp)and return the new inode, still lockediallocclaims a free dinode and gives it type T_FILEdirlinkwrites the entry (name, inum) into the parentdirlookupfinds no entry with that namenameiparentreturns the parent directory, referenced but unlocked, and the last nameilock(dp), then check thatdp->nlinkis not 0ilock(ip), setnlink = 1,iupdate
Which of these code paths hold two inode sleep-locks at the same time?
Starting from nothing, a user runs mkdir /d, mkdir /d/x, mkdir /d/y and
echo hi > /d/f. What is the nlink of directory d afterwards?
sys_unlink refuses the names . and .. (line 221) before it calls
dirlookup and ilock(ip). Apart from keeping the tree intact, what would go
wrong with the locks if unlink("d/.") were allowed through?
A program creates abcdefghijklmnop (16 characters) and then calls
open("abcdefghijklmnXY", O_RDONLY) in the same directory. What happens?
iput is dropping the last reference to an inode whose nlink is 0. Put its steps
in order.
- acquire
itable.lock; computelastand copydevandinuminto locals itrunc: free the data blocks, set size 0,iupdateacquiresleep(&ip->lock)while still holdingitable.lockifree(dev, inum): write type 0 on disk- release
itable.lock - re-acquire
itable.lock,ref--(to 0), release it valid = 0, then release the sleep-lock
fileclose wraps the iput of an inode in begin_op and end_op, even though
closing a file usually writes nothing. Why?
gdb shows the first 12 bytes of inode 24’s dinode (little-endian: type, major,
minor, nlink as 2-byte shorts, then size as a 4-byte uint):
01 00 00 00 00 00 03 00 40 00 00 00. Decode it.
Value: 0100 0000 0000 0300 40000000
rm /x removes a regular file /x that has 3 data blocks, nlink 1, and is not open
anywhere. Which blocks does this one transaction log (pass to log_write)?
In iput, click the line after which ialloc on another hart may hand this
inode’s number to a brand-new file.
Your pick: none yet (click a line in the code)
cat calls close(3) on a file that rm already unlinked. Inside fileclose →
iput, line 362’s acquiresleep has just returned and line 363 has not run yet.
What is the state of the hart running cat?
iput copies ip->dev and ip->inum into the locals dev and inum (line 358),
and line 377 calls ifree(dev, inum) rather than ifree(ip->dev, ip->inum). Why?
On a fresh disk a user runs mkdir /a, mkdir /a/b, cd /a/b, /rm /a/b, /rm /a,
and then /ls ... What happens in this kernel, and which check is responsible?
Two processes on two harts call open("same", O_CREATE|O_WRONLY) in / at nearly the
same moment; same does not exist yet. Which statements are true afterwards?
sys_link increments the file’s nlink, then unlocks the file (line 153)
before calling nameiparent and locking the target directory. Why not keep the
file locked until the new entry is written?
wc calls read(0, buf, 512). Inside the kernel, fileread must decide whether this
read goes to a pipe, the console driver or a file on disk. What does it look at?
A process creates a pipe and writes to it, and nobody ever reads. How many bytes can
it write into the empty pipe before pipewrite puts it to sleep?
True or false: after kfork, the parent and the child each have their own
struct file for the console, so the child moving a file offset or closing its copy
cannot affect the parent’s.
Why?
wc is reading from a pipe. The pipe is empty, and the last write end has been
closed (writeopen == 0). What does piperead do?
In piperead, click the line that checks whether any writer is still left on the
pipe.
Your pick: none yet (click a line in the code)
consoleintr treats some typed characters specially. Match each key with what the
kernel does when it arrives.
When you type a key, consoleintr echoes it with consputc, which calls
uartputc_sync and spins on the UART. Why does it not send the echo through
uartwrite, like a process’s write does?
A process with only descriptors 0, 1 and 2 open calls pipe(p), which succeeds.
Which statements are true right afterwards?
cat makes one write(1, buf, 512) to the console. How many times does
consolewrite call uartwrite for it?
When the last reference to a file goes away, fileclose copies *f into the local
ff, frees the slot and releases ftable.lock, and only then calls pipeclose
or iput. Why not do the cleanup while still holding ftable.lock?
In ls | wc, wc is asleep in piperead on an empty pipe. ls has written all
its output and now exits. Put the events that end wc’s reading in order.
pipeclosesetswriteopen = 0and callswakeup(&pi->nread)wcreturns fromsleep, retakespi->lockand re-checks the loop condition on line 119- The copy loop breaks at once with
i = 0, andreadreturns 0 towc wc’s read loop ends and it prints its countslscallsexit(0);kexitstarts closing its open descriptorsfileclosedrops the write end’srefto 0 and frees the ftable slot
nread and nwrite are never reset; they only count up, and positions are found with
% PIPESIZE. What does this design buy compared with two indices that wrap back to 0
at 512?
True or false: pipewrite calls copyin while holding the spinlock pi->lock, and
this is safe in this kernel.
Why?
cat with no arguments is in read(0, buf, 512) on the console. You type h, i,
then Control-D (no Enter). What does that read return?
Hart 2 is idle in its scheduler (between intr_on and intr_off) when a UART
interrupt arrives. uartintr calls consoleintr, which takes cons.lock and
echoes the key. Hart 2 is now inside uartputc_sync, just after its push_off().
What is its state?
uartwrite serializes writers with tx_lock, a sleep-lock. Why would an
ordinary spinlock not work here?
Three harts call printk at the same moment. Click the line that keeps their
messages from interleaving character by character.
Your pick: none yet (click a line in the code)
A program calls open("README", omode) with omode = 0x401. Using
kernel/fcntl.h and sys_open, decode it and predict the result.
Value: 0x401
A process’s write to the console goes consolewrite → uartwrite. Which of
these can happen on that path in this kernel?
A long program runs that never reads the console, so nobody calls consoleread.
Before it started, the console input buffer was empty (r == w == e). You now type
200 ordinary letters without pressing Enter. How many of them are echoed on the
screen?
On hart 0, cat is in uartwrite holding tx_lock, halfway through a 32-byte
batch. On harts 1 and 2 other things are happening. Which of these can put bytes on
the screen between two bytes of cat’s batch?
True or false: uartwrite calls sleep_prepare without holding any spinlock, so
if the transmit-done interrupt arrives between the LSR check on line 87 and the
sleep on line 91, the wakeup is lost and the process can sleep forever.
Why?
A program calls write(fd, buf, 10000) on a regular file, and every writei
succeeds. How many file-system transactions (begin_op … end_op pairs) does
filewrite run? (MAXOPBLOCKS is 10, BSIZE is 1024.)
Continue from cat reading h, i, Control-D: the first read returned 2. cat
writes hi and calls read(0, buf, 512) again. Why does this second read return 0
immediately instead of sleeping?
When both ends of a pipe are closed, pipeclose runs release(&pi->lock) on line
70 and only then kfree((char *)pi) on line 71. What would go wrong if the two lines
were swapped?
This is the whole user-side write function, generated by user/usys.pl. How do
the descriptor, buffer address and byte count reach the kernel?
When cat calls write(1, buf, n), what number is in register a7 at the ecall?
True or false: if a user program’s main returns 3 without calling exit, the
process crashes, because main returns to an invalid address.
Why?
user/user.ld has no ENTRY line, and the Makefile passes no -e option when it
links _cat. Where does cat start executing after kexec?
init executes printf("init: starting sh\n"). How many write system calls does
that one call make?
The shell handles cd itself instead of forking a child to run it like every other
command. Why?
Match each piece of shell syntax with the node that the parser in user/sh.c
builds for it.
Every user program except forktest is linked with ULIB = ulib.o usys.o printf.o umalloc.o. Which of these are therefore part of _cat?
For ls | wc, click the line that makes ls’s standard output (descriptor 1) refer
to the pipe.
Your pick: none yet (click a line in the code)
You type ls and press Enter. Put these steps in the order they happen in the shell
and its child.
- The child’s parsecmd builds the tree, and nulterminate writes
\0s into its copy of buf - fork1 creates a child; the shell goes on to wait
- gets reads the line one byte per
read, stopping at the newline - main checks whether the line starts with
cd - runcmd reaches the EXEC case and calls exec
- getcmd writes
$to descriptor 2
In the right child (the future wc) someone deletes line 116, close(p[1]). You run
ls | wc. What happens?
While parsing echo hi > out, parseredirs builds a redircmd whose mode
field is 0x601. Decode it with kernel/fcntl.h.
Value: 0x601
True or false: in the REDIR case, runcmd tells open which descriptor
number the file should get (rcmd->fd).
Why?
Why does the shell parse the command line in the child after fork1(), rather
than parsing first and forking afterwards?
After parsing ls | wc, where do the strings ecmd->argv[0] of the two exec nodes
point?
A freshly exec’d program’s first malloc call is malloc(100). How many
bytes does malloc (through morecore) ask the kernel for with sbrk? A
Header is 16 bytes.
cat is in its write stub (user/usys.S:31) and is about to execute ecall;
the trap has not happened yet. What is the state of the hart running it?
Which of these user library functions can make a system call?
For echo a ; echo b, click the line that guarantees a is printed before echo b
even starts.
Your pick: none yet (click a line in the code)
You type a | b | c (three programs). Counting the shell’s own fork1 in main and
every fork1 in runcmd, how many times is fork called to run this line?
You type echo hi &. The shell prints the next prompt without waiting for echo.
Which process eventually calls wait and frees echo’s process slot?
The file out contains 50 bytes. You type cat < nosuchfile > out, and nosuchfile
does not exist. What happens?
char buf[] = "abcde"; then memmove(buf + 1, buf, 4);. Which branch of
memmove runs, and what does buf hold afterwards?
The shell’s child calls exec("echo", argv) for the line echo hi there. When
echo’s start executes its first instruction, which statements are true?