Operating Systems
An operating system (OS) is the most important software that runs on a computer. It acts as an intermediary between the computer hardware and the user applications. The OS manages the computer's resources, such as the CPU, memory, and I/O devices, and provides a platform for applications to run. Without an OS, a computer would be useless, as users would have to interact directly with the hardware, which is a complex and tedious task.
Operating System Structure
The structure of an operating system defines how its various components are organized and interact with each other. A well-designed OS structure simplifies development, debugging, and maintenance. Several common structures exist:
Monolithic Structure
In a monolithic structure, the entire operating system, including process management, memory management, file management, device drivers, and system calls, runs as a single, large program in kernel mode. All components have direct access to each other.
- Advantages: High performance due to direct communication between components, simpler initial design.
- Disadvantages: Large codebase, difficult to debug and maintain, a bug in one part can crash the entire system, adding new features is challenging.
Layered Structure
This structure organizes the OS into a hierarchy of layers. Layer 0 is the hardware, and higher layers use the services of lower layers. Each layer is implemented using only operations defined by lower layers.
- Advantages: Modularity, easier to design and debug as layers can be developed and tested independently.
- Disadvantages: Performance overhead due to multiple layers of abstraction, defining the layers can be difficult.
Microkernel Structure
In a microkernel architecture, only the essential core functions of the OS (like process scheduling, memory management, and basic inter-process communication) reside in the kernel. Other services, such as device drivers, file systems, and network protocols, run as user-level processes.
- Advantages: Enhanced reliability and security (a failure in a user-level service doesn't crash the kernel), easier to extend and maintain.
- Disadvantages: Performance overhead due to frequent communication between user-level services and the microkernel.
Modular Structure (Loadable Kernel Modules)
This is a modern approach where the kernel is kept small, and additional services (like device drivers, file systems) are implemented as modules that can be loaded and unloaded dynamically into the kernel on demand. This is similar to a monolithic approach but with dynamic loading capabilities. Linux and modern Unix systems often use this structure.
- Advantages: Flexibility, efficiency, and modularity without the performance penalty of microkernels.
- Disadvantages: Still requires careful management to avoid kernel instability.
Operating System Services
Operating systems provide a wide range of services to users and applications to make computing easier and more efficient. These services can be broadly categorized as follows:
User Interface
Provides a way for the user to interact with the computer. This can be a command-line interface (CLI), where users type commands, or a graphical user interface (GUI), which uses windows, icons, and a mouse. Examples include Bash shell (CLI) and Windows/macOS desktops (GUI).
Program Execution
The OS is responsible for loading programs into memory and executing them. This involves creating processes, managing their execution, and terminating them.
File System Management
Controls the creation, deletion, reading, writing, and manipulation of files and directories on storage devices. It provides a logical view of information storage.
Input/Output (I/O) Device Management
Manages communication between the CPU and peripheral devices like keyboards, monitors, printers, and disk drives. It handles device drivers and I/O requests.
Communication
Provides mechanisms for processes to communicate with each other, either on the same machine or across a network. This includes features like pipes, sockets, and message queues.
Error Detection and Handling
The OS continuously monitors for errors, both in hardware and software, and takes appropriate action to maintain system stability and data integrity.
Resource Allocation
Manages and allocates system resources such as CPU time, memory space, and I/O devices among competing processes.
Protection and Security
Ensures that processes and users can access only those resources they are permitted to access. It protects the system from unauthorized access and malicious activities.
System Calls
System calls are the interface between an application program and the operating system kernel. When a program needs to perform an operation that requires privileged access to hardware or system resources (like reading a file, creating a process, or communicating with another process), it makes a system call. The OS then performs the requested operation on behalf of the program.
System calls are typically invoked through a special instruction (like `INT` or `SYSCALL`) that causes a software interrupt, transferring control to the OS kernel. The kernel identifies the requested system call, validates the parameters, executes the call, and returns control to the user program.
Common Categories of System Calls:
- Process Control: `create_process()`, `exit()`, `fork()`, `exec()`, `wait()`, `get_pid()`.
- File Management: `create_file()`, `delete_file()`, `open()`, `close()`, `read()`, `write()`, `seek()`.
- Device Management: `request_device()`, `release_device()`, `read_device()`, `write_device()`.
- Information Maintenance: `get_time()`, `get_system_info()`, `get_process_info()`.
- Communication: `create_pipe()`, `send()`, `receive()`, `socket()`.
Memory Trick: Think of system calls as requests you make at a government office. You can't do everything yourself (like accessing restricted files or controlling the printer directly). You have to fill out a form (system call) and submit it to the clerk (OS kernel), who then performs the action for you.
Process Management
A process is a program in execution. It is the fundamental unit of work within an operating system. Managing processes is a core responsibility of the OS, involving creation, scheduling, termination, and inter-process communication.
Process States
A process can be in one of several states during its lifetime:
- New: The process is being created.
- Running: Instructions are being executed by the CPU.
- Waiting: The process is waiting for some event to occur (e.g., I/O completion, signal).
- Ready: The process is waiting to be assigned to a CPU.
- Terminated: The process has finished execution.
Process Control Block (PCB)
The operating system maintains a Process Control Block (PCB) for each process. The PCB is a data structure that stores all the information about a process, including:
- Process state (e.g., ready, running, waiting)
- Process ID (PID)
- Program Counter (PC) - address of the next instruction to be executed
- CPU Registers - values of CPU registers
- Memory Management Information - base and limit registers, page tables
- Accounting Information - CPU burst time, time limits
- I/O Status Information - list of I/O devices allocated, list of open files
Process Creation
A parent process can create a child process using a system call like `fork()`. The child process can then execute a different program using `exec()`. When a process terminates, its resources are reclaimed by the OS.
Process Scheduling
Process scheduling is the mechanism by which the OS decides which process in the ready queue should be allocated the CPU next. Different scheduling algorithms exist (e.g., First-Come, First-Served (FCFS), Shortest Job First (SJF), Priority Scheduling, Round Robin) to optimize CPU utilization, throughput, and response time.
Context Switching
When the OS switches the CPU from one process to another, it performs a context switch. This involves saving the state of the currently running process (stored in its PCB) and loading the state of the next process to be run from its PCB. Context switching has overhead as no useful work is done during the switch.
Interprocess Communication (IPC)
Interprocess Communication (IPC) refers to the mechanisms provided by the operating system that allow multiple processes to communicate and synchronize their actions. Processes often need to share data or coordinate their activities.
Two Main Models of IPC:
- Shared Memory: Processes share a common region of memory. One process writes data into the shared memory, and another process reads it. This is generally faster than message passing because data does not need to be copied between processes.
- Message Passing: Processes communicate by sending and receiving messages to each other. The OS handles the details of message transfer, often through system calls like `send()` and `receive()`. This is simpler to implement but can be slower due to the overhead of message copying and system calls.
Common IPC Mechanisms:
- Pipes: A unidirectional communication channel. Data written to the pipe by one process can be read by another. Used for communication between related processes (e.g., parent-child).
- Message Queues: Allow processes to exchange messages, often with priorities. Messages are stored in a queue managed by the OS.
- Shared Memory: A region of memory is mapped into the address space of multiple processes. Processes can read and write to this memory directly.
- Sockets: Provide a general mechanism for communication between processes, both on the same machine and across different machines on a network.
Synchronization
Synchronization is crucial when multiple processes or threads share common resources or data. Without proper synchronization, race conditions can occur, leading to inconsistent and incorrect results. The goal of synchronization is to ensure that concurrent processes access shared resources in a controlled manner.
The Critical-Section Problem
The critical-section problem is a classic synchronization problem. A critical section is a segment of code in which a process accesses shared resources (like shared variables or files). To ensure correctness, only one process can be executing in its critical section at any given time.
A solution to the critical-section problem must satisfy three conditions:
- Mutual Exclusion: If one process is executing in its critical section, no other process can be executing in their critical section.
- Progress: If no process is executing in its critical section, and some processes wish to enter their critical section, then only those processes that are not executing in their remainder section can participate in the decision of which will enter its critical section next, and this selection cannot be postponed indefinitely.
- Bounded Waiting: There must exist a bound on the number of times that other processes are allowed to enter their critical sections after a process has made a request to enter its critical section and before that request is granted.
Solutions to the Critical-Section Problem
Various mechanisms can be used to solve the critical-section problem.
Semaphores
A semaphore is a synchronization tool introduced by Edsger Dijkstra. It is an integer variable that, apart from initialization, is accessed only through two atomic operations: `wait()` (also known as `P()`) and `signal()` (also known as `V()`).
- Initialization: Semaphores are initialized to a non-negative integer value.
- `wait(S)` operation: If `S > 0`, `S` is decremented. If `S <= 0`, the process executing `wait()` is blocked until `S` becomes greater than 0.
- `signal(S)` operation: `S` is incremented. If there are any processes blocked on semaphore `S`, one of them is unblocked.
Semaphores can be used to ensure mutual exclusion. A binary semaphore (initialized to 1) can be used as a mutex (mutual exclusion lock).
Using Semaphores for Mutual Exclusion
To protect a critical section, we can use a binary semaphore, let's call it `mutex`, initialized to 1.
Process P:
wait(mutex);
// Critical Section
signal(mutex);
When the first process enters the critical section, it executes `wait(mutex)`, decrementing `mutex` to 0. If another process tries to enter the critical section, it will execute `wait(mutex)`, find `mutex` is 0, and will be blocked. When the first process exits the critical section, it executes `signal(mutex)`, incrementing `mutex` to 1, allowing a blocked process (if any) to proceed.
Semaphore Logic: Think of a semaphore as a counter for available resources. `wait()` tries to take a resource (decrements counter, blocks if none available). `signal()` returns a resource (increments counter, wakes up a waiting process if any).
Other Synchronization Tools
While semaphores are powerful, they can be prone to errors if not used carefully (e.g., forgetting a `signal()` call can lead to deadlock). Other synchronization tools include:
- Mutex Locks: Similar to binary semaphores, primarily used for mutual exclusion.
- Monitors: Higher-level synchronization constructs that encapsulate shared data and the procedures that operate on it, ensuring that only one process can be active within the monitor at a time.
- Condition Variables: Used within monitors to allow processes to wait for specific conditions to become true.