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¶
- Raw pointer vs. reference;
.add(n)scales by the pointee — L09 §2 · Guide § Raw pointers unsafe: five operations, nothing disabled — L09 §3 · Guide § What unsafe does not do- Safe wrapper, unsafe core — L09 §3 · Guide § Before you write unsafe
- Volatile MMIO — L09 §4 · Guide § Volatile access and MMIO
core/alloc/std— L09 §5 · Guide § core, alloc, and std- The
no_stdskeleton, by build error — L09 §5 · Guide § The no_std skeleton
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¶
- Which of these need
unsafe:let p = 0x1000_0000 as *mut u8;,p.add(5),*p = 1? Do two&mutborrows of one element compile insideunsafe { }?Answer
The last two: making a pointer is just arithmetic;.addis anunsafe fn(an address outside the allocation is already UB); dereferencing is operation #1. The double borrow still fails withE0499—unsafenever touches the borrow checker. - A driver polls
while *LSR & 0x20 == 0 {}and hangs, though the device is ready. Why?Answer
Nothing in the loop writes*LSR, so the optimizer hoists the load out: "not ready now, spin forever".core::ptr::read_volatileforces the load on every iteration. - Under
#![no_std], which survive:Option,Vec,println!,core::ptr::write_volatile? Which error says the panic handler is missing?Answer
Optionandwrite_volatilelive incore.Vecneedsallocand 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.