Everything the adaptive question bank can teach and test in Operating Systems, from foundations through advanced practice. Work through it in order, or start practicing and let the questions find your level.
The distinction explains why two people running the same editor cannot see each other's buffers, and why killing one leaves the other untouched. Everything that makes a running thing an individual — its memory, its open files, its identity — belongs to the instance rather than to the bytes on disk.
Numbers are handed out and eventually reused, so one recorded earlier can name something else entirely by the time you act on it. That is why a script that stores an identifier and later kills it should verify what it is about to signal, and why long-lived tooling tracks a process by more than its number.
The states answer different questions: ready to run, waiting on something interruptible, waiting on hardware, halted by a signal, or finished but not yet collected. Two of them are frequently misread — the uninterruptible wait is not a hang you can clear, and the finished state consumes no resources but a table entry.
A task waiting inside the kernel for hardware cannot be interrupted, because the kernel would have to abandon an operation it is partway through. So even the unblockable signal has no effect until the device responds — which is why a process stuck this way survives every escalation and clears only when the storage does.
One has finished and is waiting for its parent to collect the result; the other is still running and has lost its parent. They are often confused because both look abnormal in a listing, but only the first accumulates — and the fix for it is in the parent, not in the child.
Grouping is what lets a keyboard interrupt reach every part of a pipeline rather than just one command, and a session ties those groups to one terminal. It is also why a job started in a terminal dies when the terminal closes, unless it was deliberately detached from that session.
B. Creating and replacing a process
Both sides continue from the same point with the same code, and the return value is the only thing distinguishing them — which is what makes the classic mistake, forgetting to branch, produce two processes doing identical work. Everything else about the two is a copy at that instant.
Nothing is copied at creation; the pages are shared and marked read-only, and a copy happens only when one side writes to a page. That is what makes creating a process cheap even for a large program, and it is why the memory cost of a child appears gradually rather than at once.
The identity, open files and working directory survive while the program itself is discarded and replaced, which is why a successful call never returns — there is no code left to return to. Anything that must be inherited has to be arranged before it, because afterwards the old program is gone.
The shell rearranges the child's descriptors in the window after it exists and before the new program starts, so the program is launched with its output already pointing elsewhere. That is why redirection works on programs that know nothing about it — the arrangement is finished before their first instruction.
Collecting the exit status is what frees the last trace of a finished child, so a parent that never collects leaves entries accumulating. The remedy is either to collect them, usually from the child-status signal handler, or to arrange for them to be collected automatically.
The status is a small number where zero alone means success, and a process ended by a signal is reported as a value above the ordinary range. That encoding is why an exit code in the high one-hundreds usually means something killed the process rather than the program choosing to fail.
C. Threads and context switching
Sharing the heap, globals and open files is what makes threads cheap to communicate between and what makes them dangerous — any two can touch the same data with no boundary in between. The private parts are the stack and the registers, which is why local variables are safe and everything else needs discipline.
There is one underlying operation, and what distinguishes a thread from a process is which resources the flags say to share. That is why the distinction on Linux is a spectrum rather than a category, and why a container runtime can create something that is neither exactly.
Every switch saves and restores register state, and a switch between processes additionally changes the address space, which invalidates cached translations. That difference is why thread switches are cheaper — and why a design with far more runnable tasks than cores spends real time switching.
Because the address space is shared, a fault in one thread cannot be contained to it — the memory the others depend on is the memory that was corrupted. That is the strongest argument for process isolation where fault containment matters more than communication speed.
D. Signals
Delivery interrupts whatever the target was doing and runs either a handler or a default action, so it is a notification rather than a message — there is no payload and no queue in the ordinary case. That minimalism is why signals are used for control and rarely for data.
Two signals cannot be caught, blocked or ignored, and the kernel enforces that so a misbehaving process can always be stopped or removed. Every other signal can be intercepted, which is exactly why a well-behaved shutdown uses a catchable one and a forced one is the last resort.
The polite one is a request the process can act on — flush buffers, finish a transaction, close connections — while the forcible one removes it with no opportunity to do any of that. Reaching for the second by habit is how half-written files and unreleased locks are produced.
Each names a distinct condition: an invalid memory access, a write with nobody reading, a child changing state, a terminal disappearing. Knowing them turns an opaque exit into a diagnosis, because the signal that ended a process usually says more than the program's own last log line.
A blocked standard signal is recorded as pending, and several arrivals collapse into one delivery, so counting them is not possible. That is why a handler for child-status changes must loop collecting every finished child rather than assuming one notification means one child.
A handler can begin in the middle of any instruction, including inside the allocator or the output library, so calling those from it risks deadlocking against a lock the interrupted code already holds. The standard practice is to set a flag and do the real work back in the main flow.
E. Scheduling and the run queue
A timer interrupt returns control to the kernel regardless of what the running task wants, so no program has to cooperate to keep the system responsive. That is the property a cooperative system lacks, and it is why a tight loop slows a Linux machine down rather than freezing it.
The default scheduler picks whichever runnable task has accumulated the least weighted CPU time, which approximates giving everyone an equal share without fixed slots. It explains why a task that has just woken tends to run promptly, and why a long-running batch job yields to interactive work.
It scales a task's share of contended CPU rather than granting absolute priority, so it changes nothing when there is CPU to spare. That is the usual disappointment — lowering a batch job's priority does not make an idle machine's interactive work faster, because nothing was competing.
A task that spends most of its time asleep accumulates little runtime, so it is chosen quickly when it wakes. Nothing detects interactivity explicitly; it falls out of the accounting. That is why the same scheduler serves a desktop and a batch server without a mode switch.
Tasks in a realtime class always outrank ordinary ones, which is what makes them suitable for hard latency requirements and dangerous everywhere else. A spinning realtime thread can starve the entire system, including the tools you would use to investigate it.
Linux counts tasks waiting on storage as well as tasks waiting for CPU, so a stalled disk drives the number up while every core sits idle. That single detail is why load average alone is a poor CPU metric here, and why a high figure needs a second look at what the tasks are waiting for.
F. Virtual memory, paging and reclaim
Every access goes through a translation, so a process's addresses mean nothing outside it and one process simply has no way to name another's memory. That indirection is also what makes swapping, sharing and copy-on-write possible — the mapping can change without the program knowing.
Translations are looked up through per-process tables, with a small cache holding recent ones. A miss costs a walk through those tables, which is why access patterns that touch many scattered pages are slower than the data volume suggests — the cost is in the translations, not the bytes.
Nothing is read until it is touched, so a large program starts quickly and only the parts actually used are ever loaded. The consequence is that startup cost is deferred rather than removed: the first pass through a rarely-used code path pays for reading it.
One is resolved from memory already present and costs a bookkeeping update; the other waits on storage and costs orders of magnitude more. Distinguishing them is how you tell a process that is merely growing from one that is thrashing, and the counters for each are reported separately.
The regions are arranged so the heap and the stack grow toward each other with mapped regions in between, which is why a stack overflow and a runaway allocation fail in different ways. Knowing the layout is also what makes a memory map readable when diagnosing a crash.
Pushing cold pages out is healthy and mostly invisible; the pathology is a working set larger than memory, where pages are evicted and immediately needed again. The machine then spends its time moving pages rather than computing, and it looks like every process is slow rather than one.
Cached file contents count as used and are released the moment something needs the memory, so a small free figure is the normal state of a healthy machine. Reading that number as a shortage is the most common misdiagnosis here — the meaningful figure is what is available, not what is free.
When reclaim fails the kernel scores candidates largely by footprint and kills one, which is frequently not the process that triggered the shortage. That is why the victim is often the database rather than the runaway script, and why the score can be biased deliberately for processes that must survive.
G. Memory mapping, allocation and limits
A mapping is backed either by a file or by swap, and in both cases the pages arrive when touched rather than at creation. The distinction decides what happens under memory pressure: file pages can be dropped and re-read, anonymous ones must be written somewhere first.
Shared writes are visible to everyone mapping the same region and reach the file; private writes trigger a copy and stay local. Choosing the wrong one produces either changes that mysteriously do not persist, or changes that unexpectedly do.
The allocator manages a pool it obtained earlier and only asks the kernel when that pool is exhausted, so most allocations never cross into the kernel. It also means freeing usually returns memory to the allocator rather than to the system, which is why a process's footprint rarely shrinks.
One counts address space reserved and the other counts pages actually resident, and they can differ by orders of magnitude. A runtime that reserves a large region up front shows an alarming figure in the first column while consuming very little — reading the wrong one causes a lot of unnecessary alarm.
The kernel grants more address space than it could back, on the assumption that most of it will never be written. The shortfall appears only when the pages are used, which is why an allocation can succeed and the write that touches it can be what gets the process killed.
Base addresses move on every execution, so an attacker cannot hard-code where anything will be. It also makes some debugging harder to reproduce, which is why it can be disabled for a session — and why a crash address from one run means little when compared with another.
A larger page covers far more memory per cached translation, which helps workloads touching a lot of it. The transparent form can also stall while compacting memory to find a contiguous block, and can waste memory on sparse regions — which is why latency-sensitive services often turn it off.
On a multi-socket machine, reaching memory attached to another processor costs measurably more, so where a thread runs changes how fast its own data is. Pinning threads and their memory to the same node is the remedy, and ignoring it produces performance that varies run to run for no visible reason.
Per-process ceilings are inherited by children and surface as an ordinary error rather than a crash, which is why hitting one looks like a bug in the application. The open-file limit is the one most often met in production, and it is raised in the service definition rather than in the code.
H. The user/kernel boundary and system calls
The hardware enforces the split, so a user-mode instruction attempting a privileged operation faults rather than succeeding. Every protection in the system rests on that, which is why a bug that lets user code run in the other mode is the most serious class of vulnerability there is.
Entry happens at one fixed, checked point with a number selecting the operation, so user code never jumps to an arbitrary kernel address. That narrow interface is what makes the boundary auditable — and it is why the list of system calls is a meaningful security surface to restrict.
Formatted output buffers in user space and crosses into the kernel only when the buffer is flushed, so many library calls can produce one crossing. That is why counting library calls tells you little about cost, and why output can appear in an unexpected order when the buffer flushes late.
Failure is signaled by the return value, and the error variable is only meaningful once you have seen that signal — it is not cleared by a successful call. Checking it without first checking the return is how code reports an error that happened several calls ago.
One arrives asynchronously from a device and has nothing to do with the instruction being executed; the other is raised synchronously by that instruction. The distinction matters when reading a stack trace: a trap points at the code, and an interrupt points at whatever the hardware wanted.
I. File descriptors and the open-file model
One small interface covers files, pipes, sockets, terminals and devices, which is what lets a program written for one work with another unchanged. It is also why a tool can accept input from a file or a pipe without caring — the difference is invisible at the point of reading.
The three well-known numbers are ordinary descriptors with no special protection, which is what makes redirecting them possible at all. Keeping error output separate from normal output is the reason the third exists, and it is why a pipeline can carry data while errors still reach the terminal.
Allocation always takes the lowest unused number, which is precisely why closing a standard descriptor and immediately opening a file installs that file in its place. The whole classic redirection technique depends on this being deterministic rather than arbitrary.
A duplicated descriptor shares the underlying position, so writing through one moves the other; a separate open of the same path does not. That difference explains why two processes appending through inherited descriptors interleave cleanly while two independent opens overwrite each other.
Flags at open time decide behavior that cannot be arranged afterwards — appending atomically at the end, emptying an existing file, or refusing to proceed if one already exists. The exclusive-create combination is the usual way to make a lock file, and it works only because the check and the create are one operation.
Descriptors survive into the new program unless marked otherwise, so a handle opened for one purpose can silently end up in an unrelated child. That is both a resource leak and a security problem, which is why the flag exists and why modern code sets it at open time rather than afterwards.
Removing a name leaves the data in place until the last descriptor closes, so deleting a log a process is still writing reclaims nothing and hides the growth. Truncating it in place is the operation that actually returns the space while the writer keeps running.
J. Filesystems, inodes and links
Everything about a file except its name lives in the inode, which is why a file can have several names, or none while still being open. It also explains why changing permissions updates a timestamp that is not the modification time — the inode changed, the contents did not.
A directory is a lookup table rather than a container, so renaming within one filesystem rewrites an entry and moves no data — which is why it is instant regardless of file size. Moving between filesystems is a copy and a delete, which is why that case is slow.
Both names refer to the same inode and neither is the original, so deleting one leaves the other working. The link count in the inode is what tracks how many names exist, and the data is released only when that count and the open count both reach zero.
It holds text resolved at each use, so it can point at something that does not exist, can cross filesystems, and follows whatever the target's permissions are. That late resolution is the feature and the hazard — the meaning of the link can change without the link being touched.
Three timestamps track different things — contents changed, contents read, inode changed — and the one people most often want is not portably available. Read-time updates are also frequently disabled for performance, so an unchanged read timestamp does not mean a file was never read.
The table is sized when the filesystem is made, so a volume with plenty of free space can refuse to create a file. Millions of tiny files is the pattern that does it, and the error mentions space, which sends people looking at the wrong number entirely.
K. Paths, mounts and the directory tree
Additional storage appears at a point inside the single tree rather than as a separate namespace, so the same path can be served by a different device without anything above it knowing. That is what makes moving a directory onto its own volume transparent to the programs using it.
Resolution starts either at the root or at the process's own current directory, and every process carries its own. That is why a relative path in a service can resolve somewhere unexpected — the working directory it inherits is rarely the one the author was imagining.
On a directory, the execute bit permits passing through and the read bit permits listing, and the two are independent. A directory that can be traversed but not listed is a real configuration: files inside are reachable by exact name and cannot be enumerated.
Mounting over a non-empty directory conceals whatever was in it, without deleting anything, and unmounting reveals it again. That is the explanation for disk space that seems to disappear — files written before a mount are still there, hidden underneath it, and still consuming space.
The top-level directories separate configuration, changing state, installed software and temporary space, which is what makes it possible to back up or mount them differently. Two of them are not storage at all but kernel interfaces presented as files.
L. Permission bits and ownership
Three permissions for three classes gives nine bits, and their meaning changes with the object: on a directory the execute bit means traverse rather than run. That overloading is the source of most confusion, because the same letter means different things on a file and on a directory.
Each class's three permissions pack into one digit, which is why the common modes are memorable as three numbers. The notation is compact and absolute — it sets every bit at once, which is why it can silently remove a permission the symbolic form would have left alone.
The kernel picks exactly one class and uses only its bits: owner if you own it, otherwise group, otherwise everyone else. They are not combined, so an owner with no permissions is denied even when the other classes would allow it — which looks contradictory until you know the rule.
Removing a name changes the directory, not the file, so the permission that matters is on the directory. That is why a file you cannot modify can still be deleted, and it is the reason shared directories need the extra bit that restricts removal to owners.
New files are created with the requested permissions minus the mask, which is why a program asking for a permissive mode gets something stricter. The mask is inherited from the parent process, so a file created by a service can differ from the same code run by hand.
On a world-writable directory it restricts removal to the file's owner, which is what makes a shared temporary area usable — anyone can create, nobody can delete someone else's work. Without it, world-writable and shared means anyone can remove anything.
M. Privilege, capabilities and sandboxing
Access checks consult the effective identity while the real one records who started the process, which is what lets a program temporarily act as someone else and still know who invoked it. Dropping privileges means changing the effective one — and doing it irreversibly is a separate, deliberate step.
Marking an executable this way makes it run as the file's owner rather than the caller, which is how an unprivileged user performs one narrowly-defined privileged operation. It is also a permanent invitation to abuse, so such programs are few, small, and scrutinized more than anything else on the system.
The superuser passes file-mode checks outright, so the bits describe what every other account may do and say nothing about that one. Anything meant to constrain the superuser has to come from a different mechanism, which is exactly what mandatory access control provides.
Files created inside such a directory take the directory's group rather than the creator's, which is what keeps a shared project tree accessible to the whole team. Without it, each person's files belong to their own primary group and the sharing silently stops working.
The superuser's powers are divided into individually grantable units, so a program needing exactly one — binding a low-numbered port, say — can have that one and nothing else. It converts an all-or-nothing decision into a specific one, which is what makes least privilege achievable for a service.
Policy is enforced independently of ownership, so it can refuse an action the permission bits allow — including for the superuser. That is why a confusing permission denial on a correctly-owned file is often this, and why the relevant log is the policy's own rather than the filesystem's.
N. Storage, the page cache and durability
A successful write has reached memory, not storage, so the data is immediately visible to other readers and not yet safe from a power loss. That gap is what makes writes fast, and it is why durability is a separate request rather than a property of writing.
Flushing a file's dirty pages and metadata is the operation a database commit actually waits for, which is why commit latency tracks storage flush latency rather than write throughput. Skipping it does not make the data unsafe in normal operation — only in the case the durability was for.
A newly created file's name lives in its parent directory, so flushing the file alone can leave the contents durable and the name missing after a crash. That extra step is the part most often omitted, and it is invisible until the one crash that lands in the window.
Recording an intended metadata change before applying it means recovery replays a short log instead of scanning the whole volume, which is the difference between a boot delay of seconds and one of hours. It protects metadata consistency, which is not the same as protecting your data.
Writing a temporary file, flushing it, then renaming it over the target means a reader sees either the old file complete or the new one complete, never a half-written mixture. Omitting the flush before the rename is the common error — the name can become durable before the contents do.
O. Interprocess communication
It carries bytes with no record boundaries, so a writer's separate writes can arrive merged or split and any framing has to be added by the application. Being unnamed, it is only reachable by processes that inherited it, which is why it suits a parent and its children and nothing else.
A full buffer blocks the writer and an empty one blocks the reader, so a pipeline automatically runs at the pace of its slowest stage with no coordination anywhere. That is why piping a fast producer into a slow consumer does not consume unbounded memory.
Writing with nobody reading terminates the writer by default, which is what makes a pipeline stop promptly when a downstream command has seen enough. Programs that ignore the signal must then handle the error on write instead, or they loop writing into nothing.
Because it exists in the filesystem, unrelated processes can meet through it without any inheritance. Opening one end blocks until the other arrives, which is a rendezvous rather than a failure — and a surprise the first time a script appears to hang on a plain open.
Mapping the same pages removes copying entirely, which makes it the fastest mechanism available and the only one with no built-in coordination. Every guarantee about ordering and consistency has to be supplied by the application, which is why it is usually paired with a separate locking primitive.
P. I/O models and event multiplexing
A blocking read sleeps until something arrives; the non-blocking form returns an immediate would-block error and leaves the waiting to you. Neither is better in the abstract — the second is only useful alongside something that tells you when to try again, which is what the multiplexing calls provide.
Both hand the whole watch set to the kernel on every call, so the cost grows with how many things you are watching rather than with how many are ready. At a few connections that is irrelevant; at tens of thousands it is the dominant cost of an idle server.
The kernel remembers what you are watching between calls and returns only what is ready, so the cost tracks activity rather than connection count. That is the property that makes a large number of mostly-idle connections affordable, and it is why it is the basis of every high-concurrency server here.
One keeps reporting while data remains and the other reports only the transition, which means the second obliges you to read until the would-block error or the remaining data is never announced again. Most stalls in edge-triggered code are exactly that: a partial read and no further notification.
Q. Namespaces and containers
Each kind gives a group of processes their own version of one global resource — process numbers, mounts, the network stack, user identities, the hostname. Nothing is restricted; the view is simply different, which is why a namespace alone limits visibility and not consumption.
The first process inside becomes number one there, inherits orphans, and appears under a different number outside. That also gives it the special signal semantics of an init process, which is why a container's main process ignores signals it has no handler for — a frequent cause of a container that will not stop.
A container's root is a private arrangement of mounts rather than a separate kernel or machine, so what it sees is a rearrangement of the host's own storage. That makes bind-mounting a host path into it straightforward, and it is why an escape via a careless mount is a real concern.
Identities inside map to different, unprivileged ones outside, so being the superuser within the namespace confers nothing on the host. That mapping is what makes running containers without host privileges possible, and it is why file ownership looks different depending on which side you inspect from.
Isolation here is a view rather than a virtual machine, so there is one kernel and its version and its bugs are shared by everything running on it. That is the security boundary to be honest about, and it is why a kernel-level vulnerability is a container escape rather than a contained fault.
R. cgroups v2 and resource control
One mechanism accounts for and caps consumption; the other changes what is visible. They are routinely spoken of together because containers use both, but they solve unrelated problems — and a container with a private view and no limits can still exhaust the host.
Exceeding a group's limit triggers a kill within that group while the host still has memory available, which is why a process dies on a machine that looks fine. The system-wide indicators show nothing, and the evidence is in the group's own accounting rather than in the global counters.
A weight only matters when there is contention and lets a group use idle capacity; a quota throttles it even when every other core is idle. Choosing a quota for predictability therefore buys consistent behavior at the cost of leaving capacity unused, which is often a surprise in a benchmark.
All controllers live in one tree rather than in separate per-controller hierarchies, which removes the possibility of a process being in inconsistent groups for different resources. It also changes where limits are written, which is why guidance written for the older layout does not apply.
On a systemd host each service is placed in its own group automatically, which is what makes per-service accounting and limits available without any extra setup. It also means creating groups by hand outside that structure is fighting the manager, and the settings belong in the unit instead.
S. Boot and init
Firmware finds and starts a bootloader, which loads the kernel and an initial filesystem image into memory and hands over. Each stage knows only enough to start the next, which is why a failure early in the sequence produces a message from a component with almost no diagnostic ability.
Parameters passed by the bootloader are read before any userspace process exists, which is what makes them the mechanism for recovery — naming a different root, or a different first process, when the normal path is broken. They are also visible afterwards, which makes them a poor place for secrets.
A temporary in-memory root carries the drivers needed to find the real one, which is what allows a single generic kernel to boot on hardware whose storage driver is not built in. A failure here presents as a kernel that loaded and then cannot find its root filesystem.
Exactly one userspace process is started and everything else descends from it, which is why that process is special: if it exits, there is nothing left to run. That is also why its signal handling and its responsibility for orphaned children matter more than any other process's.
Most drivers are loadable rather than compiled in, so support for hardware can appear after boot when a device is detected. That flexibility is why a generic kernel works everywhere, and why a missing module presents as hardware the system simply does not seem to have.
T. systemd and service management
A unit states what to run and under what conditions, and the manager handles supervision, restarting and accounting. That is the shift from a script that starts something to a description of what should be true — which is why the right fix is usually an edit to the unit rather than a wrapper around it.
One acts now and the other arranges for boot, and doing either leaves the other untouched. The failure mode is quiet in both directions: a service that works until the next reboot, or one configured correctly and not actually running.
A target is a named set of units to reach, which composes far better than a numbered level — a machine can reach a graphical state, a multi-user state or a rescue state, and units attach themselves to whichever is relevant.
Declaring a dependency does not say anything about order, so a unit can be started alongside the thing it depends on rather than after it. The two directives are almost always needed together, and omitting the ordering one produces failures that appear only under a fast or a slow boot.
The manager can hold a listening socket and start the service on first connection, which removes startup ordering problems and lets a rarely-used service consume nothing while idle. Timer units do the same job as cron with the addition of dependencies, logging and the manager's own accounting.
U. procfs, sysfs and inspecting a live system
These files occupy no storage and are produced at the moment they are read, which is why their sizes are reported as zero and why copying one gives a snapshot rather than a file. It also means reading them is a system call doing work, not a cheap disk read.
Each process exposes its command line, environment, open descriptors and memory map, which is precisely where the standard inspection tools get their data. Knowing that turns those tools from magic into conveniences — anything they show can be read directly when the tool is not available.
A file unlinked while still open remains reachable through the writing process's descriptor entry, so the contents can be copied back before the process exits. That is the recovery for the log somebody deleted to free space, and it works only while the process holding it is still alive.
One exposes devices, drivers and tunables as one value per file; the other holds process and kernel state. The separation matters when looking for a setting — device and driver knobs live in the newer tree, and the older one is about running software.
The kernel log has a fixed size and old entries are overwritten, so the evidence for an out-of-memory kill or a hardware error disappears if you look too late. That is why persistent logging matters — the buffer is a live view, not a record.
V. Dynamic linking and program loading
A dynamic binary resolves its libraries at load time and shares one copy in memory across every process using it; a static one carries its own and depends on nothing. The trade is deployment simplicity against memory sharing and the ability to patch a library once for everything.
The loader consults a defined sequence of locations, and knowing the order explains why a library in the expected place is ignored — something earlier in the sequence matched first. The environment override in the middle of that sequence is also why it is a classic way to substitute a library.
The error comes from the loader before any of your code runs, which is why no logging appears and no handler helps. The tool that lists a binary's dependencies names the unresolved one immediately, which makes this one of the fastest failures to diagnose once you know where it comes from.
The version embedded in the library's name marks binary compatibility, so two incompatible generations coexist as different files and each program loads the one it was built against. That is why upgrading a library rarely breaks existing binaries — the old one is still there under its own name.
A preloaded library's symbols are found before the real ones, so a function can be replaced without touching the program. That powers legitimate debugging and profiling shims, and it is equally a technique for tampering — which is why it is ignored for privileged binaries.
W. Performance and observability
A device reporting fully busy may have no backlog at all, because busy means it had something to do rather than that it was overwhelmed. Queue length and wait time are what distinguish a device working steadily from one that requests are piling up behind.
Tracing system calls shows exactly which call a hung process is blocked in and which error it keeps receiving, which usually identifies the problem immediately. The cost is a large slowdown, so it is a diagnostic for a process already misbehaving rather than something to leave running.
Sampling takes periodic snapshots and finds hot code cheaply with a statistical picture; tracing records every occurrence and costs in proportion to how often the event happens. Choosing the wrong one either perturbs the system or misses a rare event entirely.
A verifier proves the program terminates and stays within bounds before it is allowed to load, which is what makes running custom code in the kernel acceptable on a production machine. That check is also why the programs are restricted in shape — the guarantee costs expressiveness.
Three numbers: elapsed wall clock, processor time in your code, and processor time in the kernel. When the first greatly exceeds the sum of the other two, the process was waiting rather than computing — which redirects the investigation from the code to whatever it was waiting on.
X. The shell and the process runtime environment
Zero means success and every other value means a failure of some kind, which is the opposite of the convention in most expression languages and trips people up constantly. The distinctions between non-zero values are per-program, so only zero has a portable meaning.
The operators branch on the previous command's status, running the next one only on success or only on failure. They read as boolean logic and are control flow, which is why chaining a long sequence with them produces something with no error handling at all beyond stopping.
Redirections are applied left to right, so sending errors to where output currently points must come after output has been moved. Reversing the two copies the terminal first and leaves the error stream there — a difference that looks like a typo and changes the outcome completely.
An unqualified command is searched along the path in order and the first match wins, so a directory earlier in the list silently shadows a later one. That is how a local script replaces a system tool, and why the way to answer which one runs is to ask rather than assume.
Only exported variables reach a child, and no child can alter the parent's environment. That second half is why a script that sets a variable and exits changes nothing for the caller, and why the workarounds involve either sourcing the script or having it print something to evaluate.
Output is flushed per line to a terminal and in larger blocks to a pipe or file, so the same program produces differently-timed output depending on where it is sent. That is why output appears to stop when piped, and why it can arrive out of order relative to the error stream.
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
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.