← Week 1 page
# Building an Operating System ## CS 326 Operating Systems Week 1 · August 25, 2026 Then your first Rust: **Thu Aug 27** `00r_hello_rust` · **Fri Aug 28** `01r_control_flow`. [The Week 1 page](01-cs326-2026-08-25-building-an-os-and-first-rust/): **Essentials** is what Thursday and Friday assume.
Course site
cs326-f26.cs.usfca.edu
Notes: **Minute plan** (105 minutes) | Min | Segment | Slides | |---|---|---| | 0–3 | Title; this week | title, this-week | | 3–59 | Part 0 · The course, the Aug 25 deck as taught (25 slides): introductions by show of hands 5, the four jobs 14, the machine and the modes 8, why Rust 8, the semester 5, how the course runs 8 plus the live demo 5, the payoff and summary 3 | course … course-summary | | 59–73 | Part I · Thursday · `00r` (4 core, two of them exam slides) | thu-00r … exam-octal-as | | 73–88 | Part II · Friday · `01r` (4 core, one exam slide) | fri-01r … 01r-one-thing | | 88–90 | Summary | summary | | 90–100 | Before Thursday (the setup checklist), Before Friday, Next week | before-thursday, before-friday, next-week | | 100–105 | Buffer: questions, or one ↓ problem | | As taught on Aug 25, Part 0 was the whole lecture ("no coding today"), and the Rust half was a separate reading. Both fit in 105 minutes only if Part 0 keeps to about 56: introductions by show of hands, not a round of names. **Fast path** (the minimum lecture, 14 ids): this-week, course-jobs, course-semester, course-runs, course-grading, course-room, course-oslings, thu-00r, exam-hex, 00r-one-thing, exam-octal-as, fri-01r, exam-overflow, before-thursday. The Midterm 1 slide off the fast path, 01r-option, is never dropped: show it in its cut-order form, about 30 seconds. **Cut order** (cut from the top when behind): 1. Every ↓ stack (Problems 1, 2 and 4): never press down. The page has them, with solutions. 2. next-week: say it aloud on before-friday. 3. summary and course-summary: skip both; the Before slides recap by question. 4. course-objectives: one sentence: "four jobs, three modes, one fixed order, and how a session runs." 5. The live demo on course-oslings: skip it; Thursday's setup session ends with the same three commands. 6. course-why-both and course-cs315: one sentence each. 7. course-december and course-payoff: one sentence on course-semester: the `grep` you write in week 5 runs unchanged on your kernel in week 15. 8. course-why-m: 30 seconds on its bold line: S is privileged over processes, M over the board. 9. course-machine: one sentence: one address space, devices included. 10. course-modules and course-session: ten seconds each; course-runs carries the rules. 11. course-rust and course-special: one sentence each: most kernel bugs become compile errors, and `unsafe` makes the rest small and labeled. 12. course-os, course-cpu, course-memory, course-data, course-devices: leave course-jobs up instead, and name the exercise that builds each job (`35k`, `33k`/`39k`, `40k`, `45k`/`50k`). 13. course-modes: one sentence: privilege goes down by an instruction, up only by a trap. 14. course (introductions): name the two of us and skip the show of hands. 15. 01r-option: show the code, say "one arm per shape, and the compiler checks you covered both". 16. 01r-one-thing: one sentence on exam-overflow: the check guards every intermediate sum, so test at `MAX`. 17. before-friday: point at the prep page, and open Friday's session with its questions. Never cut: exam-hex, exam-octal-as, exam-overflow (the Midterm 1 slides). On a first day, also keep course-runs, course-grading and course-room: they are the rules students are held to. No Going-deeper topic ↓ slides and no Part title slides this week: Part 0's 25 verbatim slides use up the 45-slide cap. The page's Going deeper has six topics, and thu-00r and fri-01r carry the Part ids. **Midterm 1 anchors:** PS1 Problem 4 (a) binary and (d) → exam-hex; (a)'s PTE flags → Week 7 (#33k-pte) · (b), (c) → exam-overflow. Scope lines: bindings, mutability and widths → thu-00r · hex and the underscore → exam-hex · expressions vs statements and the no-semicolon return → 00r-one-thing · overflow and `wrapping_`/`checked_` → exam-overflow · `Option` → 01r-option. PS1 Part A (Problems 1–3) belongs to weeks 2–4. - **Q:** "Is the course intro on Midterm 1?" Only as background. Midterm 1's Rust starts at `00r`, and the privilege modes return with `43k`, after it. --- ## This week | Day | Exercise | You build | The one idea | |---|---|---|---| | Thu Aug 27 | setup, then `00r_hello_rust` | a working toolchain; constants and functions | a block's value is its last expression | | Fri Aug 28 | `01r_control_flow` | functions that choose, repeat, and meet overflow | overflow: debug panics, release wraps | Today: the course first, then the Rust that Thursday and Friday assume. Notes: - Start from where this goes: in `31k` your kernel describes the machine in hex constants, and in `32k` it rounds addresses without running off the top of memory. - By Friday night the toolchain works and two exercises are submitted. Midterm 1, Thu Oct 15, covers this week's Rust. - **Q:** "Is Thursday all setup?" About 45 minutes of it, the Setup page step by step, then `00r`. Bring a charged laptop. --- ## Introductions - **Greg Benson** — instructor - **Ankit Mukhopadhyay** — TA, and the author of OSlings - **You** — name, year, and one thing you wish your computer did that it does not Show of hands: - Who has taken CS 315? Written C? Written RISC-V assembly? - Who has written any Rust? Finished Rustlings? Notes: - Part 0 is the Aug 25 deck, as taught. Keep introductions to the show of hands: a round of names costs ten minutes this deck no longer has. - The hands tell you how much C and RISC-V review to promise on course-cs315. - **Q:** "Do I need to know Rust already?" No. Module 1 teaches it from `let`, starting Thursday. Rustlings is optional practice outside class. --- ## Learning Objectives - Define an operating system by the **four jobs** it does, not by a product list - Map each job onto the part of rv6 you will build - Distinguish RISC-V's **machine**, **supervisor**, and **user** modes — who runs where, and why machine mode is its own job - Order the semester's two modules, and say why the kernel is built in a fixed order - Explain why a kernel needs a way to step outside Rust's safety rules — and why Rust is still the right choice - Describe how a session runs, and what to do before Thursday Notes: - Read the six aloud and move on: each has its own slide. - **Q:** "Which of these is on Midterm 1?" None directly. Midterm 1 starts at `00r`; these are the map for the semester. --- ## What is an operating system? "Linux, macOS, Windows, Android" is a list of **examples**, not a definition — and useless for building one. Better question: what must be true for **two programs to run on one computer** without either knowing the other exists? A bare machine has exactly one of everything: one instruction stream, one span of memory, one disk, one serial port. Sharing all of that, invisibly, is four jobs. Notes: - The definition asks what must be true, not which products exist: two programs, one machine, and neither knows the other is there. - Every singular resource is shared invisibly: your editor is not written differently because a compiler happens to be running. - **Q:** "Is the kernel the whole OS?" In this course, yes: the kernel does the four jobs. The shell and the commands are programs it runs, and you write those too. --- ## The Four Jobs
flowchart TB subgraph U["User programs"] A["sh"] B["grep"] C["cat"] end subgraph K["Kernel"] K1["Multiplex the CPU"] K2["Virtualize memory"] K3["Name persistent data"] K4["Abstract devices"] end subgraph H["Hardware"] H1["one CPU"] H2["128 MiB RAM"] H3["storage"] H4["UART"] end A -->|system calls| K B -->|system calls| K C -->|system calls| K K1 --> H1 K2 --> H2 K3 --> H3 K4 --> H4
**Every one of these four is something you implement this semester.** Notes: - Everything else in a kernel (traps, locks, system calls, `fork`, `exec`) serves one of these four. - Programs reach the kernel only through system calls. That is the wall, and the modes slide comes back to it. - **Q:** "Where do `cat` and `grep` fit?" They are the user programs on top. You write them in Module 1 and run them on your own kernel in December. --- ## Job 1 · Multiplex the CPU One CPU, many programs. Run one for a few milliseconds, take the CPU away, give it to another — fast enough that it looks simultaneous. - **Process** — the kernel's record of one running program - **Context switch** — save one set of registers, restore another - **Scheduler** — the policy that picks who runs next A program that never yields must still be interrupted, and only hardware can do that: a **timer interrupt**, arriving whether or not the program consents. That is why *preemptive* multitasking needs hardware and *cooperative* does not.
Concurrency
is many things in progress at once;
parallelism
is many things executing at the same instant. rv6 has one CPU — and every hard problem in this course shows up anyway.
Notes: - A process is the kernel's record of one running program. The context switch (`35k`) is one of the few places the course drops into assembly, because naming registers one by one is something only assembly can say. It saves only the callee-saved registers plus `ra` and `sp` (Week 6). - The forcible part: only a timer interrupt can take the CPU from a program that never yields. - **Q:** "With one CPU, why would we need locks?" Interrupts: a handler can run between any two instructions. Races, locks and deadlock all appear with one CPU; more CPUs make them more frequent, not more possible. --- ## Job 2 · Virtualize Memory Two programs, both compiled to use address `0x1000`. If `0x1000` names one physical location, they corrupt each other.
flowchart LR A["Program A\nuses 0x1000"] --> M["MMU\ntranslates through\nthe kernel's map"] B["Program B\nuses 0x1000"] --> M M --> PA["physical page\nfor A"] M --> PB["physical page\nfor B"]
- Programs see **virtual** addresses; the hardware translates every one through a map the **kernel** builds - The map works in **pages** of 4096 bytes - One bit in that map separates user pages from kernel pages — that bit is the wall Notes: - The MMU rewrites every address the CPU issues, through a table the kernel builds, in 4096-byte pages. You build the table in `33k` and switch it on in `39k`. - The user bit is the wall: a wild pointer in `grep` kills `grep`, not the machine. - **Q:** "What happens to an address with no entry?" The program cannot reach it at all: the hardware refuses the access and the kernel hears about it. --- ## Job 3 · Name Persistent Data Memory is addresses. A disk is numbered blocks. Neither is a name a human can use. ```text what a program sees what the storage has / +-- notes.txt -------------> block 4192, block 4193 +-- bin/ +-- grep -------------> block 7, block 8, block 9 ``` - A **file** is bytes under a name; a **directory** is a table of names to files - The kernel keeps the map between the two; programs only ever use the names - rv6's filesystem lives in RAM — files do not survive a reboot. A scope cut, not an accident Notes: - A directory is a file whose contents are names pointing at other files. You build it in `40k`. - In RAM on purpose: surviving power loss needs a block driver, a cache and a log. - **Q:** "So my files vanish when QEMU exits?" Yes. Every boot starts from a fresh filesystem, by design. --- ## Job 4 · Abstract Devices A serial port is not a stream of bytes. It is a handful of registers at a fixed address, and sending one character means waiting for a status bit, then storing a byte. - The kernel talks to the hardware once, in a **driver**, and hides it - Everything above sees one interface — `read` and `write` on a **file descriptor** - A program cannot tell a keyboard from a file from a pipe, and that is the point
Every
println!
you have ever written bottoms out in a driver like the one you will write.
Notes: - Sending one character is waiting for a status bit, then storing a byte to a register at a fixed address. You write that driver in `45k`, and the file-descriptor layer in `50k`. - **Q:** "Why hide the device at all?" So a program written against `read` and `write` works on a keyboard, a file or a pipe, unchanged. Your `grep` is the proof in December. --- ## The machine: RISC-V on QEMU - rv6 runs on **64-bit RISC-V** — the instruction set you met in CS 315 - On **QEMU's `virt` machine**: a computer that exists only in software, with one CPU, 128 MiB of RAM, a serial port, and a timer, all at fixed addresses - In CS 315 you wrote a RISC-V emulator. QEMU is that idea, at full fidelity
Not a compromise. An emulated machine can be stopped mid-instruction and inspected. Your kernel is a real RISC-V program that would boot on silicon.
Notes: - RAM and devices share one address space: a store to one address is memory, to another it transmits a byte. That is memory-mapped I/O, and it is why a wild pointer in a kernel can do what one in an application cannot. - The Memory Map guide lists every address; the cheatsheet keeps the ones you must not misremember. - **Q:** "Could this kernel boot on real hardware?" It is a real RISC-V executable that would boot on silicon. QEMU is the choice because it can be stopped mid-instruction and inspected. --- ## Three privilege modes | Mode | Who runs here | What it can do | |---|---|---| | **Machine** | firmware — for us, rv6's own boot code | everything, untranslated | | **Supervisor** | the kernel | manage memory, handle traps, talk to devices | | **User** | your programs: `sh`, `grep`, `cat` | ordinary instructions, its own memory only | Privilege goes **down** by an explicit instruction and **up** only through a trap. A program cannot make itself privileged — it *asks*, with a **system call**, and the kernel decides. Orientation only today. The mechanism comes in November, when you build it. Notes: - The ladder has one rule: down only by an explicit instruction, up only by a trap. A system call is a deliberate trap. - Orientation only: the mechanism is `43k` and Week 11, after Midterm 1. - **Q:** "Can a user program just jump into kernel code?" No. No user instruction raises privilege; the only way up is a trap, and it lands at the address the kernel chose. --- ## Why machine mode exists **S-mode is privileged with respect to *processes*. M-mode is privileged with respect to the *board*.** - **Required** — S and U are optional; every hart leaves reset in M, untranslated. M is the floor, not a layer above the kernel - **Untranslated** — `satp` governs S and U, so somebody has to run before the kernel's page table exists - **Contains the kernel** — PMP is M-only, and every trap goes to M by default: your kernel gets page faults because `medeleg` handed them down - **Hides the board** — emulates instructions this chip lacks; owns the timer, inter-hart interrupts, power-off We run QEMU with `-bios none`, so rv6 *is* the firmware. It stays in machine mode until `43k`, where `start.rs` sets `mstatus.MPP` to supervisor, points `mepc` at `kmain`, and executes `mret`. Notes: - M is the only mode the specification requires, and every hart leaves reset in it with translation off. It is the floor, not a layer stacked above the kernel. - On real boards OpenSBI is the M-mode firmware. With `-bios none`, rv6 is its own firmware, so it stays in M until `43k`. - **Q:** "Why not keep the kernel in M for good?" S is where an operating system is written against the abstract machine; M is what makes one particular chip look like that machine. rv6 plays both parts until `43k`, then keeps only the kernel's. --- ## Why learn both Rust and an OS - An operating system is where a language's guarantees are tested hardest: no runtime beneath you, nothing to catch a bad pointer - Rust is what new systems code is written in — drivers in Linux, Android, Windows components, most new infrastructure - Learning Rust *on* a kernel means every feature arrives with a reason: ownership because pages have owners, `match` because hardware has states, traits because a scheduler is a policy
By December you will have written a kernel in the language the industry is moving kernels to.
Notes: - Every Rust feature arrives with a kernel reason: ownership because pages have owners, `match` because hardware has states, traits because a scheduler is a policy. - **Q:** "Is Rust really in kernels?" Yes: Linux merges drivers written in Rust, and Android and Windows ship Rust components. The claim is about new systems code, not rewrites. --- ## Why Rust, honestly xv6 is C. Linux is C. The burden of proof is on Rust. Kernel C bugs cluster into a few shapes: - use a pointer after the memory was freed - write past the end of a buffer - read a value another CPU is halfway through writing - free the same page twice
In application code the OS catches these. In kernel code
there is nothing beneath you
. A use-after-free in the allocator does not crash — it hands the same page to two processes, and the symptom appears ten minutes later somewhere unrelated.
Notes: - Four shapes: use after free, a write past a buffer, a value read while another CPU is halfway through writing it, a double free. - In an application the OS catches them. In a kernel nothing is beneath you, so the symptom appears ten minutes later, somewhere unrelated. - **Q:** "Does Rust make these impossible?" Most become compile errors: one owner, and many readers or one writer, never both. That is next week. --- ## An OS is special — so Rust has `unsafe` A kernel must do things no ordinary program does: - execute **privileged instructions** and read hardware registers - control **virtual memory** explicitly — build the page tables everything else relies on - build its **own allocator** — there is no runtime beneath it to ask for memory Rust's answer is not to relax the rules. It is one mechanism — the `unsafe` keyword — that lets you step outside strict memory safety in small, marked places.
The dangerous part of the kernel becomes
small and labeled
instead of being the whole program. That is the argument for Rust, in one sentence.
Notes: - Privileged instructions, every mapping, its own allocator: no compiler can check any of it. - `unsafe` does not relax the rules; it marks where you take responsibility. When memory is corrupted, the suspects are a searchable list. - **Q:** "So is rv6 full of `unsafe`?" No. It is concentrated in a few places you write yourself, starting with `21r`, and the full rules hold everywhere else. --- ## What you bring from CS 315 | You did | Here it becomes | |---|---| | C, with pointers | Rust, with ownership — the compiler checks what you used to check by hand | | RISC-V assembly and the calling convention | the **context switch**: saving one program's registers and restoring another's | | A RISC-V emulator; addresses as numbers | **page tables**: the kernel deciding what every address means | | Digital design: a processor | QEMU running **your** kernel on that processor | Expect to *review* C and RISC-V, not to have memorized them. The review is planned. Notes: - When the borrow checker complains in week 2, it is usually describing a bug you have already written in C. - The context switch in `35k` is the calling convention written out as 14 stores and 14 loads. - **Q:** "I don't remember RISC-V well. Is that a problem?" Expect to review, not to remember: the RISC-V guide and the cheatsheet keep it one page away, and Week 6 reintroduces it. --- ## The semester, end to end
flowchart LR R["Module 1\nRust, commands,\nbridges to bare metal\n00r - 21r"] K0["boot and memory\nno_std, UART,\nallocator, paging\n30k - 33k"] K4["processes and locks\nPCB, context switch,\nscheduler, MMU on\n34k - 39k"] K10["files, traps,\nconsole, kernel shell\n40k - 46k"] K18["user mode\nthe wall, ecall\n48k"] K19["exec, fds, fork,\nuser shell,\nyour commands\n49k - 53k"] R --> K0 --> K4 --> K10 --> K18 --> K19
Each step needs what the one before it built: a stack before any Rust code, an allocator before page tables, a kernel before a program can trap into it. Notes: - The order is forced: a stack before any Rust runs, an allocator before page tables (which are made of pages), a supervisor kernel before anything can trap into it. - Two steps are abrupt, and each is a single instruction: the MMU on in `39k`, and leaving machine mode in `43k`. - **Q:** "Why is `20a` in Module 1 if it boots in QEMU?" It is a bridge: the emulator arrives a week before the kernel, so you never meet QEMU and the borrow checker at once. --- ## The course in two modules | | When | What | Where it runs | |---|---|---|---| | **Module 1** | Aug 27 – Oct 2 | Rust `00r`–`08r`, the commands `10c`–`13c`, two bridges to bare metal `20a` and `21r` | your laptop, `cargo test` (`20a` boots in QEMU) | | **Module 2** | Oct 2 – Dec 4 | the kernel, `30k`–`53k` | QEMU | Midterms **Thursday, October 15** and **Thursday, November 19**. Final exam **Tuesday, December 8**, in class — no exam during finals week.
You should not fight the borrow checker
and
the hardware at once. Module 1 exists so that you never have to.
Notes: - Module 1 runs on your laptop under `cargo test`; only `20a` boots. - You write the shell twice: inside the kernel (`46k`), then as a user program with no privileges (`52k`). Everything hard about operating systems lives in the gap between the two. - **Q:** "Is there an exam in finals week?" No. The final is Tuesday, December 8, in class on the last day of the term. --- ## How the course runs - **Tuesday** — lecture (this), ending with a walk-through of Thursday's Prep page - **Thursday and Friday** — exercise sessions. Every line of code for this course is written **in the room**, on the classroom network - **Before each session** — read its Prep page, linked from the schedule. Your homework is reading, not coding - **Once per laptop** — sign in to the classroom network: join **cs326**, open **signin.cs326**, sign in with your USF account - **Before you leave** — `oslings submit`, passed or not
Why: an AI assistant can write any exercise in this course. So the exercises happen where the assistant is not — and the reading happens where it is.
Notes: - Thursday is 1h45 and Friday 1h30, in the room, with the instructor and TA present. - The prep page says what you will build, what to reread and gives a mental model, without the exercise itself. The better prepared you are, the sooner you finish. - **Q:** "What if I miss a session?" Finish it at a make-up session (office hours, on the classroom network) before the next session begins, for 75%. --- ## Grading at a glance | Component | Weight | |---|---| | Module 1 exercises | 20% | | Module 2 exercises | 30% | | Midterm 1 · Midterm 2 · Final | 15% · 15% · 20% | | Extra credit | up to +3% | Each exercise: **Pass** in class 100% · **Completed at a make-up session** 75% · **Substantial** progress submitted in class 50% · nothing 0%. Unfinished? Complete it at a **make-up session** — office hours, on the **cs326** network — before the next session. Notes: - Exams are on paper, closed book, with the cheatsheet as the one reference. They ask you to trace and explain, not to recall. - Extra-credit exercises are small and released on the day they belong to. - **Q:** "Does a half-finished exercise count?" Substantial progress submitted in class earns 50%, and finishing it at a make-up session raises it to 75%. --- ## OSlings · three commands, every session ```bash oslings update # receive the exercise this session releases oslings # read the lesson, write the code, watch the test oslings submit # commit and push, pass or fail ``` - A **test** exercise passes under `cargo test` on your laptop (Module 1) - A **QEMU** exercise passes when your kernel boots and prints `OSLINGS:PASS` - Grading re-runs the same test on a rebuilt kernel — local state cannot fake it
Live demo
— what a session looks like from your seat.
Notes: - Live demo, five minutes, from a student's seat: `oslings update`, then `oslings`, one edit from red to green, then `oslings submit`. - Grading re-runs the same test on a rebuilt kernel, so local state cannot fake a pass. - **Q:** "Should I submit if it's still red?" Always. What is committed by the end of the session is what earns credit. --- ## How a session runs
flowchart LR G["sign in (once)"] --> L["read the lesson"] --> W["write one file"] W --> R["oslings run"] R -->|fails| H["oslings hint"] H --> W R -->|passes| S["oslings submit"]
- In the room, during the session, on your own keyboard - **No Internet and no AI assistant** while you work it - What you do have: the lecture notes, the guides, **two hints** per exercise, and the TA or instructor Notes: - Two hints per exercise through `oslings hint`. The third, the answer, is never released; the reference solution arrives with the next exercise, once the make-up window has closed. - **Q:** "What if I'm stuck and out of hints?" Raise a hand. The instructor and TA are in the room for exactly that. --- ## Why the work happens in the room rv6 is modeled on **xv6** and **Octox** — both public on GitHub, therefore both in the training data of every large language model. Any model will emit a working page-table walk instantly. Pretending otherwise would be silly, so the course is arranged so the question does not arise: - Exercises are released **at the start of the session that works them** — an unreleased exercise exists in no commit you can fetch - **During a session:** no Internet, no AI assistant - **Outside a session:** use AI freely to *learn* — concepts, code you are reading, compiler errors, practice problems - The reference solution arrives with the next exercise, after the deadline The test is whether you can explain what you submitted. The TA or instructor may ask. Notes: - Say the reason plainly: xv6 and Octox are public, so any model writes a page-table walk instantly. - Outside a session, use AI freely to learn. Do not hand your code to a classmate, in either direction. - **Q:** "Can I use AI to study for the exams?" Yes, outside a session: to explain a concept, decode a compiler error, or make practice problems. The exams are on paper, so what counts is what you can trace yourself. --- ## The payoff ```text commands/src/bin/grep.rs <- one file, written in week 5 | ulib / \ host backend rv6 backend (std: read, (ecall: your write) syscalls) | | your laptop YOUR KERNEL cargo test 53k, December 4 ``` The `grep` you write in **week 5** is the same source file — not a port, the same file — that runs on the kernel you finish in **week 15**. Notes: - The same source file, not a port. The seam is `ulib`, whose two backends are picked by the target you compile for. - `grep` exits 0 on a match, 1 on none, 2 on error. Part II turns that status into a word. - **Q:** "Why not write `grep` against the kernel directly?" You could not test it until December. With `ulib` it runs under `cargo test` in week 5 and on your kernel in week 15, unchanged. --- ## What you will understand by December `grep foo notes.txt`, typed at a `$` prompt, in a shell that is a **user process**, `fork`ed and `exec`ed by another user process, on a kernel that: - boots itself into **supervisor mode** - allocates its own **pages** and builds its own **page tables** - schedules its own **processes** and services its own **interrupts**
Every layer of which you wrote. That is what this course is for.
Notes: - Name each layer and the exercise that builds it: supervisor mode `43k`, pages `32k`, page tables `33k`, processes `34k`–`36k`, interrupts `44k`, the user shell `52k`. - **Q:** "What won't rv6 do?" Files that survive a reboot, for one: a deliberate scope cut. The four jobs are all there. --- ## Summary 1. An OS is defined by **four jobs**, not a product list — and you implement all four 2. Three **privilege modes**: privilege drops by instruction, rises only by trap; a program *asks* 3. The semester has a **forced order**: each step needs the last one's substrate 4. A kernel must step outside the rules; **Rust** makes that part small and labeled 5. The course runs **in the room**: prep before, exercise during, submit before you leave 6. The **payoff**: the `grep` you write in week 5 runs on the kernel you finish in week 15 Notes: - The Part 0 recap, as taught: one sentence per line, then on to the Rust. - **Q:** "What should I read tonight?" The Setup page and Thursday's prep page, then the Week 1 page's Essentials. --- *Part I · Thursday · `00r_hello_rust`* ## Bindings, `const`, and widths ```rust let sectors = 2048; // fixed from here on let mut written = 0; // declared to change written += 512; const SECTOR: usize = 512; // compile time: the type is required let ch: u8 = b'A'; // one byte, 0x41 let now: u64 = 10_000_000; // one second of QEMU's clock ``` - Drop the `mut`: `E0384`, "cannot assign twice to immutable variable" - Widths are the hardware's: `usize` is as wide as an address, 64 bits here; a `u32` counting 10 MHz ticks runs out in about seven minutes - Rust never mixes them: `now + ch` is `E0308`, expected `u64`, found `u8` Notes: - Part I runs 14 minutes over four slides, two of them exam slides. - Read `let mut` as "this name will be assigned again". rv6 names every device address as a `const`, in capitals; leave out the type and `rustc` stops with "missing type for `const` item". - Width is a hardware decision: a UART register is 8 bits, an address 64. Integer `/` drops the remainder: `5000 / 512` is 9, and `%` gives the 392 left over. - **Q:** "Why not let the compiler infer every type?" It infers where it can. A `const` must say, and a bare literal defaults to `i32`, which is rarely what a kernel means. --- ## Hex, by hand ```text 0x A 5 3 C 1010 0101 0011 1100 = 42,300 ``` - Each hex digit stands for four bits: `0b1010_0101` is `0xA5`. Underscores are only for the reader - Each trailing hex zero is four clear low bits: `0x8003_7000`, with three, is 4 KiB-aligned; `0x8003_7010` only 16-byte aligned - `0x8003_7000 − 0x8001_0000` is `0x2_7000` bytes. Drop three hex digits: `0x27`, 39 pages Notes: - On Midterm 1: the "decode the bits" shape, and PS1 Problem 4 (a) and (d). - On this site hex is split every four digits and decimal every three, so `10_000_000` is visibly ten million. A slipped hex digit is off by a factor of sixteen, and the groups make one easy to spot. - **Q:** "Do we convert to decimal on the exam?" Rarely. Work in hex, digit by digit: alignment and page counts fall out of trailing zeros and dropped digits. --- ## The one thing to get right · `00r` ```rust fn seconds_to_ticks(s: u64) -> u64 { s * 10_000_000 } ``` The last line is the **tail expression**, the function's value. Add a `;` after it. What does `rustc` say? `E0308`, "expected `u64`, found `()`", on a body that looks right. The semicolon made the answer a statement, so the block produced `()`.
When the found type is
()
, read the last line first. The
help:
line names the fix: remove this semicolon.
Notes: - Nothing in a signature is inferred. Keep `return` for leaving early; at the end, the bare expression already is the answer. This is the Midterm 1 line "expressions vs statements, and the no-semicolon return". - Red, then green: `oslings run 00r_hello_rust` is `cargo test` in a debug build. A failed `assert_eq!` prints `left`, usually your answer, and `right`, the expected value. - **Q:** "What is `()`?" The unit type: "no value". It is what a statement leaves behind, which is why it appears in almost every `E0308` you meet this week. --- ## Octal, and `as` truncates - `0o` is octal, three bits a digit: `0o755` is `111 101 101`, `rwx r-x r-x` - `as` converts only when you ask. Widening is exact: `7u8 as u64` is 7 - Narrowing keeps the low bits, silently: `300u16 as u8` is 44 - Signed and unsigned keep the bits and change their meaning: `200u8 as i8` is −56 Notes: - On Midterm 1. Octal's one common use is Unix permissions: owner, group, others. `493`, `0x1ED`, `0o755` and `0b1_1110_1101` are one value. - `as` never fails and never warns, and that is its danger. 300 is `0x12C`; the low byte, `0x2C`, is 44. Likewise `0x1234_5678u32 as u8` is `0x78`, and `-1i32 as u32` is `0xFFFF_FFFF`. - ↓ Problem 4 practices hex, octal and `as` together. - **Q:** "Why doesn't `as` panic, like overflow does?" Keeping the low bits is what `as` means, not an error. When you want to hear about a value that does not fit, `u8::try_from(x)` returns an error instead. -- ## Problem 4: hex by hand No calculator. - **(a)** Write `0x5C` in binary and in decimal. - **(b)** A file has mode `0o640`. Write it in binary. Who may read it, and who may write it? - **(c)** Which of `0x8020_3000`, `0x8020_3010` and `0x8020_3008` are 4 KiB-aligned? Which are 16-byte aligned? - **(d)** How far above `0x8010_0000` is `0x8012_C000`, in bytes and in 4 KiB pages? - **(e)** What is `0x8012_C0A4u64 as u8`? -- ## Problem 4 · solution - **(a)** `5` is `0101` and `C` is `1100`: `0b0101_1100`, 64 + 16 + 8 + 4 = **92**. - **(b)** `110 100 000`, or `rw- r-- ---`. The owner reads and writes, the group only reads, others get nothing. - **(c)** Only `0x8020_3000` has its low twelve bits clear, so only it is 4 KiB-aligned. It and `0x8020_3010` are 16-byte aligned; `0x8020_3008` is only 8-byte aligned. - **(d)** `0x2_C000` = 2 × 65,536 + 12 × 4,096 = **180,224 bytes**. Drop three hex digits: `0x2C` = **44 pages**. - **(e)** `as u8` keeps the low byte: `0xA4`, **164**. --- *Part II · Friday · `01r_control_flow`* ## `if` is a value, and three loops ```rust let word = if status == 0 { "matched" } else { "no match" }; for reg in 0..8 { } // 0 to 7: half-open let mut n = 100; let first = loop { n += 1; if n % 7 == 0 { break n; } // the loop's value: 105 }; ``` - `if` takes a `bool` (no truthiness), always braces, and one type in every branch - `0..8` stops before 8, so `0..8` and `8..16` meet with no gap; `0..=8` includes 8 - `while` tests first; a `while` or `for` cannot `break` with a value, a `loop` can Notes: - Part II runs 15 minutes over four slides, one of them an exam slide. `grep`'s exit status from course-payoff becomes a word here. - The errors teach the rules: a number as the condition is `E0308`, expected `bool`, found `i32` (write `status != 0`); a value with no `else` is `E0317`; `break n` in a `while` is `E0571`, because a `while` can end with no `break` to supply a value. - **Q:** "Where is the ternary `? :`?" You just saw it: `if c { a } else { b }` is an expression, so Rust needs no separate operator. -- ## Problem 1: what does the compiler say? Each fails to compile. Name the error and give the smallest fix. ```rust fn kib(n: usize) -> usize { n * 1024; } // (a) fn sign(x: i32) -> i32 { if x < 0 { -1; } else { 1 } } // (b) fn is_odd(n: u32) -> bool { if n % 2 { true } else { false } } // (c) fn total(bytes: u64, extra: u32) -> u64 { bytes + extra } // (d) fn laps() -> u32 { let n = 0; for _ in 0..3 { n += 1; } n } // (e) ``` -- ## Problem 1 · solution - **(a)** `E0308`, expected `usize`, found `()`: no tail expression. Delete the `;`. - **(b)** `E0308` inside a branch: `{ -1; }` has type `()`. Delete the `;` after `-1`. - **(c)** `E0308`, expected `bool`, found `u32`. Write `n % 2 == 1`: the comparison is already the `bool`, so the `if` goes too. - **(d)** `E0308`, expected `u64`, found `u32`, plus `E0277`. Write `bytes + extra as u64`: widening is exact. - **(e)** `E0384`, cannot assign twice to immutable variable `n`. Write `let mut n = 0;`. --- ## Overflow: debug panics, release wraps A `u8` named `x` holds 150 at run time. `x + 120` is 270, which does not fit. | Expression | Debug build | Release build | |---|---|---| | `x + 120` | panics: `attempt to add with overflow` | `14` (270 − 256) | | `x.wrapping_add(120)` | `14` | `14` | | `x.checked_add(120)` | `None` | `None` | | `x.saturating_add(120)` | `255` | `255` | `cargo test` and `oslings run` build debug. It is one bug either way; only debug reports it. Notes: - On Midterm 1: PS1 Problem 4 (b) and (c). A debug build checks every operation; `--release` drops the checks for speed. Subtraction counts too: 1 from a `usize` that holds 0 panics. - Each prefix states a design: `wrapping_` counts modulo 2ⁿ (a hash, a sequence number), `checked_` says there may be no answer, `saturating_` stops at the edge (a volume knob). `sub` and `mul` have all three. - "At run time" matters. With the constant in view, `let x: u8 = 150; x + 120` does not compile: "this arithmetic operation will overflow". PS1 Problem 4 (b) as printed meets that; read it as a value that arrives at run time. - **Q:** "Which one should be my default?" None. Plain `+` where you can show it cannot overflow, and one of the three wherever it can. -- ## Problem 2: debug or release? Give each expression's value in a release build and its behavior in a debug build, for the call `f(180, 100, u32::MAX)`: ```rust fn f(a: u8, b: u8, m: u32) { … } // (1) a + b (5) b - a // (2) a.wrapping_add(b) (6) b.saturating_sub(a) // (3) a.checked_add(b) (7) m + 1 // (4) a * 2 (8) (a as u16) + (b as u16) ``` -- ## Problem 2 · solution | | Debug | Release | Why | |---|---|---|---| | (1) | panics: `attempt to add with overflow` | `24` | 280 − 256 | | (2) | `24` | `24` | the wrap, asked for | | (3) | `None` | `None` | its type is `Option
` | | (4) | panics: `attempt to multiply with overflow` | `104` | 360 − 256 | | (5) | panics: `attempt to subtract with overflow` | `176` | −80 + 256 | | (6) | `0` | `0` | clamped at the bottom | | (7) | panics: `attempt to add with overflow` | `0` | `u32::MAX` wraps to zero | | (8) | `280` | `280` | `u16` holds it: no overflow at all | Rows 1, 4, 5 and 7 are one bug in both columns; only debug says so. Row 8 is often the best fix: widen first. --- ## `Option`, in two arms `to_digit` has no answer for `'x'`, so it returns an `Option
`: `Some(d)`, or `None`. ```rust fn digit_or_zero(c: char) -> u32 { match c.to_digit(10) { Some(d) => d, None => 0, // not a digit: count it as zero } } ``` - Read `Some(d) => d` as "there is a digit; call it `d` and answer with it" - Delete the `None` arm: `E0004`, "non-exhaustive patterns: `None` not covered" Notes: - `None` is a shape of the type, not a null, so forgetting it is a compile error, not a crash. The `checked_` methods return the same type: `250u8.checked_add(10)` is `None`. - `match` is an expression, so the whole `match` is the function's result. Week 3 does enums and `match` in full. - To return one, end each path in `Some(x)` or `None`: `if secs == 0 { None } else { Some(bytes / secs) }`. - **Q:** "Can't I just call `.unwrap()`?" It compiles, and panics on `None`: the missing case becomes a crash at run time instead of an arm you wrote. --- ## The one thing to get right · `01r` A test dies with `attempt to add with overflow`, not a failed assertion, and only for inputs near the type's maximum. What happened? A sum inside the formula overflowed, although the answer fits. The check guards every intermediate result, not only the one you return.
An operation that can overflow needs one of the three methods, or a proof that it cannot.
Notes: - Say an example aloud: the average `(a + b) / 2` of two large `u8`s. The page's Problem 5 works it through. - Small inputs never reach the edge, so ordinary tests stay green. Test at `MAX`. - **Q:** "How do I find the line?" The panic names the file and line ("panicked at src/…"), and a debug build stops at the first operation that overflows. --- ## Summary · your first Rust - A binding is fixed unless `let mut`; a `const` states its type - Every integer names its width; `usize` is as wide as an address - One hex digit is four bits; `as` keeps the low bits - A block's value is its last expression; a stray `;` makes it `()` - `if` and `loop` are values; ranges are half-open - Overflow: debug panics, release wraps. Say which you mean - `Option` is `Some` or `None`, and `match` must cover both Notes: - One sentence per bullet, in the order of the page. Part 0 had its own summary. - **Q:** "How should I study this for Midterm 1?" PS1 Problem 4, then the page's For the exam section and its Problems 2 and 4. --- ## [Before Thursday](../prep/01-cs326-2026-08-27-prep-setup-and-hello-rust/): the setup session Setup first (about 30 minutes: `oslings doctor` green, then a first `oslings submit`), then `00r`: hex constants and one-expression functions under `cargo test`. Prep: about 30 minutes.
Have done: `rustup` installed, a **GitHub account**, a charged laptop.
**1.** What is `0x1000` in decimal, and how can you tell without dividing that `0x8000_0000` is a multiple of it? **2.** What does the compiler say about `fn twice(x: u64) -> u64 { x * 2; }`, and what is the fix? **3.** Pick the type and binding form: a memory address, a UART register, a free-page count that goes down. Notes: - This slide absorbs the Aug 25 deck's own Before Thursday: install Rust (`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`), have a GitHub account, read Setup and the prep, bring a charged laptop (macOS, Linux, or Windows with WSL2). Its closing line still holds: if something breaks on Thursday, that is expected, and the people who can fix it are in the room. - Walk the prep page. Your `oslings-
` repository is created for you; accepting the two invitations is Setup step 3, and you can do it at home. The three questions are the prep's own Check yourself; answers are on the prep page. - **Q:** "What if `oslings doctor` is red on Thursday?" That is what the first 45 minutes are for: the Dev Setup troubleshooting steps, and the two of us in the room. --- ## [Before Friday](../prep/01-cs326-2026-08-28-prep-control-flow/): check yourself `01r`: small `usize` functions that choose, repeat, and do arithmetic at the edge of a number, under `cargo test`. Prep: about 25 minutes. **1.** `fn cap(x: u32) -> u32 { if x > 9 { 9 } else { x }; }` — what does the compiler say, and why? **2.** The allocator's loop tests `p + PGSIZE <= stop`, not `p < stop`. What changes when `stop` is not page-aligned? **3.** `let m = usize::MAX; m + 1` — what happens under `oslings run`, and under `--release`? What would you write instead? Notes: - Walk the prep page (about 25 minutes). What you build: small `usize` functions on your laptop, the page allocator's arithmetic with the pointers removed. The three questions are the prep's own Check yourself; answers are on the prep page. - Question 3 as printed does not compile on current `rustc`: with `usize::MAX` in view, it reports "this arithmetic operation will overflow" before anything runs. Ask it of a value that arrives at run time: debug panics, and release gives 0. - **Q:** "Does `01r` run in QEMU?" No: `cargo test` on your laptop, like all of Module 1 except `20a`. --- ## Next week Week 2: ownership and borrowing. [Read its Essentials](02-cs326-2026-09-01-ownership-and-borrowing/) before Tuesday. Notes: - Thursday `02r_ownership`, Friday `03r_borrowing`: the "one owner" rule from course-rust, made exact. - **Q:** "Is ownership on Midterm 1?" Yes: moves, `Copy`, borrowing and the aliasing rule are all in scope, and Practice Set 1 Part A practices them.