Qualcomm Interview Prep β 04. Operating SystemsΒΆ
Scope. OS theory and the OS/runtime side of systems programming β processes & threads, CPU scheduling, IPC, synchronization (mutex/semaphore/condition variables), deadlock, priority inversion, race conditions, virtual memory & paging, page replacement, thrashing, segmentation, fragmentation, real-time OS scheduling,
fork/exec, system calls, Androidzygote, software watchdogs, and core dumps. Pure-C memory facts (pointers, the program memory map,mallocinternals, stack-vs-heap as a language topic) live in01_c_programming.md; C++ threading/RAII niceties in02_cpp_oop.md; kernel/driver/ioctl/HAL specifics and the Linux-kernel angle in07_embedded_linux_kernel.md; cache/MMU-as-hardware and memory hierarchy in09_computer_arch_digital_design.md; locking-in-a-design (text editor undo, screen-tearing) in10_lld_system_design.md. Overlaps are cross-linked, not duplicated.How to read each entry. Every question is answered in layers so you can stop at the depth you need: - Q β the question, phrased as interviewers actually ask it. - Frequency β how often it showed up in the 75 collected reports (tier + approximate count). - Concept β the basis β book-style fundamentals with worked examples and, where it helps, an SVG diagram. - The "wh"s β Why it exists (what problem the feature/rule solves), Where you see it (real Qualcomm/camera/embedded situations), and any important caveat. - Answer β a tight, say-it-out-loud interview answer. - Solution / good example β for "how do you implement/avoid/design X" questions, a complete, copy-pasteable code or design. - Follow-ups / gotchas β the traps interviewers spring next. - Seen in β the source reports.
Terms in bold-italics like context switch, critical section, semaphore, thrashing, page fault are defined in the Β§ Encyclopedia at the bottom β search there for any keyword.
Frequency legend (sample = 75 collected interview reports; counts are approximate and partly from aggregator pages, so treat them as directional): π₯π₯π₯ Very common (~8+ reports) Β· π₯π₯ Common (~4β7) Β· π₯ Occasional (~2β3) Β· β½ Foundational (rarely asked verbatim, but assumed and underpinning everything else).
Why OS dominates the Qualcomm loop. Qualcomm builds the SoCs that run operating systems β Snapdragon camera, modem, audio, and display subsystems are concurrent, real-time, memory-constrained worlds. Almost every report shows OS questions: "process vs thread," "mutex vs semaphore," "deadlock conditions," "priority inversion," "virtual memory / thrashing," "time-slicing scheduling," "IPC," "software watchdog," "core dumps." Interviewers chain these into real scenarios (two threads sharing a map; producer/consumer frame buffers; an RTOS task missing a deadline). Evidence base: qualcomm_camera_interview_experiences.md.
Table of contentsΒΆ
- A. Processes & threads β A1 process vs thread Β· A2 program vs process Β· A3 process states & PCB Β· A4 context switch Β· A5 thread memory & multithreading vs multiprocessing
- B. CPU scheduling β B1 schedulers & algorithms (FCFS/SJF/RR/priority) Β· B2 time-slicing (and "good for a game?") Β· B3 weighted/prioritized round robin Β· B4 scheduler vs dispatcher Β· B5 disk scheduling
- C. IPC β inter-process communication β C1 IPC overview (pipes/shmem/msgq/signals/sockets) Β· C2 signals Β· C3 sockets & a chat system
- D. Synchronization β D1 mutex vs semaphore Β· D2 binary vs counting semaphore (implement one) Β· D3 critical section / race conditions Β· D4 busy-wait / spinlock Β· D5 condition variables & producerβconsumer Β· D6 print odd/even with two threads
- E. Deadlock β E1 four Coffman conditions Β· E2 prevention / avoidance (Banker's) / detection / recovery
- F. Priority inversion β F1 priority inversion + inheritance + Mars Pathfinder
- G. Virtual memory & paging β G1 virtual memory & MMU Β· G2 demand paging & page faults Β· G3 page replacement (FIFO/LRU/optimal) Β· G4 thrashing Β· G5 segmentation vs paging Β· G6 fragmentation (internal/external)
- H. Memory management & the OS interface β H1 memory management overview Β· H2 system calls & user/kernel mode Β· H3 fork/exec
- I. Embedded / RTOS & operational β I1 RTOS & real-time scheduling Β· I2 software watchdog timer Β· I3 core dumps Β· I4 zygote (Android)
- Β§ Encyclopedia β searchable glossary
- Β§ Last-5-minutes cheat sheet
A. Processes & threadsΒΆ
A1 Β· Q: What is the difference between a process and a thread?ΒΆ
Frequency: π₯π₯π₯ Very common (~8+ reports) β the single most-asked OS question at Qualcomm.
Concept β the basis. A process is a program in execution β an independent unit of work with its own virtual address space (code, data, heap, stack), its own file descriptors, and its own PCB. A thread is a unit of execution within a process: it has its own stack, registers (program counter, stack pointer), and thread-local storage, but shares the process's address space β code, global/static data, and the heap β with its sibling threads. A process has at least one thread (the main thread); a multithreaded process has several.
| Process | Thread | |
|---|---|---|
| Address space | own, isolated (separate page tables) | shared with siblings |
| Owns | code, data, heap, stack, FDs, PID | a stack, registers, TLS, TID |
| Communication | needs IPC (pipes, shmem, sockets) | shared memory directly (must lock) |
| Creation cost | heavy (new address space, fork) |
light (share the address space) |
| Context switch | expensive (swap page tables, flush TLB) | cheaper (same address space) |
| Crash blast radius | isolated β one crash doesn't kill others | a bad pointer corrupts the whole process |
Example β same job, two ways:
/* multiprocess: fork β two ISOLATED address spaces, must use IPC to share */
pid_t pid = fork();
if (pid == 0) { /* child: its own copy of memory */ }
/* multithread: share everything; communication is just a shared variable + a lock */
pthread_t t;
pthread_create(&t, NULL, worker, &shared_state); // worker sees the SAME heap/globals
Why it exists. Two different trade-offs. Processes give isolation and safety (the MMU walls them off; one crashing won't corrupt another) at the cost of heavyweight creation and IPC overhead. Threads give cheap, fast cooperation (shared memory, light context switches) at the cost of safety β a single wild write or unsynchronized access corrupts the whole process. You pick based on whether you need isolation or fast data sharing.
Where you see it (Qualcomm). The camera HAL runs as a process isolated from the app and the framework (cameraserver), but inside it many threads handle the request queue, results callbacks, and per-stream processing while sharing frame metadata on the heap β which is exactly why those shared structures need mutexes. The modem, audio, and display stacks split work across processes for fault isolation and across threads for throughput.
Answer. "A process is a program in execution with its own isolated virtual address space, file descriptors, and PCB; a thread is a lighter unit of execution inside a process that has its own stack and registers but shares the process's code, data, and heap with sibling threads. So threads communicate cheaply through shared memory but must synchronize to avoid races, while processes are isolated and communicate via IPC. Threads are cheaper to create and context-switch; processes are safer because the MMU isolates them β one crashing process won't corrupt another, but one bad thread can take down the whole process."
Follow-ups / gotchas. "Which is cheaper to switch?" β threads (same page tables, no TLB flush). "Why do threads need locks but separate processes usually don't?" β shared address space. "How do threads communicate vs processes?" β shared globals/heap (+ lock) vs IPC. A thread crash via SIGSEGV kills the whole process. Cross-link: thread memory layout β A5; IPC β C; races/mutex β D.
Seen in: Display Sr Engineer (#2 "Explain processes vs threads"), ML & System Engineer (#11 threading), Associate SWE off-campus (#12 multithreading memory structure), System SW Engineer (#16), SDE off-campus (#20 "process vs thread"), Embedded App Developer (#22 "process and thread differences"), Associate SWE (#40 "process vs. thread differences"), off-campus 2021 (#44 "Process vs Thread"), FTE on-campus (#19 "difference between a process and program").
A2 Β· Q: What is the difference between a program and a process?ΒΆ
Frequency: π₯ Occasional (~2 reports) β a quick warm-up before A1.
Concept β the basis. A program is a passive entity: an executable file on disk β instructions and initial data sitting in ELF segments. A process is the active entity: that program loaded into memory and running, with a current state (registers, program counter), an address space, a stack, a heap, and OS bookkeeping (PCB, PID, open files). One program can spawn many processes (open three terminals β three processes from one bash binary).
Example:
/usr/bin/grep β a PROGRAM (a file)
grep foo a.txt & β a PROCESS (running instance, PID 1234)
grep bar b.txt & β ANOTHER PROCESS from the SAME program (PID 1235)
Why it matters. It pins down the active/passive distinction interviewers want before they ask about scheduling, states, and the PCB β all of which describe a process, not a program.
Where you see it (Qualcomm). One cameraserver binary; many running instances of helper daemons; understanding that "the code" (program) and "the running thing with state" (process) are different is the basis for talking about lifecycle and crashes.
Answer. "A program is a passive executable file on disk β just instructions and data. A process is an active instance of that program loaded into memory and executing, with its own address space, registers, stack, heap, and OS control block. One program can have many concurrent processes."
Follow-ups / gotchas. Leads straight into "what states can a process be in?" (A3) and "how is a process created?" (fork/exec, H3).
Seen in: FTE on-campus (#19 "Difference between a process and program"), SDE off-campus (#20 "What is process?").
A3 Β· Q: What are the process states, and what's in the Process Control Block?ΒΆ
Frequency: π₯ Occasional (~2β3 reports) β "process states in OS," "what is PCB."
Concept β the basis. A process moves through a small state machine. The classic five states:
- New β being created (resources being allocated).
- Ready β runnable, waiting in the run queue for a CPU.
- Running β currently executing on a core (only one process per core at a time).
- Waiting/Blocked β cannot proceed until an event (I/O completion, a lock, a signal).
- Terminated β finished or killed; PCB about to be reaped.
The transitions: NewβReady (admit), ReadyβRunning (dispatch by the scheduler), RunningβReady (preempt, e.g. time-slice expiry), RunningβWaiting (block on I/O), WaitingβReady (event/wake), RunningβTerminated (exit).
The PCB (Process Control Block) is the kernel's per-process record (task_struct in Linux). It holds: PID, process state, the saved CPU registers / program counter (saved here on a context switch), scheduling info (priority, queue pointers), memory-management info (page-table base / pointers), open-file table, accounting (CPU time), and IPC/signal state.
Why it exists. The OS can only run a handful of processes at once but juggles hundreds; the state machine + PCB let it park a process (saving exactly enough to resume it later) and pick another. The PCB is the saved process β without it, you couldn't restore a preempted process.
Where you see it (Qualcomm). A camera thread blocks (Waiting) on a DMA-done interrupt, then moves to Ready when the ISP signals frame completion; the scheduler dispatches it; on quantum expiry it's preempted back to Ready. Reading /proc/<pid>/status or a kernel crash dump to see a hung task's state ("D" = uninterruptible wait) is everyday driver debugging.
Answer. "A process is in one of: New (being created), Ready (runnable, waiting for the CPU), Running (executing on a core), Waiting/Blocked (waiting on I/O or an event), or Terminated. The scheduler dispatches ReadyβRunning; a quantum expiry or higher-priority arrival preempts RunningβReady; an I/O request moves RunningβWaiting, and completion moves WaitingβReady. All the per-process state the OS needs to suspend and resume it β PID, saved registers/PC, scheduling priority, memory and file info β lives in the Process Control Block, which is what gets saved and restored on a context switch."
Follow-ups / gotchas. Some texts add suspended/swapped states (process swapped out to disk). "What's saved on a context switch?" β the CPU register set into the PCB (A4). Threads have their own lighter control block (TCB) but share the process's memory info.
Seen in: SDE off-campus (#20 "process table / PCB / attributes in PCB"), On-campus Set 6 (#43 "Process states in OS").
A4 Β· Q: What is a context switch, and how does the OS perform it?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "what is context switching / how does OS perform it."
Concept β the basis. A context switch is the act of the kernel saving the state of the currently running process/thread and restoring the state of another, so the CPU can be time-shared. The "context" is the CPU register set β general registers, program counter, stack pointer, and status/flags β plus, for a process switch, the memory map (page-table base register) and possibly FPU/SIMD state.
Mechanically: (1) a trigger occurs (timer interrupt for time-slicing, a blocking syscall, or a higher-priority task becoming ready); (2) the CPU traps into the kernel; (3) the kernel saves the outgoing process's registers into its PCB; (4) the scheduler picks the next process; (5) the kernel loads that process's registers from its PCB, switches the address space (reload page-table base β TLB flush for a process switch), and returns to user mode at the new PC.
Example β what gets saved/restored (conceptual):
save: PC, SP, x0..x30, PSTATE β outgoing PCB
switch: TTBR0 (page-table base) β incoming address space (process switch only)
flush: TLB (stale virtualβphysical mappings) (process switch only)
restore: incoming PCB registers β CPU
return: to incoming process's next instruction
Why it exists / why it matters. Context switching is what makes multitasking possible on a finite number of cores β the illusion that many processes run "at once." But it's pure overhead (no user work happens during the switch) and it has a hidden cost: a process switch flushes the TLB and pollutes caches, so the new process starts with cold caches. That's why thread switches (same address space, no TLB flush) are cheaper than process switches, and why a too-small time quantum (B2) wastes CPU thrashing the scheduler.
Where you see it (Qualcomm). In a real-time camera/audio pipeline, excessive context switching (from too many threads or a tiny quantum) eats into the per-frame budget; engineers count switches (perf, vmstat, ftrace) when chasing latency/jitter. Cross-link: cache/TLB cost as hardware β 09_computer_arch_digital_design.md.
Answer. "A context switch is the kernel saving the running process's CPU state β registers, program counter, stack pointer β into its PCB and loading another process's state so the CPU can be shared. For a full process switch it also swaps the address space by reloading the page-table base register, which forces a TLB flush, so it's more expensive than a thread switch within the same process. It's triggered by a timer interrupt (time-slicing), a blocking system call, or preemption by a higher-priority task. It's necessary for multitasking but is pure overhead, so you minimize unnecessary switches in latency-critical code."
Follow-ups / gotchas. A mode switch (userβkernel via a syscall) is not the same as a context switch β you can trap into the kernel and return to the same process without switching. "Why is a thread switch cheaper?" β no address-space/TLB change. Cooperative vs preemptive: preemptive switches are forced by the timer; cooperative ones happen only when a task yields.
Seen in: SDE off-campus (#20 "What is context switching? How does OS perform context switching?"), implied across scheduling questions.
A5 Β· Q: How is memory laid out in a multithreaded process? Multithreading vs multiprocessing?ΒΆ
Frequency: π₯π₯ Common (~4 reports) β "thread memory allocation in a multi-threaded process," "multithreading vs multiprocessing."
Concept β the basis. Inside one process, each thread gets its own stack (and its own registers and thread-local storage); everything else is shared β the .text code, .data/.bss globals, the heap, file descriptors, and the single PID. So a malloc'd block is visible to every thread, but a local variable on thread A's stack is private to A.
Multithreading vs multiprocessing: - Multithreading β multiple threads in one address space. Cheap communication (shared memory), light switches, but no isolation β needs synchronization; one crash kills all. - Multiprocessing β multiple processes, each its own address space. Isolation and (on multicore) true parallelism with fault containment, but heavier creation and IPC.
Example β per-thread stack, shared heap:
int g_counter = 0; // SHARED across threads (.bss)
void *worker(void *arg){
int local = 0; // PRIVATE: each thread has its own 'local' (own stack)
g_counter++; // SHARED β DATA RACE without a lock (D3)
return NULL;
}
Why it exists. Threads exist precisely to share memory cheaply for fine-grained parallelism (e.g. split an image across cores); processes exist to isolate. The per-thread stack is mandatory because each thread has its own call chain and locals; sharing the heap/globals is the whole point (and the whole danger).
Where you see it (Qualcomm). A camera pipeline splits per-tile or per-stream work across worker threads sharing one frame buffer on the heap β fast, but every shared counter/index/queue needs a mutex. Fault-isolating subsystems (camera vs modem) into separate processes keeps one from crashing the other.
Answer. "In a multithreaded process every thread has its own stack, registers, and thread-local storage, but they all share the same code, globals, heap, and file descriptors. That makes inter-thread communication just a shared variable β fast β but it means shared data must be protected by a mutex or you get races. Multithreading is lightweight shared-memory concurrency within one address space; multiprocessing uses separate isolated address spaces β safer and fault-contained but heavier, communicating through IPC. You choose threads for cheap data sharing and processes for isolation."
Follow-ups / gotchas. "Is a global shared between threads?" β yes (so lock it). "Between processes?" β no (separate copies after fork; share explicitly via shared memory). Stack size per thread is configurable (pthread_attr_setstacksize). Cross-link: races/locks β D; fork copy-on-write β H3.
Seen in: ML & System Engineer (#11), Associate SWE off-campus (#12 "Multithreading memory structure, resource sharing"), System SW Engineer (#16 "multithreading vs multiprocessing"), FTE on-campus (#19 "Thread memory allocation in a multi-threaded process"), Associate SWE (#40 "Two threads accessing same MAP").
B. CPU schedulingΒΆ
B1 Β· Q: What CPU scheduling algorithms do you know? (FCFS, SJF, Round Robin, Priority)ΒΆ
Frequency: π₯π₯ Common (~5β6 reports) β "different types of scheduling algorithms," "OS schedulers."
Concept β the basis. The CPU scheduler decides which Ready process runs next. Key metrics: CPU utilization, throughput, turnaround time (submitβfinish), waiting time (time in Ready), and response time (submitβfirst run). Preemptive schedulers can yank the CPU away (timer/priority); non-preemptive ones run a job to completion or block.
- FCFS (First-Come First-Served) β a FIFO queue; non-preemptive. Simple and fair-by-arrival but suffers the convoy effect: one long job stuck at the front delays everyone (bad average waiting time).
- SJF (Shortest Job First) β run the shortest CPU burst next. Provably minimizes average waiting time, but needs the burst length (estimated, e.g. exponential averaging) and can starve long jobs. Preemptive variant = SRTF (Shortest Remaining Time First).
- Round Robin (RR) β FCFS with a time quantum: each process runs at most one quantum, then is preempted to the back of the queue (B2). Fair, great response time; the quantum tunes the trade-off.
- Priority scheduling β run the highest-priority Ready process; can be preemptive or not. Risk: starvation of low-priority jobs (cure: aging β raise priority over wait time) and priority inversion (F1).
- Multilevel queue / MLFQ β multiple queues by class (interactive vs batch), feedback moves jobs between them; the basis of real OS schedulers.
Worked example (bursts P1=7, P2=4, P3=1, all at t=0):
FCFS (P1,P2,P3): waits 0,7,11 β avg 6.0 (convoy: tiny P3 waits behind P1)
SJF (P3,P2,P1): waits 0,1,5 β avg 2.0 (optimal average)
RR q=3: P1 P2 P3 P1 P2 P1 β responsive, more context switches
Why it exists. Different goals need different policies: batch throughput (SJF), interactive responsiveness (RR), real-time guarantees (priority/EDF). No single algorithm wins all metrics, so OSes blend them (MLFQ).
Where you see it (Qualcomm). An RTOS in the camera/modem firmware uses priority preemptive scheduling so a hard-deadline task (sensor sync, frame interrupt) always beats a soft one; Linux's CFS handles the app/HAL side; understanding starvation/aging/priority-inversion is essential for real-time correctness.
Answer. "The classics: FCFS β a simple non-preemptive FIFO that suffers the convoy effect; SJF β shortest burst first, which minimizes average waiting time but needs burst estimates and can starve long jobs; Round Robin β each job gets a time quantum then is preempted, giving fairness and good response time; and Priority scheduling β highest priority runs, preemptive or not, with starvation cured by aging. Real systems use multilevel feedback queues that combine these. RTOS uses priority preemptive scheduling for deadline guarantees."
Solution / good example β pick the metric, then the algorithm:
Need lowest AVERAGE wait & you know bursts β SJF / SRTF
Need fairness + responsiveness (interactive) β Round Robin (small quantum)
Need hard real-time deadlines β fixed-priority preemptive (+ rate-monotonic / EDF)
Simplest, batch, bursts unknown β FCFS
Follow-ups / gotchas. "Which minimizes average waiting time?" β SJF (optimal, provably). "Downside of SJF?" β starvation + needing burst length. "How to prevent starvation in priority scheduling?" β aging. SRTF is the preemptive SJF. Be ready to draw a Gantt chart and compute average waiting/turnaround time.
Seen in: SDE off-campus (#20 "different types of scheduling algorithms," "Working of Round Robin"), Embedded App Developer (#22 "OS schedulers and algorithms"), Embedded/Systems (#23 "OS schedulers"), Engineer experienced (#39 "Scheduling, real-time OS"), Associate SWE (#40 "CPU scheduling"), kernel/embedded SWE (#15 "time-slicing scheduling").
B2 Β· Q: What is time-slicing? Is it a good scheduling choice for a game developer?ΒΆ
Frequency: π₯ Occasional (~2 reports) β Qualcomm asked this exact twist.
Concept β the basis. Time-slicing is the mechanism behind Round Robin: the OS gives each Ready process a fixed time quantum (e.g. 10β100 ms), and a timer interrupt preempts it at the end of its slice, moving it to the back of the run queue so the next process runs. It's what creates the illusion of simultaneous execution on one core and guarantees fairness and bounded response time.
The quantum is a knob: - too large β degenerates toward FCFS (poor responsiveness; a long job hogs the CPU); - too small β context-switch overhead dominates (you spend cycles switching, not computing).
Now the twist β "good for a game developer's scheduler?" For the game's own internal task scheduling, plain time-sliced round robin is usually not ideal. A game has heterogeneous, deadline-sensitive work: render must finish within the frame budget (e.g. 16.6 ms at 60 FPS), input must be low-latency, while audio/physics/AI have different rates. Round-robin treats them all equally and is oblivious to deadlines, so a low-priority background task can steal a slice the renderer needed β a dropped frame / jank. Games want priority-based / deadline-driven scheduling (render and input highest), often a frame-synchronized cooperative model, not blind time-slicing.
Example:
RR (quantum) over {render, input, audio, asset-load}:
asset-load gets an equal slice right before vsync β render misses the frame β stutter
Priority/deadline scheduling:
render+input are highest & deadline-aware β frame always lands β smooth 60 FPS
Why it exists. Time-slicing solves fairness and responsiveness for general-purpose, interactive multitasking (many users/apps). It does not solve deadline guarantees, which is what real-time and game workloads need.
Where you see it (Qualcomm). GPU/display/game workloads on Snapdragon are frame-synchronized and priority-driven; the camera preview pipeline likewise has hard per-frame deadlines. The right answer signals you understand fairness vs deadlines β a recurring Qualcomm theme.
Answer. "Time-slicing is round-robin scheduling: each process gets a fixed time quantum, and a timer interrupt preempts it so the next one runs β giving fairness and bounded response time. It's great for general interactive multitasking. But for a game it's usually a poor fit: a game has deadline-sensitive work β rendering and input must hit the frame budget β and round robin is deadline-blind and treats every task equally, so a background task can grab a slice the renderer needed and cause a dropped frame. Games want priority-based or deadline-driven scheduling, often frame-synchronized, rather than blind time-slicing. And the quantum matters: too big acts like FCFS, too small wastes CPU on context switches."
Follow-ups / gotchas. "What sets the quantum?" β timer interrupt period; tune for switch-overhead vs responsiveness. "Quantum β infinity?" β FCFS. "Quantum β 0?" β all overhead. Relate to RTOS priority preemptive (I1).
Seen in: kernel/embedded SWE (#15 "Time-slicing scheduling; is time slicing a good choice for a game developer's scheduling algorithm?").
B3 Β· Q: What are prioritized processes and weighted round robin?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "prioritized processes, weighted round robin."
Concept β the basis. Prioritized scheduling assigns each process a priority and always runs the highest-priority Ready one (preemptive variant preempts a lower-priority running task when a higher one becomes ready). Weighted Round Robin (WRR) is round robin where each process/queue gets a share proportional to its weight β a higher-weight task gets either a larger quantum or more turns per cycle. It blends RR's fairness with priority's differentiation: instead of "highest priority monopolizes," every task makes progress, but important ones get more CPU.
Example β WRR with weights P1=3, P2=1:
cycle: P1 P1 P1 P2 | P1 P1 P1 P2 | ... (P1 gets 3Γ the share, P2 still runs)
Why it exists. Strict priority scheduling starves low-priority work; plain RR ignores importance. WRR (and weighted fair queuing, its network cousin) gives proportional service β important flows get more bandwidth/CPU without starving the rest. Linux's CFS is a weighted fair scheduler in spirit ("nice" values are weights).
Where you see it (Qualcomm). Bandwidth/CPU sharing across camera, video, and display clients that all need some service but with different importance; packet schedulers in the modem use weighted fair queuing; QoS arbitration on the SoC interconnect is weighted-round-robin in hardware.
Answer. "Prioritized scheduling always runs the highest-priority ready process and can preempt lower-priority ones β but it can starve low-priority tasks, so you add aging. Weighted round robin is round robin where each task or queue gets a share of CPU proportional to its weight β more turns or a bigger quantum for higher weight β so important tasks get more service while everyone still makes progress, no starvation. It's the proportional-share middle ground between strict priority and plain RR; Linux CFS and network weighted-fair-queuing use the same idea."
Follow-ups / gotchas. Strict priority + a shared lock β priority inversion (F1). WRR weight too skewed β effectively starves the low-weight queue. Distinguish WRR (proportional) from MLFQ (feedback between levels).
Seen in: Embedded/Systems (#23 "Prioritized processes, weighted round robin"), Embedded App Developer (#22 "Priorities of OS programs").
B4 Β· Q: What is the difference between a scheduler and a dispatcher?ΒΆ
Frequency: π₯ Occasional (~1β2 reports) β "difference between Scheduler and Dispatcher."
Concept β the basis. They're two stages of getting a process onto the CPU. The scheduler is the policy β it decides which Ready process should run next (using FCFS/SJF/RR/priority). The dispatcher is the mechanism β it does the handover: performs the context switch (save old, load new), switches to user mode, and jumps to the right instruction. The small time the dispatcher takes is dispatch latency.
There are also three scheduler levels: long-term (admission β which jobs enter the system, controls degree of multiprogramming), medium-term (swapping processes in/out of memory), and short-term/CPU (picks the next process to run β the one people usually mean).
Example:
Scheduler (decision): "Run P3 next (it has the highest priority)."
Dispatcher (action): save P1's context β load P3's context β switch to user mode β jump to P3's PC
Why it exists. Separating what to run (policy) from how to switch to it (mechanism) is clean design: you can change the scheduling algorithm without touching the low-level switch code, and vice versa.
Where you see it (Qualcomm). Tuning an RTOS scheduling policy (the decision) is separate from the assembly-level context-switch routine (the dispatcher) β knowing the split helps when profiling dispatch latency in real-time code.
Answer. "The scheduler is the policy that decides which ready process runs next β FCFS, SJF, round robin, priority. The dispatcher is the mechanism that actually gives the CPU to that process: it does the context switch, switches to user mode, and jumps to the process's program counter. The scheduler chooses; the dispatcher carries it out, and the small overhead of doing so is dispatch latency."
Follow-ups / gotchas. "Three types of schedulers?" β long-term (admission), medium-term (swapping), short-term (CPU). Dispatch latency matters in real-time systems.
Seen in: Software Engineer campus (#18 "Difference between Scheduler and Dispatcher").
B5 Β· Q: Why is disk scheduling needed, and what algorithms exist?ΒΆ
Frequency: π₯ Occasional (~1 report) β "Why is Disk Scheduling needed... implement the best one."
Concept β the basis. On a spinning disk (HDD), the bottleneck is seek time β moving the head to the right track. Disk scheduling reorders pending I/O requests to minimize total head movement. Algorithms: - FCFS β serve in arrival order (fair, but lots of seeking). - SSTF (Shortest Seek Time First) β nearest request next (good throughput, can starve far requests). - SCAN ("elevator") β head sweeps to one end servicing requests, then reverses. - C-SCAN β sweeps one direction, then jumps back to the start (more uniform wait). - LOOK / C-LOOK β like SCAN/C-SCAN but only travels as far as the last request.
Example (head at 53, requests 98,183,37,122,14,124,65,67): SSTF serves 65,67,37,14,98,122,124,183 (nearest-first); SCAN sweeps up then down.
Why it exists. Mechanical seeks are ~milliseconds (millions of CPU cycles); ordering requests cuts total seek distance dramatically, raising throughput. (On SSDs there's no seek, so the OS uses simpler/no-op I/O schedulers β this matters: the "best" answer depends on the medium.)
Where you see it (Qualcomm). Mostly flash/eMMC/UFS on mobile β so the modern, correct nuance is "SSD/flash has no seek penalty, so classic disk scheduling mostly doesn't apply; you optimize for flash characteristics (wear, parallelism) instead." Saying this shows current knowledge.
Answer. "Disk scheduling reorders pending requests to minimize seek time on a mechanical disk. FCFS is fair but seeks a lot; SSTF picks the nearest request but can starve far ones; SCAN/C-SCAN are elevator algorithms that sweep across the disk for more uniform service; LOOK/C-LOOK only travel to the last request. C-SCAN/C-LOOK give the most uniform wait times, so they're often considered best for HDDs. On SSDs and mobile flash there's no seek, so these classic algorithms largely don't apply and the OS uses near-no-op I/O schedulers."
Follow-ups / gotchas. SSTF starvation; SCAN vs C-SCAN uniformity. The modern twist: flash storage. Don't confuse with CPU scheduling.
Seen in: FTE on-campus (#19 "Why is Disk Scheduling needed... implement the best disk scheduling algorithm").
C. IPC β inter-process communicationΒΆ
C1 Β· Q: What is IPC? Explain the mechanisms (pipes, shared memory, message queues, signals, sockets).ΒΆ
Frequency: π₯π₯ Common (~6β7 reports) β "IPC and follow-ups: pipes, fifos, message queues, semaphores."
Concept β the basis. Because processes have isolated address spaces (A1), they can't just share a variable β the OS must provide channels. IPC (inter-process communication) is that set of mechanisms:
| Mechanism | Model | Notes |
|---|---|---|
| Pipe (anonymous) | byte stream, unidirectional | only related processes (parent/child via fork); pipe() |
| Named pipe / FIFO | byte stream | a filesystem node β unrelated processes; mkfifo |
| Shared memory | shared region of RAM | fastest (no copy); needs its own synchronization (semaphore/mutex); shmget/mmap |
| Message queue | discrete messages | kernel-managed, typed/prioritized messages; mq_* / System V msgget |
| Signal | async notification | a number, not data; interrupts the target (C2) |
| Socket | bidirectional stream/datagram | works across machines too (TCP/UDP) or local (Unix domain) (C3) |
| Semaphore | counter | not data transfer β synchronization between processes (D) |
Shared memory vs message passing is the key axis: shared memory is fastest (processes read/write the same physical pages) but you must add synchronization yourself; message passing (queues, pipes, sockets) copies data through the kernel β slower but the kernel handles the coordination.
Example β anonymous pipe between parent and child:
int fd[2]; pipe(fd); // fd[0]=read end, fd[1]=write end
if (fork() == 0) { // child writes
close(fd[0]);
write(fd[1], "frame ready", 11);
} else { // parent reads
close(fd[1]);
char buf[16]; read(fd[0], buf, sizeof buf);
}
Why it exists. Process isolation is a feature (safety), but cooperating processes still need to exchange data and coordinate β IPC is the controlled doorway through the walls the MMU built. Different mechanisms trade speed vs convenience vs reach (same machine vs network).
Where you see it (Qualcomm). The Android camera stack passes large frame buffers between the app, cameraserver, and the HAL via shared memory / ashmem/dma-buf (zero-copy β you never copy a 12 MP frame), with Binder (Android's IPC) carrying control messages; signals handle async events; Unix-domain sockets connect daemons. Knowing "shared memory for bulk data + a semaphore to synchronize" is the camera-buffer pattern.
Answer. "IPC lets isolated processes communicate and synchronize. Pipes are unidirectional byte streams between related processes; named pipes/FIFOs extend that to unrelated ones via a filesystem node. Shared memory maps the same physical pages into multiple processes β the fastest, zero-copy option, but you must add your own synchronization with a semaphore or mutex. Message queues pass discrete kernel-managed messages. Signals are asynchronous notifications carrying just a number. Sockets give bidirectional communication that also works across machines. The big trade-off is shared memory β fast but you synchronize it yourself β versus message passing β the kernel copies and coordinates but it's slower. On Android, camera frames go through zero-copy shared memory while control goes over Binder."
Solution / good example β shared memory + semaphore (the camera-buffer pattern):
/* Producer and consumer map the SAME region; a semaphore coordinates handoff. */
int fd = shm_open("/frame", O_CREAT|O_RDWR, 0600);
ftruncate(fd, FRAME_BYTES);
void *buf = mmap(NULL, FRAME_BYTES, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
sem_t *ready = sem_open("/ready", O_CREAT, 0600, 0); // counting sem, init 0
/* producer: */ fill(buf); sem_post(ready); // signal a frame is ready
/* consumer: */ sem_wait(ready); use(buf); // block until producer posts
Follow-ups / gotchas. "Fastest IPC?" β shared memory (no kernel copy) β but it needs separate synchronization. "Pipe vs FIFO?" β related vs unrelated processes. "What's Binder?" β Android's reference-counted IPC (cross-link 07_embedded_linux_kernel.md). Pipes are unidirectional (use two for duplex). Cross-link: semaphore mechanics β D.
Seen in: Associate SWE off-campus (#12 "inter-process communication, deadlocks"), FTE on-campus (#19 "IPC ... pipes, fifos, message queues, semaphores ... codes," "chat system using IPC"), Embedded App Developer (#22 "IPC communications"), Embedded/Systems (#23 "IPC"), kernel/embedded SWE (#15), Associate SWE (#40 "inter-process communication methods"), Engineer experienced (#35/#46 "inter process communication theory").
C2 Β· Q: What are signals? How do you implement a signal handler?ΒΆ
Frequency: π₯ Occasional (~1β2 reports) β "IPC (signal implementation)."
Concept β the basis. A signal is an asynchronous software notification delivered to a process β a small integer (e.g. SIGINT=interrupt, SIGSEGV=bad memory, SIGCHLD=child changed state, SIGTERM=polite kill, SIGKILL=unblockable kill, SIGALRM=timer). The OS interrupts the process and runs a signal handler (or the default action: terminate, ignore, stop, dump core). Signals carry no payload beyond the number (and a small siginfo) β they're notifications, not data channels.
Example β install a handler safely:
#include <signal.h>
volatile sig_atomic_t g_stop = 0; // ONLY safe type to touch in a handler
void on_term(int sig){ (void)sig; g_stop = 1; } // keep handlers tiny
int main(void){
struct sigaction sa = {0};
sa.sa_handler = on_term;
sigaction(SIGTERM, &sa, NULL); // prefer sigaction over signal()
while (!g_stop) { /* work */ } // main loop polls the flag
}
Why it exists. They're the OS's way to tell a process about asynchronous events β a user pressing Ctrl-C, a timer firing, a child dying, a fault occurring β without that process polling. They're the Unix mechanism for exceptional/control-flow notifications.
Where you see it (Qualcomm). Daemons handle SIGTERM for graceful shutdown (flush buffers, release the camera) and SIGSEGV/SIGABRT to trigger a core dump (I3) for crash analysis; SIGALRM/timers drive periodic work; a watchdog may signal a stuck task.
Answer. "A signal is an asynchronous notification the OS delivers to a process β just a number like SIGTERM, SIGSEGV, or SIGCHLD β that interrupts it and runs a handler or the default action. They're for events, not bulk data. You install a handler with sigaction, and crucially the handler must be tiny and async-signal-safe: only touch a volatile sig_atomic_t flag and call only async-signal-safe functions β no malloc, no printf β because the handler runs at an arbitrary point. SIGKILL and SIGSTOP can't be caught or ignored."
Follow-ups / gotchas. Handlers run in a restricted context: async-signal-safe functions only (no malloc/printf/most libc); communicate via volatile sig_atomic_t. SIGKILL/SIGSTOP are uncatchable. Prefer sigaction over the older signal (portable semantics). Cross-link: a SIGSEGV is the runtime face of the C segfault β 01_c_programming.md D3.
Seen in: Embedded App Developer (#22 "IPC (signal implementation)").
C3 Β· Q: How would you build a chat system using IPC / sockets? (TCP vs UDP)ΒΆ
Frequency: π₯ Occasional (~1β2 reports) β "chat system using IPC," "UDP vs TCP."
Concept β the basis. A socket is a bidirectional communication endpoint. Unix-domain sockets connect processes on one machine; TCP/UDP sockets connect across the network. - TCP β connection-oriented, reliable, ordered, byte-stream, with flow/congestion control. Use when you can't lose data (chat messages, file transfer). - UDP β connectionless, unreliable, unordered, message datagrams, low overhead/latency. Use when speed beats reliability (live audio/video, where a late packet is useless).
Sketch β a TCP chat server (one thread per client):
int s = socket(AF_INET, SOCK_STREAM, 0); // TCP
bind(s, ...); listen(s, BACKLOG);
for (;;) {
int c = accept(s, NULL, NULL); // new client connection
pthread_create(&t, NULL, handle_client, &c); // fan out per client
}
/* handle_client: recv() messages, broadcast to all other connected sockets */
Why it exists. Sockets generalize IPC to a uniform "endpoint" API that works locally and across machines; TCP and UDP exist because reliability and latency are a fundamental trade-off you must pick per use case.
Where you see it (Qualcomm). Modem/networking stacks live and breathe TCP/UDP; on-device daemons use Unix-domain sockets; video/audio streaming typically rides UDP/RTP (drop a late frame rather than stall), while control signaling uses TCP β directly relevant to multimedia roles. (Networking depth β cross-link future networking file / 07_embedded_linux_kernel.md.)
Answer. "A socket is a bidirectional endpoint; for a chat I'd use TCP because messages must arrive reliably and in order. The server creates a listening socket, accepts connections, and handles each client β one thread or an event loop with select/epoll β receiving messages and broadcasting them to the others. I'd choose TCP over UDP here because TCP is reliable, ordered, and connection-oriented, whereas UDP is connectionless and lossy β UDP is the right choice for live audio/video where low latency matters more than never dropping a packet."
Follow-ups / gotchas. "TCP vs UDP?" β reliable/ordered/stream vs fast/lossy/datagram. Scaling: thread-per-client vs epoll event loop. "Why UDP for video?" β a retransmitted late frame is useless. Local IPC β Unix-domain socket (no network stack).
Seen in: FTE on-campus (#19 "creating a chat system using IPC"), Embedded/Systems (#23 "Udp vs tcp, diff").
D. SynchronizationΒΆ
D1 Β· Q: What is the difference between a mutex and a semaphore?ΒΆ
Frequency: π₯π₯π₯ Very common (~9 reports) β a Qualcomm staple, asked almost every loop.
Concept β the basis. Both protect shared data, but they're different tools:
- A mutex (mutual exclusion lock) is a locking mechanism with ownership: it's binary (locked/unlocked), and only the thread that locked it may unlock it. Because it has an owner, it can support priority inheritance (cure for priority inversion, F1) and recursion. Use it to protect a critical section β one resource, one holder at a time.
- A semaphore is a signaling mechanism β an integer counter with
wait()(P: decrement, block if 0) andsignal()(V: increment). It has no owner: any thread cansignal, even one that neverwaited. A binary semaphore (0/1) can act as a lock; a counting semaphore (0..N) tracks N available units of a resource.
| Mutex | Semaphore | |
|---|---|---|
| Purpose | mutual exclusion (locking) | signaling / counting permits |
| Ownership | yes β locker must unlock | no β anyone can signal |
| Values | locked / unlocked | 0..N (binary or counting) |
| Priority inheritance | yes (can have) | no |
| Typical use | guard a critical section | producer/consumer slots, event signaling, ISRβtask |
Example β the conceptual difference:
/* MUTEX: lock & unlock by the SAME thread, around a critical section */
pthread_mutex_lock(&m); shared++; pthread_mutex_unlock(&m);
/* SEMAPHORE: one thread signals, another waits β ownership-free handoff */
sem_wait(&items); /* consumer blocks until... */ sem_post(&items); /* ...producer signals */
Why it exists. Mutual exclusion (one-at-a-time access) and signaling/counting (how many units are free, "an event happened") are different problems. A mutex's ownership lets it offer priority inheritance and detect "you don't own this lock"; a semaphore's ownerless counter naturally models resource pools and cross-thread/ISR signaling β which a strict mutex can't (you often signal a mutex-like wait from a context that never acquired it).
Where you see it (Qualcomm). A mutex guards a shared frame-metadata struct or a request-queue index (critical section). A counting semaphore counts free slots in a bounded frame-buffer pool (producer/consumer, D5); a binary semaphore is the classic way an ISR signals a task ("DMA done") β the ISR posts, the task waits, and that ownerless handoff is exactly why you use a semaphore, not a mutex, there.
Answer. "A mutex is a lock for mutual exclusion: it's binary, it has an owner β only the thread that locked it can unlock it β and that ownership lets it support priority inheritance, which cures priority inversion. You use it to protect a critical section. A semaphore is a counter for signaling: wait decrements and blocks at zero, signal increments, and it has no owner, so any thread can signal β even one that never waited. A binary semaphore can act as a lock; a counting semaphore tracks N available units of a resource, like buffer slots. Rule of thumb: mutex for mutual exclusion, semaphore for counting resources or signaling between threads or from an ISR to a task."
Follow-ups / gotchas. "Can a binary semaphore replace a mutex?" β for plain locking yes, but you lose ownership β no priority inheritance, and you can accidentally let another thread "unlock" it. "Why use a semaphore from an ISR?" β ownerless signaling (a mutex's locker must be the unlocker; an ISR didn't lock anything). Mutex held too long across a blocking call β contention. Cross-link: priority inversion β F1; producer/consumer β D5.
Seen in: Senior Engineer F2F (#4 "C++ questions on mutex and semaphore"), ML & System Engineer (#11 "Semaphore, mutex, locking, and threading"), System SW Engineer (#16 "binary semaphores vs mutexes"), FTE on-campus (#19 "Difference between Mutex locks and Semaphores"), SDE off-campus (#20 "What is mutex?"), Embedded App Developer (#22 "Mutex/critical section/semaphores"), Embedded/Systems (#23 "Semaphore, mutex, busy wait"), off-campus 2021 (#44 "Mutex vs Binary Semaphore"), University grad (#45 "mutex and semaphore and their use cases").
D2 Β· Q: What is a binary vs counting semaphore? Implement a binary semaphore.ΒΆ
Frequency: π₯π₯ Common (~3 reports) β "Implement binary semaphore (with follow-ups)."
Concept β the basis. A semaphore is an integer with two atomic operations: wait/P (decrement; if the value would go negative, block until someone signals) and signal/V (increment; wake a waiter).
- Binary semaphore β value is 0 or 1 β mutual exclusion or a single event flag.
- Counting semaphore β value 0..N β tracks N units of a resource (e.g. N free buffer slots).
The crucial property: the test-and-modify of the counter is atomic β that's what the OS provides and what you must replicate.
Why it exists. It generalizes locking to "N permits." A counting semaphore initialized to N lets up to N threads proceed concurrently (e.g. a connection pool of size N); a binary one (init 1) is mutual exclusion; a binary one (init 0) is an event you wait on until signaled.
Where you see it (Qualcomm). Counting semaphore = number of free frame buffers in a pool; binary semaphore = "frame interrupt fired" event from ISR to processing task.
Answer (code). A correct binary semaphore needs a mutex + condition variable so waiting threads sleep (not busy-wait):
#include <pthread.h>
typedef struct {
int value; // 0 or 1 for binary; 0..N for counting
pthread_mutex_t m;
pthread_cond_t cv;
} sem_t_;
void sem_init_(sem_t_ *s, int init){ s->value = init;
pthread_mutex_init(&s->m, NULL); pthread_cond_init(&s->cv, NULL); }
void sem_wait_(sem_t_ *s){ // P
pthread_mutex_lock(&s->m);
while (s->value == 0) // WHILE, not if (guards spurious wakeups)
pthread_cond_wait(&s->cv, &s->m); // sleep + release lock atomically
s->value--;
pthread_mutex_unlock(&s->m);
}
void sem_post_(sem_t_ *s){ // V
pthread_mutex_lock(&s->m);
s->value++; // (cap at 1 here if strictly binary)
pthread_cond_signal(&s->cv); // wake one waiter
pthread_mutex_unlock(&s->m);
}
Follow-ups / gotchas. Use while (not if) around cond_wait β guards against spurious wakeups and lost races. For a strict binary semaphore, clamp value to 1 in post. The naive "just a flag" version (busy-wait on while(!flag);) burns CPU and has a race β mention the mutex+condvar version. Real sem_post is async-signal-safe (callable from a signal handler), unlike mutex lock.
Seen in: System SW Engineer (#16 "Implement binary semaphore with follow-up questions"), off-campus 2021 (#44 "Mutex vs Binary Semaphore"), ML & System Engineer (#11).
D3 Β· Q: What is a critical section / race condition? How do you handle two threads accessing the same data?ΒΆ
Frequency: π₯π₯ Common (~5 reports) β "critical section, race conditions," "two threads accessing same MAP."
Concept β the basis. A race condition is when the correctness of the result depends on the unpredictable interleaving of concurrent threads. A critical section is the code region that accesses shared data and must run atomically (one thread at a time). The fix is mutual exclusion β a lock around the critical section.
Example β the classic lost update:
int counter = 0; // shared
void *inc(void *_) {
for (int i=0;i<100000;i++) counter++; // counter++ is READ, ADD, WRITE β 3 steps!
return NULL;
}
/* Two threads β final counter is < 200000: updates are lost when their
read-modify-write interleaves. THAT is a race condition. */
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
void *inc(void *_) {
for (int i=0;i<100000;i++){
pthread_mutex_lock(&m); // enter critical section
counter++; // now atomic w.r.t. other threads
pthread_mutex_unlock(&m); // leave
}
return NULL;
}
Why it exists / why it matters. Modern CPUs and compilers reorder and pipeline; counter++ isn't atomic; on multicore two threads truly run at once. Without synchronization, shared-state updates corrupt each other β and the bug is non-deterministic (it "works" until it doesn't), making it among the hardest to reproduce and the most dangerous (data corruption, security holes).
Where you see it (Qualcomm). "Two threads accessing the same map" (Qualcomm asked this verbatim) β say a request map shared between a submit thread and a results thread; without a lock you get torn reads, lost entries, crashes. Frame-queue indices, reference counts on buffers, and 3A statistics shared across threads all need critical-section protection.
Answer. "A race condition is when the outcome depends on the timing of how threads interleave β for example two threads both doing counter++, which is really read-modify-write, so updates get lost. The critical section is the code touching shared data that must run atomically. I handle it with mutual exclusion: wrap the critical section in a mutex so only one thread is inside at a time. A correct solution needs mutual exclusion, progress, and bounded waiting. For two threads sharing a map, I'd guard every access with the map's mutex β or use a reader-writer lock if reads dominate β and keep the critical section as short as possible to reduce contention."
Solution / good example β reader/writer lock when reads dominate (the display/screen-tearing angle):
pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
/* many readers concurrently: */ pthread_rwlock_rdlock(&rw); read(map); pthread_rwlock_unlock(&rw);
/* one exclusive writer: */ pthread_rwlock_wrlock(&rw); write(map); pthread_rwlock_unlock(&rw);
Follow-ups / gotchas. volatile does not fix races (no atomicity/ordering β use a mutex or _Atomic; cross-link 01_c_programming.md B3). Keep critical sections short. Reader/writer locks for read-heavy data (this is exactly the reader-writer / screen-tearing producer-consumer scenario in 10_lld_system_design.md). TOCTOU (time-of-check-to-time-of-use) is a race too.
Seen in: System SW Engineer (#16 "critical sections, synchronization, race conditions"), Embedded/Systems (#23 "Race conditions, ex"), SDE off-campus (#20 "What is Critical Section?"), Associate SWE (#40 "Two threads accessing same MAP, mutex, locking"), University grad (#45 "Identify thread race conditions"), Display Sr Engineer (#2 reader-writer synchronization).
D4 Β· Q: What is a spinlock / busy-wait, and how does it differ from a mutex?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "Deadlocks, Spinlocks, and their differences," "busy wait."
Concept β the basis. A spinlock is a lock where a thread that can't acquire it busy-waits β loops ("spins") checking the lock in a tight loop β instead of sleeping. A busy-wait is any such polling loop. Contrast with a mutex, which (when contended) blocks the thread: the OS deschedules it and wakes it when the lock frees.
Example:
/* spinlock: spin (waste CPU) until acquired β NO context switch */
while (__atomic_test_and_set(&lock, __ATOMIC_ACQUIRE)) { /* spin */ }
... critical section ...
__atomic_clear(&lock, __ATOMIC_RELEASE);
/* mutex: if contended, the thread SLEEPS (yields the CPU) until woken */
pthread_mutex_lock(&m); ... pthread_mutex_unlock(&m);
Why it exists / when to use which. A blocking mutex costs two context switches (sleep, then wake) β worth it for long waits, wasteful for very short ones. A spinlock avoids that overhead and is ideal when the critical section is tiny and you're on a multiprocessor (another core will release it in nanoseconds). But spinning wastes CPU while you wait and is a disaster on a uniprocessor (you spin holding the only CPU, so the lock holder can never run to release it). Rule: spinlocks for very short critical sections in kernel/SMP/interrupt context where you can't sleep; mutexes for longer waits in user space.
Where you see it (Qualcomm). The Linux kernel uses spinlocks to protect short critical sections that run in interrupt context (where you must not sleep) β e.g. a driver's ISR touching a shared queue; user-space camera code uses sleeping mutexes. Knowing "you cannot sleep in an ISR, so you spin" is core driver knowledge.
Answer. "A spinlock busy-waits β it loops checking the lock instead of sleeping β so it has no context-switch overhead and is right for very short critical sections on a multiprocessor, especially in interrupt context where you're not allowed to sleep. A mutex instead blocks the thread, descheduling it until the lock is free, which costs context switches but doesn't waste CPU β better for longer waits. Spinning wastes CPU and is dangerous on a uniprocessor because the spinner can starve the lock holder. So: spinlocks for tiny waits where you can't sleep, mutexes for everything else."
Follow-ups / gotchas. Never sleep while holding a spinlock; never spin in user space for long. Busy-wait also describes a bad while(!ready); poll β prefer a condvar/semaphore so the thread sleeps. Cross-link: kernel spinlocks/ISR rules β 07_embedded_linux_kernel.md.
Seen in: FTE on-campus (#19 "Deadlocks, Spinlocks, and their differences"), SDE off-campus (#20 "What is spin lock?"), Embedded/Systems (#23 "busy wait").
D5 Β· Q: Explain condition variables and solve the producerβconsumer (bounded buffer) problem.ΒΆ
Frequency: π₯π₯ Common (~4 reports, incl. the display/screen-tearing variant) β producer/consumer & synchronization.
Concept β the basis. A condition variable (CV) lets a thread sleep until a condition becomes true, always paired with a mutex. wait(cv, m) atomically releases m and sleeps; on signal/broadcast it wakes and re-acquires m. CVs avoid busy-waiting. The producerβconsumer (bounded buffer) problem is the canonical use: producers add items to a fixed-size buffer, consumers remove them; producers must block when full, consumers when empty.
The classic semaphore solution uses three primitives: a counting semaphore empty (init N, free slots), a counting semaphore full (init 0, filled slots), and a binary semaphore/mutex mutex (guards the buffer indices).
Why it exists. It decouples producers from consumers running at different rates, with a buffer absorbing bursts β and does so without busy-waiting (threads sleep when they can't proceed). It's the backbone of every pipeline/queue.
Where you see it (Qualcomm). Exactly the camera/display data flow: the sensor/ISP produces frames into a bounded buffer pool; the encoder/display consumes them. The Qualcomm display interview's screen-tearing problem (SoC producer + panel consumer sharing a frame buffer) is this pattern with reader/writer sync. Audio ring buffers are the same.
Answer. "A condition variable lets a thread sleep until a predicate holds, always used with a mutex β wait atomically drops the lock and sleeps, and a signaller wakes it. It avoids busy-waiting. The producer-consumer problem is the canonical case: with a bounded buffer I use a counting semaphore empty (free slots, init N), a counting semaphore full (filled slots, init 0), and a mutex to protect the buffer. The producer does wait(empty), lock, insert, unlock, signal(full); the consumer does wait(full), lock, remove, unlock, signal(empty). That blocks the producer when full and the consumer when empty, with no busy-waiting. The one gotcha is lock ordering β take the counting semaphore before the mutex, or you can deadlock."
Solution / good example β full bounded-buffer with semaphores:
#include <semaphore.h>
#include <pthread.h>
#define N 8
int buf[N], in = 0, out = 0;
sem_t empty, full;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void init_pc(void){ sem_init(&empty, 0, N); sem_init(&full, 0, 0); }
void produce(int item){
sem_wait(&empty); // block if no free slot
pthread_mutex_lock(&mutex); // enter critical section
buf[in] = item; in = (in + 1) % N;
pthread_mutex_unlock(&mutex);
sem_post(&full); // one more filled slot
}
int consume(void){
sem_wait(&full); // block if buffer empty
pthread_mutex_lock(&mutex);
int item = buf[out]; out = (out + 1) % N;
pthread_mutex_unlock(&mutex);
sem_post(&empty); // one more free slot
return item;
}
Follow-ups / gotchas. Order matters: sem_wait(empty/full) before lock(mutex) β locking first then blocking on a full/empty buffer while holding the mutex deadlocks. Use while (not if) around a CV predicate (spurious wakeups). The reader-writer variant (many readers, one writer) is the screen-tearing answer. Cross-link: LLD framing of this β 10_lld_system_design.md.
Seen in: Display Sr Engineer (#2 / #6 "Design Algorithm to Address Screen Tearing ... reader-writer synchronization"), System SW Engineer (#16 "synchronization"), ML & System Engineer (#11), FTE on-campus (#19).
D6 Β· Q: Two threads β one prints odd numbers, the other even β synchronize them.ΒΆ
Frequency: π₯ Occasional (~2 reports) β Qualcomm's favorite "show me synchronization" coding task.
Concept β the basis. Two threads must alternate (1,2,3,4,...). This is a synchronization/ordering problem: each thread must wait its turn. The cleanest solution is a mutex + condition variable with a shared "whose turn" flag (or two semaphores ping-ponging).
Why it's asked. It's a compact test that you can write correct multithreaded code: shared state, a lock, a wait/signal handoff, and termination β without races or deadlock.
Where you see it (Qualcomm). Stands in for any "strictly alternate / hand off between two threads" pattern (e.g. double-buffering: one thread fills buffer A while the other drains B, then swap).
Answer (code) β mutex + condition variable:
#include <pthread.h>
#include <stdio.h>
#define MAX 10
int turn = 0; // 0 = even's turn, 1 = odd's turn
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
void *evens(void *_){
for (int n = 0; n <= MAX; n += 2){
pthread_mutex_lock(&m);
while (turn != 0) pthread_cond_wait(&cv, &m); // wait my turn
printf("%d ", n);
turn = 1; pthread_cond_signal(&cv); // hand off to odd
pthread_mutex_unlock(&m);
}
return NULL;
}
void *odds(void *_){
for (int n = 1; n <= MAX; n += 2){
pthread_mutex_lock(&m);
while (turn != 1) pthread_cond_wait(&cv, &m);
printf("%d ", n);
turn = 0; pthread_cond_signal(&cv);
pthread_mutex_unlock(&m);
}
return NULL;
}
/* main: pthread_create both, pthread_join both β prints 0 1 2 3 ... 10 */
Follow-ups / gotchas. while (not if) around cond_wait. Termination: make sure the last thread can't get stuck waiting (broadcast on exit if needed). A two-semaphore solution works too (each thread waits its own, posts the other's). Cross-link: same skill as D5.
Seen in: Associate SWE off-campus (#12 "Print odd and even numbers sequentially using two different threads"), System SW Engineer (#16 "2 threads ... odd ... even ... use mutex to synchronize").
E. DeadlockΒΆ
E1 Β· Q: What is a deadlock, and what are the necessary conditions for it?ΒΆ
Frequency: π₯π₯π₯ Very common (~8 reports) β "necessary conditions for a deadlock."
Concept β the basis. A deadlock is a state where a set of processes/threads are each blocked forever, each waiting for a resource held by another in the set β so none can proceed. The classic illustration: two threads, each holding one lock and waiting for the other's.
Coffman's four necessary conditions β all four must hold simultaneously for deadlock to be possible: 1. Mutual exclusion β at least one resource is held in non-shareable mode (only one holder at a time). 2. Hold and wait β a process holds at least one resource while waiting to acquire others. 3. No preemption β a resource can't be forcibly taken; it's released only voluntarily by its holder. 4. Circular wait β there's a closed chain of processes, each waiting for a resource the next one holds.
Example β the textbook two-lock deadlock:
/* Thread A */ /* Thread B */
lock(L1); lock(L2);
lock(L2); // waits... lock(L1); // waits... β both stuck forever (circular wait)
Why it matters. Deadlock is a liveness failure β the system hangs with no crash, often only under rare timing. The four-conditions framing is powerful because breaking any one prevents deadlock β it's the recipe for every prevention strategy (E2).
Where you see it (Qualcomm). A capture thread and a stats thread acquiring a frame lock and a config lock in opposite orders β intermittent hang; a driver taking two kernel locks inconsistently β a hung task the watchdog eventually catches. Deadlock-by-inconsistent-lock-order is the #1 real concurrency bug.
Answer. "A deadlock is when a group of threads are each blocked forever, every one waiting for a resource another in the group holds, so nobody makes progress. It needs all four Coffman conditions at once: mutual exclusion β a resource only one thread can hold; hold and wait β holding one while waiting for another; no preemption β resources can't be forcibly taken; and circular wait β a cycle of threads each waiting on the next. Because all four are necessary, breaking any single one prevents deadlock β that's the basis of prevention."
Follow-ups / gotchas. "Difference from starvation?" β deadlock = circular blocked-forever; starvation = a thread perpetually loses out but the system progresses. "Detect it?" β look for a cycle in the resource-allocation graph (single-instance resources). The most practical prevention: a global lock ordering (kills circular wait). Cross-link: livelock (threads keep changing state but make no progress).
Seen in: ML & System Engineer (#11 "What is a deadlock... necessary conditions"), FTE on-campus (#19 "What are Deadlocks"), foundit (#9 "deadlock avoidance schemes"), Tech Profiles (#24 "What is Deadlock?"), System Engineer (#26 "What is deadlock"), Embedded App Developer (#22 "Deadlock situations"), Associate SWE (#40 "Deadlock and real-time examples").
E2 Β· Q: How do you handle deadlock β prevention, avoidance, detection, recovery? (Explain Banker's algorithm.)ΒΆ
Frequency: π₯π₯ Common (~4 reports) β "describe a few deadlock avoidance schemes," "methods to avoid/prevent."
Concept β the basis. Four strategies along a spectrum from strict-but-safe to permissive:
1. Prevention β structurally ensure one of the four Coffman conditions can never hold: - break mutual exclusion β make resources shareable where possible (rarely feasible); - break hold-and-wait β require a process to request all resources up front (low utilization) or release all before requesting more; - allow preemption β let the OS take a resource back (works for CPU/memory, not for, say, a half-written file); - break circular wait β impose a global ordering on resources and always acquire in that order (the practical, widely-used one).
2. Avoidance β allow the conditions but never enter an unsafe state, using advance knowledge of maximum needs. The Banker's algorithm is the canonical avoidance algorithm: before granting a request, it simulates the allocation and checks whether the system stays in a safe state (a sequence exists in which every process can finish). If yes, grant; if not, make the requester wait β even though the resources are free.
3. Detection β allow deadlocks, periodically run a detection algorithm (find a cycle in the wait-for / resource-allocation graph), and act when one is found.
4. Recovery β once detected, break it: kill a process (abort), or preempt/roll back a resource to a checkpoint.
Banker's algorithm β the data and the safety check:
For m resource types, n processes:
Available[m] free units of each resource
Max[n][m] each process's declared MAXIMUM need
Allocation[n][m] currently held
Need = Max - Allocation still required
Safety check (is the state safe?):
Work = Available; Finish[i] = false for all i
Repeat: find an i with Finish[i]==false AND Need[i] <= Work
if found: Work += Allocation[i]; Finish[i] = true // i can finish, frees its resources
If all Finish[i]==true β SAFE (a safe sequence exists); else UNSAFE.
Why it exists. Each strategy trades safety for utilization/overhead. Prevention is simple but wastes resources; avoidance (Banker's) is less restrictive but needs max-needs declared up front and runs a check per request; detection+recovery lets you run unrestricted and clean up rarely. Most general-purpose OSes (Linux) actually use the "ostrich" approach β ignore deadlock, since it's rare and prevention/avoidance is costly β and rely on the developer to order locks correctly.
Where you see it (Qualcomm). The pragmatic answer for driver/HAL code is prevention by lock ordering β define a global lock hierarchy and always acquire in that order; tools like lockdep catch violations. Banker's is mostly an interview/theory topic (real systems rarely declare max needs), but knowing it shows depth.
Answer. "Four approaches. Prevention removes one of the four Coffman conditions structurally β in practice, impose a global lock ordering so circular wait can't happen. Avoidance allows the conditions but never enters an unsafe state given knowledge of maximum needs β that's the Banker's algorithm: before granting a request it checks whether a safe sequence still exists in which every process can finish; if not, the requester waits even though resources are free. Detection lets deadlocks happen, then finds a cycle in the resource-allocation graph and recovers by killing or rolling back a process. Most real OSes don't bother β they use the ostrich approach and rely on correct lock ordering. For my own code, consistent lock ordering plus keeping critical sections short is the practical defense."
Solution / good example β prevention by lock ordering (the one you'd actually ship):
/* Always acquire locks in a FIXED global order β no circular wait β no deadlock. */
void transfer(Account *a, Account *b, int amt){
Account *first = (a->id < b->id) ? a : b; // lower id first, ALWAYS
Account *second = (a->id < b->id) ? b : a;
pthread_mutex_lock(&first->m);
pthread_mutex_lock(&second->m);
a->bal -= amt; b->bal += amt;
pthread_mutex_unlock(&second->m);
pthread_mutex_unlock(&first->m);
}
Follow-ups / gotchas. "Safe vs unsafe vs deadlocked": unsafe β deadlocked (it's just risk); safe guarantees no deadlock. Banker's needs max claims known in advance β its big limitation. trylock + back-off is another prevention tactic (release and retry instead of waiting). Cross-link: the four conditions β E1.
Seen in: foundit (#9 "Describe a few deadlock avoidance schemes"), Embedded/Systems (#23 "Deadlock, methods to avoid, prevent"), Embedded App Developer (#22 "Deadlock situations (detection, prevention)"), ML & System Engineer (#11).
F. Priority inversionΒΆ
F1 Β· Q: What is priority inversion, and how do you fix it? (Tell me the Mars Pathfinder story.)ΒΆ
Frequency: π₯π₯ Common (~4 reports) β "Priority Inversion, ex," asked repeatedly in embedded loops.
Concept β the basis. Priority inversion is when a high-priority task is blocked by a low-priority task β effectively inverting their priorities. The dangerous form: a low-priority task holds a lock the high-priority task needs; meanwhile a medium-priority task (that doesn't need the lock) preempts the low task and runs, so the low task can't finish and release the lock β so the high task is stuck behind the medium one indefinitely (unbounded priority inversion).
The fix β priority inheritance: while a low-priority task holds a lock that a high-priority task is waiting on, the low task temporarily inherits the high task's priority, so no medium task can preempt it; it finishes the critical section quickly, releases the lock, and drops back to its base priority. (Alternative: the priority ceiling protocol β a lock's priority is preset to the highest of any task that can use it.)
The Mars Pathfinder story (the canonical example). In 1997, NASA's Mars Pathfinder lander kept resetting on Mars. The cause was priority inversion on VxWorks: a low-priority meteorological task and a high-priority information-bus task shared data guarded by a mutex. The bus task would block on the mutex held by the met task; a medium-priority communications task would then preempt the met task, so the met task couldn't release the mutex β the high-priority bus task missed its deadline, a watchdog timer noticed and reset the system. The fix: the VxWorks mutex had a flag to enable priority inheritance, which had been left off; JPL flipped it (uploaded a patch that set the global from FALSE to TRUE) and the resets stopped.
Example β what saves you:
Without inheritance: Low holds R; High blocks on R; Medium preempts Low β High starves (Pathfinder reset)
With inheritance: Low holds R and is BOOSTED to High's priority β Medium can't preempt β Low finishes β High runs
Why it matters. In a real-time system, a missed deadline can be catastrophic (a frame dropped, a control loop diverging, a spacecraft resetting). Priority inversion silently breaks the priority guarantees an RTOS is supposed to provide.
Where you see it (Qualcomm). Any RTOS firmware in the camera/modem/audio path where tasks of different priorities share a lock (a sensor-sync ISR-driven task vs a low-priority logging task touching the same buffer). Qualcomm asks this because the camera pipeline is full of priority-differentiated tasks sharing resources β and the "use a mutex with priority inheritance, not a plain semaphore" lesson is directly applicable.
Answer. "Priority inversion is when a high-priority task is blocked waiting on a resource held by a low-priority task. It gets dangerous and unbounded when a medium-priority task that doesn't need the resource preempts the low-priority holder β now the low task can't finish and release the lock, so the high task is stuck behind the medium one indefinitely. The fix is priority inheritance: while the low task holds a lock a high task wants, it temporarily inherits the high task's priority so nothing medium can preempt it; it finishes fast, releases, and drops back. The famous case is the Mars Pathfinder, which kept resetting because a VxWorks mutex had priority inheritance turned off β once JPL enabled it, the resets stopped. That's also why you use a mutex with priority inheritance rather than a plain binary semaphore to guard a critical section shared across priorities."
Follow-ups / gotchas. Priority inheritance is exactly why a mutex (ownership) can offer it but a plain semaphore can't (D1). Priority ceiling is the alternative protocol. The Pathfinder watchdog reset is also a nice tie-in to software watchdogs (I2). Cross-link: mutex vs semaphore β D1; RTOS β I1.
Seen in: Embedded App Developer (#22 "priority inversion"), Embedded/Systems (#23 "Priority Inversion, ex"), off-campus 2021 (#44 "Priority Inversion"), University grad (#45 "priority inversion problem and the priority inheritance solution").
G. Virtual memory & pagingΒΆ
G1 Β· Q: What is virtual memory, and how does virtual-to-physical translation work?ΒΆ
Frequency: π₯π₯ Common (~4 reports) β "Virtual Memory," "Virtual to physical address conversion (MMU)."
Concept β the basis. Virtual memory gives each process its own large, contiguous virtual address space, decoupled from physical RAM. Memory is divided into fixed-size pages (virtual) and frames (physical, e.g. 4 KB). The MMU translates a virtual address to a physical one on every access via the process's page table; a TLB caches recent translations to make it fast.
A virtual address splits into a page number (indexes the page table β gives the physical frame number) and an offset (passes through unchanged). So physical address = frame Γ pageSize + offset. A page-table entry also holds permission/valid bits.
Why it exists. Several wins at once: (1) isolation β each process has its own address space, so it can't touch another's memory (the MMU enforces it β a stray access faults; cross-link 01_c_programming.md D3 segfault); (2) more memory than RAM β pages not in RAM live on disk and are brought in on demand (G2); (3) simpler programming β every process sees the same flat layout regardless of where it physically lands; (4) sharing β the same physical frame (e.g. a shared library, or copy-on-write pages) can map into many processes.
Where you see it (Qualcomm). Every process on the SoC (camera, modem, apps) runs in its own virtual space, isolated by the MMU. DMA by the ISP/sensor goes through an IOMMU/SMMU (the device-side MMU) that translates device addresses β central to camera buffer management and security. Knowing virtualβ physical is essential when a driver hands hardware a physical (or IOVA) address but the CPU sees a virtual one.
Answer. "Virtual memory gives each process its own large virtual address space decoupled from physical RAM, divided into fixed-size pages mapped to physical frames. On every memory access the MMU translates the virtual address using the process's page table β the high bits index the page table to get a frame number, the low bits are the offset that passes through unchanged β and a TLB caches recent translations for speed. It buys process isolation enforced by hardware, the ability to use more memory than physical RAM via demand paging to disk, a uniform address layout, and page sharing. An illegal access β unmapped or wrong permission β traps as a page fault or segfault."
Follow-ups / gotchas. "Where's the page table?" β in physical memory, base in a register (e.g. TTBR/CR3); multi-level page tables save space. "What makes it fast?" β the TLB (a TLB miss walks the page table; a flush happens on a process switch). On 64-bit, page tables are hierarchical (4 levels). Cross-link: MMU/cache as hardware β 09_computer_arch_digital_design.md.
Seen in: off-campus 2021 (#44 "Virtual Memory," "Virtual to physical address conversion (MMU)," "Page table location"), System SW Engineer (#16 "paging, virtual memory"), Engineer C++ (#62 "virtual memory").
G2 Β· Q: What is demand paging? What is a page fault and how is it handled?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "Page fault and steps followed to handle it."
Concept β the basis. Demand paging means a page is loaded into RAM only when it's first accessed, not all up front β a process starts with few pages resident and faults the rest in lazily. A page fault is the trap the MMU raises when a process accesses a page whose page-table entry is marked not present (valid bit = 0).
Page-fault handling steps: 1. MMU traps to the OS page-fault handler (access to a not-present page). 2. The OS checks the access is legal (in a mapped region with right permissions). If illegal β SIGSEGV (segfault). If legal but just not resident β continue. 3. Find a free frame (or evict one via a page-replacement algorithm if none free β G3; if the victim is dirty, write it back to disk first). 4. Read the page from disk (backing store / swap / file) into the frame. 5. Update the page table (mark present, set frame number), update the TLB. 6. Restart the faulting instruction β now it succeeds.
A fault that just needs a table update (page already in RAM, e.g. shared/COW) is a minor fault; one requiring disk I/O is a major fault.
Why it exists. Loading only what's touched means faster startup and the ability to run programs larger than RAM (and to over-commit memory). It's also how copy-on-write fork works β pages are shared read-only and faulted to copies only on first write (H3).
Where you see it (Qualcomm). Lazy-loading a large app/library on a memory-constrained phone; copy-on-write after fork/zygote (I4); a major fault stalling a thread (disk/flash latency) β relevant to latency tuning in long-running camera daemons.
Answer. "Demand paging loads a page into RAM only when it's first accessed, so a process starts with few pages and faults the rest in lazily. A page fault is the MMU trapping when you touch a page marked not-present. The handler checks the access is legal β if not it's a segfault β then finds a free frame, evicting and writing back a victim page if memory is full, reads the needed page from disk, updates the page table and TLB, and restarts the faulting instruction. A minor fault just fixes up the table; a major fault needs disk I/O. It lets programs be bigger than RAM and start faster, and it's the basis of copy-on-write fork."
Follow-ups / gotchas. Distinguish a legal page fault (handled, transparent) from an illegal access (segfault). Too many faults β thrashing (G4). The instruction is re-executed, so it must be restartable. Cross-link: segfault β 01_c_programming.md D3; page replacement β G3.
Seen in: Software Engineer campus (#18 "Page fault and steps followed to handle it").
G3 Β· Q: What page-replacement algorithms do you know (FIFO, LRU, optimal)? What is Belady's anomaly?ΒΆ
Frequency: π₯ Occasional (commonly expected / aggregator-reported) β implied by paging/virtual-memory and "LRU cache" questions.
Concept β the basis. When a page fault occurs and no free frame exists, the OS must evict a resident page. The page-replacement algorithm picks the victim, aiming to minimize future faults: - FIFO β evict the oldest-loaded page. Simple, but ignores usage; suffers Belady's anomaly. - Optimal (OPT/MIN) β evict the page that won't be used for the longest time in the future. Provably minimal faults, but unimplementable (needs the future) β a benchmark only. - LRU (Least Recently Used) β evict the page least recently used, approximating optimal by assuming the past predicts the future. Good, but exact LRU is costly (timestamps/stack); real systems approximate it (the clock / second-chance algorithm using a reference bit).
Belady's anomaly: for FIFO, increasing the number of frames can increase the number of page faults β counterintuitive. Stack algorithms like LRU and OPT do not suffer Belady's anomaly (they satisfy the inclusion property: the pages present with n frames are a subset of those with n+1).
Example (reference string 1,2,3,4,1,2,5,1,2,3,4,5 β FIFO with 3 vs 4 frames yields MORE faults with 4: Belady's anomaly.)
Why it exists. RAM is finite; the choice of victim hugely affects fault rate (and thus performance, since a major fault costs ~a million CPU cycles). LRU/clock approximate the unattainable optimal cheaply.
Where you see it (Qualcomm). The OS page cache and the related LRU cache that Qualcomm asks you to implement (a HashMap + doubly-linked list, O(1) get/put) β the eviction policy concept is identical. (LRU-cache coding problem β 03_dsa.md / 10_lld_system_design.md.)
Answer. "When a fault hits and no frame is free, a page-replacement algorithm picks the victim. FIFO evicts the oldest page β simple but it ignores usage and can suffer Belady's anomaly, where adding frames increases faults. Optimal evicts the page used farthest in the future β minimal faults but unimplementable, just a benchmark. LRU evicts the least-recently-used page, approximating optimal; exact LRU is expensive so real OSes use the clock/second-chance approximation with a reference bit. LRU and optimal are stack algorithms and don't suffer Belady's anomaly; FIFO does."
Follow-ups / gotchas. "Which can't be implemented?" β optimal (needs the future). "Which suffers Belady's?" β FIFO (and second-chance), not LRU/OPT. Real Linux uses an approximate-LRU (two-list/active-inactive). Cross-link: implementing an LRU cache β 03_dsa.md.
Seen in: off-campus 2021 (#44 paging context), Tech Profiles (#24 "What is LRU Cache? / LFU cache"), FTE on-campus (#19 "LRU cache data structures"), Engineer C++ (#31/#62 "LRU Cache").
G4 Β· Q: What is thrashing? What causes it and how do you fix it?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "thrashing," "defragmentation, thrashing, virtual memory."
Concept β the basis. Thrashing is when a system spends more time paging (swapping pages in/out of disk) than doing useful work β the page-fault rate skyrockets and CPU utilization collapses. It happens when the sum of processes' working sets exceeds physical RAM: each process doesn't have enough frames to hold its actively-used pages, so it constantly faults, evicting a page another process immediately needs back.
The vicious cycle: high paging β low CPU utilization β the OS (mistakenly) thinks it can admit more processes β even less RAM per process β more paging.
Fixes / prevention: - Working-set model β track each process's working set (pages used in a recent window) and only run processes whose working sets fit in RAM. - Page-fault frequency (PFF) β monitor fault rate; if too high, give the process more frames (or suspend/swap it out entirely). - Reduce degree of multiprogramming β suspend/swap out some processes (medium-term scheduler). - Add RAM / kill memory hogs (the OOM killer on Linux).
Why it matters. Thrashing turns a busy system into a useless one β throughput drops to near zero. Recognizing it (low CPU + high disk/swap activity) is a key diagnostic skill.
Where you see it (Qualcomm). Memory-constrained phones: too many heavy apps + a camera session can push the device into paging/low-memory-killer territory, killing background apps; long-running daemons leaking memory eventually thrash. Knowing "low CPU + high swap = thrashing, reduce the working set" is real triage.
Answer. "Thrashing is when a system spends most of its time paging instead of executing β the page-fault rate explodes and CPU utilization collapses. It happens when the combined working sets of running processes exceed physical RAM, so everyone constantly faults in pages that just got evicted. It can spiral because low CPU utilization tempts the scheduler to admit even more processes. You fix it by honoring the working-set model β only run what fits in RAM β using page-fault-frequency control to give faulting processes more frames or suspend them, reducing the degree of multiprogramming, or simply adding RAM. On Linux the low-memory/OOM killer is the blunt last resort."
Follow-ups / gotchas. "Symptom?" β high paging, low CPU utilization. "Root cause?" β working set > available frames. Don't confuse with fragmentation (G6) or defragmentation. Locality of reference is why working sets exist. Cross-link: virtual memory β G1.
Seen in: Embedded App Developer (#22 "virtual memory/thrashing"), Engineer C++ (#62 "defragmentation, thrashing, virtual memory, memory management").
G5 Β· Q: What is segmentation, and how does it differ from paging?ΒΆ
Frequency: π₯ Occasional (commonly expected / aggregator-reported) β implied by memory-management questions.
Concept β the basis. Segmentation divides a process's address space into variable-size, logical segments that match the program's structure β code, data, stack, heap β each with a base and limit. An address is (segment number, offset). Paging instead divides memory into fixed-size pages with no relation to program structure.
| Paging | Segmentation | |
|---|---|---|
| Unit | fixed-size page | variable-size segment (logical unit) |
| Address | page # + offset | segment # + offset |
| Fragmentation | internal (last page partly empty) | external (gaps between segments) |
| Visible to programmer | no (transparent) | yes (logical units) |
| Protection | per page | per segment (natural: code RO, etc.) |
Real systems often combine them (segmented paging / paged segments): segments divided into pages, getting logical structure and no external fragmentation. (x86 historically had segmentation; modern OSes are essentially flat-paged.)
Why it exists. Segmentation matches the programmer's view (separate code/data/stack with natural per-segment protection); paging matches the hardware's need for simple, fixed-size allocation with no external fragmentation. Each solves a different problem, hence the hybrid.
Where you see it (Qualcomm). The program memory map's segments (.text/.data/.stack β cross-link 01_c_programming.md D1) are the logical-segmentation idea; the underlying mapping to RAM is paged. Per-segment permissions (code read-only/executable, stack no-execute) are what catch a wild write as a segfault.
Answer. "Segmentation splits the address space into variable-size logical segments β code, data, stack, heap β each addressed as segment-number plus offset, with natural per-segment protection matching how the programmer thinks. Paging splits memory into fixed-size pages with no relation to program structure, addressed as page-number plus offset. Paging causes internal fragmentation; segmentation causes external fragmentation. Paging is transparent to the programmer; segments are visible. Modern systems combine them β paged segmentation β to get logical structure without external fragmentation; mainstream OSes today are essentially flat and paged."
Follow-ups / gotchas. "Which has internal vs external fragmentation?" β paging internal, segmentation external (G6). A segmentation fault's name comes from this history but today means any illegal memory access. Cross-link: memory map β 01_c_programming.md.
Seen in: Tech Profiles (#24 "internal and external fragmentation," "overlays"), implied by #19/#22 memory-management discussions.
G6 Β· Q: What is fragmentation β internal vs external? How do you fight it?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "internal vs. external fragmentation."
Concept β the basis. Fragmentation is wasted memory from how it's allocated.
- Internal fragmentation β wasted space inside an allocated block: you asked for 10 bytes but got a 16-byte (page/block-aligned) chunk β 6 bytes wasted within the allocation. Caused by fixed-size allocation (paging, fixed pools, struct padding β cross-link 01_c_programming.md F3).
- External fragmentation β free memory exists but is split into small non-contiguous pieces, so a large request fails despite enough total free memory. Caused by variable-size allocation over time (segmentation, a long-running heap).
Fixes:
- External: compaction (relocate allocations to coalesce free space β paging avoids the need by decoupling virtual/physical); paging itself (any free frame works); coalescing adjacent free blocks on free.
- Internal: smaller allocation granularity; slab/pool allocators that match block size to object size (fixed-size frame descriptors β zero internal waste and no external fragmentation).
Why it matters. A long-running embedded/camera daemon can fail a large malloc despite plenty of total free RAM purely due to external fragmentation β a real field failure (cross-link 01_c_programming.md C2). Determinism-critical paths avoid the general heap for this reason.
Where you see it (Qualcomm). Choosing slab/pool allocators for fixed-size frame/metadata buffers to guarantee no fragmentation and deterministic allocation in the camera pipeline; understanding why a daemon's large allocation fails after days of uptime. Paging is the OS-level answer to external fragmentation.
Answer. "Internal fragmentation is space wasted inside an allocated block β you get a fixed-size chunk bigger than you asked for, like rounding up to a page or struct padding. External fragmentation is free memory broken into scattered small pieces, so a big request fails even though the total free memory is enough. You fight external fragmentation with compaction, coalescing freed blocks, or paging β which sidesteps it by mapping any free frame. You reduce internal fragmentation with finer granularity or slab/pool allocators sized to the object. In embedded camera code we use fixed-size pools for frame buffers precisely to get deterministic allocation with no fragmentation."
Follow-ups / gotchas. "Paging β which fragmentation?" β internal only (no external, since any frame works). "Segmentation β which?" β external. Padding is internal fragmentation at the struct level. Slab allocator = the embedded cure. Cross-link: heap internals β 01_c_programming.md C2.
Seen in: Tech Profiles (#24 "internal and external fragmentation"), University grad (#45 "internal vs. external fragmentation"), Engineer C++ (#62 "memory management").
H. Memory management & the OS interfaceΒΆ
H1 Β· Q: What is memory management in an OS?ΒΆ
Frequency: π₯π₯ Common (~5 reports) β "Memory management concepts," "why paging was needed."
Concept β the basis. Memory management is the OS subsystem that controls how memory is allocated to processes and reclaimed β tracking which parts of memory are in use, allocating/freeing on request, and providing the virtual memory abstraction (paging, swapping, protection) that isolates processes and lets them use more memory than physical RAM. It spans: allocation (frames to processes), address translation (MMU/page tables, G1), protection (per-page permissions), swapping/demand paging (G2), and replacement (G3) β plus the user-space allocator (malloc, cross-link 01_c_programming.md C2) sitting on top.
Why it exists. Multiple processes must safely share finite RAM without stepping on each other, must each see a clean address space, and must collectively be able to use more memory than physically present. Memory management is the machinery that makes all that work β it's why paging exists (the answer to "why was paging needed?": to allocate memory non-contiguously, isolate processes, and eliminate external fragmentation).
Where you see it (Qualcomm). Every aspect of running camera/modem/app workloads on a memory-tight SoC: frame-buffer allocation (often special DMA-able/dma-buf memory), IOMMU-mediated device memory, low-memory handling, and choosing pool allocators for determinism.
Answer. "Memory management is the OS subsystem that allocates and reclaims memory across processes and provides the virtual-memory abstraction. It tracks which memory is used, hands out and frees frames, translates virtual to physical addresses through the MMU and page tables, enforces per-page protection, and swaps pages to disk via demand paging so processes can exceed physical RAM and stay isolated. Paging was needed to allocate memory non-contiguously, isolate processes, and avoid external fragmentation. On top of it, the user-space allocator hands out heap memory via malloc."
Follow-ups / gotchas. "Why paging?" β non-contiguous allocation, isolation, no external fragmentation. Ties together G1βG6. Distinguish OS-level memory management from the C library allocator. Cross-link: malloc/heap β 01_c_programming.md; MMU hardware β 09_computer_arch_digital_design.md.
Seen in: Embedded App Developer (#22 "Memory management concepts"), Embedded/Systems (#23 "Memory management"), FTE on-campus (#19 "Memory Management and why paging was needed"), Associate SWE (#40 "Memory management"), Engineer C++ (#62 "memory-management concepts").
H2 Β· Q: What is a system call? What's the difference between user mode and kernel mode?ΒΆ
Frequency: π₯ Occasional (~3 reports) β "Different System calls and why we use them," "user mode and kernel mode."
Concept β the basis. A system call is the controlled entry point through which a user program requests a privileged service from the kernel β I/O, memory, process control. The CPU runs in two privilege levels: user mode (restricted β can't touch hardware or other processes' memory directly) and kernel mode (full privilege). A syscall performs a mode switch: a trap/svc instruction switches to kernel mode, the kernel validates and executes the request, then returns to user mode.
Categories of system calls: process control (fork, exec, exit, wait), file management (open, read, write, close), device management (ioctl), information (getpid, time), communication (pipe, socket, shmget), and memory management (brk/sbrk, mmap, munmap, mprotect).
Example β a library call wraps a system call:
printf("hi"); // LIBRARY call (libc) β buffers, formats, then calls...
write(1, "hi", 2); // SYSTEM call β traps into the kernel to do the actual I/O
Why it exists. Privileged operations (touching hardware, other processes' memory, the page tables) must be mediated so a buggy/malicious user program can't compromise the system. The user/kernel split + the narrow syscall gateway is the protection boundary; the kernel validates every request at the door.
Where you see it (Qualcomm). ioctl is the userβdriver channel β the camera HAL talks to a kernel driver via ioctls; mmap maps a device's DMA buffer into user space; the user/kernel/HAL boundary is a recurring Qualcomm topic (cross-link 07_embedded_linux_kernel.md). The cost of a syscall (mode switch) is why you batch I/O.
Answer. "A system call is the controlled gateway for a user program to ask the kernel for a privileged service β I/O, memory, process control. The CPU runs in user mode, which is restricted, or kernel mode, which is fully privileged; a syscall traps in via a mode switch, the kernel validates and performs the request, then returns to user mode. The split exists for protection β user code can't directly touch hardware or other processes' memory, so the kernel mediates everything. The memory-management syscalls are mmap, munmap, mprotect, and brk/sbrk; ioctl is the usual user-to-driver channel. A library call like printf runs in user space and only becomes a system call when it actually needs the kernel β write."
Follow-ups / gotchas. Library call vs system call (only the latter traps to the kernel β cross-link 01_c_programming.md G1). A mode switch is not a context switch (A4). "Memory-management syscalls?" β mmap/munmap/mprotect/brk. Cross-link: kernelβuserβHAL β 07_embedded_linux_kernel.md.
Seen in: Software Engineer campus (#18 "Different System calls and why do we use them," "Kernel vs OS"), University grad (#45 "What are system calls? memory management system calls? user mode and kernel mode"), SDE off-campus (#20 "What is system call? User space and kernel space").
H3 Β· Q: Explain fork() and exec(). What does fork return? What is copy-on-write?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "How Process is created?", "what happens at process creation."
Concept β the basis. fork() creates a new process by duplicating the calling (parent) process β the child gets a copy of the parent's address space, file descriptors, and registers. The famous quirk: fork returns twice β it returns the child's PID to the parent and 0 to the child (and -1 on failure) β so one call, two return values, distinguishing who's who. exec() (the execv/execlp family) replaces the current process image with a new program β same PID, brand-new code/data/stack, starting at the new program's entry point. The standard idiom is fork then exec: fork a child, then have the child exec the target program (this is how a shell launches a command).
Copy-on-write (COW): modern fork doesn't physically copy the parent's memory. Instead parent and child share the same physical pages, marked read-only; pages are duplicated lazily only when either process writes to one (a write triggers a minor page fault that makes a private copy). So fork is cheap even for a large process, and fork+exec doesn't waste a full copy that exec would immediately discard.
Example:
pid_t pid = fork();
if (pid < 0) { perror("fork"); }
else if (pid == 0) { execlp("ls", "ls", "-l", NULL); /* child becomes 'ls' */ }
else { int st; waitpid(pid, &st, 0); /* parent waits for child */ }
Why it exists. Separating "make a new process" (fork) from "run a new program" (exec) is elegant Unix design: between the two, the child can set up redirections, environment, and privileges. COW makes the otherwise-wasteful copy nearly free.
Where you see it (Qualcomm). Android's zygote (I4) is literally a fork (without exec) of a warm, pre-initialized process, relying on copy-on-write so every app starts from a shared, already-loaded runtime β a direct, high-value tie-in. Daemons spawning helpers use fork+exec.
Answer. "fork creates a child process that's a copy of the parent; it returns twice β the child's PID to the parent and 0 to the child, or -1 on failure β so each side knows its role. exec replaces the current process image with a new program, keeping the same PID but starting fresh at the new entry point. The usual pattern is fork then exec β fork a child, set things up, then exec the target β which is how a shell runs commands. Modern fork uses copy-on-write: parent and child share physical pages read-only and only copy a page when one writes to it, so fork is cheap. Android's zygote uses exactly this β it forks a pre-warmed runtime so apps start fast and share memory via COW."
Follow-ups / gotchas. "Why return twice?" β one address space becomes two; both resume after fork. "fork without exec?" β child runs the same program (zygote, or a worker pool). "What does exec keep?" β PID, open FDs (unless close-on-exec); replaces memory image. vfork is the old no-copy optimization, largely superseded by COW. Cross-link: COW & page faults β G2; zygote β I4.
Seen in: SDE off-campus (#20 "How Process is created?"), implied by #19 (process creation) and #15 (zygote).
I. Embedded / RTOS & operationalΒΆ
I1 Β· Q: What is a real-time OS (RTOS), and how does its scheduling differ from a general-purpose OS?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "What is RTOS and how does it differ from OS?", "real-time OS."
Concept β the basis. An RTOS is an OS that guarantees deterministic timing β a task's response happens within a bounded, predictable deadline, every time. The goal isn't raw throughput (like a general-purpose OS) but predictability. Two flavors: hard real-time (a missed deadline = system failure β airbag, motor control, sensor sync) and soft real-time (occasional misses degrade quality but are tolerable β video/audio frames).
Key RTOS traits vs a GPOS (Linux/Windows): - Scheduling: fixed-priority preemptive (a higher-priority ready task immediately preempts a lower one), often rate-monotonic or EDF β vs fairness-oriented (CFS/round-robin) in a GPOS. - Bounded latencies: deterministic interrupt latency and context-switch time; small, predictable kernel paths. - Priority inheritance mutexes to bound priority inversion (F1). - Often no demand paging (paging adds unpredictable latency) β memory is locked/resident.
Why it exists. Many embedded tasks must meet deadlines, not just eventually finish. A GPOS optimizes average throughput and can have unbounded worst-case latency (it might page, or let a fair scheduler delay you) β unacceptable when a sensor sample or a motor pulse must land on time.
Where you see it (Qualcomm). Camera/modem/audio firmware and DSP code often run on an RTOS (or a real-time executive) where frame interrupts, sensor synchronization, and codec deadlines are hard: a late frame is a glitch. The Snapdragon's various processors (modem DSP, sensor hub) run real-time workloads. Priority-preemptive scheduling + priority-inheritance mutexes + watchdogs (I2) is the RTOS toolkit.
Answer. "An RTOS guarantees deterministic, bounded timing β a task responds within a predictable deadline every time β rather than optimizing average throughput like a general-purpose OS. Hard real-time means a missed deadline is a failure; soft real-time tolerates occasional misses. It uses fixed-priority preemptive scheduling, often rate-monotonic or EDF, so a high-priority task preempts immediately; it has bounded, predictable interrupt and context-switch latency; it uses priority-inheritance mutexes to bound priority inversion; and it often avoids demand paging because paging adds unpredictable latency. In a camera or modem pipeline, frame and sensor-sync deadlines are real-time, which is why this matters."
Follow-ups / gotchas. "Hard vs soft real-time?" β failure vs degraded quality. "Why priority inheritance in an RTOS?" β bound priority inversion (F1). "Why avoid paging?" β determinism. Rate-monotonic (static priorities by period) vs EDF (dynamic by deadline). Cross-link: priority inversion β F1; watchdog β I2.
Seen in: Engineer (#50 "What is RTOS and how does it differ from OS?"), Engineer experienced (#39 "Scheduling, real-time OS, and file system").
I2 Β· Q: What is a software watchdog timer, and what is it for?ΒΆ
Frequency: π₯ Occasional (~2 reports) β "Software watchdog timers," asked in embedded loops.
Concept β the basis. A watchdog timer is a countdown timer that resets the system (or takes recovery action) if it isn't periodically "kicked" (fed/refreshed) by software within a timeout. The healthy software pets the watchdog regularly; if the system hangs (deadlock, infinite loop, crashed task), the kicks stop, the timer expires, and the watchdog forces a reset to recover. A hardware watchdog is an independent timer peripheral (survives a fully wedged CPU); a software watchdog is a task/timer in the OS that monitors other tasks (each task must check in; a missed check-in triggers logging, a core dump, or a reset).
Example (pattern):
watchdog_start(2000); // 2 s timeout
for (;;) {
do_periodic_work();
watchdog_kick(); // "I'm alive" β must happen before 2 s elapses
}
/* if do_periodic_work() ever hangs > 2 s β no kick β watchdog fires β reset/recover */
Why it exists. Embedded systems run unattended with no human to hit reset. A watchdog is the last-resort recovery that turns a permanent hang into an automatic recovery β essential for reliability in the field. (It's exactly what reset the Mars Pathfinder when priority inversion caused a missed deadline β F1.)
Where you see it (Qualcomm). Snapdragon subsystems (modem, camera, DSP, the AP) have watchdogs: if a subsystem hangs, the watchdog triggers a subsystem restart (SSR) or a full reset and a crash log β keeping the phone alive instead of bricked. A stuck camera thread that stops kicking gets caught and the subsystem restarts.
Answer. "A watchdog timer is a countdown that resets the system or triggers recovery if software fails to periodically kick it within a timeout. In healthy operation the code refreshes it regularly; if the system hangs β deadlock, infinite loop, a dead task β the kicks stop, the timer expires, and it forces a reset. A hardware watchdog is an independent peripheral that survives a wedged CPU; a software watchdog is an OS task that monitors others and expects each to check in. It exists because embedded devices run unattended, so it's the last-resort automatic recovery from a hang. On Snapdragon, watchdogs trigger subsystem restarts with a crash log rather than letting the device freeze β and a watchdog is exactly what reset Mars Pathfinder when priority inversion caused a deadline miss."
Follow-ups / gotchas. Kicking it from the wrong place (e.g. an ISR that always runs even when the main loop is stuck) defeats it β kick only on real forward progress. Hardware vs software watchdog. Ties to core dumps (I3, capture state before reset) and priority inversion (F1, Pathfinder). Cross-link: subsystem restart β 07_embedded_linux_kernel.md.
Seen in: Embedded App Developer (#22 "Software watchdog timers"), Embedded/Systems (#23 "Software watchdog").
I3 Β· Q: What is a core dump, and how do you use it for debugging?ΒΆ
Frequency: π₯ Occasional (~1β2 reports) β "Error handling, core dumps," "memory overflow and crash handling."
Concept β the basis. A core dump is a snapshot of a crashed process's memory and CPU state (registers, stack, heap, mapped regions) written to a file when it terminates abnormally β typically on SIGSEGV (bad memory), SIGABRT (assert/abort), SIGFPE, etc. You load it into a debugger post-mortem to see exactly where and why it died β the call stack (backtrace), variable values, and the faulting instruction β without having to reproduce the crash live.
Example β enable, crash, analyze:
ulimit -c unlimited # allow core files (size limit off)
./camera_daemon # crashes β produces a 'core' file
gdb ./camera_daemon core # post-mortem
(gdb) bt # backtrace β the exact call chain at the crash
(gdb) frame 2 # inspect a frame
(gdb) print *ctx # examine variables/state at the moment of death
Why it exists. Many crashes are rare or field-only (a specific input, a race, a device). A core dump captures the exact state at the moment of failure so you can debug it after the fact β invaluable when you can't attach a live debugger or reproduce on demand.
Where you see it (Qualcomm). A camera/modem daemon crashes on a device in test β the core dump (or a tombstone, Android's core-dump equivalent, plus logcat) gives the backtrace pinpointing the bad pointer/register access. Combined with watchdog-triggered dumps (I2), you get post-mortems of hangs too. This is bread-and-butter bring-up/debug work.
Answer. "A core dump is a file capturing a crashed process's memory and CPU state β registers, stack, heap β written when it dies on a signal like SIGSEGV or SIGABRT. You load it into a debugger like gdb for a post-mortem: a backtrace shows the exact call chain at the crash, and you can inspect variables and the faulting instruction without reproducing it live. You enable it with ulimit -c unlimited. It's essential for rare or field-only crashes you can't catch interactively. On Android the equivalent is a tombstone plus logcat, and watchdog-triggered dumps let you post-mortem hangs too."
Follow-ups / gotchas. Need symbols (-g, or a separate symbol file) for a useful backtrace. On Android: tombstones in /data/tombstones, debuggerd. AddressSanitizer catches the bug at the access (better than a post-mortem when you can rebuild). Cross-link: segfault causes β 01_c_programming.md D3; signals β C2.
Seen in: Embedded App Developer (#22 "Error handling, core dumps"), Associate SWE (#40 "Memory overflow and crash handling scenarios").
I4 Β· Q: What is the zygote in Android, and why does it exist?ΒΆ
Frequency: π₯ Occasional (~1 report) β "What is the function of zygote?"
Concept β the basis. In Android, the zygote is a special pre-warmed process started at boot that has already loaded and initialized the runtime (ART/Dalvik) and preloaded the common framework classes and resources every app needs. When you launch an app, Android doesn't start a fresh runtime β it forks the zygote (no exec), and the new app process inherits the already-initialized runtime and preloaded classes. Thanks to copy-on-write (H3), all those preloaded pages are shared read-only across every app until an app writes to them.
Why it exists. Two big wins: (1) fast app startup β forking a warm process is far quicker than cold-starting and initializing a VM + loading thousands of framework classes per app; (2) memory savings β every app shares one read-only copy of the preloaded runtime/classes via COW instead of each loading its own. It's a textbook application of fork + copy-on-write to amortize expensive one-time initialization.
Example (conceptual):
boot: start zygote β load ART + preload ~thousands of framework classes/resources
app launch: zygote.fork() β child = new app process, instantly has the warm runtime
COW: preloaded class pages shared read-only across ALL apps β big memory saving
Where you see it (Qualcomm). This is the Android app-startup mechanism on every Snapdragon phone; understanding it ties together fork, copy-on-write, and demand paging in a concrete, name-droppable way β and Qualcomm (a kernel/embedded SWE report) asked it directly. (Android framework specifics β cross-link 07_embedded_linux_kernel.md.)
Answer. "The zygote is a pre-warmed Android process started at boot that has already initialized the ART runtime and preloaded the common framework classes and resources. To launch an app, Android forks the zygote instead of cold-starting a new runtime, so the app immediately inherits the warm, already-initialized environment β that makes startup fast. And because of copy-on-write, all those preloaded pages are shared read-only across every app and only copied when an app writes to one, which saves a lot of memory. It's essentially fork plus copy-on-write used to amortize expensive runtime initialization across all apps."
Follow-ups / gotchas. Zygote forks but does not exec (the child keeps the shared runtime β that's the whole point). The COW sharing is why preloading helps memory, not just speed. There's a separate zygote per ABI (32/64-bit). Ties directly to fork/COW (H3) and demand paging (G2). Cross-link: Android internals β 07_embedded_linux_kernel.md.
Seen in: kernel/embedded SWE (#15 "What is the function of zygote?").
Β§ Encyclopedia β searchable glossaryΒΆ
Every bold-italic term used above is defined here, alphabetically, with why it exists / where you see it and a micro-example. Use your editor's find (βF / Ctrl-F) to jump to a term.
Aging β gradually raising a waiting process's priority so it can't be starved forever. Why/where: cures starvation in priority scheduling (B1); a long batch job eventually outranks a flood of short ones.
Atomic operation β an operation that completes indivisibly β no other thread can observe a half-done state. Why: counter++ is not atomic (read-modify-write) β races (D3); use a mutex or _Atomic. __atomic_fetch_add(&c,1,__ATOMIC_SEQ_CST);
Banker's algorithm β a deadlock-avoidance algorithm that grants a resource request only if the system stays in a safe state (E2). Why: never enter a state from which deadlock is possible. Where: theory/interviews; needs max needs known up front; safety check is O(mΒ·nΒ²).
Belady's anomaly β for FIFO page replacement, adding frames can increase page faults (G3). Why notable: counterintuitive; LRU/optimal (stack algorithms) don't suffer it.
Binary semaphore β a semaphore with values 0/1 (D2). Where: mutual exclusion, or an ISR-to-task event flag (init 0). Unlike a mutex it has no owner.
Busy-wait β looping to poll a condition instead of sleeping (D4). Why bad: wastes CPU; where ok: a spinlock for a tiny critical section on SMP. while(!ready); β prefer a condvar.
Condition variable β lets a thread sleep until a predicate holds, paired with a mutex (D5). Why: avoids busy-waiting. Gotcha: loop with while (spurious wakeups). while(!ready) pthread_cond_wait(&cv,&m);
Context switch β saving one process/thread's CPU state into its PCB and restoring another's (A4). Why: enables multitasking; cost: TLB flush on a process switch β thread switches are cheaper.
Convoy effect β short jobs stuck waiting behind one long job under FCFS (B1), wrecking average waiting time. Fix: SJF or round robin.
Copy-on-write (COW) β parent and child share physical pages read-only after fork; a page is copied only on first write (H3). Why: makes fork cheap; where: fork/exec, Android zygote.
Core dump β a file capturing a crashed process's memory + registers for post-mortem debugging (I3). Where: ulimit -c unlimited then gdb ./app core + bt; Android = a tombstone.
Counting semaphore β a semaphore with values 0..N, tracking N units of a resource (D2/D5). Where: free slots in a bounded buffer (empty/full).
Critical section β the code region accessing shared data that must run one-thread-at-a-time (D3). Why: prevent race conditions; fix: a mutex. Must satisfy mutual exclusion + progress + bounded waiting.
Deadlock β a set of threads each blocked forever waiting on a resource another holds (E1). Conditions: all four Coffman conditions. Fix: break one (usually lock ordering).
Demand paging β load a page into RAM only on first access (G2). Why: faster startup, run programs bigger than RAM; mechanism: a page fault brings the page in.
Dispatcher β the mechanism that hands the CPU to the scheduler-chosen process: does the context switch, mode change, and jump (B4). Contrast: the scheduler decides; the dispatcher acts.
Dispatch latency β the time the dispatcher takes to stop one process and start another. Where: a key real-time metric.
Exec β replaces the current process image with a new program, same PID (H3). Where: fork+exec to launch a command; zygote notably forks without exec.
External fragmentation β free memory split into scattered pieces so a big request fails despite enough total free (G6). Cure: compaction or paging. Where: long-running heaps/segmentation.
FCFS (First-Come First-Served) β non-preemptive FIFO scheduling (B1). Downside: the convoy effect.
FIFO (page replacement) β evict the oldest-loaded page (G3). Downside: ignores usage; suffers Belady's anomaly.
Fork β creates a child process duplicating the parent; returns twice (child PID to parent, 0 to child) (H3). Why: Unix process creation, paired with exec; cheap via copy-on-write.
Fragmentation β wasted memory from allocation: internal (inside a block) or external (between blocks) (G6).
Internal fragmentation β wasted space inside an allocated block (e.g. page rounding, struct padding) (G6). Where: paging causes only internal fragmentation.
IPC (Inter-Process Communication) β mechanisms for isolated processes to exchange data/coordinate: pipes, shared memory, message queues, signals, sockets, semaphores (C1). Why: process address spaces are isolated.
ISR (Interrupt Service Routine) β code the CPU jumps to on a hardware interrupt. Where: must be short, can't sleep β use spinlocks, signal a task via a binary semaphore. (Detail β 07_embedded_linux_kernel.md.)
Kernel mode β the privileged CPU mode where the kernel runs with full hardware access (H2). Contrast: user mode; entered via a system call (mode switch).
LRU (Least Recently Used) β evict the page unused for the longest past time (G3). Why: approximates optimal; real: clock/second-chance approximates LRU cheaply. No Belady's anomaly.
Message queue β kernel-managed IPC of discrete, possibly prioritized messages (C1). Where: mq_*/System V msgget.
MMU (Memory Management Unit) β hardware translating virtualβphysical addresses via page tables and enforcing per-page permissions (G1). Where: every memory access; illegal β segmentation fault. (Device side = IOMMU/SMMU.)
Mode switch β userβkernel transition on a system call/trap (H2). Note: not the same as a context switch β you can trap and return to the same process.
Multiprocessing β concurrency via multiple isolated processes (A5). Trade: isolation/fault-containment vs heavier creation + IPC.
Multithreading β concurrency via multiple threads sharing one address space (A5). Trade: cheap sharing vs needing synchronization; one crash kills all.
Mutex β a mutual-exclusion lock with ownership (only the locker unlocks); can support priority inheritance (D1). Where: guard a critical section.
Optimal (page replacement) β evict the page used farthest in the future (G3). Why: provably minimal faults but unimplementable (needs the future) β a benchmark only.
Page β a fixed-size unit of virtual memory (e.g. 4 KB) mapped to a physical frame (G1). Where: paging/demand paging.
Page fault β a trap when a process accesses a not-present page (G2). Handled: bring the page in (minor = table fixup; major = disk I/O); illegal access β segmentation fault.
Page replacement β choosing a victim page to evict when memory is full (G3): FIFO/LRU/optimal/clock.
Paging β mapping fixed-size virtual pages to physical frames via the MMU (G1/G5). Why: non-contiguous allocation, isolation, no external fragmentation.
PCB (Process Control Block) β the kernel's per-process record (PID, state, saved registers, scheduling/memory/file info) (A3). Where: saved/restored on a context switch; Linux task_struct.
Pipe β a unidirectional byte-stream IPC between related processes (C1); named pipe/FIFO extends to unrelated ones. pipe(fd);
Preemption β forcibly taking the CPU from a running task (timer/priority) (B1). Where: preemptive scheduling, time-slicing; absence is a Coffman condition for deadlock.
Priority inheritance β a low-priority lock holder temporarily inherits a waiting high-priority task's priority (F1). Why: bounds priority inversion; story: Mars Pathfinder. A mutex can offer it; a semaphore can't.
Priority inversion β a high-priority task blocked by a lower-priority one, made unbounded by a medium task preempting the holder (F1). Fix: priority inheritance / priority ceiling.
Process β a program in execution with its own isolated address space, FDs, and PCB (A1/A2). Contrast: a thread (shares the address space).
Race condition β a bug where correctness depends on thread interleaving (D3). Fix: mutual exclusion around the critical section. Note: volatile does not fix it.
Round Robin (RR) β FCFS with a time quantum; preempt at quantum end (B1/B2). Why: fairness + response time; quantum too small β switch overhead, too big β FCFS.
RTOS (Real-Time OS) β an OS guaranteeing bounded, deterministic timing (I1). Where: camera/modem/DSP firmware; uses fixed-priority preemptive scheduling + priority inheritance; often no demand paging.
Safe state β a system state with a sequence in which every process can finish (E2). Why: the Banker's algorithm keeps the system safe; unsafe β deadlocked (just risky).
Scheduler β the policy deciding which Ready process runs next (B1/B4). Levels: long/medium/short-term. Contrast: the dispatcher carries it out.
Segmentation β dividing the address space into variable-size logical segments (code/data/stack) (G5). Causes: external fragmentation; contrast: paging (fixed size, internal fragmentation).
Segmentation fault (SIGSEGV) β a trap on an illegal memory access caught by the MMU. Where: null/wild/dangling deref, write to read-only. Detail β 01_c_programming.md D3.
Semaphore β an integer with atomic wait/signal, for signaling/counting; no owner (D1/D2). Binary vs counting. Where: producer/consumer slots, ISRβtask signaling.
Shared memory β an IPC region mapped into multiple processes β the fastest (no copy) but needs its own synchronization (C1). Where: camera frame buffers (mmap/dma-buf).
Signal β an asynchronous notification (a number) delivered to a process (C2). Where: SIGSEGV/SIGTERM/SIGCHLD; handlers must be async-signal-safe; communicate via volatile sig_atomic_t.
Socket β a bidirectional IPC/network endpoint (C3). Where: TCP (reliable) vs UDP (fast/lossy); Unix-domain for local. Video/audio β UDP; chat β TCP.
Spinlock β a lock whose waiter busy-waits instead of sleeping (D4). Where: very short critical sections, kernel/interrupt context (can't sleep); bad on a uniprocessor.
Starvation β a process perpetually denied a resource/CPU while others proceed (B1). Contrast: deadlock (everyone stuck). Fix: aging.
System call β the controlled gateway into the kernel for a privileged service (H2). Where: read/write/mmap/ioctl; memory-mgmt: mmap/munmap/mprotect/brk. Library call vs syscall β 01_c_programming.md G1.
Thrashing β spending more time paging than working when working sets exceed RAM (G4). Symptom: low CPU + high swap. Fix: working-set model / reduce multiprogramming.
Thread β a unit of execution inside a process; own stack/registers, shared code/heap/globals (A1/A5). Where: cheap parallelism; needs mutexes for shared data.
Time-slicing β giving each process a time quantum, preempting at the end via a timer interrupt β the Round Robin mechanism (B2). Where: fair multitasking; not ideal for deadline-driven (game/RTOS) work.
TLB (Translation Lookaside Buffer) β a cache of recent virtualβphysical translations in the MMU (G1). Why: avoid a page-table walk per access; flushed on a process context switch.
Turnaround / waiting / response time β scheduling metrics: submitβfinish / time in Ready / submitβfirst-run (B1). Why: different algorithms optimize different ones.
User mode β the restricted CPU mode user programs run in; no direct hardware access (H2). Contrast: kernel mode; cross via a system call.
Virtual memory β per-process virtual address spaces decoupled from physical RAM via paging (G1). Why: isolation, more memory than RAM, sharing.
Watchdog timer β a countdown that resets/recovers the system if software stops kicking it (I2). Where: unattended embedded systems; Snapdragon subsystem restarts; reset Mars Pathfinder.
Weighted Round Robin (WRR) β Round Robin giving each task a CPU share proportional to its weight (B3). Why: proportional service without starvation; kin: Linux CFS, weighted fair queuing.
Working set β the set of pages a process is actively using in a recent time window (G4). Why: if all working sets fit in RAM, no thrashing.
Zygote β Android's pre-warmed process that forks to start apps fast, sharing preloaded runtime/classes via copy-on-write (I4). Why: fast startup + memory savings.
Β§ Last-5-minutes cheat sheetΒΆ
- Process = isolated address space + PCB; thread = shares code/heap/globals, own stack/registers. Threads cheap to switch (no TLB flush) but need locks; processes isolated (MMU) but need IPC.
- Process states: New β Ready β Running β (Waiting on I/O) β Terminated. PCB holds saved registers/state; context switch = save old PCB, load new (TLB flush on process switch). Mode switch β context switch.
- Scheduling: FCFS (convoy effect) Β· SJF (min avg wait, can starve, needs burst) Β· RR (time-slice; quantum too small=overhead, too big=FCFS) Β· Priority (starvationβaging) Β· WRR (proportional share). Scheduler decides, dispatcher acts. Time-slicing is bad for deadline-driven game/RTOS work.
- IPC: pipe (related) Β· FIFO (unrelated) Β· shared memory (fastest, you synchronize it) Β· message queue Β· signal (async, no data) Β· socket (TCP reliable / UDP fast-lossy). Camera = zero-copy shared mem + Binder.
- Mutex = lock, owner unlocks, can do priority inheritance β guard critical section. Semaphore = counter, no owner, anyone signals; binary (lock/event) vs counting (N slots). ISRβtask = binary semaphore.
- Race = result depends on interleaving (e.g.
counter++); fix with a mutex around the critical section (mutual exclusion + progress + bounded waiting).volatiledoes not fix races. Spinlock = busy-wait (short CS, can't-sleep contexts). - Producerβconsumer: counting sems
empty(N)/full(0) + mutex; take the counting sem before the mutex or deadlock. - Deadlock needs all 4 Coffman: mutual exclusion, hold-and-wait, no preemption, circular wait β break one (usually lock ordering). Banker's = avoidance (stay in a safe state); detection = cycle in RAG; recovery = kill/rollback.
- Priority inversion: high blocked by low, medium preempts low β priority inheritance (boost the holder). Mars Pathfinder = inheritance flag off β watchdog reset.
- Virtual memory: MMU + page table map pageβframe (offset passes through); TLB caches it. Demand paging loads on first touch; page fault brings it in (illegal access = segfault).
- Page replacement: FIFO (Belady's anomaly) Β· Optimal (best, unimplementable) Β· LRU (approximates optimal, no anomaly; clock in practice).
- Thrashing = paging > working; symptom low CPU + high swap; fix = working-set / fewer processes. Fragmentation: internal (in block; paging) vs external (between blocks; segmentation) β pools/slabs, compaction, paging.
- Syscall = trap to kernel (mode switch); memory syscalls
mmap/munmap/mprotect/brk;ioctl= userβdriver. fork returns twice (PID to parent, 0 to child); fork+exec to launch; COW makes fork cheap. - RTOS = bounded deterministic timing, fixed-priority preemptive, priority-inheritance mutexes, often no paging. Watchdog = kick-or-reset (last-resort recovery). Core dump = post-mortem (
ulimit -c unlimited;gdb ./app core+bt). Zygote = fork a warm runtime + COW β fast Android app start.
Evidence base: qualcomm_camera_interview_experiences.md. Frequencies are approximate (sample = 75 reports, some via aggregator pages). Diagrams in assets/. Cross-references: C memory/pointers/malloc/segfault β 01_c_programming.md Β· C++ threading/RAII β 02_cpp_oop.md Β· LRU-cache & data structures β 03_dsa.md Β· kernel/drivers/HAL/ioctl/Android internals β 07_embedded_linux_kernel.md Β· MMU/cache/memory hierarchy as hardware β 09_computer_arch_digital_design.md Β· producer-consumer/screen-tearing/text-editor LLD β 10_lld_system_design.md.