Quiz Calibration
Attempt the quiz before reading this page. Preserve the original answer, correction and help received. These explanations calibrate retrieval; successful recall alone does not complete the module or its practical evidence gates. For context such as shared schemas, use the question page.
Question 1: Process vs Program
State the difference between a program and a process in one sentence each.
Calibration: A program is the executable artifact on disk (machine code + metadata). A process is a running instance of that program with a pid, its own virtual address space, an fd table, and a place in the scheduler. Many processes can share one program; a process is not a program.
Question 2: fork Return Values
A C program calls pid_t p = fork();. In which process is p == 0? What does a non-zero positive value mean, and what does a negative value mean?
Calibration: p == 0 in the child. A positive p is the child's pid, and the process is the parent. p < 0 means the fork failed (no process was created); check errno.
Question 3: Syscall Sequence Tracing
Given this strace excerpt from a program invoked as ./prog input.txt:
openat(AT_FDCWD, "input.txt", O_RDONLY) = 3
read(3, "hello\nworld\n", 4096) = 12
write(1, "hello\nworld\n", 12) = 12
close(3) = 0
Write a C program whose source would produce exactly this sequence. What assumption did you have to make about the size of input.txt?
Calibration: A program that opens the file (getting fd 3 because 0, 1, 2 are already taken), calls read once with a 4 KB buffer, writes the result to stdout, and closes. Example skeleton: int fd = open(argv[1], O_RDONLY); char buf[4096]; ssize_t n = read(fd, buf, sizeof buf); write(1, buf, n); close(fd);. The assumption is that the file fits in one read -- in reality you must loop read because a single call is not guaranteed to return everything.
Question 4: Shell Redirection Mechanics
When a shell runs ./prog > out.txt, what exactly does the shell do to the child's fd table before exec?
Calibration: fork, then in the child: open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644) to get some fd (say, 3), dup2(3, 1) to make fd 1 point at out.txt, then close(3), then execvp. After exec, the program's printf/write(1, ...) writes to out.txt without any changes to the program itself.
Question 5: Race Condition Diagnosis from Output
Two threads each increment a shared long counter one million times. Final value observed across 10 runs: 1384122, 1602910, 1441098, 1712045, 1500388, .... None reaches 2000000. Explain the specific race.
Calibration: counter++ compiles to three instructions: load counter into a register, add 1, store back. Two threads can both load the same old value, both add 1, both store back -- one update is lost. The more interleavings, the lower the final value. Fix with atomic_fetch_add(&counter, 1) or guard the increment with a pthread_mutex_t.
Question 6: mmap vs read
A program repeatedly accesses random 8-byte records in a 10 GB file. Would you reach for mmap or read/lseek? Defend your choice.
Calibration: mmap. Random access means each read/lseek is a syscall + memory copy; with mmap, the kernel page cache holds the working set, and a random access is a user-mode pointer dereference (free if the page is cached). For streaming sequential reads, the choice is more even and read with a large buffer is often simpler.
Question 7: Producer-Consumer Correctness
In the canonical producer-consumer, why is while (empty) cond_wait(¬_empty, &m); used instead of if (empty) cond_wait(¬_empty, &m);? Give two independent reasons.
Calibration: (1) Spurious wakeups. POSIX allows cond_wait to return with no signal sent. (2) Race between signaller and waiter. Another consumer, waking first, may drain the last item before our thread re-acquires the mutex; on return from wait we must re-check the predicate. if proceeds unconditionally and can dequeue from an empty buffer; while re-checks.
Question 8: FD Table Through pipe + fork + dup2
Before: parent has pipe returning p[0]=3, p[1]=4. Parent forks. Each child wants to be one side of A | B. Write out the fd table of each child right before exec, assuming A is the writer.
Calibration: | Process | fd 0 | fd 1 | fd 3 | fd 4 | | --- | --- | --- | --- | --- | | child running A | stdin (terminal) | pipe write end (dup'd from 4) | closed | closed | | child running B | pipe read end (dup'd from 3) | stdout (terminal) | closed | closed | | parent after closes | terminal | terminal | closed | closed |
The critical closes: each child closes both p[0] and p[1] (the duplicates still point at the pipe via fd 0 or fd 1). The parent closes both so that when child A exits, child B sees EOF on its read end.
Question 9: Signal Safety
Why is calling printf from inside a signal handler dangerous, and what should you do instead in a long-running program?
Calibration: printf is not async-signal-safe. It takes an internal stdio lock; if the main program was already inside printf when the signal arrived, the handler's printf will deadlock or corrupt the buffer. Inside a handler, use only async-signal-safe functions (write, _exit, kill, ...). The standard pattern is to set a volatile sig_atomic_t flag in the handler and let the main loop notice it on its next iteration.
Question 10: Debugging Tool Selection
A server suddenly spikes to 100% CPU and responses time out. Pick the first diagnostic tool to reach for and justify the choice. Then pick the first tool if the same server is instead hung at 0% CPU and justify that choice.
Calibration: 100% CPU -> perf top -p <pid>. The process is burning CPU in user code; sampling profiling will show which function. strace would show few or no syscalls and waste time. 0% CPU -> strace -p <pid>. The process is blocked in a syscall; strace will print the exact syscall (usually read, futex, or accept) it is waiting in. perf would show no CPU samples because there are none.
Question 11: (from S4M1, C Fundamentals)
Why does the C declaration char *p = "hello"; produce a string you cannot modify, while char p[] = "hello"; does?
Calibration: The first makes p point into a read-only data segment (string literal). Writing through p is undefined behavior and usually crashes with SIGSEGV. The second allocates a writable array on the stack and copies the literal into it; modifications work.
Question 12: (from S4M2, Memory and Pointers)
In a C program, where does malloc-returned memory physically come from, assuming a glibc userspace?
Calibration: glibc malloc satisfies small allocations from a per-thread arena it maintains, backed by large chunks obtained from the kernel via sbrk (the program break) or mmap (for large allocations). It is not one syscall per malloc; it is many mallocs per syscall.
Question 13: (from S4M3, Computer Organization)
Explain why accessing a pointer in a tight inner loop can be dramatically faster the second time than the first.
Calibration: The first access is likely a cache miss -- the data is not in L1/L2/L3 cache and must be fetched from DRAM (hundreds of cycles). Subsequent accesses hit the cache (a few cycles). The same reasoning explains why mmap'd files appear "fast on warm runs": the kernel page cache holds the pages.
Question 14: (from S3M1, Software Design)
A programmer wraps every syscall in a "safety" class with 20 methods. Discuss in terms of cohesion and SRP.
Calibration: Cohesion is low because 20 disparate syscall wrappers do not share data or purpose. SRP is violated because open, socket, mmap, and pthread_create answer to different stakeholders (file I/O, networking, memory, threading). Break into focused utilities per domain; the "safety" concern (error checking) is a cross-cutting concern best solved by a small check(rc, msg) helper, not a god-class.
Question 15: (from S2M2, Algorithms)
What is the time complexity of a correct wc -c implementation over a file of size N bytes, in terms of syscalls and in terms of user-mode work?
Calibration: User-mode work is O(N) -- you must touch every byte to distinguish newlines. Syscalls are O(N / B) where B is the read-buffer size; with B=4096, a 4 GB file costs about 10^6 syscalls, so the buffer size is a meaningful constant factor. Alternatively, mmap makes syscalls O(1) amortized and turns the work into O(N) user-mode loads (with page faults).