COMP 3150Operating Systems

01 Overview

CourseCOMP 3150 / CMIS 3150 — Operating Systems
Weight20% of the final grade
IssuedWeek 10 (mid-Module 3)
Proposal dueWeek 11 (end of Module 3)
Final submissionWeek 17 (end of Module 5, before examination period)
DemonstrationWeek 18 (during the examination period)
Estimated effort50–60 hours over 7 weeks
SubmissionOne archive containing source code, Makefile, tests, test logs, design document, technical report, and demonstration materials (see § 11)
EnvironmentLinux VM, gcc / clang, make, git, gdb, valgrind, perf, strace, and the toolchain required by your chosen project (see § 04–§ 08)
CLOs assessedCLO1, CLO2, CLO3, CLO4, CLO5, CLO6, CLO7, CLO8

The final project is worth 20% of the final grade. Late submission penalty: 10% per day up to three days. After three days, the project is accepted only with documented extenuating circumstances. The demonstration is a required component; a project submitted without a demonstration receives a maximum of 60% of the project grade.

02 Learning outcomes assessed

On successful completion of the final project you will be able to

  • Design a substantial software system that applies multiple operating-system concepts in an integrated way.
  • Implement the system with clean, modular, well-tested code that compiles without warnings and runs without leaks or data races.
  • Validate the system with a rigorous test suite, including unit tests, integration tests, stress tests, and failure-injection tests.
  • Measure the system's performance and interpret the results quantitatively, using appropriate metrics and methodology.
  • Document the design, implementation, testing, and findings in a professional technical report.
  • Demonstrate the working system to an audience, explain the design decisions, and answer questions about the code and the trade-offs.
  • Reflect on the relationship between the abstraction (the textbook concept) and the implementation (the code), and identify the sources of complexity in real systems.

The final project assesses all eight course learning outcomes (CLO1–CLO8), with emphasis on CLO3 (scheduling, synchronization, deadlock), CLO4 (memory and virtual-memory management), CLO5 (file systems, storage, I/O), CLO6 (Linux systems programming), CLO7 (security, virtualization, cloud), and CLO8 (design and implementation).

03 Choose your project

You must choose exactly one of the five project options below. Each option is a substantial undertaking that requires 50–60 hours of work, and each integrates multiple course topics. Read all five before deciding, discuss your choice with the instructor during office hours, and submit a one-page proposal (see § 10) before you begin.

Option A — Educational Operating-System Kernel

Build a small but complete operating-system kernel for x86-64 that boots, manages memory, schedules processes, handles interrupts, and runs a simple user-space program.

Topics: boot process, paging, process scheduling, interrupts, system calls, device drivers.

Option B — Container Runtime

Build a container runtime from scratch — including image pulling, root file-system assembly, namespace and cgroup configuration, and container lifecycle management.

Topics: namespaces, cgroups, capabilities, seccomp, layered file systems, networking.

Option C — Type-2 Hypervisor

Build a type-2 hypervisor that uses KVM to run a guest virtual machine, including VM creation, memory mapping, virtual CPU management, and virtio-based I/O.

Topics: CPU virtualisation, memory virtualisation, virtio, live migration, snapshotting.

Option D — Distributed/Cloud OS Component

Build a component of a distributed operating system — a replicated key-value store, a cluster scheduler, or a distributed file system — with fault tolerance and consistency.

Topics: replication, consensus, consistency models, fault tolerance, cluster scheduling.

Option E — OS Observability and Diagnostic Suite

Build a suite of tools that observe and diagnose the behaviour of a running Linux system: a system-call tracer, a scheduler analyser, a memory profiler, and an I/O observer, integrated into a single dashboard.

Topics: ptrace, eBPF, /proc and /sys, perf events, time-series analysis.

How to choose

Choose the option that (a) interests you most, (b) builds on what you already know from the assignments and labs, and (c) is feasible within the time available. The kernel (Option A) and the hypervisor (Option C) are the most technically demanding; the container runtime (Option B) and the observability suite (Option E) are the most practical; the distributed component (Option D) is the most open-ended. None of the options requires a specific prior experience beyond the course, but each will demand significant independent learning. If you are unsure, schedule an office-hours meeting to discuss your background and interests before the proposal is due.

04 Option A — Educational Operating-System Kernel

4.1 Goal

Build a small but complete operating-system kernel for x86-64. The kernel must boot on QEMU, set up a basic memory-management environment, schedule processes, handle interrupts and system calls, and run a user-space program that prints output to the screen or to the serial port. The kernel is not required to be POSIX-compatible or to run existing binaries; it must demonstrate the concepts taught in the course.

4.2 Functional requirements

  1. A-F1 — Boot. The kernel boots from a Multiboot-compatible bootloader (GRUB, or a custom Multiboot header). It initialises the serial port (COM1) for output, sets up a GDT and IDT, and prints a startup banner.
  2. A-F2 — Memory management. The kernel parses the memory map provided by the bootloader, sets up a physical-memory allocator (a bitmap or a free-list), and enables paging with a kernel page directory and page tables. It provides functions to allocate and free physical pages and to map and unmap virtual pages.
  3. A-F3 — Interrupts and exceptions. The kernel installs handlers for the CPU exceptions (page fault, general protection fault, divide-by-zero, breakpoint) and for at least the timer interrupt. The handlers print diagnostic information and, for a page fault, report the faulting address and the error code.
  4. A-F4 — Timer and scheduling. The kernel programs the PIT or the local APIC timer to fire at a fixed rate. It implements a scheduler (Round Robin, priority, or MLFQ — your choice; justify in the report) that switches between kernel threads. The kernel supports at least four concurrent kernel threads, each of which prints a message periodically.
  5. A-F5 — System calls. The kernel implements at least the following system calls: write (write a string to the console), exit (terminate the calling process), fork or spawn (create a new process), and yield (voluntarily give up the CPU). System calls are invoked from user space via the int 0x80 instruction or the syscall instruction.
  6. A-F6 — User mode. The kernel switches to user mode (ring 3) to run a user-space program. The program is loaded from a small, custom file format (for example, a flat binary with a header) or embedded in the kernel image at boot. The program calls write to print a message and exit to terminate.
  7. A-F7 — Fault isolation. A user-space program that triggers a page fault or an illegal instruction is terminated by the kernel with a diagnostic message, and the kernel continues to run.
  8. A-F8 — Console output. The kernel supports formatted output (at least printf-like with %d, %x, %s, %p, and %%) to the serial port. All diagnostic and user output uses this facility.

4.3 Required analyses

  1. Boot trace. Capture the complete serial output from boot to the first user-space program and annotate each phase (GDT/IDT setup, memory-map parsing, paging enabled, timer initialised, scheduler started, first system call).
  2. Context-switch cost. Measure the time (in timer ticks or, if you have a high-resolution timer, in nanoseconds) for a context switch between two kernel threads. Repeat 1,000 times and report the mean and standard deviation.
  3. Page-fault handling. Instrument the page-fault handler to count faults of each type (not-present, protection, user/kernel) and report the counts at the end of a run. Explain the expected counts in terms of the user program's behaviour.
  4. Scheduler comparison. If you implement more than one scheduling policy, compare them on a workload of four kernel threads that alternate CPU bursts with short sleeps. Report the average waiting time and the number of context switches for each policy.
  5. Source of complexity. Identify the three parts of the kernel that were most difficult to implement, and explain why — referring to hardware specifics (GDT format, page-table layout, interrupt stack discipline) where relevant.

4.4 Resources

  • OSDev Wiki (wiki.osdev.org) — the standard reference for hobby OS development.
  • Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3 (System Programming Guide).
  • Bran's Kernel Development Tutorial (a classic step-by-step guide).
  • Silberschatz et al., Chapters 1–2 (OS structures) and 9–10 (memory management).
  • Tanenbaum & Woodhull, Operating Systems: Design and Implementation (for a complete small-kernel case study).

05 Option B — Container Runtime

5.1 Goal

Build a container runtime from scratch — that is, a program that can pull a container image from a registry, assemble a root file system from the image's layers, launch a process in that file system with its own namespaces and cgroup limits, and manage the container's lifecycle (start, stop, status, logs, remove). The runtime is not required to be compatible with the OCI runtime specification in every detail, but it must implement the core mechanisms that real runtimes use.

5.2 Functional requirements

  1. B-F1 — Image pulling. The runtime accepts an image reference (for example, alpine:3.19 or ubuntu:22.04) and pulls the image's layers from a registry (Docker Hub or a local registry). It parses the image manifest, downloads the layers (by digest), and verifies each layer's digest.
  2. B-F2 — Root file-system assembly. The runtime assembles the container's root file system by applying the image's layers in order to a fresh directory. Each layer is a tar archive (in the OCI/Docker format); the runtime extracts each layer, handling whiteouts (deletion markers) correctly.
  3. B-F3 — Namespaces. The runtime launches the container's process in new mount, PID, UTS, IPC, network, and user namespaces. It sets the hostname (UTS), maps UIDs and GIDs (user), and configures the loopback interface in the network namespace.
  4. B-F4 — cgroups. The runtime creates a cgroup for the container, sets CPU and memory limits (configurable), and places the container's process in the cgroup. It reads the container's resource usage (CPU time, memory peak) from the cgroup files.
  5. B-F5 — Capabilities and seccomp. The runtime drops all capabilities by default and grants only those specified in the container configuration (for example, CAP_NET_BIND_SERVICE). It installs a seccomp filter that denies dangerous system calls (for example, ptrace, reboot, kexec_load) unless explicitly allowed.
  6. B-F6 — Lifecycle management. The runtime supports the following subcommands:
    • create <image> <name> — assemble the root file system and create a container definition.
    • start <name> — launch the container's process.
    • exec <name> <command> — run an additional command inside a running container.
    • ps — list containers and their states.
    • logs <name> — print the container's stdout/stderr.
    • stop <name> — send SIGTERM, wait, then SIGKILL.
    • rm <name> — remove the container and its root file system.
  7. B-F7 — State persistence. The container's metadata (image reference, root file-system path, cgroup path, PID, status) is persisted to disk so that the runtime's state survives across invocations.
  8. B-F8 — Logging. The container's stdout and stderr are captured (via a pipe or a file) and can be retrieved with the logs subcommand.

5.3 Required analyses

  1. Isolation verification. For each namespace, demonstrate that the container's process sees a different view than the host: process list (PID), hostname (UTS), mount points (mount), network interfaces (network), user IDs (user). Include the output of the relevant commands from inside and outside the container.
  2. Resource-limit verification. Run a CPU-bound and a memory-bound workload inside a container with a CPU quota and a memory limit. Show that the limits are enforced (using time, top, or the cgroup files) and that the container is terminated or throttled when it exceeds them.
  3. Capability and seccomp verification. Show that the container cannot perform an operation that requires a dropped capability (for example, binding a low-numbered port, or mounting a file system) and cannot perform a system call that the seccomp filter denies (for example, ptrace). Contrast with a container that has the capability granted.
  4. Startup time. Measure the time from start to the first line of output for a small container (for example, alpine running echo hello). Report the mean and standard deviation over 20 runs. Compare to docker run on the same image and explain any difference.
  5. Comparison with Docker. For at least three container operations (start, exec, stop), compare your runtime's behaviour with Docker's. What does Docker do that your runtime does not? What does your runtime do that Docker does not? Explain the differences in terms of the design choices each makes.

5.4 Resources

  • Open Container Initiative (OCI) runtime and image specifications.
  • Linux man-pages: man 7 namespaces, man 7 cgroups, man 2 unshare, man 2 clone, man 2 setns, man 2 seccomp, man 7 capabilities.
  • Turnbull, J. The Docker Book (for background on the container model).
  • Kane, S. P., & Matthias, K. Docker: Up & Running.
  • Source code of runc and crun (available on GitHub) — for reference, not for copying.

06 Option C — Type-2 Hypervisor

6.1 Goal

Build a type-2 hypervisor that uses Linux's KVM subsystem to run a guest virtual machine. The hypervisor creates a VM, maps guest memory into the VM's address space, sets up a virtual CPU, loads a guest kernel image, and runs the guest until it executes a privileged instruction (which the hypervisor emulates) or exits. The hypervisor is not required to be a full VMM; it must demonstrate the core mechanisms of CPU and memory virtualisation.

6.2 Functional requirements

  1. C-F1 — KVM interface. The hypervisor opens /dev/kvm, creates a VM with KVM_CREATE_VM, creates a virtual CPU with KVM_CREATE_VCPU, and manages the vCPU's memory (the kvm_run structure) via mmap.
  2. C-F2 — Guest memory. The hypervisor allocates a region of memory for the guest (for example, 64 MB), maps it into the VM's address space with KVM_SET_USER_MEMORY_REGION, and loads a guest kernel image (a small binary or an existing kernel) into that region.
  3. C-F3 — CPU state. The hypervisor initialises the vCPU's registers (via KVM_GET_SREGS and KVM_SET_SREGS and the general-purpose registers) so that the guest starts in a defined state (for example, 16-bit real mode for a BIOS-style boot, or 32/64-bit protected mode for a custom kernel).
  4. C-F4 — Run loop. The hypervisor runs the guest in a loop: KVM_RUN enters the guest, and the guest exits for one of several reasons (I/O port access, MMIO, halt, shutdown, or an error). The hypervisor inspects the exit reason, handles it, and re-enters the guest.
  5. C-F5 — I/O emulation. The hypervisor emulates at least the serial port (COM1) and one additional device (for example, the PIT timer or a simple block device). Guest output to the serial port is printed to the host's standard output; guest input from the serial port is read from the host's standard input.
  6. C-F6 — Guest boot. The hypervisor boots a real guest: a small custom kernel that prints a message and halts, or an existing kernel (for example, a minimal Linux kernel built for the purpose) that boots far enough to print a message on the serial console.
  7. C-F7 — Virtio. The hypervisor implements at least one virtio device (for example, virtio-console or virtio-blk) using the virtio MMIO or PCI transport. The guest uses the virtio driver to communicate with the hypervisor through a shared ring buffer.
  8. C-F8 — Snapshot and restore (optional, for extra credit). The hypervisor can save the VM's state (memory, CPU registers, device state) to a file and restore it later.

6.3 Required analyses

  1. Exit-reason histogram. Instrument the run loop to count how many times the guest exits for each reason (I/O, MMIO, halt, etc.). Report the histogram for a boot of the guest kernel and explain the expected distribution.
  2. Context-switch cost. Measure the time for a round-trip from host to guest and back (the time to execute KVM_RUN plus the time the guest runs before exiting). Report the mean and standard deviation over 1,000 round-trips. Compare to the cost of a regular system call on the same machine.
  3. Guest vs native. Run a small CPU-bound benchmark (for example, a loop that computes a hash) inside the guest and on the host. Report the ratio of the times. Explain the overhead in terms of the guest's privileged instructions and the hypervisor's emulation.
  4. Memory virtualisation. Measure the guest's memory throughput (for example, with a memcpy benchmark) and compare it to the host's. If you implemented nested paging (EPT), measure the improvement relative to a shadow-paging implementation, if you also implemented shadow paging for comparison.
  5. Device emulation overhead. Compare the cost of an I/O operation through the emulated device (for example, a serial write) with the cost through the virtio device. Report the ratio and explain the difference.

6.4 Resources

  • Linux kernel documentation: Documentation/virt/kvm/ (API documentation, including the KVM_RUN interface).
  • The KVM source tree, especially arch/x86/kvm/ (for reference, not for copying).
  • Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3, Chapters 23–33 (VMX and virtualisation).
  • The kvmtool project (a small, readable VMM in userspace, on GitHub).
  • Rust-VMM / Firecracker (a production-grade microVM implementation, for reference).
  • Silberschatz et al., Chapter 18 (virtual machines).

07 Option D — Distributed/Cloud OS Component

7.1 Goal

Build one component of a distributed operating system — a replicated key-value store, a cluster scheduler, or a distributed file system — with fault tolerance, consistency, and scalability as first-class design goals. The component runs on a small cluster (at least three nodes) simulated on a single machine or on several VMs, and must continue to function correctly when a node fails.

7.2 Functional requirements

Choose one of the following three sub-options. Each has its own functional requirements; all share the same non-functional requirements (§ 7.3).

Sub-option D1 — Replicated key-value store

  1. D1-F1 — API. The store supports put(key, value), get(key), and delete(key). Values are byte strings of arbitrary length (up to a configurable maximum).
  2. D1-F2 — Replication. Each key-value pair is replicated on at least three nodes. The replication strategy is configurable (primary-backup, quorum, or chain replication).
  3. D1-F3 — Consistency. The store provides one of: linearizability, sequential consistency, or eventual consistency. The choice must be documented and its consequences explained.
  4. D1-F4 — Fault tolerance. If a node fails, the store continues to serve reads and writes from the surviving nodes and, when the failed node recovers, brings it up to date.
  5. D1-F5 — Rebalancing. When a node joins or leaves the cluster, the store redistributes keys so that the replication factor is maintained.
  6. D1-F6 — Client library. A small client library (in C or Python) that hides the distribution from the application, sends requests to the appropriate node, and retries on failure.

Sub-option D2 — Cluster scheduler

  1. D2-F1 — Job model. The scheduler accepts jobs (from a client) that specify a command, a resource requirement (CPU and memory), and a priority. Each job runs as one or more tasks on a worker node.
  2. D2-F2 — Placement. The scheduler places tasks on worker nodes according to a policy (bin-packing, spreading, or fair-share — your choice; justify). It monitors each worker's resource usage and reschedules tasks when a worker fails.
  3. D2-F3 — Worker agent. Each worker runs an agent that registers with the scheduler, reports its resources, launches and monitors tasks, and reports task completion.
  4. D2-F4 — Fault tolerance. If a worker fails, its tasks are rescheduled on surviving workers. If the scheduler fails, a standby scheduler takes over (with state replicated from the primary).
  5. D2-F5 — Queue management. The scheduler maintains a queue of pending jobs, applies the placement policy to select the next job, and supports priority-based preemption.
  6. D2-F6 — Observability. A status endpoint reports the cluster state: which nodes are up, which jobs are running, and how resources are allocated.

Sub-option D3 — Distributed file system

  1. D3-F1 — API. The file system supports create, open, read, write, close, unlink, and stat on files identified by a path (with at least one level of directories).
  2. D3-F2 — Chunked storage. Files are split into fixed-size chunks (for example, 64 KB) that are distributed across storage nodes. Each chunk is replicated on at least three nodes.
  3. D3-F3 — Metadata service. A metadata service (itself replicated for fault tolerance) maps file paths to chunk locations. The metadata service is the single source of truth for the file namespace.
  4. D3-F4 — Consistency. The file system provides a well-defined consistency model (for example, close-to-open consistency), documented in the report.
  5. D3-F5 — Fault tolerance. If a storage node fails, reads and writes continue from the surviving replicas, and the failed node's chunks are re-replicated on the surviving nodes.
  6. D3-F6 — Client library. A client library (in C or Python) that implements the file-system API by communicating with the metadata service and the storage nodes.

7.3 Non-functional requirements (all sub-options)

  1. D-N1 — Fault injection. The system includes a fault-injection mechanism (a script or a command) that can kill a node, delay network messages, or partition the network. The fault-injection mechanism is used in the tests.
  2. D-N2 — Determinism. Given the same sequence of operations, the same set of nodes, and the same seed for any randomness, the system's behaviour is reproducible.
  3. D-N3 — Observability. Each node exposes a status endpoint that reports its state (role, peers, resource usage) and a log of the operations it has processed.
  4. D-N4 — Configuration. The cluster is configured by a file (or command-line arguments) that specifies the node addresses, the replication factor, the consistency model, and the fault-tolerance parameters.
  5. D-N5 — Scalability. The system's throughput scales with the number of nodes, at least up to five nodes, for a workload that is not bottlenecked by a single key.

7.4 Required analyses

  1. Fault-tolerance scenarios. For each fault scenario (node failure, network partition, scheduler failure), report: the time to detect the failure, the time to recover, the impact on the client (how many operations failed), and the consistency of the state after recovery.
  2. Performance under failure. Measure the throughput and latency of the system under normal operation and under each fault scenario. Plot the two and interpret the difference.
  3. Consistency model. Design and run a test that demonstrates the consistency model in action. For a linearizable system, show that a read after a write sees the write. For an eventually consistent system, show that the read may see a stale value and bound the staleness.
  4. Scalability. Measure the throughput as the number of nodes increases from one to five. Plot the speedup and compare to the prediction of Amdahl's Law (using the coordination overhead as the serial fraction).
  5. Comparison with a real system. Compare your system's design and behaviour with a real distributed system (for example, etcd, ZooKeeper, Cassandra, or HDFS). In what ways is your system simpler? What did you omit that the real system needs? What did you choose to do differently, and why?

7.5 Resources

  • Tanenbaum, A. S., & Van Steen, M. Distributed Systems (available free online).
  • Kleppmann, M. Designing Data-Intensive Applications. O'Reilly, 2017.
  • Lamport, L. Time, Clocks, and the Ordering of Events in a Distributed System. Communications of the ACM, 1978.
  • Ongaro, D., & Ousterhout, J. In Search of an Understandable Consensus Algorithm (Raft). USENIX ATC, 2014.
  • DeCandia, G., et al. Dynamo: Amazon's Highly Available Key-Value Store. ACM SOSP, 2007.
  • Ghemawat, S., Gobioff, H., & Leung, S.-T. The Google File System. ACM SOSP, 2003.

08 Option E — OS Observability and Diagnostic Suite

8.1 Goal

Build a suite of tools that observe and diagnose the behaviour of a running Linux system. The suite integrates several independent tools — a system-call tracer, a scheduler analyser, a memory profiler, and an I/O observer — into a single dashboard that presents the data in a form an operator or developer can use to understand the system's behaviour. The suite is used to answer specific questions about a workload's performance.

8.2 Functional requirements

  1. E-F1 — System-call tracer. The tracer attaches to a running process (or launches one), records every system call with its arguments and return value, and reports the calls grouped by type and by duration. It must use ptrace (the classic mechanism) and demonstrate that it can handle at least the common calls (read, write, open, close, mmap, fork, execve, clone).
  2. E-F2 — Scheduler analyser. The analyser reads /proc/<pid>/sched and /proc/<pid>/status periodically and records the process's scheduling behaviour: how much CPU time it receives, how often it is preempted, how long it waits in the ready queue, and how often it migrates between CPUs. It presents this data as a time series.
  3. E-F3 — Memory profiler. The profiler reads /proc/<pid>/smaps and /proc/<pid>/status periodically and records the process's memory usage: resident set size, virtual size, shared and private pages, and the breakdown by mapping (heap, stack, shared libraries, anonymous mappings). It presents the memory usage over time and identifies the largest consumers.
  4. E-F4 — I/O observer. The observer reads /proc/<pid>/io and /proc/diskstats periodically and records the process's I/O behaviour: bytes read and written, the rate of system calls, and the time spent blocked on I/O. It presents the data as a time series and correlates it with the system-call trace.
  5. E-F5 — Dashboard. The dashboard integrates the four tools into a single view. It shows the target process's CPU, memory, and I/O usage as time-series charts, and it shows the system-call trace as an annotated timeline. It allows the user to select a time range and to drill down into the calls that occurred in that range.
  6. E-F6 — Analysis reports. The dashboard produces a text report (or a machine-readable JSON file) that summarises the observed behaviour: the top 10 system calls by count and by total time, the peak memory usage, the total I/O, and any anomalies the tool has detected (for example, a period of very high system-call latency, or a memory leak).
  7. E-F7 — eBPF extension (optional, for extra credit). Implement at least one of the four tools using eBPF (via BCC or libbpf) instead of the /proc-based approach, and compare the two implementations on overhead and accuracy.

8.3 Required analyses

  1. Overhead. Measure the overhead of each tool on a target process: how much slower the process runs when the tool is attached, and how much CPU the tool itself consumes. Report the overhead as a percentage and explain the source (for example, ptrace stops the target on every system call).
  2. Accuracy. Design a workload with known behaviour (for example, a program that reads a known number of bytes and calls a known number of system calls). Verify that each tool reports the expected values. Discuss the sources of inaccuracy (sampling error, missed events, kernel-side accounting).
  3. Case study. Use the suite to diagnose the behaviour of a real program. Choose a program with non-trivial behaviour (for example, a web server under load, a compiler compiling a large project, or a database running a query). Present the data the suite produced and write a short diagnosis: what is the program doing, where is it spending its time, and what would you change to improve its performance?
  4. Comparison with existing tools. Compare each of your tools with an existing tool that performs the same function (strace for the tracer, pidstat for the scheduler, pmap or valgrind --tool=massif for the memory profiler, iostat for the I/O observer). What does your tool do better? What does the existing tool do better? Why?
  5. eBPF comparison (if implemented). Compare the eBPF implementation of one tool with the /proc-based implementation. Report the overhead of each and the accuracy of each, and explain the trade-offs.

8.4 Resources

  • Linux man-pages: man 2 ptrace, man 5 proc, man 2 perf_event_open, man 1 strace, man 1 ltrace.
  • Gregg, B. Systems Performance: Enterprise and the Cloud, 2nd ed. Addison-Wesley, 2020.
  • Gregg, B. BPF Performance Tools. Addison-Wesley, 2019.
  • Linux kernel documentation: Documentation/trace/, Documentation/bpf/.
  • The BCC and libbpf projects on GitHub (for the optional eBPF extension).

09 Common requirements for all options

The following requirements apply to all five options. They are the non-functional requirements that distinguish a professional project from a working prototype.

9.1 Technical design

  1. The project is decomposed into modules with clear interfaces. Each module has a header file that declares its public interface, and a source file that implements it.
  2. The code has no global mutable state other than the project's own well-documented state structures. Data is passed explicitly to the functions that need it.
  3. Every system call's return value is checked. Errors are reported with context (what was being done, which argument, which file). The program does not crash on an expected error (for example, a missing file, a permission denial, or a network timeout).
  4. The project uses at least one concurrency mechanism (threads, processes, or asynchronous I/O) and demonstrates correct use of synchronization primitives where shared state is involved.
  5. The design is documented in a design document (see § 10) that explains each module's responsibility and the interfaces between them.

9.2 Testing and validation

  1. The project includes a test suite with at least 20 automated tests. The tests are run by a single command (for example, make test or ./run-tests.sh). The output is a pass/fail summary and a coverage report.
  2. The test suite covers: unit tests for each module, integration tests for the main workflows, stress tests (with large inputs or many concurrent operations), and failure-injection tests (using the mechanism described in the option's requirements).
  3. The project runs cleanly under valgrind --leak-check=full (and, where threads are used, under valgrind --tool=helgrind or valgrind --tool=drd). Any exceptions are documented in the report.
  4. The project includes a benchmark or measurement script that reproduces the performance data in the report. The script takes the parameters (for example, the number of nodes or the workload size) as arguments and produces machine-readable output.

9.3 Documentation and professional quality

  1. The project is hosted in a Git repository with meaningful commit history. Each commit has a clear message that describes what changed and why. The repository is included in the submission as a Git bundle or as a directory with the .git/ subdirectory.
  2. The project includes a README.md with: a one-paragraph description of the project, build instructions, usage examples, and a summary of the tests and how to run them.
  3. Every source file begins with a header comment stating its purpose, its author, and the date. Every non-trivial function is documented with a short comment stating its contract (what it does, what it requires of its arguments, what it returns, and how it can fail).
  4. The code compiles with gcc -Wall -Wextra -Werror -O2 (or clang with equivalent flags) without warnings. If the project is in a language other than C, it compiles with the strictest reasonable warnings for that language.
  5. The project has a Makefile (or equivalent build system) with a default target, a clean target, and a test target.

9.4 Ethics and safety

  1. If your project involves running untrusted code (container runtime, hypervisor), you must run it in a virtual machine or a sandbox that you control. Do not run untrusted code on the shared university infrastructure.
  2. If your project involves network communication (distributed component), do not use the university network for experiments that could interfere with other users. Use a private network (a virtual switch, or loopback with multiple ports).
  3. If your project involves security testing (observability suite, security-related components), do not attack systems you do not own. Use test systems and test data.

10 Proposal and milestones

10.1 Project proposal

Before you begin, submit a one-page project proposal (in Markdown or PDF) that contains:

  1. The project option you have chosen and a one-sentence statement of the project's goal.
  2. A list of the functional requirements you will implement, with an indication of which are core and which are optional or for extra credit.
  3. A sketch of the architecture (a diagram and a paragraph of prose) showing the main modules and the interfaces between them.
  4. A list of the technologies and libraries you will use, and a note on any you will need to learn.
  5. A schedule that maps the seven weeks to the milestones in § 10.2, with a note on the risks and how you will mitigate them.

The proposal is reviewed by the instructor and returned with comments within one week. You may begin implementation as soon as you have received the comments. If the proposal is not submitted by the deadline, the project is subject to a 5% penalty.

10.2 Milestones

The final project spans seven weeks. The milestones below are not graded separately, but they are checked during office hours and are a strong predictor of the final grade.

Seven-week schedule
Week Milestone Artefact
Week 10 Proposal One-page proposal as described in § 10.1.
Week 11 Skeleton and interfaces The project compiles and runs a "hello world" for each major module. All interfaces are defined. The Makefile works. The Git repository is set up.
Week 12 Core functionality The main workflow of the project works end-to-end on a simple input. The first tests pass.
Week 13 Full functionality All core requirements are implemented. The optional requirements are either implemented or explicitly deferred.
Week 14 Testing and validation The full test suite passes. Valgrind is clean (or the exceptions are documented). The benchmark script reproduces the performance data.
Week 15 Analysis and report writing All required analyses are complete. The report is drafted.
Week 16 Review and polish The code is reviewed against the common requirements. The report is revised. The demonstration is rehearsed.
Week 17 Final submission The archive is assembled and submitted. The design document and the report are complete.
Week 18 Demonstration A 20-minute live demonstration (see § 12) followed by a 10-minute question-and-answer session.

11 Submission instructions

Submit a single archive named fp-<lastname>-<studentnumber>.tar.gz containing the following files and directories. The structure below is generic; adapt the file names to your chosen option.

  • src/ — the source code, organised into modules with clear interfaces. Every source file compiles with the project's warning flags.
  • include/ — the public header files, if your project separates headers from sources.
  • Makefile — the build system with default, clean, and test targets.
  • tests/ — the test suite:
    • tests/unit/ — unit tests for each module.
    • tests/integration/ — integration tests for the main workflows.
    • tests/stress/ — stress tests.
    • tests/fault/ — failure-injection tests (for Options B, C, and D).
    • tests/run-tests.sh — the script that runs all the tests and prints the summary.
  • bench/ — the benchmark and measurement scripts, plus the raw output files from the runs reported in the report.
  • configs/ — configuration files for the various experiments, with comments.
  • logs/ — the outputs of the required analyses and the validation runs:
    • logs/valgrind.txt — leak-check output.
    • logs/helgrind.txt or logs/drd.txt — race-detector output (if applicable).
    • logs/analysis.txt — the output of the required analyses.
    • logs/demo.txt — the transcript of the rehearsal of the demonstration.
  • docs/ — the documentation:
    • docs/design.md (or .pdf) — the design document.
    • docs/report.pdf (or .md) — the technical report (§ 13).
    • docs/demo-slides.pdf — the slides used in the demonstration (optional but recommended).
  • README.md — the one-page project description with build and usage instructions.
  • project.bundle — a Git bundle of the project's repository (git bundle create project.bundle --all), so that the commit history is preserved.

Archive hygiene

Do not include compiled objects (*.o), executables, the generated large test inputs, or editor backup files. The archive should be self-contained: after extraction, make should build the project, and tests/run-tests.sh should run the tests. If your project requires a large input that cannot be included, provide a script that generates it from a seed.

12 Demonstration

The demonstration is a required component of the final project. It is a 20-minute live presentation followed by a 10-minute question-and-answer session, scheduled during the examination period (Week 18). The demonstration is worth 10% of the project grade; a project submitted without a demonstration receives a maximum of 60% of the project grade.

12.1 Format

  1. Introduction (2 minutes). State the project's goal, the option you chose, and the one-sentence summary of what the system does.
  2. Architecture (3 minutes). Show the architecture diagram and explain the main modules and the interfaces between them. Do not read from the report; summarise.
  3. Live demonstration (10 minutes). Run the system on a prepared scenario. Walk the audience through what is happening: what command you are running, what the system is doing, and what the output means. For distributed or fault-tolerant systems, include a fault scenario (kill a node, partition the network) and show that the system recovers.
  4. Analysis (3 minutes). Show the key performance data and the key correctness evidence. State the conclusions: what works, what does not, and what you would do differently.
  5. Conclusion (2 minutes). Summarise what you learned and what you would extend if you had another month.
  6. Questions (10 minutes). The audience (the instructor and, optionally, other students) may ask questions about the design, the code, the testing, or the trade-offs.

12.2 Evaluation

The demonstration is evaluated on:

  • Clarity — the audience can follow what is being shown and why it matters.
  • Completeness — the demonstration covers the core functionality, not just a trivial subset.
  • Robustness — the demonstration works as advertised, without unplanned failures or fumbling. (A planned failure-injection scenario is not a "failure" in this sense — it is a demonstration of robustness.)
  • Depth — the answers to the questions show that you understand the design and the trade-offs, not just the code.
  • Professionalism — the demonstration is rehearsed, the slides are clear, and the presenter manages the time well.

Rehearsal

Rehearse the demonstration at least twice before the scheduled slot: once alone (to time it) and once in front of a friend (to test whether the explanation is clear to someone who has not seen the project). Record the rehearsal and watch it to catch habits (filler words, rushing, reading from the slides). Prepare a backup recording of the demonstration in case the live system fails on the day.

13 Technical report

The technical report is worth 10% of the project grade and is the primary evidence of your understanding of the design and the trade-offs. It should be 15–20 pages (excluding code listings and raw logs) and must contain the following sections.

  1. Introduction (1 page). The problem, the goal, the option chosen, and a summary of the findings.
  2. Background and related work (1–2 pages). The existing systems or tools that solve a similar problem, and what your project does differently. Cite at least five sources (textbooks, papers, or documentation).
  3. Design (4–5 pages). The architecture, the modules, the interfaces, the data structures, the concurrency model, and the error handling. A diagram of the architecture. The design decisions and the alternatives you considered, with reasons.
  4. Implementation (3–4 pages). The most interesting parts of the implementation: the algorithms, the data structures, and the parts of the code that were hardest to get right. Code listings in the appendix, referenced from the body.
  5. Testing and validation (2–3 pages). The test suite, the coverage, the results of the required tests, and the failure-injection scenarios. The Valgrind and Helgrind output. Any deviations and their diagnoses.
  6. Evaluation (3–4 pages). The required analyses for your option, presented as tables and plots. For each analysis, state the question, the method, the result, and the interpretation. The interpretation must be quantitative wherever possible.
  7. Reflection (1–2 pages). What you learned, what you would do differently, what the project taught you about the relationship between the abstraction and the implementation, and what you would extend if you had more time.

Style requirements

The report must be written in clear academic prose. Bullet lists are acceptable for enumerations of requirements and test results, but the analysis and the reflection must be prose. Code listings are placed in an appendix and are referenced from the body. Figures must be captioned and referenced in the text. The report must be self-contained: a reader should not need the source code to understand the design and the findings.

14 Grading rubric

The project is marked out of 100 points. The criteria, their weights, and the associated CLOs are given below, followed by the five-level descriptors for each criterion.

Final Project grading rubric — weights and CLOs
Criterion Weight CLOs
Technical design 20% CLO3, CLO4, CLO5, CLO8
Functionality 25% CLO2, CLO3, CLO4, CLO5, CLO6
Complexity and depth 15% CLO8
Testing and validation 15% CLO2, CLO3, CLO4, CLO5
Documentation 10% CLO8
Demonstration 10% CLO6, CLO8
Professional quality 5% CLO6, CLO8
Total 100%

14.1 Technical design (20 points)

Level Descriptor
Exemplary (90–100%) The architecture is modular, with clear interfaces and well-justified decisions. The design document is detailed enough for a reader to re-implement the system. The concurrency model is correct and efficient. The data structures are appropriate for the workload. The design takes into account failure modes, resource limits, and scalability.
Proficient (80–89%) The architecture is modular and the interfaces are clear. The design decisions are documented. The concurrency model is correct. A few design decisions could be improved but do not affect correctness.
Developing (70–79%) The architecture is functional but monolithic in places. The interfaces are not consistently documented. The concurrency model works but is not optimal. The design document describes the code rather than the rationale.
Beginning (60–69%) The architecture is not modular. Adding a new feature would require modifying many parts of the code. The design document is missing or a restatement of the assignment.
Not demonstrated (<60%) There is no coherent design; the code is a single large function or a collection of unrelated fragments.

14.2 Functionality (25 points)

Level Descriptor
Exemplary (90–100%) All core functional requirements are implemented and work as specified. Several optional requirements are also implemented. The system handles the edge cases correctly (empty inputs, large inputs, malformed inputs, concurrent access, failures). The behaviour on the required test cases is correct.
Proficient (80–89%) All core functional requirements are implemented and work on the tested inputs. A few edge cases are not handled or are handled incorrectly but are documented. Optional requirements are partially implemented.
Developing (70–79%) Most core functional requirements are implemented, but one or two are missing or incomplete. The system works on simple inputs but fails on some edge cases.
Beginning (60–69%) Several core functional requirements are missing. The system works on the simplest inputs but fails on most non-trivial ones.
Not demonstrated (<60%) The system does not run, or it implements only a small fraction of the required functionality.

14.3 Complexity and depth (15 points)

Level Descriptor
Exemplary (90–100%) The project demonstrates a deep understanding of the operating-system concepts it applies. It goes beyond a straightforward implementation of the assignment: it addresses non-obvious issues, makes and justifies non-trivial design decisions, and integrates multiple course topics in a coherent way. The complexity is appropriate to the 20% weight.
Proficient (80–89%) The project demonstrates a solid understanding of the concepts it applies. It addresses the main issues and integrates several topics. Some non-obvious issues are not addressed, or the integration of topics is shallow.
Developing (70–79%) The project applies the concepts but at a shallow level. It addresses the obvious issues but does not go into depth. The integration of topics is minimal.
Beginning (60–69%) The project is a straightforward implementation with little depth. It does not engage with the non-obvious aspects of the problem.
Not demonstrated (<60%) The project is trivial or does not demonstrate understanding of the concepts.

14.4 Testing and validation (15 points)

Level Descriptor
Exemplary (90–100%) The test suite is comprehensive (at least 20 tests across unit, integration, stress, and fault-injection categories). The tests are automated and produce a clear pass/fail summary. Valgrind and Helgrind are clean (or the exceptions are documented and justified). The required analyses are complete and the results are reproducible.
Proficient (80–89%) The test suite is adequate. Most required tests pass. Valgrind and Helgrind are clean or have minor issues that are documented. The required analyses are present.
Developing (70–79%) The test suite covers the basic functionality but not the edge cases or the failure scenarios. Some tests fail. Valgrind reports leaks or invalid accesses. The analyses are incomplete.
Beginning (60–69%) The test suite is minimal or missing. The required analyses are absent or superficial.
Not demonstrated (<60%) No meaningful testing is performed.

14.5 Documentation (10 points)

Level Descriptor
Exemplary (90–100%) The report is well-structured, evidence-based, and reflective. The design document is detailed and clear. The README is complete. All code is documented with clear function-level comments. The report is a pleasure to read.
Proficient (80–89%) The report covers all required sections. The design document and the README are present. Most code is documented. Some sections are light on detail or interpretation.
Developing (70–79%) The report is missing one or more required sections. The design document restates the code without explaining the rationale. Many functions lack comments.
Beginning (60–69%) The report is disorganised or significantly inaccurate. The design document is missing or superficial. The code has little or no documentation.
Not demonstrated (<60%) No report is submitted, or the report is a copy of another source.

14.6 Demonstration (10 points)

Level Descriptor
Exemplary (90–100%) The demonstration is clear, complete, and well-rehearsed. It covers the core functionality and a non-trivial scenario. The system works as advertised. The answers to the questions show deep understanding of the design and the trade-offs. The presenter manages the time well.
Proficient (80–89%) The demonstration covers the core functionality. The system works. The answers to the questions are correct but not deep. The presentation is clear but not polished.
Developing (70–79%) The demonstration covers only a trivial subset of the functionality, or the system fails on part of the demonstration. The answers to the questions are shallow or incorrect.
Beginning (60–69%) The demonstration is disorganised, or the system does not work on the demonstrated scenario. The presenter cannot answer basic questions about the design.
Not demonstrated (<60%) No demonstration is given, or the project does not run at all.

14.7 Professional quality (5 points)

Level Descriptor
Exemplary (90–100%) The code compiles cleanly with strict warnings. The Git history is meaningful and well-organised. The README is complete and accurate. The archive is well-organised. The project is presented in a form that a professional colleague could pick up and extend.
Proficient (80–89%) The code compiles with warnings enabled. The Git history is present but sparse. The README is present. The archive is organised. A colleague could understand the project with some effort.
Developing (70–79%) The code compiles but with warnings. The Git history is minimal or absent. The README is incomplete. The archive is disorganised.
Beginning (60–69%) The code does not compile cleanly. No Git history. No README. The archive is disorganised or incomplete.
Not demonstrated (<60%) The project does not build, or the submission is incomplete.

15 Academic integrity

The final project is individual work unless the instructor has explicitly approved a team project (see § 15.1). You may discuss general approaches with classmates, and you may consult textbooks, papers, and online resources for specific technical questions. You must not copy code from another student, from a previous offering of the course, or from an online source without attribution, and you must not share your code with another student.

The following are specifically permitted:

  • Using open-source libraries for components that are not the point of the project (for example, a JSON parser, an argument parser, or a testing framework), provided you disclose the library, its licence, and its role in the project.
  • Using the course's lab and assignment code as a starting point for individual components, provided you understand it and can explain it.
  • Using code-generation tools (for example, an AI assistant) for boilerplate and well-understood algorithms, provided you review the generated code, understand it, take responsibility for it, and disclose its use in the report.
  • Reading the source code of open-source projects that solve a similar problem, to understand how they approach it. You must not copy their code; if a design idea is inspired by a specific project, cite it in the report.

The following are prohibited:

  • Copying code from another student or from a public repository without attribution.
  • Having another person write any part of your code, your report, or your presentation.
  • Submitting a demonstration that misrepresents the functionality of the system (for example, showing a recorded demo of a feature that is not actually implemented).
  • Attacking systems you do not own as part of a security-related project.

Suspected violations are handled according to the university's academic integrity policy. The penalty for a first offence is a grade of zero on the project; a second offence results in a grade of F in the course.

15.1 Team projects

The instructor may approve a team project of up to three students, provided the team submits a joint proposal that clearly delineates each member's responsibilities and the additional scope that justifies the team size (for example, a larger system, an additional option, or a more ambitious analysis). Team projects are assessed on the same rubric, with the additional expectation that the scope and the quality are proportionally larger. Each member must be able to answer questions about every part of the project in the demonstration.

16 General resources

  • Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces. Free online at pages.cs.wisc.edu/~remzi/OSTEP/.
  • Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed.
  • Tanenbaum, A. S., & Bos, H. Modern Operating Systems, 4th ed.
  • Kerrisk, M. The Linux Programming Interface.
  • Love, R. Linux Kernel Development, 3rd ed.
  • Linux man-pages (man7.org/linux/man-pages/).
  • OSDev Wiki (wiki.osdev.org) — for Option A.
  • Open Container Initiative (opencontainers.org) — for Option B.
  • KVM documentation (linux-kvm.org) — for Option C.
  • Kleppmann, M. Designing Data-Intensive Applications — for Option D.
  • Gregg, B. Systems Performance, 2nd ed. — for Option E.
  • Git documentation (git-scm.com/doc) — for the repository and the Git bundle.
  • Valgrind documentation (valgrind.org/docs/) — for the leak-check and race-detector tools.
👤