📋 Course Overview
Course Objectives:
- Understand core OS concepts and architecture
- Manage processes, memory, storage, and I/O effectively
- Learn synchronization, concurrency, and deadlock resolution
- Explore file systems, protection mechanisms, and modern OS case studies
Unit I: OS Overview & Structure
1.1 Definition and Functions
Operating System: An operating system is a comprehensive system software that serves as the fundamental interface between computer hardware and user applications. It is a complex software system that manages all hardware resources, provides essential services to application programs, and creates a user-friendly environment for human-computer interaction. The OS acts as a resource manager, coordinator, and service provider that ensures efficient, secure, and reliable operation of the entire computing system.
Detailed Functions of an Operating System:
- Resource Management: Efficiently allocates and manages CPU, memory, storage, and I/O devices among competing processes, ensuring optimal utilization and preventing resource conflicts
- Process Management: Creates, schedules, terminates, and coordinates processes, handling process synchronization, communication, and deadlock prevention
- Memory Management: Manages primary and secondary memory, implementing virtual memory systems, memory protection, and efficient memory allocation strategies
- File System Management: Organizes and manages files and directories, providing hierarchical storage, access control, data persistence, and backup mechanisms
- Device Management: Controls and coordinates hardware devices through device drivers, handling device allocation, buffering, and error recovery
- Security and Protection: Implements comprehensive access control, user authentication, data encryption, and system protection mechanisms
- User Interface: Provides both command-line and graphical interfaces for user interaction, enabling efficient human-computer communication
- Network Management: Handles network communication, protocols, distributed computing support, and remote resource access
- Error Handling: Detects, reports, and recovers from hardware and software errors, ensuring system stability and reliability
- Performance Monitoring: Tracks system performance metrics, resource usage patterns, and provides optimization recommendations
Core Functions of OS:
- Process Management: Creating, scheduling, and terminating processes
- Memory Management: Allocating and deallocating memory space
- File System Management: Organizing and tracking files and directories
- Device Management: Controlling peripheral devices
- Security and Protection: Ensuring system and user data security
- User Interface: Providing user-friendly interface
1.2 Types of Operating Systems
Batch Operating System
Definition: A batch operating system is a type of OS that processes jobs in groups or batches without user interaction. Jobs with similar requirements are grouped together and executed sequentially, maximizing system utilization and throughput while minimizing idle time.
Detailed Characteristics:
- Batch Processing: Jobs are collected and executed in batches without user interaction during execution
- High Throughput: Optimized for processing large volumes of similar jobs efficiently
- Low Response Time: No immediate user interaction, resulting in longer turnaround times
- Job Scheduling: Uses sophisticated algorithms to optimize job ordering and resource allocation
- Resource Efficiency: Maximizes CPU and memory utilization through batch optimization
- Error Handling: Comprehensive error detection and recovery mechanisms for batch operations
- Historical Context: Developed for early mainframe systems where computing resources were expensive and limited
- Modern Applications: Still used in data processing centers, scientific computing, and large-scale data analysis
Time-Sharing Operating System
Definition: A time-sharing operating system allows multiple users to access and use computer resources simultaneously by rapidly switching between different user programs. It provides each user with the illusion of having exclusive access to the computer while efficiently sharing CPU time and other resources among all active users.
Detailed Characteristics:
- Multi-User Support: Multiple users can access the system simultaneously through terminals or network connections
- CPU Time Sharing: CPU time is divided into small time slices and allocated among multiple users in a round-robin fashion
- Interactive Response: Provides quick response times for interactive applications and user commands
- Resource Sharing: Efficiently shares memory, storage, and peripheral devices among multiple users
- Process Scheduling: Implements sophisticated scheduling algorithms to ensure fair resource allocation
- Memory Management: Uses virtual memory and paging to support multiple concurrent processes
- Security and Isolation: Provides user authentication, access control, and process isolation for security
- File System: Hierarchical file system with user-specific directories and access permissions
- Network Support: Built-in networking capabilities for remote access and distributed computing
- Modern Examples: Unix, Linux, Windows, macOS, Android, iOS
Real-Time Operating System
Definition: A real-time operating system (RTOS) is designed to process data and respond to events within strict time constraints. It guarantees that critical tasks are completed within specified deadlines, making it essential for applications where timing is critical for system safety and functionality.
Detailed Characteristics:
- Predictable Timing: Guarantees response times within specified limits for critical operations
- Deterministic Behavior: Provides consistent and predictable system behavior under all conditions
- Priority-Based Scheduling: Uses priority-based scheduling to ensure critical tasks meet deadlines
- Minimal Latency: Minimizes interrupt latency and context switching overhead
- Resource Management: Efficient resource allocation with predictable timing characteristics
- Error Handling: Robust error detection and recovery mechanisms for system reliability
- Two Types:
- Hard Real-Time: Missing a deadline results in system failure (e.g., medical devices, aircraft control)
- Soft Real-Time: Missing a deadline degrades performance but doesn't cause failure (e.g., multimedia streaming)
- Applications: Medical devices, automotive systems, industrial automation, aerospace, telecommunications
- Examples: VxWorks, QNX, FreeRTOS, RTEMS, Windows CE, Linux with RT patches
1.3 OS Services and System Calls
OS Services:
- Program Execution: Loading programs into memory and running them
- I/O Operations: Managing input/output operations
- File System Manipulation: Creating, deleting, reading, writing files
- Communication: Inter-process communication
- Error Detection: Detecting and handling errors
- Resource Allocation: Allocating resources to processes
- Accounting: Tracking resource usage
- Protection and Security: Protecting system and user data
System Calls:
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child process executing\n");
exit(0);
} else if (pid > 0) {
wait(NULL);
printf("Child process completed\n");
}
int fd = open("file.txt", O_RDONLY);
if (fd == -1) {
perror("Error opening file");
return 1;
}
char buffer[1024];
int bytes_read = read(fd, buffer, 1024);
if (bytes_read > 0) {
printf("Read %d bytes\n", bytes_read);
}
close(fd);
int device_fd = open("/dev/device", O_RDWR);
if (device_fd != -1) {
ioctl(device_fd, command, arg);
close(device_fd);
}
return 0;
}
1.4 Architecture Styles
Monolithic Architecture
Definition: A monolithic architecture is an OS design where all operating system services run in kernel mode as a single large program. The entire operating system runs as a single process in kernel space, with all system calls, device drivers, file systems, and memory management functions integrated into one large executable. This architecture provides direct access to hardware and efficient system calls but can be complex to maintain and debug.
Detailed Characteristics:
- Single Kernel Space: All OS components run in privileged kernel mode with direct hardware access
- Efficient System Calls: Direct function calls between OS components without inter-process communication overhead
- Hardware Access: Direct access to hardware resources without abstraction layers
- Performance: High performance due to minimal overhead and direct memory access
- Complexity: Large, complex codebase that can be difficult to maintain and debug
- Reliability: A bug in any component can crash the entire system
- Security: All components run with full privileges, increasing security risks
- Examples: Traditional Unix kernels, early Linux kernels, MS-DOS
Advantages:
- Better performance due to direct communication
- Efficient resource sharing
- Simple design and implementation
Disadvantages:
- Difficult to maintain and modify
- System crash affects entire OS
- Large kernel size
Example: Traditional Unix, Linux (partially)
Microkernel Architecture
Definition: A microkernel architecture is an OS design where only essential core services run in kernel mode, while all other operating system services run as separate user processes. The microkernel provides only basic services like process management, memory management, and inter-process communication (IPC), with all other services (file systems, device drivers, networking) implemented as user-space servers that communicate through message passing.
Detailed Characteristics:
- Minimal Kernel: Kernel contains only essential services: process management, memory management, and IPC
- User-Space Services: File systems, device drivers, networking, and other services run as user processes
- Message Passing: Communication between services occurs through well-defined message passing interfaces
- Modularity: Services can be added, removed, or modified without affecting the core kernel
- Fault Isolation: A failure in one service doesn't crash the entire system
- Security: Services run with limited privileges, reducing security risks
- Portability: Core kernel is hardware-independent and easily portable
- Performance Overhead: Message passing introduces communication overhead
- Complexity: More complex design due to distributed service architecture
- Examples: QNX, MINIX, Mach, L4, seL4, HelenOS
1.5 Generations of Operating Systems
| Generation |
Period |
Characteristics |
Examples |
| First (1945-1955) |
1945-1955 |
Vacuum tubes, no OS, machine language |
ENIAC, UNIVAC |
| Second (1955-1965) |
1955-1965 |
Transistors, batch systems, assembly language |
IBM 1401, FORTRAN |
| Third (1965-1980) |
1965-1980 |
Integrated circuits, multiprogramming, time-sharing |
Unix, Multics |
| Fourth (1980-1990) |
1980-1990 |
Personal computers, GUI, networking |
MS-DOS, Mac OS |
| Fifth (1990-Present) |
1990-Present |
Mobile computing, cloud, virtualization |
Android, iOS, Windows 10 |
Unit II: Process & Thread Management
2.1 Concept of Processes
Process: A process is a program in execution that represents the basic unit of work in an operating system. It is a dynamic entity that consists of an executable program, associated data, and system resources. A process is created when a program is loaded into memory and begins execution, and it terminates when the program completes or is terminated. Each process has its own address space, system resources, and execution context that are isolated from other processes.
Detailed Process Components:
- Program Counter (PC): Points to the next instruction to execute in the program
- Process Stack: Contains temporary data, function parameters, return addresses, and local variables
- Data Section: Contains global variables and static data that persist throughout program execution
- Heap: Memory dynamically allocated during runtime using functions like malloc() and free()
- Text Section: Contains the actual program code and instructions that are executed
- Process State: Current state of the process (new, ready, running, waiting, terminated)
- CPU Registers: Current values of all CPU registers including general-purpose and special-purpose registers
- Process ID (PID): Unique identifier assigned to each process by the operating system
- Parent Process ID (PPID): Identifier of the process that created this process
- File Descriptors: List of open files and their associated file descriptors
- Memory Limits: Information about memory allocation and limits for the process
- Priority and Scheduling Information: Process priority, scheduling parameters, and CPU usage statistics
Process Control Block (PCB)
struct PCB {
int process_id;
int parent_id;
char process_state;
int priority;
int program_counter;
int cpu_registers[16];
int stack_pointer;
int base_pointer;
int memory_limits;
int page_table_ptr;
int memory_usage;
int list_of_open_files;
int working_directory;
int cpu_usage;
int waiting_time;
int arrival_time;
int burst_time;
int message_queue;
int semaphores;
int child_processes[10];
int num_children;
};
Process Life Cycle
Process States:
- New: Process is being created
- Ready: Process is waiting to be assigned to a processor
- Running: Instructions are being executed
- Waiting: Process is waiting for some event to occur
- Terminated: Process has finished execution
Context Switching
Context Switch: A context switch is the process of saving the current state (context) of a running process or thread and loading the saved state of another process or thread so that execution can resume from the same point later. This is a fundamental mechanism in multitasking operating systems that allows multiple processes to share a single CPU by rapidly switching between them, creating the illusion of concurrent execution. The context switch involves saving all the information needed to restore the process later, including CPU registers, program counter, memory management information, and other process-specific data.
Detailed Context Switch Process:
- State Preservation: Saves the current process's CPU registers (general-purpose registers, program counter, stack pointer, base pointer, status registers), memory management information (page table pointers, memory limits), and process-specific data (file descriptors, working directory, process state)
- Process Control Block Update: Updates the Process Control Block (PCB) of the current process with the saved context information, including CPU usage statistics, waiting time, and current state
- Scheduler Invocation: Calls the CPU scheduler to determine which process should run next based on scheduling algorithms and process priorities
- Memory Management Switch: Switches memory management context, including page tables, memory protection settings, and virtual memory mappings for the new process
- State Restoration: Loads the saved context of the new process from its PCB, restoring all CPU registers, program counter, and process-specific information
- Execution Transfer: Transfers control to the new process, resuming execution from the exact point where it was previously interrupted
- Cache Invalidation: May invalidate CPU cache entries to ensure the new process doesn't access stale data from the previous process
- Performance Overhead: Context switching has significant overhead due to the time required to save and restore process state, making it a critical factor in system performance
2.2 CPU Scheduling
Scheduler Types
- Long-term Scheduler (Job Scheduler): Selects processes from the job pool and loads them into memory
- Medium-term Scheduler: Swaps processes in and out of memory
- Short-term Scheduler (CPU Scheduler): Selects which process should be executed next
Scheduling Algorithms
First Come First Serve (FCFS)
void fcfs_scheduling(struct Process processes[], int n) {
int current_time = 0;
float total_waiting_time = 0;
for(int i = 0; i < n; i++) {
if(current_time < processes[i].arrival_time) {
current_time = processes[i].arrival_time;
}
processes[i].waiting_time = current_time - processes[i].arrival_time;
total_waiting_time += processes[i].waiting_time;
current_time += processes[i].burst_time;
processes[i].completion_time = current_time;
}
float avg_waiting_time = total_waiting_time / n;
printf("Average Waiting Time: %.2f\n", avg_waiting_time);
}
Shortest Job First (SJF)
void sjf_scheduling(struct Process processes[], int n) {
int current_time = 0;
int completed = 0;
while(completed < n) {
int shortest_job = -1;
int min_burst = INT_MAX;
for(int i = 0; i < n; i++) {
if(processes[i].arrival_time <= current_time &&
processes[i].remaining_time > 0 &&
processes[i].burst_time < min_burst) {
shortest_job = i;
min_burst = processes[i].burst_time;
}
}
if(shortest_job == -1) {
current_time++;
continue;
}
processes[shortest_job].waiting_time = current_time - processes[shortest_job].arrival_time;
current_time += processes[shortest_job].burst_time;
processes[shortest_job].completion_time = current_time;
processes[shortest_job].remaining_time = 0;
completed++;
}
}
Round Robin (RR)
void round_robin(struct Process processes[], int n, int quantum) {
int current_time = 0;
int completed = 0;
while(completed < n) {
for(int i = 0; i < n; i++) {
if(processes[i].remaining_time > 0 &&
processes[i].arrival_time <= current_time) {
if(processes[i].remaining_time > quantum) {
current_time += quantum;
processes[i].remaining_time -= quantum;
} else {
current_time += processes[i].remaining_time;
processes[i].waiting_time = current_time - processes[i].arrival_time - processes[i].burst_time;
processes[i].remaining_time = 0;
completed++;
}
}
}
}
}
Priority Scheduling
void priority_scheduling(struct Process processes[], int n) {
int current_time = 0;
int completed = 0;
while(completed < n) {
int highest_priority = -1;
int min_priority = INT_MAX;
for(int i = 0; i < n; i++) {
if(processes[i].arrival_time <= current_time &&
processes[i].remaining_time > 0 &&
processes[i].priority < min_priority) {
highest_priority = i;
min_priority = processes[i].priority;
}
}
if(highest_priority == -1) {
current_time++;
continue;
}
processes[highest_priority].waiting_time = current_time - processes[highest_priority].arrival_time;
current_time += processes[highest_priority].burst_time;
processes[highest_priority].completion_time = current_time;
processes[highest_priority].remaining_time = 0;
completed++;
}
}
2.3 Threads
Thread vs Process
| Characteristic |
Process |
Thread |
| Definition |
Program in execution |
Segment of a process |
| Creation |
Independent |
Within process |
| Memory |
Separate memory space |
Shared memory space |
| Context Switch |
Expensive |
Inexpensive |
| Communication |
Inter-process communication |
Shared memory |
Thread Implementation
User Threads
#include <pthread.h>
void* thread_function(void* arg) {
int thread_id = *(int*)arg;
printf("Thread %d is running\n", thread_id);
return NULL;
}
int main() {
pthread_t threads[5];
int thread_ids[5];
for(int i = 0; i < 5; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
}
for(int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
printf("All threads completed\n");
return 0;
}
2.4 Process Synchronization
Critical Section Problem
Critical Section: A critical section is a segment of code in a multi-process or multi-threaded program where shared resources (such as variables, data structures, files, or hardware devices) are accessed or modified. It is called "critical" because if multiple processes or threads execute this code simultaneously, it can lead to race conditions, data corruption, inconsistent states, or other unpredictable behavior. The fundamental requirement is that only one process or thread should execute in its critical section at any given time, ensuring mutual exclusion and data integrity.
Detailed Characteristics of Critical Sections:
- Mutual Exclusion: Ensures that only one process can execute the critical section at any time, preventing concurrent access to shared resources
- Progress: If no process is executing in its critical section, then a process that wants to enter its critical section should be allowed to do so without unnecessary delay
- Bounded Waiting: A process should not wait indefinitely to enter its critical section, preventing starvation of processes
- Shared Resource Protection: Protects shared variables, data structures, files, databases, or hardware devices from concurrent access
- Race Condition Prevention: Prevents race conditions where the outcome depends on the relative timing of process execution
- Data Consistency: Ensures that shared data remains in a consistent and valid state throughout program execution
- Atomic Operations: Makes operations on shared resources appear atomic (indivisible) to other processes
- Deadlock Prevention: Must be designed to avoid deadlock situations where processes wait indefinitely for each other
Peterson's Algorithm
int turn = 0;
int flag[2] = {0, 0};
void enter_critical_section(int process_id) {
int other = 1 - process_id;
flag[process_id] = 1;
turn = other;
while(flag[other] == 1 && turn == other) {
}
}
void exit_critical_section(int process_id) {
flag[process_id] = 0;
}
Semaphores
#include <semaphore.h>
sem_t mutex;
sem_t empty;
sem_t full;
void producer() {
while(1) {
sem_wait(&empty);
sem_wait(&mutex);
sem_post(&mutex);
sem_post(&full);
}
}
void consumer() {
while(1) {
sem_wait(&full);
sem_wait(&mutex);
sem_post(&mutex);
sem_post(&empty);
}
}
Mutexes
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_function(void* arg) {
pthread_mutex_lock(&mutex);
printf("Thread %d in critical section\n", *(int*)arg);
pthread_mutex_unlock(&mutex);
return NULL;
}
Classic Concurrency Problems
Dining Philosophers Problem
#include <pthread.h>
#include <semaphore.h>
#define N 5
sem_t chopsticks[N];
sem_t mutex;
void* philosopher(void* arg) {
int id = *(int*)arg;
int left = id;
int right = (id + 1) % N;
while(1) {
printf("Philosopher %d is thinking\n", id);
sem_wait(&mutex);
sem_wait(&chopsticks[left]);
sem_wait(&chopsticks[right]);
sem_post(&mutex);
printf("Philosopher %d is eating\n", id);
sem_post(&chopsticks[left]);
sem_post(&chopsticks[right]);
}
return NULL;
}
Readers-Writers Problem
#include <pthread.h>
#include <semaphore.h>
sem_t mutex;
sem_t write_lock;
int read_count = 0;
void* reader(void* arg) {
int id = *(int*)arg;
while(1) {
sem_wait(&mutex);
read_count++;
if(read_count == 1) {
sem_wait(&write_lock);
}
sem_post(&mutex);
printf("Reader %d is reading\n", id);
sem_wait(&mutex);
read_count--;
if(read_count == 0) {
sem_post(&write_lock);
}
sem_post(&mutex);
}
return NULL;
}
void* writer(void* arg) {
int id = *(int*)arg;
while(1) {
sem_wait(&write_lock);
printf("Writer %d is writing\n", id);
sem_post(&write_lock);
}
return NULL;
}
Unit III: Deadlocks & Memory Management
3.1 Deadlock
Deadlock Definition
Deadlock: A deadlock is a situation in a multi-process system where two or more processes are unable to proceed because each is waiting for a resource that is held by another process in the set. It is a state of permanent blocking where processes are stuck in a circular wait condition, preventing any of them from making progress. Deadlocks are a fundamental problem in concurrent systems and can cause system-wide performance degradation or complete system failure if not properly managed.
Detailed Characteristics of Deadlocks:
- Permanent Blocking: Once a deadlock occurs, the involved processes remain blocked indefinitely unless external intervention occurs
- Resource Contention: Deadlocks arise from competition for limited system resources such as CPU, memory, files, or hardware devices
- Circular Dependency: Processes form a circular chain where each process holds resources needed by the next process in the chain
- System-wide Impact: Deadlocks can affect multiple processes and potentially the entire system, not just the directly involved processes
- Predictable Conditions: Deadlocks occur only when all four necessary conditions are simultaneously satisfied
- Detection Difficulty: Deadlocks can be difficult to detect in complex systems with many processes and resources
- Recovery Complexity: Resolving deadlocks often requires process termination or resource preemption, which can be disruptive
Deadlock Conditions
Four necessary conditions for deadlock:
- Mutual Exclusion: At least one resource must be non-shareable, meaning only one process can use it at a time. This condition prevents multiple processes from simultaneously accessing the same resource, creating the potential for resource contention.
- Hold and Wait: A process must be holding at least one resource and waiting for additional resources that are currently held by other processes. This condition allows processes to hold resources while waiting for others, creating the potential for circular waiting.
- No Preemption: Resources cannot be forcibly removed from a process that is holding them. The process must voluntarily release the resource when it completes its task, preventing forced resource reallocation.
- Circular Wait: A set of processes must be waiting for each other in a circular manner, where each process holds a resource needed by the next process in the chain, creating a closed loop of resource dependencies.
Deadlock Prevention
- Eliminate Mutual Exclusion: Use shareable resources
- Eliminate Hold and Wait: Request all resources at once
- Allow Preemption: Force resource release
- Eliminate Circular Wait: Use resource ordering
Banker's Algorithm
#define MAX_PROCESSES 5
#define MAX_RESOURCES 3
int available[MAX_RESOURCES];
int maximum[MAX_PROCESSES][MAX_RESOURCES];
int allocation[MAX_PROCESSES][MAX_RESOURCES];
int need[MAX_PROCESSES][MAX_RESOURCES];
int finish[MAX_PROCESSES] = {0};
int is_safe_state() {
int work[MAX_RESOURCES];
int safe_sequence[MAX_PROCESSES];
int count = 0;
for(int i = 0; i < MAX_RESOURCES; i++) {
work[i] = available[i];
}
while(count < MAX_PROCESSES) {
int found = 0;
for(int i = 0; i < MAX_PROCESSES; i++) {
if(!finish[i]) {
int can_allocate = 1;
for(int j = 0; j < MAX_RESOURCES; j++) {
if(need[i][j] > work[j]) {
can_allocate = 0;
break;
}
}
if(can_allocate) {
for(int j = 0; j < MAX_RESOURCES; j++) {
work[j] += allocation[i][j];
}
safe_sequence[count] = i;
finish[i] = 1;
count++;
found = 1;
}
}
}
if(!found) {
return 0;
}
}
return 1;
}
3.2 Memory Management
Memory Allocation Techniques
Memory Allocation: Memory allocation is the process of assigning portions of computer memory to programs and processes for their execution. The operating system manages memory allocation to ensure efficient use of available memory resources, prevent memory conflicts between processes, and provide optimal performance. Different allocation techniques are used depending on the system requirements, memory constraints, and performance goals.
1. Partitioning Techniques:
- Fixed Partitioning: Memory is divided into fixed-size partitions of predetermined sizes. Each partition can hold exactly one process, and processes must fit entirely within a partition. This method is simple to implement but suffers from internal fragmentation when processes are smaller than their allocated partitions.
- Variable Partitioning: Memory is divided dynamically based on the actual size of processes. Partitions are created and destroyed as processes arrive and depart, allowing for more efficient memory utilization. This method eliminates internal fragmentation but may create external fragmentation over time.
- Best Fit: Allocates the smallest partition that is large enough to hold the process. This minimizes internal fragmentation by finding the closest match between process size and partition size, but requires searching through all available partitions.
- Worst Fit: Allocates the largest available partition to a process. This approach leaves larger remaining fragments, which may be more useful for future allocations, but can lead to inefficient memory utilization.
- First Fit: Allocates the first partition that is large enough to hold the process. This is the fastest allocation method as it stops searching at the first suitable partition, but may not be the most efficient in terms of memory utilization.
Paging
struct page_table_entry {
int frame_number;
int valid_bit;
int dirty_bit;
int reference_bit;
int protection;
};
Virtual Memory
Virtual Memory: Virtual memory is a memory management technique that allows a computer to use more memory than is physically available by temporarily transferring data from random access memory (RAM) to disk storage. It creates an illusion of a large, contiguous memory space by combining physical memory and disk storage, enabling programs to run even when they require more memory than is physically available. Virtual memory provides memory isolation between processes, efficient memory utilization, and support for programs larger than physical memory.
Detailed Virtual Memory Characteristics:
- Memory Abstraction: Provides a uniform address space that is independent of the actual physical memory layout
- Process Isolation: Each process has its own virtual address space, preventing one process from accessing another's memory
- Memory Protection: Implements access control mechanisms to prevent unauthorized memory access and ensure system security
- Demand Paging: Pages are loaded into memory only when needed, reducing initial memory requirements and improving system performance
- Page Sharing: Allows multiple processes to share the same physical pages for code and data, reducing memory usage
- Memory Overcommitment: Enables the system to allocate more virtual memory than physical memory, improving memory utilization
- Transparent Implementation: Applications are unaware of the virtual memory system and work with virtual addresses transparently
Page Replacement Algorithms:
- FIFO (First In First Out): Replaces the oldest page in memory, simple to implement but may not consider page usage patterns
- LRU (Least Recently Used): Replaces the page that has not been accessed for the longest time, based on the principle of locality
- Optimal: Replaces the page that will not be used for the longest time in the future, theoretical algorithm used for comparison
- Clock Algorithm: Uses a circular buffer with a reference bit to approximate LRU behavior with lower overhead
- Second Chance: Gives pages a second chance before replacement by checking the reference bit
- Working Set: Keeps only the pages that are actively being used by each process in memory
Thrashing
Thrashing: Thrashing is a severe performance degradation condition in virtual memory systems where the operating system spends an excessive amount of time swapping pages between main memory and secondary storage (disk) instead of executing actual user processes. This occurs when the system is overwhelmed with page faults, causing a continuous cycle of page swapping that dramatically reduces system performance and responsiveness.
Detailed Causes and Characteristics of Thrashing:
- Memory Overcommitment: When too many processes are competing for limited physical memory, causing frequent page faults and continuous swapping
- Inefficient Page Replacement: Poor page replacement algorithms that don't consider locality of reference, leading to unnecessary page swaps
- Working Set Exceedance: When the total working set size of all active processes exceeds the available physical memory capacity
- Locality Violation: Processes that don't exhibit good spatial or temporal locality, causing frequent page faults
- Memory Fragmentation: Internal or external fragmentation that reduces effective memory utilization
- I/O Bottleneck: Slow disk I/O operations that cause page swapping to become a major performance bottleneck
- System Response Degradation: Dramatic increase in response time and decrease in throughput as the system becomes unresponsive
- CPU Utilization Drop: CPU utilization drops significantly as the system spends most time waiting for I/O operations
Unit IV: File Systems & I/O Management
4.1 File Systems
File Concept
File: A file is a named collection of related information that is stored on secondary storage devices such as hard disks, solid-state drives, or optical media. Files serve as the basic unit of data storage and organization in computer systems, providing a logical abstraction over physical storage devices. Files can contain various types of data including text, images, programs, databases, or any other digital information, and they are managed by the operating system's file system component.
Detailed File Characteristics:
- Persistence: Files provide permanent storage that survives system reboots and power failures, unlike volatile memory
- Named Access: Files are accessed through human-readable names that provide a convenient way to reference stored data
- Hierarchical Organization: Files are organized in directory structures that allow logical grouping and easy navigation
- Access Control: Files support various protection mechanisms including read, write, execute permissions and user-based access control
- Metadata Storage: Files store additional information about themselves including creation time, modification time, size, and ownership
- Device Independence: Files provide a uniform interface regardless of the underlying storage device characteristics
- Sharing Capability: Files can be shared between multiple users and processes with appropriate access controls
- Backup and Recovery: Files support backup and recovery operations for data protection and disaster recovery
File Attributes:
- Name: Human-readable identifier that uniquely identifies the file within its directory context
- Type: File extension or internal type that indicates the file's format and how it should be processed
- Location: Pointer to file location on device, including device ID, track, sector, and block information
- Size: Current file size in bytes, which may change as the file is modified during its lifetime
- Protection: Access control information including read, write, execute permissions for owner, group, and others
- Time, Date, User ID: Metadata for tracking creation time, last modification time, last access time, and file ownership
Directory Structures
struct directory_entry {
char filename[32];
int inode_number;
char file_type;
long file_size;
time_t creation_time;
time_t modification_time;
};
File Allocation Methods
1. Contiguous Allocation:
- Storage Method: Files are stored in consecutive blocks on the disk, with each file occupying a continuous sequence of disk blocks
- Fast Sequential Access: Excellent performance for sequential file access since all blocks are adjacent and can be read in a single disk operation
- External Fragmentation Problem: Creates external fragmentation as files are created and deleted, leaving gaps that may be too small for new files
- File Size Limitation: Requires knowing the maximum file size in advance, which can be difficult to predict
- Simple Implementation: Straightforward to implement and manage, with direct block addressing
2. Linked Allocation:
- Storage Method: Files are stored as a linked list of disk blocks, where each block contains a pointer to the next block in the file
- No External Fragmentation: Eliminates external fragmentation since any free block can be used to store file data
- Slow Random Access: Poor performance for random access since the system must traverse the linked list to reach a specific block
- Space Overhead: Each block contains a pointer, reducing the effective storage capacity for actual file data
- Reliability Issues: If one block pointer is corrupted, the entire file becomes inaccessible
3. Indexed Allocation:
- Storage Method: Uses an index block that contains pointers to all the blocks belonging to a file, similar to a page table in memory management
- Supports Random Access: Excellent random access performance since any block can be accessed directly through the index
- Overhead of Index Block: Each file requires an index block, which consumes additional disk space
- Flexible File Size: Supports files of varying sizes without the fragmentation problems of contiguous allocation
- Complex Implementation: More complex to implement and manage compared to other allocation methods
4.2 I/O Management
Disk Structure and Scheduling
Disk Structure: A disk consists of multiple platters, each with two surfaces. Each surface is divided into concentric circles called tracks, and tracks are further divided into sectors. The disk head moves across tracks to read/write data, and the time taken for this movement is called seek time. The disk scheduling algorithms optimize the order of disk requests to minimize total seek time and improve overall disk performance.
Disk Performance Metrics:
- Seek Time: Time taken to move the disk head from current track to target track
- Rotational Latency: Time for the desired sector to rotate under the disk head
- Transfer Time: Time to transfer data between disk and memory
- Total Access Time: Sum of seek time, rotational latency, and transfer time
- Throughput: Number of I/O operations completed per unit time
int fcfs(int requests[], int n, int head) {
int total_seek = 0;
for(int i = 0; i < n; i++) {
total_seek += abs(requests[i] - head);
head = requests[i];
}
return total_seek;
}
int sstf(int requests[], int n, int head) {
int total_seek = 0;
int visited[1000] = {0};
for(int i = 0; i < n; i++) {
int min_distance = INT_MAX;
int min_index = -1;
for(int j = 0; j < n; j++) {
if(!visited[j]) {
int distance = abs(requests[j] - head);
if(distance < min_distance) {
min_distance = distance;
min_index = j;
}
}
}
total_seek += min_distance;
head = requests[min_index];
visited[min_index] = 1;
}
return total_seek;
}
int scan(int requests[], int n, int head, int direction) {
int total_seek = 0;
int visited[1000] = {0};
for(int i = 0; i < n-1; i++) {
for(int j = 0; j < n-i-1; j++) {
if(requests[j] > requests[j+1]) {
int temp = requests[j];
requests[j] = requests[j+1];
requests[j+1] = temp;
}
}
}
if(direction == 1) {
for(int i = 0; i < n; i++) {
if(requests[i] >= head) {
total_seek += abs(requests[i] - head);
head = requests[i];
}
}
for(int i = n-1; i >= 0; i--) {
if(requests[i] < head) {
total_seek += abs(requests[i] - head);
head = requests[i];
}
}
} else {
for(int i = n-1; i >= 0; i--) {
if(requests[i] <= head) {
total_seek += abs(requests[i] - head);
head = requests[i];
}
}
for(int i = 0; i < n; i++) {
if(requests[i] > head) {
total_seek += abs(requests[i] - head);
head = requests[i];
}
}
}
return total_seek;
}
RAID Organizations
RAID (Redundant Array of Independent Disks): RAID is a storage technology that combines multiple physical disk drives into a single logical unit for the purposes of data redundancy, performance improvement, or both. RAID systems distribute data across multiple disks using various techniques such as striping, mirroring, and parity to achieve different levels of fault tolerance and performance characteristics.
RAID Levels and Their Characteristics:
- RAID 0 (Striping):
- Data is distributed across multiple disks in stripes
- Provides maximum performance improvement through parallel I/O operations
- No redundancy - if one disk fails, all data is lost
- Storage efficiency: 100% (no space overhead)
- Best for: High-performance applications where data loss is acceptable
- RAID 1 (Mirroring):
- Data is duplicated on two or more identical disks
- Provides excellent fault tolerance - can survive failure of all but one disk
- Read performance is improved due to parallel reads
- Write performance may be slightly degraded
- Storage efficiency: 50% (half the space is used for redundancy)
- Best for: Critical data that requires high availability
- RAID 5 (Distributed Parity):
- Uses distributed parity across all disks in the array
- Can survive failure of one disk without data loss
- Good read performance, moderate write performance
- Storage efficiency: (n-1)/n where n is the number of disks
- Write penalty due to parity calculation
- Best for: Balanced performance and fault tolerance
- RAID 6 (Double Parity):
- Uses two parity blocks distributed across all disks
- Can survive failure of two disks simultaneously
- Higher fault tolerance than RAID 5
- Storage efficiency: (n-2)/n where n is the number of disks
- Higher write penalty due to dual parity calculation
- Best for: High-reliability requirements with larger disk arrays
- RAID 10 (Striped Mirrors):
- Combines RAID 0 striping with RAID 1 mirroring
- Excellent performance and fault tolerance
- Can survive failure of one disk in each mirrored pair
- Storage efficiency: 50% (same as RAID 1)
- Requires minimum of 4 disks
- Best for: High-performance applications requiring fault tolerance
unsigned char calculate_parity(unsigned char data[], int size) {
unsigned char parity = 0;
for(int i = 0; i < size; i++) {
parity ^= data[i];
}
return parity;
}
unsigned char recover_data(unsigned char data[], int size,
unsigned char parity, int failed_index) {
unsigned char recovered = parity;
for(int i = 0; i < size; i++) {
if(i != failed_index) {
recovered ^= data[i];
}
}
return recovered;
}
Unit V: Protection, Security & Case Studies
5.1 Protection & Security
Access Control Models
Access Control: Access control is a security mechanism that regulates who or what can view or use resources in a computing environment. It is a fundamental concept in security that minimizes risk to the business or organization. Access control systems perform authorization identification, authentication, access approval, and accountability of entities through login credentials including passwords, personal identification numbers (PINs), biometric scans, and physical or electronic keys.
Access Control Models:
- Discretionary Access Control (DAC): The owner of the resource determines who can access it and what permissions they have
- Mandatory Access Control (MAC): Access is controlled by the system based on security labels and clearance levels
- Role-Based Access Control (RBAC): Access is granted based on the role of the user in the organization
- Attribute-Based Access Control (ABAC): Access is determined by evaluating attributes of the user, resource, and environment
struct access_control_entry {
char subject[32];
char object[32];
int permissions;
};
struct access_control_matrix {
struct access_control_entry entries[1000];
int entry_count;
};
int check_access(char* subject, char* object, int permission) {
for(int i = 0; i < matrix.entry_count; i++) {
if(strcmp(matrix.entries[i].subject, subject) == 0 &&
strcmp(matrix.entries[i].object, object) == 0) {
return (matrix.entries[i].permissions & permission) == permission;
}
}
return 0;
}
struct role {
char role_name[32];
int permissions;
};
struct user {
char username[32];
char roles[10][32];
int role_count;
};
int check_rbac_access(struct user* user, char* resource, int permission) {
for(int i = 0; i < user->role_count; i++) {
if(has_role_permission(user->roles[i], resource, permission)) {
return 1;
}
}
return 0;
}
Authentication Mechanisms
Authentication: Authentication is the process of verifying the identity of a user, device, or system. It is a critical component of security that ensures only authorized entities can access protected resources. Authentication mechanisms use various factors including something you know (passwords), something you have (tokens), and something you are (biometrics) to establish identity with varying levels of security and convenience.
Authentication Methods and Their Characteristics:
- Password-based Authentication:
- Traditional username/password combination
- Advantages: Simple to implement, widely supported, low cost
- Disadvantages: Vulnerable to brute force, phishing, and social engineering
- Security measures: Password complexity requirements, account lockout, encryption
- Best practices: Strong passwords, regular changes, secure storage
- Biometric Authentication:
- Uses unique physical or behavioral characteristics
- Types: Fingerprint, retina/iris scan, voice recognition, facial recognition, gait analysis
- Advantages: Difficult to forge, convenient, no need to remember credentials
- Disadvantages: Can be expensive, privacy concerns, false acceptance/rejection rates
- Applications: Mobile devices, secure facilities, financial transactions
- Token-based Authentication:
- Uses physical or digital tokens for authentication
- Types: Smart cards, security tokens, USB keys, mobile apps
- Advantages: High security, difficult to duplicate, time-based codes
- Disadvantages: Can be lost or stolen, additional hardware cost
- Examples: RSA SecurID, Google Authenticator, YubiKey
- Multi-factor Authentication (MFA):
- Combines multiple authentication methods for enhanced security
- Factors: Knowledge (password), possession (token), inherence (biometric)
- Advantages: Significantly higher security, defense in depth
- Disadvantages: More complex setup, potential user resistance
- Implementation: Two-factor (2FA), three-factor (3FA), adaptive authentication
- Certificate-based Authentication:
- Uses digital certificates for identity verification
- Components: Public key infrastructure (PKI), certificate authorities (CA)
- Advantages: Strong cryptographic security, non-repudiation
- Disadvantages: Complex infrastructure, certificate management overhead
- Applications: SSL/TLS, VPN, enterprise authentication
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
struct user_account {
char username[32];
char password_hash[64];
char salt[16];
int failed_attempts;
time_t lockout_time;
};
void hash_password(const char* password, const char* salt, char* hash) {
char salted_password[256];
sprintf(salted_password, "%s%s", password, salt);
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, salted_password, strlen(salted_password));
SHA256_Final((unsigned char*)hash, &ctx);
}
int verify_password(struct user_account* account, const char* password) {
char computed_hash[64];
hash_password(password, account->salt, computed_hash);
return strcmp(account->password_hash, computed_hash) == 0;
}
int verify_totp(const char* secret, int code) {
time_t current_time = time(NULL);
int time_step = 30;
int counter = current_time / time_step;
int expected_code = generate_totp(secret, counter);
return (code == expected_code ||
code == generate_totp(secret, counter - 1) ||
code == generate_totp(secret, counter + 1));
}
5.2 Operating System Case Studies
Unix/Linux Operating System
Unix/Linux: Unix is a family of multitasking, multiuser computer operating systems that derive from the original AT&T Unix, developed in the 1970s at the Bell Labs research center. Linux is a Unix-like operating system kernel that was created by Linus Torvalds in 1991. The combination of Linux kernel with GNU tools and other software forms what is commonly known as the Linux operating system.
Key Features and Architecture:
- Monolithic Kernel Architecture:
- All operating system services run in kernel space
- Direct hardware access for better performance
- Loadable kernel modules for extensibility
- Efficient system calls and inter-process communication
- Process Management:
- Fork-exec model for process creation
- Process hierarchy with parent-child relationships
- Process states: running, ready, blocked, zombie
- Process scheduling with priority-based algorithms
- File System Philosophy:
- "Everything is a file" - devices, processes, network connections
- Hierarchical directory structure starting from root (/)
- Virtual file system (VFS) for multiple file system support
- File permissions: read, write, execute for owner, group, others
- Shell and Command Line:
- Command-line interface as primary user interface
- Shell scripting for automation and system administration
- Pipeline concept for command chaining
- Environment variables for configuration
- Multi-user and Multi-tasking:
- Multiple users can work simultaneously
- Preemptive multitasking with time-sharing
- User isolation and resource protection
- System administration through superuser (root) privileges
Microsoft Windows Operating System
Microsoft Windows: Windows is a family of proprietary graphical operating systems developed by Microsoft. It was first introduced in 1985 and has evolved through multiple versions, becoming the dominant desktop operating system worldwide. Windows combines elements of both monolithic and microkernel architectures to provide a balance of performance and modularity.
Key Features and Architecture:
- Hybrid Kernel Architecture:
- Combines monolithic and microkernel design principles
- Core services in kernel space for performance
- User-mode subsystems for stability and security
- Hardware abstraction layer (HAL) for device independence
- Process and Thread Management:
- Thread-based architecture with lightweight processes
- Symmetric multiprocessing (SMP) support
- Preemptive multitasking with priority-based scheduling
- Process isolation and memory protection
- Registry System:
- Centralized hierarchical database for system configuration
- Stores settings for hardware, software, and user preferences
- Registry hives for different system components
- Registry editing tools for system administration
- Graphical User Interface:
- GUI as primary user interface with desktop metaphor
- Window management and desktop environment
- Start menu and taskbar for application access
- Theme support and customization options
- Security and File System:
- NTFS file system with advanced permissions
- User Account Control (UAC) for privilege elevation
- Windows Defender for malware protection
- BitLocker for disk encryption
Android Mobile Operating System
Android: Android is a mobile operating system based on a modified version of the Linux kernel and other open source software, designed primarily for touchscreen mobile devices such as smartphones and tablets. It was developed by Google and the Open Handset Alliance, with the first commercial version released in 2008.
Key Features and Architecture:
- Linux Kernel Foundation:
- Based on modified Linux kernel for hardware abstraction
- Hardware drivers and system services in kernel space
- Memory management and process scheduling
- Security features and power management
- Application Runtime:
- Android Runtime (ART) for native Android apps
- Dalvik Virtual Machine for Java applications
- Just-In-Time (JIT) and Ahead-of-Time (AOT) compilation
- Garbage collection for memory management
- Application Framework:
- Rich set of APIs for application development
- Activity Manager for application lifecycle
- Content Providers for data sharing
- Notification Manager and Window Manager
- Security Model:
- Sandboxed applications with isolated processes
- Permission-based access control
- Application signing for authenticity
- SE Linux for mandatory access control
- User Interface:
- Touch-optimized interface design
- Gesture recognition and multi-touch support
- Adaptive layouts for different screen sizes
- Material Design principles for consistent UI
Practical Component / Lab Work
6.1 Process Management Programs
Process Management Programs: These programs demonstrate fundamental concepts in operating system process management, including process synchronization, inter-process communication, and resource sharing. The producer-consumer problem is a classic synchronization problem that illustrates the challenges of coordinating multiple processes that share a common resource.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define BUFFER_SIZE 5
#define MAX_ITEMS 10
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
sem_t empty, full, mutex;
void* producer(void* arg) {
int item;
for(int i = 0; i < MAX_ITEMS; i++) {
item = i;
sem_wait(&empty);
sem_wait(&mutex);
buffer[in] = item;
printf("Producer: Produced item %d at position %d\n", item, in);
in = (in + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&full);
usleep(100000);
}
return NULL;
}
void* consumer(void* arg) {
int item;
for(int i = 0; i < MAX_ITEMS; i++) {
sem_wait(&full);
sem_wait(&mutex);
item = buffer[out];
printf("Consumer: Consumed item %d from position %d\n", item, out);
out = (out + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&empty);
usleep(150000);
}
return NULL;
}
int main() {
pthread_t producer_thread, consumer_thread;
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);
printf("Producer-Consumer demonstration completed.\n");
return 0;
}
6.2 File I/O Handling
File I/O Handling: File Input/Output operations are fundamental to operating systems, allowing programs to read from and write to persistent storage. These operations demonstrate how the operating system manages file access, error handling, and resource management. File I/O programs showcase concepts like file descriptors, buffering, error checking, and proper resource cleanup.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define BUFFER_SIZE 4096
#define MAX_PATH 256
int copy_file(char* source, char* destination) {
FILE *src, *dst;
char buffer[BUFFER_SIZE];
size_t bytes_read, bytes_written;
long total_bytes = 0;
long file_size;
if(source == NULL || destination == NULL) {
fprintf(stderr, "Error: Invalid file paths\n");
return -1;
}
if(strcmp(source, destination) == 0) {
fprintf(stderr, "Error: Source and destination are the same\n");
return -1;
}
src = fopen(source, "rb");
if(src == NULL) {
fprintf(stderr, "Error opening source file '%s': %s\n", source, strerror(errno));
return -1;
}
fseek(src, 0, SEEK_END);
file_size = ftell(src);
fseek(src, 0, SEEK_SET);
dst = fopen(destination, "wb");
if(dst == NULL) {
fprintf(stderr, "Error opening destination file '%s': %s\n", destination, strerror(errno));
fclose(src);
return -1;
}
printf("Starting file copy: %s -> %s\n", source, destination);
printf("File size: %ld bytes\n", file_size);
while((bytes_read = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
bytes_written = fwrite(buffer, 1, bytes_read, dst);
if(bytes_written != bytes_read) {
fprintf(stderr, "Error writing to destination file: %s\n", strerror(errno));
fclose(src);
fclose(dst);
return -1;
}
total_bytes += bytes_written;
if(file_size > 0) {
int progress = (int)((total_bytes * 100) / file_size);
printf("\rProgress: %d%% (%ld/%ld bytes)", progress, total_bytes, file_size);
fflush(stdout);
}
}
if(ferror(src)) {
fprintf(stderr, "\nError reading source file: %s\n", strerror(errno));
fclose(src);
fclose(dst);
return -1;
}
fclose(src);
fclose(dst);
printf("\nFile copied successfully: %ld bytes\n", total_bytes);
return 0;
}
int ensure_directory(char* path) {
char temp_path[MAX_PATH];
char *token;
strcpy(temp_path, path);
token = strtok(temp_path, "/");
while(token != NULL) {
if(mkdir(token, 0755) != 0 && errno != EEXIST) {
fprintf(stderr, "Error creating directory '%s': %s\n", token, strerror(errno));
return -1;
}
token = strtok(NULL, "/");
}
return 0;
}
int main(int argc, char* argv[]) {
if(argc != 3) {
printf("Usage: %s <source_file> <destination_file>\n", argv[0]);
return 1;
}
char *last_slash = strrchr(argv[2], '/');
if(last_slash != NULL) {
*last_slash = '\0';
if(ensure_directory(argv[2]) != 0) {
return 1;
}
*last_slash = '/';
}
if(copy_file(argv[1], argv[2]) == 0) {
printf("File copy operation completed successfully.\n");
return 0;
} else {
printf("File copy operation failed.\n");
return 1;
}
}