IPC and OS Fundamentals Notes

IPC and OS Fundamentals Notes

IPC Overview and Motivation

  • IPC stands for inter-process communication. It is needed because processes are isolated from each other; they each have their own memory address space and cannot easily share information directly.
  • Intra-process communication (within the same process) can use shared memory, but cross-process communication requires IPC mechanisms.
  • IPC methods covered: pipes, FIFOs (named pipes), message queues, Unix domain sockets, and shared memory. Each has different characteristics, trade-offs, and use cases.

Mode Switching and CPU Privilege Levels

  • Question framing from the previous lecture: mode switch questions (2 or 3) relate to switching between CPU modes.
  • What is a mode switch?
    • It refers to switching between CPU modes, notably user mode and kernel mode (sometimes called Ring 3 and Ring 0 in x86 terminology).
    • It happens when control transfers from user space to the kernel (e.g., via a system call) or when handling an interrupt/exception.
    • After the kernel handles the event, control resumes in user mode at the appropriate point in user code.
  • Concrete triggers for user-to-kernel mode switch:
    • System calls (e.g., invoking kernel services).
    • Interrupts or exceptions, while in user mode.
  • After handling in the kernel, control returns to user mode, resuming execution where the process left off (assuming the interrupted task is still runnable).
  • Three key aspects that occur during a mode switch: 1) A switch to kernel mode (Ring 0) occurs. 2) Save CPU context (e.g., save the Program Counter (PC), stack pointer, and other registers) so execution can resume later.
    • Example: save the PC value so you know where to resume after handling the interrupt/system call.
    • Notation: extsave(extPC,extSP,extregisters)ext{save}( ext{PC}, ext{SP}, ext{registers})
      3) Retrieve the handler address and invoke it. The handler address is mapped from the event type (e.g., system call number or interrupt ID).
    • Example: system call uses an interrupt number such as 0x800x80 on x86 to index into the system call table and obtain the handler.
  • This process is largely performed by hardware (and memory systems in cooperation with the CPU); the CPU/ hardware automatically saves context and dispatches to the appropriate handler. The software (kernel) orchestrates the high-level flow, while hardware handles the low-level context saving and jumping.
  • After the handler finishes, the kernel restores the previously saved context and resumes user-space execution by restoring the PC value to the next instruction to execute.
  • Precise phrasing: during a mode switch the CPU may change from user mode to kernel mode, save the context (PC, stack, registers), fetch the handler address (e.g., via interrupt vector or system call table), invoke the handler, and then restore the context to resume user-space execution.

What Happens During an Interrupt/Exception/Signal

  • Interrupts: generated by hardware (e.g., timer, IO devices, keyboard, mouse, network card). They are asynchronous events from external hardware.
  • Exceptions: software-triggered events (e.g., division by zero, page faults, breakpoints). These are triggered by the CPU due to exceptional conditions in software execution.
  • Signals: software-visible notifications delivered to processes (e.g., kill, SIGINT, SIGCHLD, SIGFPE). They originate from various sources (API invocations, system conditions, or exception handling converted into signals).
  • Key statement to remember for exams: an interrupt is generated by hardware. The rest of the flow follows from there (exception handling and signal delivery are related but have distinct origins and handling paths).
  • Example taxonomy and concrete instances:
    • Interrupts: timer interrupt, I/O interrupt, keystroke interrupt from keyboard hardware.
    • Exceptions: divide-by-zero, page faults, breakpoints. Some exceptions can trigger signals to user space (e.g., a divide-by-zero can generate SIGFPE).
    • Signals: generated as a result of programmatic events (e.g., API calls like kill), or converted from exceptions (e.g., division-by-zero → SIGFPE).
  • The lifecycle of a typical interrupt/exception:
    • Hardware generates an interrupt -> CPU switches to kernel mode -> CPU saves context (PC, SP, registers) -> dispatches to the appropriate handler -> handler runs in kernel -> on completion, PC and context are restored and control returns to user mode.
  • An exam-oriented example: handling a divide-by-zero
    • CPU detects division by zero → exception occurs.
    • The exception is handled by the kernel, which may convert the exception into a signal (e.g., SIGFPE) delivered to the offending process.
    • If a user-space SIGFPE handler is installed, control returns to user space to execute that handler; otherwise, the default handler may terminate the process.
  • Practical nuance: handlers are installed either by the kernel (default handlers) or via user-space signal handlers; the kernel may deliver a signal and then trigger a context switch back to the user process.

Keystroke Handling: A Concrete IPC Story

  • A common interview question: how does the system respond to a keystroke?
    • Hardware detects a keystroke and raises an interrupt.
    • The kernel interrupt handler (installed by the keyboard driver) runs in kernel mode and reads the character from the keyboard buffer into the kernel space buffer.
    • The kernel then moves from the current user process to the keyboard handler (mode switch), and the driver may place the keystroke into a per-kernel queue or buffer.
    • The interrupt may trigger a scheduler decision: the process that was blocked waiting for input (e.g., a GETCHAR call) becomes READY, and eventually the scheduler gives it CPU time, switching back to user mode and resuming the user process where it left off (e.g., reading the keystroke).
    • In between, the PowerPoint-like analogy: mode switch to handle the interrupt, then switch back and resume the user process; the blocked process is moved to READY when input is available, and then to RUNNING once it gets CPU time.
  • Important exam takeaway: every interrupt ultimately leads to a mode switch, handler invocation, and a potential context switch back to the requesting user process.

Shells, Zombie Processes, and IPC in Context

  • Implementing a simple shell (three core system calls):
    • fork: create a new child process.
    • exec (execve or related): replace the child’s image with the command to run.
    • wait (wait or waitpid): the parent blocks until the child exits and reaps its exit status.
    • Flow: Parent creates a child with fork → child executes a command via exec → parent waits for the child to exit → parent can then prompt for the next command.
  • Zombie processes:
    • When a child process exits, it becomes a zombie until the parent performs wait to read its exit status and perform cleanup.
    • Zombies occupy process-table entries and some resources until harvested by the parent.
    • If many children exit and are not waited on, zombie processes accumulate, causing resource leakage and potential system instability.
    • Rationale for zombies: they allow the parent to learn whether the child completed normally or abnormally and to obtain the child’s exit status for trustworthiness of results.
  • The term “zombie threat” highlights the need to regularly reap children to avoid accumulation of zombie processes.
  • Connection to IPC: shells use IPC-like interactions (signals, process creation, and inter-process communication for input/output redirection and command piping) to coordinate between parent and child processes.

IPC Mechanisms: Details and Trade-offs

  • General goal: IPC techniques enable passing information between processes. A large portion of IPC is about streaming data, message boundaries, synchronization, and performance.
  • Pipe (unnamed pipe): a one-way byte stream between related processes (usually parent and child).
    • Creation: create a pipe; it returns two file descriptors (read and write ends).
    • After fork: both processes inherit the pipe file descriptors; close unused ends to avoid undesired data flow.
    • Data is a continuous byte stream; there is no inherent message boundary.
  • FIFO (named pipe): a named IPC endpoint stored in the filesystem.
    • Advantage: can be accessed by unrelated processes by name; supports multiple writers/readers.
    • Data still a byte stream with no explicit message boundaries.
  • Message queue: preserves message boundaries in kernel space.
    • Compared to pipes: each message is delivered as a discrete unit; the receiver sees distinct messages, not a continuous byte stream.
    • Useful when you need to know how many messages were sent/received and when messages arrive.
  • Domain sockets (Unix domain sockets): local interprocess communication with socket-like semantics.
    • Bidirectional communication: can be used as full-duplex channels (two-way streams).
    • API resembles network sockets (bind, listen, accept, connect, send, recv); can be used with stream or datagram semantics.
    • Often preferred over pipes/queues when both directions, multiple clients, and rich IPC semantics are needed.
    • In practice, a single socket may be backed by two internal pipes or queues; domain sockets provide a clean, flexible interface.
  • Shared memory: the most efficient IPC method but the most complex to manage correctly.
    • A region of physical memory is mapped into the virtual address spaces of two or more processes so that updates by one process are visible to others immediately.
    • Extremely fast because it bypasses kernel data copies for each message; most of the communication happens in user space after the initial mapping.
    • Synchronization is essential to avoid race conditions, lost updates, and data corruption.
    • Setup typically involves mapping a memory region into multiple processes; Linux supports multiple approaches:
    • System V shared memory (shmget/shmat/shmdt, shmctl): classic API.
    • POSIX shared memory (shmopen, mmap, shmunlink): file-system-backed, more modern and portable.
    • Anonymous shared memory via fork: after fork, the child inherits the mapped region; additional mappings via shm_open can map the same region by name.
  • Comparison and guidance on use:
    • Pipe: simple, fast between related processes; unidirectional; best for streaming data between a parent and a child.
    • FIFO: cross-process, can involve unrelated processes; still byte-stream oriented; multiple writers/readers possible with naming.
    • Message queue: boundary-preserving; good when messages represent discrete units; supports multiple producers/consumers with clear boundaries.
    • Unix domain sockets: flexible, bidirectional, network-like interface; suitable for complex local IPC; can emulate pipes or queues with appropriate design.
    • Shared memory: highest performance; require explicit synchronization (mutexes, semaphores, or user-space coordination); best for high-throughput data sharing and data-parsing workloads.
  • Practical notes on synchronization with shared memory:
    • Without synchronization, you risk overwriting data, reading incomplete messages, or reading stale data.
    • Synchronization primitives (semaphores, mutexes, condition variables, memory barriers) are essential when using shared memory.
    • Other IPC methods (pipes, FIFOs, queues, sockets) provide built-in OS-level synchronization for their data flows, reducing the need for explicit synchronization in most cases.
  • Platform coverage: all these IPC mechanisms are supported across Linux, Unix, Windows, Mac, Android, etc.
  • Important organizational cheat-sheet:
    • Shared memory is fastest but requires explicit synchronization.
    • Sockets (domain or network) provide flexible, network-like semantics with built-in synchronization at the OS level.
    • Pipes and FIFOs are simple streaming mechanisms; use for straightforward producer-consumer patterns.
    • Message queues provide clear message boundaries for discrete communications.

Synchronization and Practical Implications

  • Synchronization issues are central to effective IPC, especially with shared memory.
  • Without proper synchronization, shared memory can cause race conditions, data corruption, and subtle bugs; rigorous design is required.
  • In contrast, other IPC mechanisms handle synchronization for you (e.g., the kernel coordinates access to pipes, queues, and sockets).
  • The emphasis in the course: how to reason about synchronization in user space to optimize performance with shared memory and to design correct IPC patterns.
  • Practical takeaway: shared memory is highly valued for speed in big data and high-performance workloads, but mastery of synchronization is essential to realize its benefits.

Exam-Oriented Takeaways and Final Tips

  • Core definitions to memorize:
    • Interrupts: hardware-generated events; lead to kernel-mode handling and context switch.
    • Exceptions: software-initiated events (e.g., divide-by-zero); can be converted into signals for user-space handling.
    • Signals: software-visible notifications delivered to processes (e.g., SIGINT, SIGFPE, SIGCHLD).
  • Always start explanations with: "An interrupt is generated by hardware" for exam clarity.
  • For divide-by-zero, explain the flow: exception → OS handles → converted to signal (SIGFPE) → user-space signal handler (if installed) or default termination.
  • When describing shells and IPC in exams, mention the classic trio of system calls: fork, execve (or exec family), and wait (or waitpid) to implement a simple shell loop with process creation, command execution, and synchronization.
  • Zombie processes are a critical concept: they occur when a child exits but has not been reaped by the parent; they waste process-table entries and memory resources; ensure proper wait/reaping to avoid zombie buildup.
  • When choosing an IPC mechanism, consider: whether you need bidirectional communication, message boundaries, cross-process access, and performance requirements. Shared memory is fastest but requires careful synchronization; domain sockets offer flexible, network-like semantics for local IPC; pipes/ FIFOs/ queues are simpler for many common tasks.

Quick Concept Recap (Key Phrases to Remember)

  • Mode switch = switch between user mode and kernel mode; triggered by system calls, interrupts, exceptions.
  • Program Counter (PC) value is saved and later restored to resume execution after kernel handling.
  • Interrupts are hardware-generated events; exceptions are software-triggered; signals are software-visible notifications.
  • Keystroke handling demonstrates a typical interrupt → kernel handler → user-space scheduling path.
  • SIGFPE is the signal associated with divide-by-zero; default handlers may terminate the process unless a custom handler is installed.
  • Shell implementation relies on fork, exec, and wait; zombie processes arise if the parent does not reap children.
  • IPC mechanisms: pipe (unnamed), FIFO (named), message queue, Unix domain sockets, shared memory; each has distinct boundaries, performance, and synchronization characteristics.
  • Shared memory is fastest but requires explicit synchronization; multiple methods exist in Linux (System V, POSIX, and fork-based mappings).
  • Synchronization matters most for shared memory; other IPC methods provide kernel-managed synchronization for their data transfers.