Skip to content

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

Session: Fri Oct 2, 1h30 · Exercises: 21r_unsafe_bridge · 30k_kernel_basics · Prep time: ~45 min · Lecture: L09 Leaving std: no_std and Bare-Metal 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
L09 §1–4 15 min
L09 §5–6 10 min
Guide § What unsafe does through § Volatile access and MMIO 10 min
Guide § The no_std skeleton and § Symptoms and their causes 5 min
Setup §6 — target installed? 5 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 E0499unsafe 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 (substantial credit), then finish by Monday 11:59 pm and submit again.

If you finish early

Work L09 Problem 1 and Problem 5, then start reading Thursday's prep page and its lecture, Boot: From Reset to kmain. Afterward, the xv6 book chapter 2 through §2.6, and The Rustonomicon chapters 1–3.