What is a Process?
A process is an instance of a program in execution. It contains the program code and its activity. Depending on the operating system (OS), a process can be composed of multiple threads of execution that execute instructions concurrently.
Components of a Process
Program Code: The executable code for the process.
Memory: Includes the following segments:
Text Segment: Contains the actual compiled code of the program.
Data Segment: Contains global and static variables.
Heap: Used for dynamic memory allocation during process execution.
Stack: Holds local variables, function parameters, return addresses, and handles function calls and returns.
Process Control Block (PCB): The PCB is a data structure in the operating system kernel containing crucial information about a process. Key elements include:
PID: Process ID
Process State: Current state of the process
Program Counter: Address of the next instruction to execute
CPU Registers: Contents of all process-specific registers
Memory Management Information: Base and limit registers, page tables
I/O Status Information: List of I/O devices allocated to the process
Accounting Information: CPU used, clock time elapsed since start, time limits
States of a Process
A process in general will be in either of these states
New: The process is being created.
Ready: The process is waiting to be assigned to a processor.
Running: Instructions are being executed.
Waiting: The process is waiting for some event to occur (e.g., I/O completion).
Terminated: The process has finished execution.
Process Attributes
PID: Unique identifier for each process.
PPID: Parent Process ID, which identifies the parent process.
UID/GID: User and Group IDs, indicating the user and group ownership of the process.
State: Current state of the process (e.g., running, sleeping).
Priority: Determines the order in which processes are scheduled.
Command: The command that initiated the process.
Process States in linux
Running (R): The process is either running or ready to run.
Interruptible Sleep (S): The process is sleeping, waiting for a signal or an event to complete.
Uninterruptible Sleep (D): The process is sleeping, usually waiting on I/O.
Stopped (T): The process has been stopped, typically by receiving a signal.
Zombie (Z): The process has completed execution but still has an entry in the process table.
Process Scheduling
The OS uses a scheduler to manage processes and allocate CPU time. Scheduling algorithms include:
First-Come, First-Served (FCFS): Processes are attended in the order they arrive.
Shortest Job Next (SJN): The process with the smallest execution time is selected next.
Round Robin (RR): Each process gets a small unit of CPU time in a cyclic order.
Priority Scheduling: Processes are scheduled based on priority.
Multilevel Queue Scheduling: Processes are divided into different queues based on their priority.
Process Lifecycle
1. Process Creation
Forking:
The
fork()
system call creates a new process by duplicating the calling process. This new process is called the child process.The child process inherits the parent's code, data, and open files.
The
fork()
call returns a zero to the child process and the child's PID to the parent process.
pid_t pid = fork();
if (pid == 0) {
// This is the child process
} else if (pid > 0) {
// This is the parent process
} else {
// Fork failed
}
Executing a New Program:
After a
fork()
, theexec()
family of functions (such asexecl()
,execp()
,execv()
) is used to replace the process's memory space with a new program.This is typically done in the child process to run a different program.
execl("/bin/ls", "ls", "-l", (char *) NULL);
Using clone():
The
clone()
system call creates a new process with more control over what is shared between the parent and child processes.It is commonly used to create threads.
int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND;
pid_t pid = clone(child_function, child_stack, flags, NULL);
2. Process States
A process can exist in several states during its lifecycle:
TASK_RUNNING: The process is either currently executing on a CPU or is ready to run.
TASK_INTERRUPTIBLE: The process is sleeping and waiting for an event or signal. It can be interrupted by signals.
TASK_UNINTERRUPTIBLE: The process is sleeping but cannot be interrupted by signals, typically waiting for I/O operations.
TASK_STOPPED: The process execution has been stopped, usually by receiving a signal.
TASK_TRACED: The process is being traced by another process, typically a debugger.
TASK_ZOMBIE: The process has terminated, but its parent has not yet read its exit status. It remains in the process table until cleaned up.
TASK_DEAD: The process is in the process of being removed from the system and will not return.
3. Process Scheduling
The Linux scheduler determines which process runs next based on its scheduling policy and priority.
Completely Fair Scheduler (CFS): The default scheduler in Linux that aims to fairly allocate CPU time to processes.
Scheduling Policies:
SCHED_OTHER: Default time-sharing scheduler policy.
SCHED_FIFO: First-In, First-Out real-time scheduling policy.
SCHED_RR: Round-Robin real-time scheduling policy.
Processes have a priority value, and the scheduler uses this priority to determine the execution order
4. Context Switching
Context Switching: The process of storing the state of a currently running process and restoring the state of the next process to run.
The context includes the program counter, CPU registers, memory mappings, and open files.
Context switching is essential for multitasking, allowing the CPU to switch between different processes to give the appearance of simultaneous execution.
5. Process Termination
Normal Termination: The process completes its execution and calls
exit()
.
exit(0);
Signal Termination: The process receives a signal that causes it to terminate, such as
SIGTERM
orSIGKILL
.
kill -SIGTERM pid
Abnormal Termination: The process encounters an error or an exception that causes it to terminate unexpectedly.
When a process terminates:
It moves to the
TASK_ZOMBIE
state.In this state, it has finished execution but remains in the process table until the parent process reads its exit status using
wait()
orwaitpid()
.
int status;
pid_t pid = wait(&status);
or
pid_t pid = waitpid(child_pid, &status, 0);
6. Cleanup and Reaping
Zombie Processes: Processes that have completed execution but have not been cleaned up by their parent. They still occupy an entry in the process table.
Reaping: The parent process must read the exit status of the terminated child process using
wait()
orwaitpid()
to remove the zombie process from the process table.If the parent process does not reap its children, the
init
process (PID 1) will eventually adopt and clean up the orphaned zombie processes.
Inter-Process Communication (IPC)
Linux provides various IPC mechanisms:
Pipes: Allow communication between related processes.
int pipefd[2];
pipe(pipefd);
if (fork() == 0) {
close(pipefd[1]);
read(pipefd[0], buffer, sizeof(buffer));
} else {
close(pipefd[0]);
write(pipefd[1], "Hello", 5);
}
Named Pipes (FIFOs): Allow communication between unrelated processes.
mkfifo("/tmp/myfifo", 0666);
Message Queues: Facilitate message-based communication between processes.
key_t key = ftok("file", 65);
int msgid = msgget(key, 0666 | IPC_CREAT);
Shared Memory: Allows multiple processes to access the same memory space.
int shmid = shmget(IPC_PRIVATE, 1024, 0666 | IPC_CREAT);
char *str = (char*) shmat(shmid, NULL, 0);
Semaphores: Used to manage access to shared resources.
sem_t *sem = sem_open("/semaphore", O_CREAT, 0644, 1);
Sockets: Used for communication over a network.
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
Process Synchronization
When multiple processes access shared resources, synchronization mechanisms are used to prevent conflicts:
Mutexes: Mutual exclusion objects to prevent simultaneous access.
Semaphores: Counting mechanisms for resource availability.
Monitors: High-level synchronization construct that allows safe access to shared resources.
Process vs. Thread
Process: Has its own memory space; more resource-intensive.
Thread: Shares memory space with other threads within the same process; lighter and faster context switching.
Context Switching?
Context switching is the process of storing the state of a currently running process or thread so that it can be resumed later, and then loading the state of another process or thread to begin or resume its execution. This mechanism is fundamental to multitasking in an operating system, allowing multiple processes to share a single CPU efficiently.
Why is Context Switching Necessary?
Multitasking: To give the illusion of simultaneous execution of multiple processes.
Responsive Systems: To ensure that higher-priority tasks get CPU time as needed.
Resource Management: To optimize CPU utilization by switching to other tasks when the current task is waiting for I/O operations.
Components of a Context Switch
Program Counter (PC): Holds the address of the next instruction to be executed.
CPU Registers: General-purpose and special-purpose registers that need to be preserved.
Memory Management Information: Page tables, base/limit registers, etc.
Process Control Block (PCB): A data structure that stores all the necessary information about a process, including the above components and additional data like process state, PID, and scheduling information.
Steps in a Context Switch
Saving the Context of the Current Process:
Save the program counter, registers, and other processor state information to the PCB of the current process.
Update the PCB to reflect the process state as
TASK_INTERRUPTIBLE
orTASK_UNINTERRUPTIBLE
, depending on the reason for the switch.
Selecting the Next Process to Run:
The scheduler selects the next process to run based on the scheduling algorithm (e.g., Round Robin, Priority Scheduling).
The chosen process's PCB is loaded to retrieve its saved state.
Loading the Context of the Next Process:
Load the saved state from the PCB of the next process, including the program counter and CPU registers.
Update the process state to
TASK_RUNNING
.
Executing the Next Process:
The CPU resumes execution of the next process from where it left off.
Context Switching Overhead
CPU Cycles: Context switching consumes CPU cycles without performing useful work for the user processes.
Cache Flushing: Switching between processes can result in cache misses, as the CPU cache may need to be flushed and reloaded with the new process's data.
System Calls: Context switches often occur in response to system calls, adding to the overhead.
Factors Affecting Context Switching
Process Priority: Higher-priority processes are selected more frequently.
CPU Affinity: Binding processes to specific CPUs can reduce the overhead of context switching.
I/O Operations: Processes performing I/O operations may be switched out frequently to keep the CPU busy.
Reducing Context Switching Overhead
Optimize Code: Minimize unnecessary system calls and context switches.
Processor Affinity: Use processor affinity to keep processes on specific CPUs.
Efficient Scheduling: Choose scheduling algorithms that minimize context switches, such as those that batch similar processes together.
References
https://tldp.org/LDP/tlk/kernel/processes.html
https://opensource.com/article/19/4/interprocess-communication-linux-storage
https://www.halolinux.us/kernel-architecture/process-life-cycle.html
http://www.linux-tutorial.info/?page_id=245
https://www.geeksforgeeks.org/context-switch-in-operating-system/
https://www.netdata.cloud/blog/understanding-context-switching-and-its-impact-on-system-performance/