Processes * A process is the unit of execution in the OS. The OS allocates memory and schedules execution at the granularity of processes. For every process, the OS maintains a process control block (PCB), which has all the process-related information like PID, page tables, kernel stack (to save context during traps and context switches) and so on. The OS uses the list of PCBs to schedule processes on a CPU. * Logical address space of a process: the compiler assigns logical addresses starting from 0 to the code and data in the executable. When the OS loads a process to execute, it creates a memory image of the process in main memory. What does the memory image of a process contain? The program executable (code, data, etc.), the stack (used during function calls), the heap (for dynamic memory allocations), and so on. * The virtual address space of a process is divided into logical pages, and each of these pages is placed into separate physical frames in memory. While placing a process in memory, the OS constructs a page table that stores the mappings between logical page numbers and physical frame numbers. This page table is made available to a special piece of hardware called MMU (memory management unit). The CPU requests code and data from memory using virtual addresses. The MMU uses the page table to translate these into physical addresses before the requests reach the main memory. Note that the OS is only involved in creating and maintaining page table entries, and is not involved in address translation on every memory access (that happens in hardware by the MMU). * The virtual address space also has addresses for code of common libraries (e.g., C library) and the kernel. These common pieces of code used by all processes are part of the virtual address space of many processes, but only one physical copy exists in memory. That is, the page table entries of these pieces of code in all processes point to the same physical memory. * How is a process created? During bootup, the OS creates an init process. Every subsequent process is created by forking from an existing process. When a process makes a fork system call to spawn a child process, the OS creates a new PCB for the child process, and copies the memory of the parent into the child. Now, the parent and child processes can be separately scheduled and executed by the OS. (Some operating systems implement "copy-on-write" fork, i.e., a copy of the memory is made only when one of the parent or child makes a change.) * The child can execute the same code as the parent, or can overwrite the parent's executable to run a different code via the exec system call. A process terminates via the exit system call, and its memory is cleaned up when the parent invokes the wait system call. A process that exits is in the zombie state until the parent calls wait to reap it. * Example of the shell as a simple usecase of fork-exec-wait system calls. * The execution of a process can halt temporarily due to an interrupt/trap/system call event. The OS saves the context on the PCB kernel stack, services the interrupt, and restores context. Interrupts are due to external events (e.g., network packet arrived, keyboard character arrived, or disk block has been read), with the timer interrupt being a special case. A timer interrupt goes off periodically on every CPU to force a process to checkin with the OS once in a while. * Blocking and non blocking system calls: it is not possible to return to the same process immediately after some system calls (e.g., read from disk or network can block until data is available). In such cases, a process that makes the system call is said to block. In such cases, the OS scheduler switches context to another process from the list of processes that are ready to run. Once the event to unblock the process happens (e.g., disk block is ready and the disk raises an interrupt), the OS services the interrupt and marks the blocked process as ready ro run. This ready process can be subsequently scheduled, and which point the original system call returns in the user's code. * States of a process: running, ready, blocked, terminated/zombie. * A non-preemptive kernel context switches only when a process voluntarily blocks or terminates. On the other hand, a preemptive kernel (most modern kernels) can context switch at any time, e.g., when a timer interrupt occurs and the OS decides that the process has run for too long. * Some applications have multiple processes in order to make progress when one of the processes blocks. Take the example of a web server. When servicing the request of a client, the server process may need to block when waiting for a disk read, and becomes unavailable to other clients. In such cases, the server can spawn a new child process for each client. This child can block when required, and be dedicated to processing a single client. The main server process can continue to listen for new requests. * If an application has multiple processes, how do these processes communicate? Since processes do not share memory, they need explicit Inter Process Communication (IPC) mechanisms to communicate with each other, like shared memory, sockets, pipes, message queues and so on.