Where to Go Next
A roadmap of the systems-programming topics that follow this course — processes, threads, I/O, sockets, kernels, sanitizers, and modern alternatives
You now have the foundation: types, pointers, arrays, strings, the stack, the heap, structs, and the bug classes that haunt manual memory management. That is enough to read most of the C source code in the world and write small to medium programs of your own.
What's left is the systems part of systems programming — the APIs that connect C programs to the operating system, the network, and other processes — plus the modern tooling and language alternatives that build on what C taught you.
This page is a roadmap, not a tutorial. Each topic deserves its own course, and there are many great ones out there.
1. Processes and signals
A process is a running program: its address space, open files, credentials, and one or more threads of execution.
fork()— duplicate the current processexecve()family — replace the current process image with a new programwait()/waitpid()— reap childrenexit()/_exit()— terminate a processgetpid(),getppid(),getuid()— identitysignal()/sigaction()— asynchronous notifications (SIGINT,SIGTERM,SIGSEGV,SIGCHLD, …)
Together these are how shells run commands, how init supervises
services, and how Unix's "do one thing well" philosophy becomes
pipelines.
Read: Advanced Programming in the Unix Environment by W. Richard Stevens (APUE) and the man pages for the syscalls themselves.
2. Threads and synchronization
Within one process, multiple threads share the same address space. That makes communication cheap and corruption easy.
- POSIX threads:
pthread_create,pthread_join,pthread_mutex_t,pthread_cond_t - C11 threads:
<threads.h>provides a portable subset - Atomics:
<stdatomic.h>— lock-free counters, flags, sequence numbers - The memory model: acquire / release, sequential consistency,
happens-before — what does "thread A wrote 5 to
x" actually guarantee for thread B?
Threads are how you keep all the cores busy in compute-heavy work; they are also how you create the world's most subtle bugs (data races, deadlocks, ABA problems).
Read: The Little Book of Semaphores, C++ Concurrency in Action (the C and C++ memory models are very similar).
3. File I/O and the VFS
Above stdio.h lives the standard library's buffered I/O (fopen,
fread, fwrite, fprintf). Below it lives the raw POSIX layer:
open/close/read/write— the four core syscallslseek,dup,dup2,pipe— file-descriptor plumbingstat/fstat— file metadatammap— map a file (or anonymous memory) into the address spacefcntl,ioctl— the "everything else" syscalls
Understanding the difference between file descriptors (small ints
in the kernel's per-process table) and FILE * (a libc buffer
wrapping a descriptor) is a turning point.
4. Networking and sockets
The Berkeley sockets API is C through and through:
socket,bind,listen,accept,connectsend/recv,sendto/recvfromgetaddrinfo,freeaddrinfo— DNS and address handling- TCP vs UDP, blocking vs non-blocking,
select/poll/epoll(Linux) /kqueue(BSD/macOS) /IOCP(Windows) — the many shapes of "wait for many descriptors at once"
Once you can write an echo server, you can read most of nginx.
Read: Beej's Guide to Network Programming (free, classic).
5. mmap and shared memory
mmap is one of the most powerful syscalls. It lets you:
- Map a file into memory and read/write it as if it were an array
- Allocate large anonymous regions (some allocators use this instead
of
sbrk) - Share memory between processes via
MAP_SHARED - Use copy-on-write fork semantics on huge data without copying
It's also how databases like SQLite implement memory-mapped I/O modes and how dynamic loaders bring shared libraries into your address space.
6. The kernel boundary and syscalls
Every interaction with the outside world ultimately becomes a syscall — a controlled transition from user mode to kernel mode. Learn what is on each side of that line:
strace(Linux),dtruss(macOS),truss(Solaris) — observe which syscalls a program makes/proc/<pid>/on Linux — a window into kernel-side state- vDSO and how
gettimeofdayavoids a real syscall seccompand Linux namespaces — the building blocks of containers
If this excites you, the next step is reading the Linux kernel source or a teaching OS like xv6.
7. Sanitizers and dynamic analysis
These are the tools that turn C from "trust the programmer" into "trust but verify."
| Tool | What it catches | How to enable |
|---|---|---|
| AddressSanitizer (ASan) | UAF, double-free, OOB, leaks | -fsanitize=address |
| UndefinedBehaviorSanitizer (UBSan) | Signed overflow, alignment, NULL deref | -fsanitize=undefined |
| MemorySanitizer (MSan) | Uninitialized reads | -fsanitize=memory (clang) |
| ThreadSanitizer (TSan) | Data races | -fsanitize=thread |
| Valgrind / memcheck | UAF, leaks, uninit | run valgrind ./prog, no recompile |
clang-tidy, cppcheck | Static patterns | run on the source tree |
| Hardened libc | Detects double-free, fortify-source overflows | usually on by default in distros |
Make -Wall -Wextra -Werror -fsanitize=address,undefined part of
your CI from day one of any new C project.
8. Build systems and packaging
Real C projects rarely use one giant clang *.c -o out. Common
options:
- Make — the classic, still excellent for small projects
- CMake — the most widely used cross-platform meta-build system
- Meson + Ninja — modern, fast, declarative
- Bazel / Buck — for huge monorepos
- Autotools — older but you will meet it in GNU packages
For dependencies: pkg-config, vcpkg, Conan, system package
managers (apt, pacman, brew).
9. Embedded and freestanding C
C without an OS underneath you — for microcontrollers, bootloaders, and kernels themselves.
- Cross-compilation:
arm-none-eabi-gcc,xtensa-esp32-elf-gcc - Linker scripts: telling the linker where flash and RAM live
volatilefor memory-mapped I/O, interrupt service routines- No
malloc(or a very limited one), often noprintf - Real-time operating systems: FreeRTOS, Zephyr, ChibiOS
The mental model you built on the stack/heap pages transfers directly; you just lose the comforts of a hosted environment.
10. Modern alternatives that build on C
Once you understand C, several modern languages will feel like "C with some of the foot-guns removed":
- C++ — superset that adds RAII, generics (templates), and a huge standard library. The natural step up when you need classes/containers but cannot leave the C ABI.
- Rust — the most successful "memory-safe systems language" yet. The borrow checker enforces at compile time the ownership discipline you have been writing by hand in C.
- Zig — explicit allocators, compile-time code execution, excellent C interop. A "better C" that does not try to be C++.
- Go — garbage-collected but still systems-oriented (good concurrency primitives, fast static binaries, dead-simple build).
Any of them will be much easier to learn now that you understand why their safety features exist.
11. Reading recommendations
A short, opinionated list:
- K&R — The C Programming Language by Kernighan & Ritchie. Still the gold-standard tour of the language itself.
- APUE — Advanced Programming in the Unix Environment by Stevens. The reference for syscalls on Unix-like systems.
- CSAPP — Computer Systems: A Programmer's Perspective by Bryant & O'Hallaron. Ties C to the machine — assembly, caches, linking, virtual memory, concurrency.
- TLPI — The Linux Programming Interface by Michael Kerrisk. The exhaustive Linux complement to APUE.
- C: A Modern Approach by K. N. King — gentler introduction with many exercises.
- Modern C by Jens Gustedt — focused on C99/C11/C17 idioms.
For tools:
- The man pages:
man 3 malloc,man 2 read,man 7 epoll, … - Compiler Explorer (godbolt.org) — see exactly what the compiler turns your C into.
- Hacker's Delight by Henry Warren — bit-twiddling and low-level tricks.
12. Practice projects
The fastest way to consolidate everything you've learned:
- A tiny shell —
fork+execve+ a parser. ~500 lines. - A
cat/wc/grepreimplementation. Teaches you file I/O. - A toy
malloc. Implementmmalloc/mfreeon top ofmmapwith a free-list. You will never look at the heap the same way. - A TCP echo server. First blocking, then with
select, then withepoll. - A static-site generator or a JSON parser. Pure parsing — great for practicing struct + heap discipline.
- A linked allocator in an embedded RTOS — if you have a board.
A final note
C is old. It has rough edges. Modern languages do many things better. But every operating system, every database, every browser, every language runtime has C (or near-C) at its core. Understanding C is understanding the layer of abstraction directly above the silicon — and that understanding pays dividends for the rest of your career, no matter which language you spend your days in.
Thank you
Thanks for working through Systems Programming & Memory Management in C. If something in the course stuck — or didn't — please file an issue on the Dataslope repository. Happy hacking.