Understanding Linux Process Lifecycle: From Creation to Termination
This article provides a comprehensive, step‑by‑step explanation of Linux process management, covering the definition of a process, PCB structure, task organization, state transitions, creation system calls (fork, vfork, clone), execution with exec, scheduling, waiting, termination, and the kernel’s resource allocation strategies.
1. Linux Process Basics
A process is the running instance of a program and the basic unit for resource allocation and scheduling in Linux. It has its own address space (text, data, and stack) and represents an executing program.
1.1 Process Control Block (PCB)
The kernel describes each process with a task_struct (PCB) that contains fields such as memory descriptor, file table, signal information, and more.
Identifier: unique ID for the process.
Status: current state, exit code, signal.
Priority: scheduling priority.
Program Counter: address of the next instruction.
Memory pointers: code, data, stack, and shared memory.
Context data: register values.
I/O info: pending I/O requests and open files.
Accounting: CPU time, clock ticks, limits.
1.2 Organization of task_struct
Linux uses three data structures to manage task_struct objects:
Linked lists for traversing all processes.
Tree structures to represent parent‑child relationships.
Hash tables for fast lookup by PID.
Parent processes monitor children via wait(), retrieve exit codes, and clean up zombie processes.
1.3 Process States and Transitions
Processes move among five states:
Running – currently executing on the CPU.
Ready – has all resources except the CPU.
Blocked – waiting for I/O or other events.
Created – being set up before entering Ready.
Exited – terminated and awaiting resource reclamation.
Typical transitions include Ready→Running, Running→Ready (time‑slice expiration), Running→Blocked (I/O wait), and Blocked→Ready (event completion).
2. Process Creation
2.1 fork()
The fork() system call creates a new child process that is a copy of the parent. Modern kernels use Copy‑On‑Write (COW) to share pages until one process writes to them.
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child PID: %d
", getpid());
} else if (pid > 0) {
printf("Parent PID: %d, child PID: %d
", getpid(), pid);
} else {
perror("fork");
}
return 0;
}If fork() fails, errno indicates the reason (e.g., exceeding the per‑user process limit ulimit -u or insufficient memory).
2.2 vfork()
vfork()creates a child that shares the parent’s address space until it calls exec() or _exit(). It is useful when the child immediately replaces itself with another program, but misuse can cause deadlocks.
2.3 clone()
clone()is a flexible system call that can create threads or processes with fine‑grained sharing of resources. Its prototype is:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);Setting CLONE_VM makes the child share the parent’s memory, effectively creating a thread.
2.4 Kernel Threads
Kernel threads run entirely in kernel space, have mm = NULL, and cannot access user memory. They are created via the same internal do_fork() path as user processes.
3. Process Execution (exec)
The exec family (e.g., execl, execv) replaces the current process image with a new program while keeping the same PID.
#include <stdio.h>
#include <unistd.h>
int main() {
execl("/bin/ls", "ls", "-l", NULL);
perror("execl failed");
return 0;
}On success, exec never returns; on failure it returns –1 and sets errno.
4. Waiting for Child Processes
4.1 wait()
wait()blocks the parent until any child terminates, returning the child’s PID and filling a status integer that can be examined with macros like WIFEXITED and WEXITSTATUS.
4.2 waitpid()
waitpid(pid, &status, options)allows waiting for a specific child or using WNOHANG for non‑blocking checks.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
sleep(3);
_exit(10);
} else {
int status;
pid_t ret;
do {
ret = waitpid(pid, &status, WNOHANG);
if (ret == 0) {
printf("Child still running...
");
sleep(1);
}
} while (ret == 0);
if (ret == pid && WIFEXITED(status))
printf("Child exited with code %d
", WEXITSTATUS(status));
}
return 0;
}4.3 Zombie Processes
If a parent never calls wait(), terminated children become zombies, holding their PID until the parent or init reaps them.
5. Process Termination
Termination can be normal (return from main or call exit()) or abnormal (signals, fatal errors). exit() runs atexit handlers, flushes stdio buffers, and then invokes the kernel _exit(). _exit() terminates immediately without cleanup.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void cleanup() { printf("cleanup
"); }
int main() {
atexit(cleanup);
printf("Before exit
");
_exit(0); // cleanup() not called, buffer not flushed
}6. Kernel Perspective: Resource Allocation & Scheduling
6.1 Resource Allocation
The kernel allocates virtual memory pages to a process and maps them to physical pages via page tables. It also manages file descriptors (stdin=0, stdout=1, stderr=2) in a per‑process descriptor table.
6.2 Scheduling Strategies
Linux supports real‑time policies ( SCHED_FIFO, SCHED_RR) and the default Completely Fair Scheduler (CFS). CFS uses a red‑black tree keyed by each task’s virtual runtime ( vruntime) to select the most “hungry” task, ensuring fair CPU distribution.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
