Operating Systems & Concurrency
Core systems internals: virtual memory paging, translation lookaside buffers (TLB), process control blocks (PCB), thread synchronization, race conditions, and Coffman deadlock conditions.
Process Address Space vs Thread Shared Memory
A Process is an executing program instance with its own isolated virtual memory space (Text, Data, BSS, Heap, Stack) managed by the OS Process Control Block (PCB). A Thread is a lightweight unit of CPU execution within a process that shares the heap, data, and code segments, but possesses its own private Stack and Program Counter.
- Context Switching Cost:Process context switch requires flushing the CPU Translation Lookaside Buffer (TLB) and switching page tables (CR3 register in x86). Thread context switch is ~10x cheaper because memory mappings remain identical.
- Race Conditions:Occurs when multiple concurrent threads read and modify shared memory without synchronization, yielding non-deterministic bugs.
- Mutex vs Counting Semaphore:A Mutex provides exclusive binary locking with ownership (only the locking thread can unlock). A Semaphore is a signaling mechanism with an integer counter (any thread can post/signal).
- Process Creation (fork + exec):
fork()clones the parent process via Copy-On-Write (COW) page tables.execve()replaces the virtual address space with a new executable binary. - Critical Section Protection via Mutex: Acquire lock (
pthread_mutex_lock), perform atomic modification on shared state, and release lock (pthread_mutex_unlock). - Deadlock & The 4 Coffman Conditions: A deadlock occurs if and only if all 4 conditions hold simultaneously: 1. Mutual Exclusion, 2. Hold and Wait, 3. No Preemption, 4. Circular Wait.
An OS memory optimization where parent and child processes share the exact same physical memory pages marked as read-only after fork(), duplicating a page only when one process attempts a write.
Amdahl's Law for Parallel Speedup
Process vs Thread Comparison
| Attribute | Process | Thread |
|---|---|---|
| Memory Space | Isolated (Private Page Tables) | Shared Heap, Data & Code; Private Stack |
| Creation Overhead | High (Fork + Page Tables) | Low (~10-20KB stack) |
| Inter-Communication | IPC (Pipes, Sockets, Shared Memory) | Direct Heap Memory Access |
| Crash Impact | Isolated (Parent survives) | Can crash entire process |
To prevent Deadlocks in multi-threaded programs, ALWAYS acquire multiple mutex locks in a globally defined, consistent hierarchical order (e.g. always lock Resource A before Resource B).
1import threading2import time3import queue45buffer = queue.Queue(maxsize=3)67def producer():8 for i in range(5):9 item = f'Task-{i}'10 buffer.put(item) # Blocks if buffer full11 print(f'Produced: {item}')1213def consumer():14 for _ in range(5):15 item = buffer.get() # Blocks if buffer empty16 print(f' Consumed: {item}')17 buffer.task_done()1819t1 = threading.Thread(target=producer)20t2 = threading.Thread(target=consumer)2122t1.start()23t2.start()24t1.join()25t2.join()26print('Producer-Consumer Completed Without Deadlock!')
Produced: Task-0 Produced: Task-1 Produced: Task-2 Consumed: Task-0 Produced: Task-3 Consumed: Task-1 Produced: Task-4 Consumed: Task-2 Consumed: Task-3 Consumed: Task-4 Producer-Consumer Completed Without Deadlock!
1#include <stdio.h>2#include <pthread.h>34long counter = 0;5pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;67void* worker(void* arg) {8 for (int i = 0; i < 100000; i++) {9 pthread_mutex_lock(&lock);10 counter++; // Critical section11 pthread_mutex_unlock(&lock);12 }13 return NULL;14}1516int main() {17 pthread_t t1, t2;18 pthread_create(&t1, NULL, worker, NULL);19 pthread_create(&t2, NULL, worker, NULL);20 pthread_join(t1, NULL);21 pthread_join(t2, NULL);22 printf("Final Counter: %ld\n", counter);23 return 0;24}
Final Counter: 200000
Quick Syntax & Reference Sheet
| Concept / Rule | Syntax / Usage |
|---|---|
| POSIX Thread Spawn | pthread_create(&thread_id, NULL, worker_func, arg) |
| Mutex Lock / Unlock | pthread_mutex_lock(&lock); /* critical section */ pthread_mutex_unlock(&lock); |
| Deadlock Prevention | Eliminate Circular Wait by enforcing global ascending lock acquisition order. |
Common Pitfalls & Developer Mistakes
Unlocking a Mutex from a different thread than the one that locked it
Why it happens: POSIX Mutexes enforce strict thread ownership. Unlocking from another thread results in undefined behavior.
# Correct signaling pattern:
sem_post(&semaphore); // Thread A signals
sem_wait(&semaphore); // Thread B waits