Skip to content

Prep: unsafe, and Leaving std — 21r · 30k

Session: Fri Oct 2, 1h30 · Exercises: 21r_unsafe_bridge · 30k_kernel_basics · Prep time: ~25 min · Lecture: Week 6 · Below Rust

What you will build

First, on your laptop, the inner loop of a UART driver: a raw pointer to a fixed address, volatile register access at base-plus-offset, a safe wrapper that refuses a bad offset, and a plain-memory byte copy. The tests substitute a byte array for the chip's register block. Second, the first kernel crate: a Rust binary that tells the compiler no OS exists beneath it and supplies the one function demanded in return. oslings run checks that each register access lands in its own slot and nothing past the end is touched, then that the kernel builds for riscv64gc-unknown-none-elf — no QEMU yet.

Concepts you need

Read before class

What Time
Week 6 · Friday · 21r Raw pointers, unsafe, and volatile, all three sections 4 min
Week 6 · Friday · 30k Leaving std, both sections 2 min
Unsafe guide: What unsafe does through Raw pointers 7 min
Unsafe guide: The no_std skeleton and Symptoms and their causes 4 min
Setup: Check your environment — target installed? 2 min

Mental model

QEMU's virt board has a "test finisher" register at 0x10_0000; storing 0x5555 there powers the machine off — how oslings ends every kernel run.

const FINISHER: *mut u32 = 0x10_0000 as *mut u32;   // safe: a number with a type

pub fn power_off() -> ! {                            // safe wrapper; `!` = never returns
    // promise: this address is the register
    unsafe { core::ptr::write_volatile(FINISHER, 0x5555) }
    loop {}                                          // store did not take: spin
}

Making the pointer is safe; only the store needs unsafe, in a one-line block with its promise above it. The store is volatile because the address is a chip, not RAM: as *FINISHER = 0x5555 the compiler may reorder, merge, or delete it. Nothing mentions std; core::ptr survives #![no_std], so this compiles unchanged on bare RISC-V. Every kernel driver has this shape: a small unsafe core with a named promise, wrapped in safe Rust.

Check yourself

  1. Which of these need unsafe: let p = 0x1000_0000 as *mut u8;, p.add(5), *p = 1? Do two &mut borrows of one element compile inside unsafe { }?
    AnswerThe last two: making a pointer is just arithmetic; .add is an unsafe fn (an address outside the allocation is already UB); dereferencing is operation #1. The double borrow still fails with E0499 — unsafe never touches the borrow checker.
  2. A driver polls while *LSR & 0x20 == 0 {} and hangs, though the device is ready. Why?
    AnswerNothing in the loop writes *LSR, so the optimizer hoists the load out: "not ready now, spin forever". core::ptr::read_volatile forces the load on every iteration.
  3. Under #![no_std], which survive: Option, Vec, println!, core::ptr::write_volatile? Which error says the panic handler is missing?
    AnswerOption and write_volatile live in core. Vec needs alloc and an allocator you have not written; println! needs an OS. The error: `#[panic_handler]` function required, but not found.

What "done" looks like

oslings run is green, then oslings submit before you leave. Not green? Submit anyway — the tests that pass earn their share — then finish it at a make-up session (office hours, on the class network) for three quarters, or anywhere else for half, and submit again.

If you finish early

Work Week 6 · Problem 2: Which lines need unsafe?, then run the rustc --print cfg command from Week 6 · Reading riscv64gc-unknown-none-elf yourself. Then start reading next Thursday's prep page and its lecture, Week 7 · From Reset to Page Tables. Afterward, the xv6 book chapter 2 through section 2.6, and The Rustonomicon chapters 1–3.