OS / Linux
Linux processes, file descriptors, IPC, pipes, permissions, signals, and package managers.
34 questions
JuniorTheoryVery commonWhich system call creates a new thread, and how does it differ from fork?
Which system call creates a new thread, and how does it differ from fork?
pthread_create() (POSIX) spawns a new thread inside the current process: it shares the address space, file descriptors, and heap. fork() creates a whole new process with its own copied memory. CreateThread is the Windows equivalent, not POSIX.
Common mistakes
- ✗Thinking
fork()creates a thread — it creates a full process with a copied address space - ✗Believing threads from
pthread_create()get private memory like a forked process does - ✗Calling
CreateThreadportable POSIX code — it is Windows-only
Follow-up questions
- →What does
pthread_create()ultimately call on Linux under the hood? - →How do
fork()and threads interact — what is unsafe to do betweenforkandexec?
JuniorTheoryVery commonWhat are file descriptors? Describe the standard FDs.
What are file descriptors? Describe the standard FDs.
An FD is a small non-negative integer indexing the process's open-file table. Standard: 0=stdin, 1=stdout, 2=stderr. Kernel entry stores offset, flags (O_RDONLY, O_NONBLOCK), inode ref. open() returns an FD; close() releases; FDs survive fork(). dup2(old, new) redirects. Limit: ulimit -n.
Common mistakes
- ✗Not closing file descriptors in forked children — if the child doesn't exec, it inherits all parent FDs; close them with
O_CLOEXECor explicitclose()beforeexec() - ✗Leaking FDs in long-running processes — eventually hits the limit and
open()returnsEMFILE; always close FDs in error paths - ✗Assuming FD 0/1/2 are always the terminal — a daemon launched by systemd may have them redirected to
/dev/null; always check
Follow-up questions
- →How does
epolluse file descriptors and what is the difference between level-triggered and edge-triggered modes? - →What happens to a file descriptor after
exec()ifO_CLOEXECis not set?
JuniorTheoryVery commonWhat are the similarities and differences between processes and threads?
What are the similarities and differences between processes and threads?
Both are OS-scheduled units. A process has its own address space, FDs, and heap (isolated); a thread shares memory and open files with siblings inside its process. Threads are cheaper but less isolated.
Common mistakes
- ✗Thinking
forkcopies memory immediately — it uses copy-on-write; pages are shared until written - ✗Not joining or detaching threads — destructor of
std::threadwith a running thread callsstd::terminate - ✗Treating inter-process communication as a simple function call — serialisation and latency are non-trivial
Follow-up questions
- →What are the advantages of using multiple processes instead of multiple threads for fault isolation?
- →How does
std::jthread(C++20) improve overstd::threadfor RAII?
JuniorTheoryVery commonWhat is a PID? How does the OS manage processes?
What is a PID? How does the OS manage processes?
PID is a unique kernel-assigned integer; init/systemd has PID 1. Kernel tracks processes in a table (task_struct): PID, PPID, state (running/sleeping/zombie/stopped), VM map, FDs, signal handlers, regs, priority. fork() creates a CoW child; exec() replaces the image; wait() reaps zombies. ps, top, /proc/[pid]/ show state.
Common mistakes
- ✗Forgetting to call
wait()afterfork()— the child becomes a zombie (still in the process table) until the parent reaps it; leaking zombie processes - ✗Assuming PIDs are sequential — PIDs wrap around after reaching the maximum (default 32768 on Linux); don't store a PID and assume the same process is alive later
- ✗Sending SIGKILL to a zombie — zombie processes are already dead and cannot receive signals; only
wait()from the parent removes them
Follow-up questions
- →What is the difference between a process and a thread at the kernel level (
clone()syscall)? - →What does
/proc/[pid]/mapsshow and how is it useful for debugging?
MiddleTheoryVery commonHow do fork and exec work, and why are they always used together?
How do fork and exec work, and why are they always used together?
fork() creates an exact copy of the process — same memory (via copy-on-write), same fds. exec*() replaces the current image with a new program (fd table preserved by default). To run a new program: fork, then call exec in the child; parent stays and waits. The fork-then-exec split is more flexible (and harder) than Windows' CreateProcess.
Common mistakes
- ✗Calling
execwithout checking for errors — if exec fails, you have a duplicate process - ✗Forgetting that fork in multi-threaded program copies only the calling thread
- ✗Allocating in fork-vs-exec window in a multithreaded program — possible deadlock
Follow-up questions
- →Why is
posix_spawnrecommended over fork+exec for performance? - →What is
vforkand why is it dangerous?
MiddleTheoryVery commonWhat is a system call and how is it different from a regular function call?
What is a system call and how is it different from a regular function call?
A syscall switches CPU from user to kernel mode for things the process can't do directly (open files, alloc memory). Mechanism: special instruction (syscall on x86_64, svc on ARM), args in registers, kernel handles it. Hundreds of cycles vs tens for a function call — hence batched I/O (writev, io_uring). strace traces them.
Common mistakes
- ✗Treating syscalls as cheap and doing one per byte read
- ✗Forgetting that syscalls can be interrupted by signals (EINTR)
- ✗Using glibc wrappers and thinking that's the syscall — wrappers add buffering, errno handling
Follow-up questions
- →How does
vDSOaccelerate some syscalls? - →What does
io_uringchange for high-throughput I/O?
JuniorTheoryCommonWhat is UID and how does Linux manage permissions?
What is UID and how does Linux manage permissions?
UID/GID identify the owner of a process/file. Each process has real, effective (for checks), and saved UID (for seteuid restore). Permissions: three rwx groups — owner, group, others (9 bits, e.g., rwxr-xr-- = 754). Special bits: setuid, setgid, sticky (on dir: only owner deletes). UID 0 = root: bypasses most checks.
Common mistakes
- ✗Using setuid binaries for privilege escalation without careful validation — setuid programs are attack vectors (e.g.,
sudovulnerabilities); audit them thoroughly - ✗Confusing file permissions with ACLs — standard Unix permissions support one user+group per file; ACLs (getfacl/setfacl) allow per-user/group rules
- ✗Forgetting the umask — new files are created with permissions = mode & ~umask; a umask of 022 removes write for group and others by default
Follow-up questions
- →What are Linux capabilities and how do they replace the all-or-nothing root model?
- →How does
sudodiffer fromsuin terms of UID management?
MiddleTheoryCommonWhat is an OS? Key components of a general-purpose OS (Linux).
What is an OS? Key components of a general-purpose OS (Linux).
Components: kernel (CFS scheduler, MMU/paging, I/O drivers); processes (fork/exec/wait, signals, namespaces); memory (virtual memory, mmap, OOM killer); FS (VFS over ext4/XFS/btrfs); network (TCP/IP, sockets, netfilter); IPC (pipes, shared memory, futex); syscalls — user/kernel boundary via syscall.
Common mistakes
- ✗Confusing the kernel with the OS — the kernel is the core component; the OS includes kernel + userspace tools (glibc, shell, init system, etc.)
- ✗Thinking virtual memory is always larger than physical — virtual address space is large (128 TB on x86-64 user space) but each mapping must be backed by physical pages or swap
- ✗Underestimating context switch cost — saving/restoring registers, flushing TLB (if ASID not supported), pipeline stalls: costs 1–10 µs; design to minimize switches
Follow-up questions
- →What is the difference between a monolithic kernel (Linux) and a microkernel (L4, seL4)?
- →How does
mmapdiffer frommallocin terms of OS interaction?
MiddleTheoryCommonWhat is a context switch and what does the OS save?
What is a context switch and what does the OS save?
A context switch is when the CPU stops running one thread and starts another. The kernel saves the outgoing thread's register file, stack pointer, instruction pointer, and (for process-to-process switches) page tables; restores the incoming thread's. Cost: ~1-10μs plus indirect TLB/cache pollution.
Common mistakes
- ✗Spawning hundreds of threads expecting linear speed-up — context switching dominates
- ✗Measuring single-syscall latency without accounting for context-switch tail effect
- ✗Confusing voluntary (yield, blocking I/O) and involuntary (preemption) switches
Follow-up questions
- →How does TLB shootdown happen during process switches?
- →What is
perf schedand what does it show?
MiddleTheoryCommonAfter fork(), are the child's writes to inherited memory visible to the parent?
After fork(), are the child's writes to inherited memory visible to the parent?
No. fork() gives the child its own private address space; pages start shared as copy-on-write, but the first write by either side triggers a private copy. Parent and child see only their own writes — sharing needs explicit mmap(MAP_SHARED) or other IPC.
Common mistakes
- ✗Thinking copy-on-write means parent and child keep sharing memory after a write
- ✗Believing
fork()physically duplicates all pages up front — it shares them lazily via CoW - ✗Assuming heap allocations remain shared post-
fork()so changes propagate to the parent
Follow-up questions
- →How does copy-on-write interact with
fork()followed immediately byexec()? - →What does
mmapwithMAP_SHAREDvsMAP_PRIVATEchange for memory shared acrossfork()?
MiddleTheoryCommonAfter a process opens a file and calls fork(), can the child read that file?
After a process opens a file and calls fork(), can the child read that file?
Yes. fork() duplicates the parent's file descriptor table, so the child gets its own fd numbers pointing to the same open file descriptions. The child can read the file, and parent and child share one file offset — a read in either advances it for both.
Common mistakes
- ✗Thinking the child must re-
open()the file because descriptors don't survivefork() - ✗Assuming parent and child get independent file offsets — they share one until either closes the fd
- ✗Confusing
O_CLOEXEC(affectsexec, notfork) with descriptor inheritance acrossfork()
Follow-up questions
- →What is the difference between a file descriptor, an open file description, and an inode?
- →How does
O_CLOEXECchange descriptor inheritance whenexecfollowsfork?
MiddleTheoryCommonWhich call handles many connections in one thread portably across Unix systems?
Which call handles many connections in one thread portably across Unix systems?
poll() — it is POSIX, so it works the same on Linux, the BSDs, and macOS, and unlike select() it has no FD_SETSIZE cap on descriptor numbers. epoll (Linux) and kqueue (BSD/macOS) scale better for huge descriptor sets but are non-portable, so poll() is the portable choice.
Common mistakes
- ✗Believing
epollis portable because it is the default in popular Linux servers - ✗Thinking
poll()has the sameFD_SETSIZElimit asselect() - ✗Reaching for
epoll/kqueuedirectly instead of an abstraction likelibeventwhen portability matters
Follow-up questions
- →Why does
poll()still scale poorly compared toepollfor tens of thousands of descriptors? - →What does a portable abstraction like
libeventorlibuvpick on each platform?
MiddleTheoryCommonHow do you pass data between processes? IPC mechanisms.
How do you pass data between processes? IPC mechanisms.
Pipes (parent/child), named pipes/FIFOs, sockets (cross-machine), shared memory (fastest but needs sync), message queues, signals (very limited data), and files.
Common mistakes
- ✗Using shared memory without synchronisation — concurrent writes without a mutex/semaphore cause corruption
- ✗Not handling broken pipes — writing to a closed-read-end pipe sends SIGPIPE, crashing the writer unless masked
- ✗Using SysV IPC (msgget/shmget) instead of POSIX equivalents — SysV is harder to clean up on abnormal exit
Follow-up questions
- →What is
AF_UNIX(Unix domain socket) and how does it differ from a TCP socket in terms of performance? - →How do you use
mmapwithMAP_SHAREDfor shared memory between processes?
MiddleTheoryCommonHow do you put a file descriptor in non-blocking mode, and why would you?
How do you put a file descriptor in non-blocking mode, and why would you?
Call fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK). Then a read() or write() that would otherwise block — e.g. writing to a pipe no reader is draining — returns -1 with errno set to EAGAIN/EWOULDBLOCK instead of stalling. This lets one thread service many descriptors via poll() without ever getting stuck.
Common mistakes
- ✗Using a bare
F_SETFL, O_NONBLOCKand wiping existing flags instead of OR-ing intoF_GETFL - ✗Treating
EAGAINas a fatal error rather than a retry-later signal - ✗Assuming non-blocking
write()always writes everything — it can return a short count
Follow-up questions
- →After
poll()reports a descriptor readable, can a non-blockingread()still returnEAGAIN? - →Why is non-blocking mode shared across all descriptors duplicated from the same open file?
MiddleTheoryCommonHow does a typical Linux scheduler decide which thread to run?
How does a typical Linux scheduler decide which thread to run?
Linux default is CFS: each runnable task accumulates vruntime; scheduler picks the lowest vruntime, weighted by nice. RT policies (SCHED_FIFO, SCHED_RR) preempt CFS by priority. EEVDF (newer kernels) replaces CFS. CPU pinning (taskset, sched_setaffinity) limits a thread to a CPU set for cache locality.
Common mistakes
- ✗Using SCHED_FIFO without bounding work — it can starve everything else
- ✗Treating
niceas percentage CPU — it's a ratio between competing tasks - ✗Pinning every thread thinking it's faster — often hurts when load is uneven
Follow-up questions
- →What is a context switch and how expensive is it?
- →How does
cgroupsv2 cpu controller layer over the scheduler?
MiddleTheoryCommonWhat happens when a signal arrives and the process installed no handler for it?
What happens when a signal arrives and the process installed no handler for it?
The signal's default disposition runs — every signal has one. The default may be Term (terminate, e.g. SIGUSR1, SIGTERM), Core (terminate + dump core, e.g. SIGSEGV), Ignore (e.g. SIGCHLD), or Stop (e.g. SIGSTOP).
Common mistakes
- ✗Assuming an un-handled signal is just ignored — most defaults actually terminate the process
- ✗Thinking every signal's default is termination —
SIGCHLDandSIGURGdefault to Ignore - ✗Believing
SIGKILLorSIGSTOPcan be caught or have their default changed — they cannot
Follow-up questions
- →Which two signals can never be caught, blocked, or ignored, and why?
- →What restrictions apply to code running inside a signal handler (async-signal-safety)?
MiddleTheoryCommonWhat are POSIX signals and which functions are async-signal-safe?
What are POSIX signals and which functions are async-signal-safe?
Signals are async notifications: SIGINT, SIGTERM, SIGKILL (uncatchable), SIGSEGV, SIGCHLD. Install handlers via sigaction. Inside a handler only async-signal-safe functions are allowed (POSIX list: write, _exit, kill); printf, malloc, STL are unsafe. Standard pattern: set volatile sig_atomic_t flag, handle in main loop.
Common mistakes
- ✗Calling
printffrom a signal handler — UB if it's interrupting another printf - ✗Using mutexes in signal handlers — deadlock if the handler interrupts the lock holder
- ✗Trying to catch SIGKILL or SIGSTOP — impossible by design
Follow-up questions
- →How does
signalfdlet you handle signals as file descriptors? - →Why is
pthread_sigmaskimportant in multi-threaded programs?
MiddleTheoryCommonWhat is virtual memory and how does the OS map it to physical RAM?
What is virtual memory and how does the OS map it to physical RAM?
Each process gets its own virtual address space; the OS and MMU translate virtual pages (4KB) to physical frames via page tables. Pages may be unmapped, shared, copy-on-write, swapped, or mmap'd to files.
Common mistakes
- ✗Confusing committed and resident memory in OOM debugging
- ✗Assuming
mallocimmediately reserves physical RAM — overcommit means it doesn't - ✗Using huge stack arrays without realising guard pages limit stack
Follow-up questions
- →What is the difference between RSS, VSZ, and PSS in
top? - →How does
mmapdiffer fromreadfor file I/O performance?
JuniorTheoryOccasionalWhat is a pipe and named pipe? How are they used for IPC?
What is a pipe and named pipe? How are they used for IPC?
A pipe is a unidirectional in-kernel byte stream between related processes. pipe(fds) creates fds[0] (read), fds[1] (write); shell | uses pipes. FIFO (mkfifo) has a filesystem path so unrelated processes open by name. Blocks on full (writer) or empty (reader) buffer. Alternatives: Unix sockets, shared memory, message queues.
Common mistakes
- ✗Not closing unused ends of a pipe — if the parent doesn't close
fds[0], the child'sread()never returns EOF because the kernel sees the write end still open - ✗Using a pipe for bidirectional communication — pipes are strictly unidirectional; use two pipes or a socketpair() for bidirectional data flow
- ✗Assuming the pipe buffer is infinite — it defaults to 64 KB on Linux; large writes block until the reader consumes data
Follow-up questions
- →How does
SCM_RIGHTSallow passing file descriptors over a Unix domain socket? - →What is the difference between a pipe and a Unix domain socket?
JuniorTheoryOccasionalWhy use select() instead of sleep() for a timed pause?
Why use select() instead of sleep() for a timed pause?
select() takes a struct timeval timeout with microsecond precision, so it can pause for fractions of a second. More importantly, it waits on file-descriptor readiness at the same time, ending the pause early if a descriptor is ready. Plain sleep() accepts only whole seconds and wakes for nothing else.
Common mistakes
- ✗Thinking
sleep()can take a fractional argument like0.5 - ✗Believing
select()only waits on descriptors and cannot act as a pure timer - ✗Forgetting that
select()updates thetimevalon Linux, so it must be reset before reuse in a loop
Follow-up questions
- →How would you do a sub-second sleep without involving any descriptors?
- →What does
select()return if its timeout elapses with no descriptor ready?
JuniorTheoryOccasionalHow do stat() and lstat() differ when given a symbolic link?
How do stat() and lstat() differ when given a symbolic link?
stat() follows the symlink: it reports the target file — its real size, and a type like S_IFREG. lstat() does not follow it: it reports the link itself, with type S_IFLNK and a size equal to the length of the path string the link stores.
Common mistakes
- ✗Assuming
stat()on a symlink describes the link — it describes the target it points to - ✗Expecting
lstat()size to be the target's size — it is the byte length of the stored path string - ✗Walking a directory tree with
stat()and missing or looping on symlinks instead of usinglstat()
Follow-up questions
- →When walking a directory tree, why is
lstat()the safer default? - →How does
O_NOFOLLOWgive the same not-following guarantee foropen()?
JuniorTheoryOccasionalWhy is creating a thread cheaper than creating a process at the OS level?
Why is creating a thread cheaper than creating a process at the OS level?
A process has its own address space, FD table, PID — creation duplicates page tables (CoW), allocates PCB, FD table. A thread shares parent's address space and FDs — kernel allocates only TCB, kernel stack, TLS. Linux represents both as task_struct; threads share more state. Thread create is ~10× faster than fork.
Common mistakes
- ✗Treating thread create as free in benchmarks — it's still microseconds
- ✗Spawning a thread per request without a pool — costs add up under load
- ✗Confusing kernel threads with user-space cooperative threads (fibers, coroutines)
Follow-up questions
- →Why does
pthread_createusecloneunder the hood? - →What is a thread pool and when does it help?
MiddleTheoryOccasionalHow do you create a file only if it does not already exist?
How do you create a file only if it does not already exist?
Open it with open(path, O_CREAT|O_EXCL, mode). The kernel checks for the file's absence and creates it as one atomic operation; if the file exists the call fails with EEXIST. A separate stat()/access() check then open() is a TOCTOU race.
Common mistakes
- ✗Doing a
stat()/access()existence check beforeopen()— opens a TOCTOU race a concurrent process can win - ✗Assuming
O_CREATalone refuses to open an existing file — withoutO_EXCLit just opens the existing one - ✗Expecting
O_EXCLto truncate or overwrite — it makes the call fail withEEXISTinstead
Follow-up questions
- →Why is
O_EXCLhistorically unreliable on NFS, and what mitigates it? - →How would you create a guaranteed-unique temp file instead?
MiddleTheoryOccasionalWhy do reentrant _r variants like readdir_r and strtok_r exist?
Why do reentrant _r variants like readdir_r and strtok_r exist?
Functions like readdir and strtok keep per-process static state, so concurrent calls from two threads corrupt each other. The _r variants take a caller-supplied buffer, making them reentrant. Note: modern glibc readdir is thread-safe per DIR*, so readdir_r is now deprecated.
Common mistakes
- ✗Assuming any libc function is thread-safe by default
- ✗Calling
strtokon two strings interleaved across threads and expecting independent state - ✗Preferring
readdir_rfor new code, unaware it is deprecated andreaddirperDIR*is the modern answer
Follow-up questions
- →What is the difference between a reentrant function and a thread-safe one?
- →Why was
readdir_rdeprecated rather than just fixed?
MiddleTheoryOccasionalWhat is the difference between RSS, VSZ, and PSS in process memory metrics?
What is the difference between RSS, VSZ, and PSS in process memory metrics?
VSZ: total virtual address space mapped — includes unused/lazy/file-mapped regions. RSS: physical pages in RAM for this process; counts shared pages fully toward each user. PSS: like RSS but shared pages are divided among sharing processes — the only metric that adds up across processes to total RAM.
Common mistakes
- ✗Summing RSS across processes — overcounts shared libraries
- ✗Using VSZ as a sign of memory leak — it can grow without RAM use (e.g.
mmap PROT_NONE) - ✗Ignoring
AnonymousvsFile-backeddistinction in smaps
Follow-up questions
- →What is
oom_score_adjand how does it influence kill order? - →How does
vm.overcommit_memorychange the picture?
SeniorTheoryOccasionalWhat are cgroups in Linux and what are they used for?
What are cgroups in Linux and what are they used for?
cgroups limit and account resource usage of a process group: CPU, memory, I/O, PIDs, network. They form the resource side of containers (Docker, k8s, systemd). cgroups v2 unifies the hierarchy (one tree, multiple controllers). Used for: bounding runaway processes, fair sharing across services, per-group OOM scoring, accurate metrics.
Common mistakes
- ✗Setting only memory limit without swap limit — can fall through to swap
- ✗Mixing v1 and v2 hierarchies — v2 only supports unified
- ✗Forgetting that cgroup limits affect OOM target selection
Follow-up questions
- →What is the difference between cgroup v1 and v2?
- →How does
systemduse cgroups for service isolation?
SeniorTheoryOccasionalDifference between kernel-level and user-level threads.
Difference between kernel-level and user-level threads.
Kernel threads (1:1) are OS-scheduled — true multi-core parallelism, but switching costs microseconds. User threads (M:N) are runtime-scheduled — cheap and fast, but a blocking syscall stalls the underlying OS thread.
Common mistakes
- ✗Assuming user-level threads avoid all OS overhead — every blocking syscall (read, sleep) still enters the kernel; the benefit is only when the runtime intercepts and async-dispatches them
- ✗Conflating coroutines with threads — coroutines are cooperative (yield explicitly) and run on one or more OS threads; they provide no automatic parallelism
- ✗Forgetting that 1:1 threads are limited by OS thread stack (typically 8 MB each); spawning thousands requires reducing stack size or switching to coroutines/async
Follow-up questions
- →How does Go's goroutine scheduler (M:N) handle blocking syscalls with network I/O?
- →What is the C++ async/coroutine executor model and how does it relate to thread pools?
SeniorTheoryOccasionalWhat does the dynamic loader do and how does LD_PRELOAD work?
What does the dynamic loader do and how does LD_PRELOAD work?
On exec of a dynamic ELF, kernel loads the loader (ld-linux.so); loader maps program, then DT_NEEDED libs (search: LD_LIBRARY_PATH, /etc/ld.so.cache, RPATH, RUNPATH, default), resolves symbols, runs _init/ctors. LD_PRELOAD=/path/lib.so forces a library first so its symbols intercept later ones — for debug, sandbox, replacing malloc.
Common mistakes
- ✗Using
LD_LIBRARY_PATHin production scripts — surprising override behaviour - ✗Confusing RPATH and RUNPATH — RPATH is checked before LD_LIBRARY_PATH, RUNPATH after
- ✗Trying
LD_PRELOADagainst setuid binaries — disabled for security
Follow-up questions
- →What is
dlopenand how does it differ from auto-loaded libraries? - →How does
lddwork and why is running it on untrusted binaries dangerous?
SeniorPerformanceOccasionalWhen does mmap beat read for file I/O and what are the trade-offs?
When does mmap beat read for file I/O and what are the trade-offs?
mmap maps a file into the address space; reads become pointer dereferences with on-demand paging. Wins for random access in large files and zero-copy IPC. Loses to read on small files, short scans, networked filesystems (SIGBUS on errors).
Common mistakes
- ✗Using mmap on a network filesystem and getting unexpected SIGBUS / hang
- ✗Forgetting
madvise(MADV_RANDOM)/MADV_SEQUENTIALto hint the kernel's prefetch - ✗Not handling SIGBUS for I/O errors — segfault on read
Follow-up questions
- →What is
MAP_POPULATEand when does it help? - →How does memory-mapped IO interact with fork and copy-on-write?
SeniorTheoryOccasionalWhat are Linux namespaces and how do they enable containers?
What are Linux namespaces and how do they enable containers?
Namespaces isolate a process's view of system resources. Types: PID (own process tree), NET (own stack), MNT (own mounts), UTS (hostname), IPC (System V/POSIX IPC), USER (uid/gid mapping), CGROUP, TIME. A container = process with a set of namespaces (isolation) + cgroups (limits) + rootfs. Created via unshare syscall or clone flags.
Common mistakes
- ✗Confusing namespaces (view isolation) with cgroups (resource limits) — different mechanisms
- ✗Believing containers are VMs — they share the host kernel
- ✗Running unprivileged containers without USER namespace mapping
Follow-up questions
- →How does a USER namespace allow rootless containers?
- →What does
nsenterdo?
SeniorTheoryOccasionalHow do you synchronize between different processes?
How do you synchronize between different processes?
Use OS primitives outside any process: named semaphores (sem_open), file locks (flock/fcntl), pthread_mutex with PTHREAD_PROCESS_SHARED in shared memory, futexes, or message queues.
Common mistakes
- ✗Placing
std::mutexin shared memory — it uses in-process addresses; usepthread_mutex_twithPTHREAD_PROCESS_SHAREDattribute instead - ✗Not handling process crash cleanup — if a process dies holding a named semaphore, it stays locked; prefer robust mutexes (
PTHREAD_MUTEX_ROBUST) or watchdog recovery - ✗Forgetting
msyncorstd::atomic_thread_fencewhen writing to shared memory — CPU and compiler reordering is not bounded by process boundaries
Follow-up questions
- →What is the difference between a POSIX named semaphore and a SysV semaphore?
- →How do you implement a lock-free ring buffer in shared memory between processes?
SeniorTheoryOccasionalProcess A is reading a file when process B unlinks it. What happens?
Process A is reading a file when process B unlinks it. What happens?
Process A keeps reading normally. unlink only removes the directory entry (the name); the inode and its data blocks survive while any open fd references them. The file just can't be opened by name anymore. Disk space is reclaimed when the last fd closes.
Common mistakes
- ✗Believing
unlinkdeletes the file immediately, breaking readers that still hold the fd - ✗Confusing
unlink(drops one directory link) with destroying the inode (last link AND last fd both gone) - ✗Expecting disk space to free the moment
unlinkreturns — it frees only after the finalclose
Follow-up questions
- →How does this behaviour make
tmpfile()and the unlink-then-use pattern safe? - →Why can
dfanddudisagree, and how doeslsoffind a deleted-but-open file?