A series of implementations from the operating systems course. They belong together because they share a property: you use all of these every day, and you do not really know how any of them work until you have written one.
A shell
A shell in C, supporting both builtin and external commands, and able to handle several commands joined by pipes.
fork / exec / wait look straightforward in a textbook. Making a | b | c actually work is where you discover the detail lives entirely in file descriptor management — which fd to close, when to close it, and what happens if you miss one (answer: the program does not fail, it simply hangs there forever).
A file system
A file system, also in C. Finishing it changed how the inode abstraction feels entirely — it stops being a noun and becomes a data structure you are responsible for maintaining.
→ OSLAB4-File-System-Implementation
Multithreading and a Linux kernel module
Two parts: a multithreaded program, and a kernel module that genuinely compiles into the Linux kernel.
The instructive thing about kernel modules is that there is no safety net. Break a userspace program and you get a segfault. Break something in kernel space and the machine stops. It makes you reconsider how much your ordinary programming relies on the operating system cleaning up after you.
→ OSLab3-Multithreading-Program-Linux-Kernel-Module
Sender / receiver with semaphores
Inter-process synchronisation using semaphores, implemented as a sender and receiver pair. The shortest item in the series, and the first place I met the problem where a program is correct nine runs out of ten — race conditions do not fire every time, which makes them much harder to convince yourself you have actually fixed.