Keentune

Operating Systems curriculum

24 chapters
·
135 concepts
·
free
Everything the adaptive question bank can teach and test in Operating Systems, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Operating Systems guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. Processes: identity, state and lifecycle
a program is a file on disk; a process is one running instance with its own address space, and the same program can run many times at once
every process carries a PID and its parent's PID; a PID is recycled after the process is reaped, so a stale PID can name someone else
R runnable, S interruptible sleep, D uninterruptible sleep, T stopped, Z zombie
a task blocked in the kernel waiting on hardware cannot be killed even by SIGKILL until that I/O returns
a zombie has exited but nobody read its status; an orphan is still running and gets re-parented to init
job control signals a whole process group, and a session binds groups to one controlling terminal
B. Creating and replacing a process
fork returns 0 in the child and the child's PID in the parent; both continue from the same line
the child does not get a memory copy; pages are shared read-only and duplicated only when one side writes
execve keeps the PID, open descriptors and cwd but throws away the old program's text, heap and stack, so it never returns on success
the shell rearranges the child's descriptors after fork and before exec, which is why the new program needs no cooperation to be redirected
wait/waitpid collects the exit status and frees the process entry; a parent that never waits accumulates zombies
an exit code is 0–255 with 0 meaning success, and a process killed by signal N is reported by the shell as 128+N
C. Threads and context switching
threads of a process share heap, globals and the descriptor table but each gets its own stack and registers
Linux creates threads and processes with the same clone call; the flags decide what is shared and what is copied
a switch saves and restores register state, and switching between processes also changes address space, so it costs more than switching threads
an unhandled fault in one thread terminates every thread, because they share one address space
D. Signals
an asynchronous notification that interrupts the target and runs a handler or the signal's default action
every signal can be caught, blocked or ignored except SIGKILL and SIGSTOP, which the kernel enforces
SIGTERM asks the process to shut down and lets it flush state; SIGKILL removes it immediately with no cleanup
SIGSEGV invalid memory access, SIGPIPE write with no reader, SIGCHLD a child changed state, SIGHUP the terminal went away
a blocked standard signal is marked pending, and several deliveries collapse into one
a handler can run in the middle of any instruction, so calling malloc or printf from it risks a deadlock
E. Scheduling and the run queue
the kernel takes the CPU back on a timer interrupt; a running task never has to volunteer
the default scheduler runs whichever runnable task has accumulated the least CPU time, rather than a fixed round-robin slot
nice runs from −20 to 19 and scales a task's share of contended CPU; it is not a hard priority and does nothing on an idle machine
a task that sleeps often has low accumulated runtime, so it is picked promptly on wake and feels responsive
SCHED_FIFO and SCHED_RR always outrank normal tasks, so a spinning realtime thread can starve the rest of the system
Linux load counts runnable plus uninterruptible tasks, so a disk stall inflates it with no CPU shortage at all
F. Virtual memory, paging and reclaim
each process gets a private virtual address space and the MMU translates every access, which is what makes one process unable to touch another's memory
translations live in per-process page tables and the TLB caches recent ones; a miss costs a page-table walk
a page is not brought in until it is touched, so a large executable starts without reading all of it
a minor fault is resolved from memory already in RAM, a major fault waits on disk and is orders of magnitude slower
text and read-only data low, heap growing up, the mmap region, and the stack growing down from high addresses
reclaim pushes cold anonymous pages to swap, and when the working set exceeds RAM the machine spends its time paging instead of computing
cached file pages count as used but are reclaimable on demand, so a low "free" number is the normal healthy state
when reclaim fails the kernel kills a chosen victim scored mostly by memory footprint, not the process that happened to ask last
G. Memory mapping, allocation and limits
a mapping is backed either by a file or by swap, and in both cases nothing is read until the page is touched
MAP_SHARED writes are seen by other mappers and reach the file; MAP_PRIVATE writes are copy-on-write and stay local
the allocator serves small requests from an already-obtained heap and only calls the kernel when it needs more, so free rarely returns memory to the OS
VSZ counts reserved address space while RSS counts resident pages, so a huge VSZ is not memory consumed
Linux grants more virtual memory than it can back, and the shortfall surfaces only when the pages are actually written
stack, heap and library base addresses move on every exec, so an attacker cannot hardcode a target address
a 2 MB page covers far more memory per TLB entry, but transparent huge pages can stall on allocation and waste memory, which is why latency-sensitive services often disable them
on a multi-socket machine, memory attached to another node is measurably slower to reach, so where a thread runs changes how fast its own data is
per-process ceilings on open files, stack size, address space and core dumps are inherited by children and surface as EMFILE or a refused allocation rather than a crash
H. The user/kernel boundary and system calls
user mode cannot touch devices or another process's memory; only kernel mode can, and hardware enforces the split
a syscall enters the kernel at one fixed, checked entry point with a number and arguments — it is not a call to an arbitrary kernel address
printf is buffering in libc on top of the write syscall, so one library call is not one crossing
a syscall signals failure by returning −1 and setting errno; errno is meaningless after a call that succeeded
an interrupt arrives asynchronously from a device, a trap is raised synchronously by the instruction being executed
I. File descriptors and the open-file model
files, pipes, sockets, terminals and devices are all read and written through the same small descriptor API
0 is stdin, 1 stdout, 2 stderr; they are ordinary descriptors and can be redirected independently
open always returns the lowest unused descriptor, which is exactly what makes the close-then-open redirection trick work
a duplicated descriptor shares one file offset, while a second open of the same path gets an independent one
O_APPEND makes every write land atomically at the end, O_TRUNC empties an existing file, O_CREAT with O_EXCL refuses to clobber
descriptors survive exec unless O_CLOEXEC is set, which is how a private file handle leaks into an unrelated child program
unlink removes the name, but the blocks are released only when the last descriptor closes, so truncating beats deleting a live log
J. Filesystems, inodes and links
type, permissions, owner, timestamps, size and block pointers live in the inode; the file's name does not
a directory is a table of name-to-inode entries, which is why renaming within a filesystem moves no data
hard links share one inode and a link count, so there is no "original" and no copy
a symbolic link holds text resolved at each use, so it can dangle, can cross filesystems, and follows the target's permissions
mtime tracks content, atime tracks reads, ctime tracks inode changes such as a chmod, and creation time is not portably available
a filesystem with free space can still refuse new files once its inode table is full, which millions of tiny files will do
K. Paths, mounts and the directory tree
everything hangs off a single /; another disk appears at a mount point rather than as a separate drive letter
resolution starts at the root or at the process's own current directory, which each process carries and can change
x on a directory permits passing through it and r permits listing it, so a readable file can sit in an unlistable directory
mounting over a non-empty directory conceals its contents until unmount; nothing is deleted
/etc configuration, /var changing state, /usr installed programs, /tmp scratch, /proc and /sys kernel interfaces
L. Permission bits and ownership
nine bits give read, write and execute separately to the owner, the group and everyone else
r is 4, w is 2, x is 1 per class, so 644 is rw-r--r-- and 755 is rwxr-xr-x
the kernel uses the owner bits if you own the file, otherwise the group bits, otherwise other — the classes are not combined
you may delete a file you cannot write, because removing a name needs write on the directory
new files receive the requested mode minus the umask, which is why a 666 request commonly lands as 644
with the sticky bit set on /tmp anyone can create files but only the owner can remove them
M. Privilege, capabilities and sandboxing
access checks use the effective UID while the real UID records who launched the process
a setuid executable runs with the file owner's identity, which is how an ordinary user updates a root-owned password file
UID 0 passes the file-mode checks outright, so the bits defend against every user except root
files created inside a setgid directory take its group, which is how a shared project tree stays writable by a team
root's powers are divided into units such as CAP_NET_BIND_SERVICE, so a service can bind a low port without becoming root
SELinux or AppArmor policy can deny an action the file bits allow, including for root, because it is enforced independently of ownership
N. Storage, the page cache and durability
a successful write has only reached the page cache, so the data is visible to readers but not yet durable
fsync flushes a file's dirty pages and metadata to stable storage, and it is the call a database commit actually waits on
a newly created file needs its parent directory synced as well, or the name can be missing after a crash
the filesystem records an intended metadata change before applying it, so recovery replays a log instead of scanning the whole disk
write a temp file, fsync it, then rename over the target, and a reader sees either the whole old file or the whole new one
O. Interprocess communication
an anonymous pipe is a one-way kernel buffer between related processes with no message boundaries
a full pipe blocks the writer and an empty one blocks the reader, which is how a shell pipeline throttles itself without any coordination
writing to a pipe whose read end is closed kills the writer with SIGPIPE, which is why a pipeline ends early when head exits
a FIFO lives in the filesystem so unrelated processes can meet, and opening one end blocks until the other end arrives
mapping the same pages removes all copying but provides no locking, so correctness is entirely the application's problem
P. I/O models and event multiplexing
a blocking read sleeps until data arrives, while O_NONBLOCK returns EAGAIN at once and leaves the waiting to you
both pass the whole watch set on every call, so cost grows with the number of descriptors rather than with activity
the kernel remembers what you are watching and returns only ready descriptors, so cost tracks events, not connections
level-triggered keeps reporting while data remains; edge-triggered reports the transition once, so you must read until EAGAIN or stall forever
Q. Namespaces and containers
a namespace gives a set of processes their own PIDs, mounts, network stack, users, hostname or IPC objects
the first process in a PID namespace is PID 1 inside it, inherits orphans there, and is invisible under that PID outside
a container's root filesystem is a private mount tree, not a separate kernel or a separate machine
root inside a user namespace maps to an unprivileged UID outside it, which is what makes rootless containers possible
isolation is a view, not a virtual machine, so kernel version and kernel bugs cross the boundary
R. cgroups v2 and resource control
cgroups account for and cap CPU, memory, I/O and process counts, whereas namespaces only change what a process can see
exceeding a cgroup's memory limit triggers an OOM kill within that group, even while the host has memory to spare
a CPU weight only matters under contention, while a CPU quota throttles the group even when every other core is idle
v2 puts all controllers in one tree, replacing v1's separate per-controller hierarchies
on a systemd host every service gets its own cgroup, which is how per-unit resource accounting and limits are applied
S. Boot and init
firmware (UEFI, or legacy BIOS) starts a bootloader such as GRUB, which loads the kernel and its initramfs into memory
the bootloader passes parameters like root= and init= that the kernel reads before any userspace process exists
a temporary in-memory root supplies the drivers needed to find and mount the real root filesystem
the kernel starts exactly one userspace process, and every other process descends from it
most drivers are loadable modules rather than compiled in, so hardware support can appear after boot
T. systemd and service management
a unit file states what to run and under what conditions, and systemd supervises, restarts and accounts for it
start runs a service now, enable makes it start at boot, and doing one does not do the other
a target is a named set of units to reach, which is more expressive than a numbered runlevel
Requires= declares a dependency and After= declares an order, so a correct unit usually needs both
systemd can hold a listening socket and start the service on first connection, and timer units replace cron with unit semantics and logging
U. procfs, sysfs and inspecting a live system
/proc files occupy no disk and are produced by the kernel at the moment you read them
/proc/PID exposes cmdline, environ, fd, maps and status, which is where ps, top and lsof actually get their data
a file that was unlinked while still open remains readable through /proc/PID/fd
/sys exposes devices, drivers and tunables one value per file, while /proc holds process and kernel state
the kernel log has a fixed size, so OOM kills and hardware errors age out and disappear if you look too late
V. Dynamic linking and program loading
a dynamic binary resolves shared libraries at load time and shares one copy in memory; a static one embeds its own and needs no loader
the dynamic loader consults the binary's RUNPATH, then LD_LIBRARY_PATH, then the ldconfig cache and default directories
"error while loading shared libraries" comes from the loader, not your code, and ldd names the unresolved dependency
the soname's major version marks ABI compatibility, so two majors of one library coexist as different files
a preloaded library's symbols win over the real ones, which powers both debugging shims and attacks
W. Performance and observability
a device can be 100% busy with no backlog; queue length and wait time are what show real saturation
tracing syscalls reveals which call a hung process is blocked in and what errno it keeps getting, at a large slowdown
sampling captures stacks at intervals to find hot code cheaply; tracing records every event and costs proportionally more
a verifier proves a BPF program terminates and stays in bounds before it is loaded, which is what makes in-kernel tracing safe in production
real is wall clock, user is CPU spent in your code, sys is CPU spent in the kernel, and real far exceeding the other two means waiting
X. The shell and the process runtime environment
zero means success and any non-zero value means failure, and $? holds the last command's code
&& runs the next command only if the previous one succeeded, || only if it failed
>file 2>&1 puts both streams in the file, while 2>&1 >file copies the terminal first and leaves stderr on it
an unqualified command is searched along PATH in order, so an earlier directory silently shadows a later one
only exported variables reach a child process, and a child can never change the parent's environment
stdout is line-buffered to a terminal but block-buffered to a pipe or file, so output can appear late or out of order next to stderr
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Operating Systems practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC