William Caron-Bastarache
IFT 2245 · Operating Systems

Building Operating System Components in C

Five systems written from scratch in C: a Unix shell, a scheduler running on two simulated cores, a virtual memory manager with a TLB and demand paging, a FAT32 reader working on raw disk bytes, and a Turing machine interpreter.

What this is

Coursework for IFT 2245, the operating systems course at Université de Montréal. Five assignments across one semester, each building the thing the course had just finished explaining. Not simulations of the idea in a high-level language: the shell forks, the scheduler runs threads on a shared queue, and the memory manager evicts pages to a backing store because it has fewer frames than pages.

The common thread is that every one of them fails loudly if the memory handling is wrong, which is most of what makes them worth having written.

How it was graded

Hidden test suites on every submission, run automatically. The scheduler went further: it was scored on measured performance against reference implementations, with memory errors subtracted from the total. That combination is unusual for coursework, because passing the tests was not enough on its own.

IFT 2245, Operating Systems, Université de Montréal
C, CMake, pthreads, bison and flex, Check, Valgrind, gdb, Docker, GitHub Actions
Hidden tests, performance against baselines, Valgrind cleanliness
Assignment repositories are not published. Happy to walk through any of it.

The five systems

A tape of cells with a read and write head over one of them
01

Turing machine interpreter

Parses a machine description into transition records, then simulates the tape.

  • The standard library helpers were off limits, so string length, memory copying and line reading were written by hand
  • First project of the course, and where the habit of pairing every allocation with its free started
A command line feeding three processes chained by pipes
02

Unix shell

Tokenizer, parser and executor, written from scratch.

  • Pipelines of any length, plus ;, && and || with short-circuit evaluation
  • Exit status propagated like a real shell, including the convention for a child killed by a signal
  • Hardest part: the parent has to close each pipe's write end, or the reader never sees end of file and the pipeline hangs
A queue of processes passing through a lock into two cores
03

Scheduler on two cores

A ready queue shared by two worker threads, scored on speed rather than just correctness.

  • Guarded by a mutex and a condition variable, with waiting in a loop so a spurious wake-up cannot pop an empty queue
  • Shutdown travels through the queue as a sentinel, not a flag every thread polls
  • Dequeued nodes are freed after the unlock, keeping the critical section short
  • Judged against First Come First Served, Shortest Job First and Round Robin, plus references we never saw
  • Tuning: swept the time quantum across eight values on the fifty-process benchmark. Too small and context switches eat the gain, too large and it degrades to first come first served
Eight virtual pages mapping into three physical frames, one spilling to disk
04

Virtual memory manager

Paging over a 16-bit address space: 256 pages of 256 bytes, against only 32 physical frames.

  • Addresses split with a mask and a shift
  • Resolved through the TLB, then the page table, then a fault that loads from a backing store file
  • A reverse frame-to-page map lets an eviction invalidate the right page table entry, and only dirty pages get written back
  • Reports its own TLB hit rate and page fault count, which is what makes the cost of a miss concrete
A chain of clusters read out of a grid of raw disk bytes
05

FAT32 reader

Reads a real filesystem image as raw bytes.

  • Fields assembled byte by byte rather than casting a struct over the buffer, so host byte order does not matter
  • Sector size and cluster geometry read from the boot block instead of assumed
  • Walks the cluster chain to resolve a path, normalising short names and skipping deleted and long-filename entries

What these exercises actually teach

A cache miss stops being abstract

When your own code prints the TLB hit rate every run, you watch it move as you change the replacement policy.

Lock scope is a decision, not a style

Whether a free happens inside or outside the critical section is how long every other thread waits.

Defaults deserve a benchmark

A time quantum looks like a constant somebody already chose correctly. It is a parameter with a measurable objective attached.

Course coverage

AreaTopics
ProcessesProcess control blocks, state transitions, context switching, fork and exec, inter-process communication, shared memory, pipes
ThreadsThreads versus processes, user and kernel threads and the mappings between them, pthreads, thread pools, thread-local storage, Amdahl's law
SynchronisationRace conditions, critical sections, mutexes, semaphores, condition variables, classic synchronisation problems
SchedulingFirst come first served, shortest job first, shortest remaining time, round robin, priority and multilevel feedback queues, burst estimation, real-time scheduling, processor affinity and load balancing, Little's law
DeadlocksThe four necessary conditions, resource allocation graphs, prevention versus avoidance, the banker's algorithm, detection and recovery
MemoryBase and limit registers, address binding, paging and segmentation, page table sizing, the memory management unit
Virtual memoryDemand paging, page fault handling and cost, replacement policies, working sets, thrashing
StorageFile system interfaces and implementation, allocation methods, free space management, disk scheduling, containerisation
C itselfMemory layout across text, data, heap and stack, struct alignment and padding, dynamic allocation and the discipline around it, debugging with gdb and Valgrind

Contact Me

Interested in this project? Write me.