← Back to Course
# Building an Operating System ## CS 326 Operating Systems L01 · August 25, 2026
Course site
usfca-cs-tools.github.io/USF-CS326-F26.github.io
--- ## 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? --- ## 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 — at the level of who runs where - 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 --- ## 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. --- ## 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.** --- ## 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.
--- ## 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 --- ## 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 --- ## 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.
--- ## 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.
--- ## Three privilege modes | Mode | Who runs here | What it can do | |---|---|---| | **Machine** | firmware, and a few lines of rv6 at boot | everything | | **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. --- ## 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.
--- ## 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.
--- ## 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.
--- ## 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. --- ## 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. --- ## 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 December 11–17.
You should not fight the borrow checker
and
the hardware at once. Module 1 exists so that you never have to.
--- ## 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 - **At the start of each session** — register your laptop with the CS 326 class server on the classroom Wi-Fi; that is how the exercise reaches you - **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.
--- ## 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 after class** 75% · **Substantial** progress submitted in class 50% · nothing 0%. Finishing after class: Thursday's exercise by **Thursday 11:59 pm**, Friday's by **Monday 11:59 pm**. --- ## 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.
--- ## How a session runs
flowchart LR G["register"] --> 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 --- ## 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. --- ## 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**. --- ## 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.
--- ## 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 --- ## Before Thursday 1. Install Rust: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` 2. Make sure you have a **GitHub account** 3. Read the **Setup** page and Thursday's **Prep** page — both linked from the schedule 4. Bring a charged laptop: macOS, Linux, or Windows with WSL2 Thursday is the **setup session**: toolchain, repository, OSlings, and your first exercise, `00r_hello_rust`. No coding today.
If something breaks on Thursday, that is expected — the people who can fix it are in the room.