`**, atomically reference-counted, shares one heap value until the last owner lets go:
```rust
let a = Arc::new(Gauge::new()); // one Gauge on the heap; count 1
let panel = Panel { g: Arc::clone(&a) }; // count 2
let log = Arc::clone(&a); // count 3, still one Gauge
drop(a); // count 2: the first owner is not special
let n = Arc::strong_count(&log); // n == 2
drop(panel); drop(log); // count 0: Gauge dropped, page freed
```
Read `Arc::clone(&a)` as "one more pointer, one more on the count": nothing is copied. `Arc` hands out only `&T`, so shared data that changes needs a lock inside.
Notes:
- `a.level += 1` fails with "cannot assign to data in an `Arc`": an `Arc` is for sharing, and changing shared data still takes a lock inside it.
- The count uses an atomic add, so a clone on one hart and a drop on another cannot lose an update: Problem 1 again, solved with `fetch_add`.
- **Q:** "Is `a.clone()` different from `Arc::clone(&a)`?" No, it is the same call. `Arc::clone(&a)` is the style that says only a pointer is copied.
---
## The one thing to get right · `38k`
Two permits, two waits. Why does the third wait succeed too?
`[fail] no permits should remain after taking both`
The count went to −1: a permit that never existed was handed out.
The count never goes below zero.
Notes:
- A later post only brings the count back to 0. The semaphore has handed out three channels from a pool of two, and it will refuse the next honest wait.
- **Q:** "Could a negative count ever mean something?" In some blocking designs it counts the sleepers waiting for a permit. Here nothing sleeps, so a negative count is always a bug.
---
## The lost wakeup
A blocking wait that tests "count is 0", then sleeps, has a gap:
```text
waiter tests: count is 0
poster count = 1; wakes the sleepers: nobody is asleep yet
waiter sleeps, beside a free permit
```
The cure: test under a lock, become `Sleeping` before releasing it, and retest in a `while`. rv6's `try_wait` refuses at zero and never sleeps: no gap.
Notes:
- Midterm 2 (Thu Nov 19). Practice Set 2 Problem 6 (c). `Sleeping` is the state `34k` defined and nothing used. xv6's `sleep` is handed the caller's lock and takes the process lock before releasing it; `wakeup` needs that process lock, so no wakeup fits between the test and the sleep.
- This week's refusing wait is a deferral, not a solution: once something else can run to post, a wait must sleep, and the gap returns.
- **Q:** "Why `while` and not `if`?" A wakeup says the count changed, not that a permit is yours. Another waiter may take it first, so a woken waiter tests again.
---
## Bounded buffers
N slots: `empty` = N, `full` = 0, `mutex` = 1, and `empty + full` = N between operations.
```text
producer P(empty) P(mutex) put V(mutex) V(full)
consumer P(full) P(mutex) take V(mutex) V(empty)
```
Take the counting semaphore first: a producer blocked on `empty` while holding `mutex` stalls every consumer.
empty and full are one count read from two ends; mutex guards the indices.
Notes:
- Midterm 2 (Thu Nov 19). Practice Set 2 Problem 6: (a) traces `empty` and `full`; (b) is the swapped order. Every slot is free or full, never both and never neither: that is the invariant.
- The exam traces it one row per operation, with both counts after each.
- ↓ below: Problem 3 (a buffer of two).
- **Q:** "Why two semaphores, not one?" Two parties wait on two conditions: a producer for a free slot, a consumer for a full one. Each semaphore blocks one of them.
--
## Problem 3: a bounded buffer of two
Use the bounded buffer with N = 2 and a blocking P.
- **(a)** The producer offers three items; then one consumer runs once. Give `empty` and `full` after each step, and say where the third production waits.
- **(b)** Start over, empty. A consumer's first two lines are swapped to `P(mutex); P(full)`, and it runs before any producer. Show the deadlock.
--
## Problem 3 · solution
**(a)**
| After | `empty` | `full` |
|---|---|---|
| production 1 | 1 | 1 |
| production 2 | 0 | 2 |
| production 3 | waits in `P(empty)`, holding nothing | 2 |
| the consumption | 1, taken at once by production 3 | 1 |
| production 3 finishes | 0 | 2 |
The third producer waits before `mutex`, so the consumer can still get in, and its `V(empty)` is exactly what the producer needed.
**(b)** The consumer takes `mutex`, then waits on `full` = 0 while holding it. The producer passes `P(empty)` and waits on `mutex`. Each needs what only the other can give: a circular wait. Consumers, too, take the counting semaphore first.
---
# Part III · Friday
## `39k_virtual_memory`: the next fetch is translated
In `33k` only software read your tree; now the MMU [walks](07-cs326-2026-10-06-boot-physical-pages-and-sv39/#33k-walk) it on every access.
- The **bootstrap paradox**: the fetch after the switch is translated, with the same `pc` and `sp`
- The way out: an **identity map**, `va == pa`
- Map everything the kernel will fetch, load or store after the switch, devices included
Notes:
- Part III runs 28 minutes over six slides. The recap (`33k` was Oct 9, before Midterm 1 and the break): building a table takes only stores; using one is the hardware's job, and it enforces R, W and X at the first leaf. `satp` gives the root, VPN[2], VPN[1] and VPN[0] index three tables, and the leaf's page plus the copied offset is the answer. Practice Set 2 Problem 8 is that walk; the Week 7 deck's exam-satp slide works one.
- Ask the room "what will the kernel touch after the switch?" before pointing at the last bullet. Whatever you leave out stops existing. Give each region a start and a length, not an end.
- ↓ below: how xv6 and Linux do it (optional).
- **Q:** "If `va == pa`, why turn translation on at all?" For the kernel nothing changes yet, and that is the point. From `48k` each program gets a table of its own, where `va` and `pa` differ.
--
## How xv6 and Linux do it
- **xv6** splits its kernel map at `etext`, the end of `.text`: code R X, the rest R W. That is W^X for one extra mapping.
- **Linux** runs at a high virtual address but is loaded wherever the bootloader chose, so the fetch after its switch cannot translate to itself. On RISC-V it points `stvec` at the high address of that next instruction first: the fetch faults, and the trap lands exactly there.
- Linux also tags TLB entries with ASIDs, so a process switch need not flush, and asks other harts to flush by interrupt, since `sfence.vma` reaches only its own.
---
## `satp` by hand
MODE in bits 63..60 (8 is Sv39, 0 is off), an address-space ID in 59..44 (0 in rv6), and in 43..0 the root's **page number**: its address without the low 12 bits. A root at `0x8052_1000`:
```text
root table at 0x0000_0000_8052_1000
drop the low 12 bits 0x0000_0000_0008_0521 the PPN: three hex digits gone
MODE 8 in bits 63..60 0x8000_0000_0000_0000
both fields together 0x8000_0000_0008_0521 the value to install
```
Read it upward to decode. MODE 0 is legal and means "translate nothing".
Notes:
- Midterm 2 asks for the `satp` encoding, and Practice Set 2 Problem 7 is this slide both ways. Decode the kernel's own from week 7, `0x8000_0000_0008_7FFF`: MODE 8, root at `0x87FF_F000`, the allocator's first page.
- MODE 0 is legal, so the hardware never complains about a missing mode: a wrong value fails quietly.
- ↓ below: Problem 4 (`satp` both ways).
- **Q:** "Why a page number and not the address?" The root is page-aligned, so its low 12 bits are always 0. Dropping them lets 44 bits name any page in a 56-bit physical space.
--
## Problem 4: `satp` both ways
- **(a)** A root sits at `0x8043_7000`. What goes in `satp`?
- **(b)** Decode `0x8000_0000_0008_7FC2`: is the root in RAM?
- **(c)** Decode `0x0000_0000_0008_0437`: what happens once it is installed?
- **(d)** Decode `0x8000_0000_8043_7000`: where does the hardware look for the root?
--
## Problem 4 · solution
**(a)** `0x8043_7000 >> 12` is `0x8_0437`; with 8 at bit 60, `0x8000_0000_0008_0437`.
**(b)** MODE 8, Sv39. The low 44 bits are `0x8_7FC2`, so the root is at `0x87FC_2000`, inside `0x8000_0000..0x8800_0000`: yes.
**(c)** MODE 0, Bare: QEMU translates nothing, so the kernel runs on and nothing says the table was never used. (The current privileged spec asks for zeros beside MODE 0 and leaves this pattern UNSPECIFIED.)
**(d)** The low 44 bits hold `0x8043_7000`, an address where a page number belongs. Three zero digits appended put the root at `0x804_3700_0000`, about 8 TiB up, where there is nothing: the first translated access fails inside the walk.
---
## Two instructions, and the TLB
```asm
csrw satp, t0 # the satp value; the next fetch is translated
sfence.vma zero, zero # discard every cached translation
```
The **TLB** caches recent translations, sparing most accesses the three-read walk. It is not coherent with memory: rewrite a PTE and the cached copy can stay.
```text
load 0x8000_2F00 miss: walk, cache page 0x8000_2000 -> 0x8000_2000
leaf now names page 0x8060_0000; load again: may hit, still 0x8000_2F00
sfence.vma; load again: miss, walk, 0x8060_0F00
```
Fence after writing `satp` and after changing an entry the hardware may have used. A table built before it is installed needs none; a mid-stream switch (`48k`) fences on both sides.
Notes:
- Both instructions are given; you write neither. **`sfence.vma`** also orders earlier page-table stores before later walks. A table built before it is installed needs no fence: nothing has cached it yet.
- Practice Set 2 Problem 9 (b): after `csrw satp`, required; after building the table with translation off, not required; around a table switch in mid-stream, as in `uservec` (week 12), required on both sides.
- ↓ below: when you must flush (optional).
- **Q:** "Why doesn't the hardware keep the TLB coherent itself?" It would have to check every store against every cached translation. Page tables change rarely, so the kernel says when, with one instruction.
--
## When you must flush
- RISC-V needs `sfence.vma` after writing `satp`, after changing an entry the hardware may have used, and even after making an invalid entry valid: a hart may remember that an address was unmapped.
- rv6 never fences while building a table, correctly, by an invariant: a table is complete before it is installed.
- Add lazy allocation or copy-on-write, and every edit needs a fence.
- QEMU never remembers a failed walk, so a missing fence after adding a page passes every test here and can fail on a real board.
---
## Silence, and checking first
A wrong table fails in silence: printing goes through the table that failed.
Check on the ground, not in the air: the test inspects your table before the switch, so a mistake prints `[fail]`.
When a failure is silent, check before the dangerous step.
39k runs in machine mode, which translation ignores. From 43k, this table carries every access.
Notes:
- Like a phone whose only way to report "no signal" is a text: the error report needs the translation that broke. Here a mistake prints a `[fail]` line before anything is switched on.
- The machine-mode footnote: in `39k` the table carries nothing yet, so passing proves the table right. `43k` moves the kernel to supervisor mode, and from then on it trusts the table.
- ↓ below: reading the silence in GDB (optional).
- **Q:** "Then why install the table at all in `39k`?" To make the switch once, on its own, while every mistake still prints. `43k` inherits a table that has already been checked.
--
## Reading the silence
When the kernel goes quiet after the switch, attach GDB and read four registers:
| Register | What it says |
|---|---|
| `satp` starting with 8 | the switch happened |
| `scause` 12 | an instruction page fault: the table refused a fetch |
| `stval` | the address that faulted |
| `pc` stuck at 0 | the fault trapped to an unset vector and faulted again |
The spec says a root that points at no memory gives an access fault, `scause` 1; QEMU 10 reports that case as 12 too, so check `satp` itself before blaming a mapping. [QEMU and GDB](../guides/qemu-gdb/#diagnostic-playbook) has the playbook.
---
## The one thing to get right · `39k`
Every mapping checks out. Why does `satp` still fail?
`[fail] make_satp: wrong root page number`
The low 44 bits hold a page number, not an address. An address there names a root 4,096 times higher, about 8 TiB up, where live hardware could not even read it.
satp names the root by its page number, never by its address.
Notes:
- Without the check, from `43k` on, this is silence: `0x8000_0000_8052_1000` still fits the 44-bit field, so nothing complains until the first translated access fails inside the walk. In `39k` itself machine mode ignores `satp`, and the bad value would pass unnoticed. Practice Set 2 Problem 7 calls it the classic error.
- **Q:** "Which fault does that give?" The spec says an access fault, `scause` 1, since the walk cannot read a PTE; QEMU 10 reports a page fault, 12, for this case too. Either way, read `satp` before blaming a mapping (↓ under 39k-verify).
---
## Megapages, and 66 pages
Identity-map RAM, 128 MiB, in 4 KiB pages:
```text
RAM 0x8000_0000 .. 0x8800_0000
leaves 0x0800_0000 >> 12 = 32,768
level-0 tables 32,768 / 512 = 64
level-1 tables all under root entry 2 = 1
total 64 + 1 + the root = 66 pages
```
A level-1 leaf maps a 2 MiB **megapage**, so 64 leaves in one level-1 table cover RAM: the root and that table, 2 pages.
Notes:
- Midterm 2 (Thu Nov 19). Practice Set 2 Problem 9 (c). 66 pages is 264 KiB of tables to say "every address is itself". rv6 skips megapages to keep the walk one shape.
- Scattered pages cost more tables than contiguous ones: every distinct 2 MiB region needs its own level-0 table (↓ what a table costs).
- ↓ below: what a table costs, then Problem 5 (count the tables).
- **Q:** "Why not map the UART with a megapage too?" A 2 MiB leaf would also map every device register beside it. Problem 5 (b) asks the same of a device page.
--
## What a table costs
- A table's size follows how scattered its pages are, not how many there are
- Two 4 KiB pages in one 2 MiB region share a level-0 table; in different 2 MiB regions they need two; in different 1 GiB regions, two level-1 tables too
- A few device pages spread across the low gigabyte can cost more tables than a contiguous megabyte of RAM
- A hardened kernel splits RAM by permission, code R X and data R W: **W^X**, at the price of one more boundary per split, placed from a linker symbol
--
## Problem 5: count the tables
A small kernel identity-maps RAM from `0x8000_0000` to `0x8040_0000` in 4 KiB pages, plus one device page at `0x2000_0000`.
- **(a)** How many page-table pages, root included?
- **(b)** How many if RAM uses 2 MiB megapages, and why not the device page?
- **(c)** How many for just the pages `0x801F_F000` and `0x8020_0000`?
--
## Problem 5 · solution
**(a)** 6. RAM's 1,024 pages fill two level-0 tables below one level-1 table, under root entry 2. The device is under root entry 0, level-1 entry 256: one level-1 and one level-0 table. With the root, 1 + 3 + 2 = 6.
**(b)** 4. RAM becomes two level-1 leaves, and its level-0 tables vanish. The device keeps a 4 KiB page, since a 2 MiB leaf would also map every device register beside it.
**(c)** 4: the root, a level-1 table and two level-0 tables. The pages are adjacent but straddle a 2 MiB boundary, level-1 entries 0 and 1.
---
## Summary
- CAS makes read-and-write one step; Acquire takes, Release gives back
- `UnsafeCell` + `unsafe impl Sync`; `Drop` unlocks
- A lock a handler takes: interrupts off
- Permits never below zero; a page per allocation; `Arc` shares
- After `satp`, the next fetch is translated: identity-map
- `satp` holds a page number; `sfence.vma` drops stale entries
- Midterm 2: TAS/CAS, deadlock, lost wakeup, bounded buffers, megapages
Notes:
- One sentence per bullet, in the order of the page.
- **Q:** "How should I study this week for Midterm 2?" The page's Problems 1 to 5 now, then Practice Set 2 Parts B and C on paper when it goes out on Nov 10. Then redraw from memory what the kernel must still reach after the switch: its code, stack and statics, its page tables, and each device (fri-39k).
---
## [Before Thursday](../prep/10-cs326-2026-10-29-prep-spinlocks-and-semaphores/): check yourself
You build a spinlock and a semaphore on it; the kernel gets its heap. Prep: about 30 minutes.
**1.** `if !busy { busy = true; }` guards a critical section on rv6's single hart, interrupts enabled. Why can it still fail?
**2.** A struct holds an `UnsafeCell`. Why does the compiler reject `static X: ThatStruct`, and what does `unsafe impl Sync for ThatStruct` claim?
**3.** After `let b = Arc::clone(&a);`, how many copies of the value exist, and how can either mutate it?
Notes:
- Walk the prep page (about 30 minutes). What you build: a spinlock claimed by compare-and-exchange and reached only through a guard that unlocks in `Drop`; then a semaphore on that lock that refuses at zero, with a page-per-allocation heap that makes `Box`, `Vec` and `Arc` work. Green is `OSLINGS:PASS` for each.
- The three questions are the prep's own Check yourself (thu-37k, 37k-cell, 38k-arc); answers are on the prep page.
- **Q:** "Why two exercises in one session?" `38k` stands on `37k`: the semaphore's count lives behind your lock. Finish `37k` first.
---
## [Before Friday](../prep/10-cs326-2026-10-30-prep-virtual-memory/): check yourself
You identity-map the kernel and pack its `satp`. Prep: about 20 minutes.
**1.** GDB shows `satp = 0x8000_0000_0008_7FFF`. Is paging on, and where is the root table?
**2.** Your kernel survives the switch, prints `OSLINGS:PASS`, then QEMU never exits and the harness times out. Which region is missing?
**3.** Right after the switch, `scause` reads 1, not 12. Do you suspect a mapping or `satp`?
Notes:
- Walk the prep page (about 20 minutes). What you build: the kernel's identity-mapped page table and the `satp` value that names it; green is `OSLINGS:PASS`.
- The three questions are the prep's own Check yourself (39k-satp, fri-39k, 39k-one-thing); answers are on the prep page. Questions 2 and 3 picture a kernel already in supervisor mode, as from `43k`: in `39k` a missing region prints a `[fail]` line before the switch. On question 3 the spec's answer is `satp`, but QEMU 10 reports 12 for that case too (↓ under 39k-verify). On question 2, runner.rs looks for `OSLINGS:PASS` first, so that run is green after a 10-second wait; the symptom is the wait, not a red.
- **Q:** "Do I need Thursday's locks on Friday?" No. `39k` stands on `33k`'s tree and `32k`'s allocator; the locks come back in `40k`.
---
## Next week
[Week 11 · Files, Boot Order, and Traps](11-cs326-2026-11-03-filesystems-boot-order-and-traps/), Tuesday, Nov 3: read its Essentials before Tuesday.
Notes:
- `40k` puts the whole filesystem behind one of this week's locks, and from `43k` the kernel runs in supervisor mode, where this week's table carries every access.
- **Q:** "When is Midterm 2?" Thursday, Nov 19, in class. Practice Set 2 goes out Nov 10, and the Nov 17 lecture is the review.