Processes and Signals
Process Lifecycle
Section titled “Process Lifecycle”Every running program in Linux is a process — an instance of an executing program with its own Virtual address space, file descriptors, and execution context. The kernel manages processes through A task_struct (in include/linux/sched.h), which tracks PID, state, scheduling priority, open Files, signal handlers, and more.
Creating Processes: fork``exec``wait
Section titled “Creating Processes: fork``exec``wait”The POSIX process creation model consists of three operations:
sequenceDiagram
participant Parent as Parent Process
participant Kernel as Kernel
participant Child as Child Process
Parent->>Kernel: fork()
Kernel-->>Parent: Returns child_pid
Kernel-->>Child: Returns 0
Note over Parent,Child: Both processes execute from here
Child->>Kernel: exec("/bin/program")
Note over Child: Child"s memory replaced with new program
Parent->>Kernel: waitpid(child_pid)
Kernel-->>Parent: Returns child exit statusfork(2)
Section titled “fork(2)”fork creates a new process by duplicating the calling process. The child is an exact copy — same Code, same data, same open file descriptors, same signal dispositions. The only difference is the Return value (parent gets child PID, child gets 0).
pid_t pid = fork();if (pid == -1) { perror("fork"); exit(1);} else if (pid == 0) { // Child process printf("Child PID: %d\n", getpid());} else { // Parent process printf("Parent PID: %d, Child PID: %d\n", getpid(), pid);}Modern Linux uses Copy-on-Write (COW) pages for fork: the parent’s page tables are duplicated, But the physical pages are shared and marked read-only. When either process writes to a page, a copy Is made. This means fork is O(n) in page table size, not O(n) in memory.
execve(2)
Section titled “execve(2)”execve replaces the current process’s memory image with a new program. The PID remains the same, But the code, data, heap, and stack are replaced. Open file descriptors with the close-on-exec flag Cleared remain open across exec.
// In the child process after fork:char *argv[] = {"/bin/ls", "-la", "/tmp", NULL};char *envp[] = {"PATH=/usr/bin:/bin", NULL};execve("/bin/ls", argv, envp);// If execve returns, it failedperror("execve");exit(1);Variants of exec: execl``execlp``execle``execv``execvp``execvpe — they differ in how Arguments and environment are passed.
wait(2) / waitpid(2)
Section titled “wait(2) / waitpid(2)”A parent must call wait (or waitpid) to collect the child’s exit status. If a child terminates And the parent does not wait, the child becomes a zombie (state Z) — it retains its PID and Exit status in the kernel’s process table until the parent waits.
int status;pid_t pid = waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) { printf("Child exited with code %d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) { printf("Child killed by signal %d\n", WTERMSIG(status));} else if (WIFSTOPPED(status)) { printf("Child stopped by signal %d\n", WSTOPSIG(status));}Process Identification
Section titled “Process Identification”| Identifier | Field in task_struct | Description |
|---|---|---|
| PID | pid | Process identifier (unique system-wide) |
| TID | pid | Thread identifier (same namespace as PID) |
| PPID | real_parent->pid | Parent process ID |
| PGID | group_leader->pid | Process group ID (for signal delivery) |
| SID | signal->leader->pid | Session ID (for job control) |
| UID/EUID | real_cred/cred | Real and effective user ID |
| GID/EGID | real_cred/cred | Real and effective group ID |
In Linux, threads are implemented as processes that share certain resources (address space, file Descriptors, signal handlers). Each thread has its own TID, but all threads in a process share the Same PID (the thread group leader’s TID). The clone(2) system call controls exactly what is Shared:
// Create a thread (shares address space, FDs, signal handlers)clone(thread_fn, stack, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, arg);
// fork is equivalent to:clone(NULL, 0, SIGCHLD, 0);# View process/thread IDsps -eLf # LWP (Lightweight Process) = TIDps -T -p $PID # Show threads of a specific processls /proc/$PID/task/ # Each directory is a TID
# Process relationshipsps -eo pid,ppid,pgid,sid,commpstree # Tree view of process hierarchyProcess States
Section titled “Process States”A Linux process can be in one of several states, tracked by the state field in task_struct:
stateDiagram-v2
[*] --> TASK_NEW: fork/clone
TASK_NEW --> TASK_RUNNING: sched_fork
TASK_RUNNING --> TASK_RUNNING: preempted (still runnable)
TASK_RUNNING --> TASK_INTERRUPTIBLE: wait for I/O, sleep
TASK_RUNNING --> TASK_UNINTERRUPTIBLE: wait for disk I/O
TASK_RUNNING --> TASK_DEAD: exit
TASK_RUNNING --> TASK_STOPPED: SIGSTOP/SIGTSTP
TASK_INTERRUPTIBLE --> TASK_RUNNING: I/O complete, signal
TASK_UNINTERRUPTIBLE --> TASK_RUNNING: I/O complete
TASK_STOPPED --> TASK_RUNNING: SIGCONT
TASK_DEAD --> [*]: release resources
TASK_DEAD --> ZOMBIE: parent has not waited
ZOMBIE --> [*]: parent calls wait| State | Code | Description |
|---|---|---|
| TASK_RUNNING | R | Runnable (either executing or on a run queue) |
| TASK_INTERRUPTIBLE | S | Sleeping, waiting for an event (can be interrupted by signals) |
| TASK_UNINTERRUPTIBLE | D | Sleeping, waiting for disk I/O (cannot be interrupted) |
| TASK_STOPPED | T | Stopped by SIGSTOP``SIGTSTPOr ptrace |
| TASK_TRACED | t | Stopped by debugger (ptrace) |
| EXIT_ZOMBIE | Z | Terminated, parent has not called wait |
| EXIT_DEAD | X | Completely dead, waiting to be reaped |
# View process statesps -eo pid,stat,comm
# State codes in ps output:# R running or runnable# S interruptible sleep# D uninterruptible sleep# T stopped# Z zombie# I idle kernel thread# + foreground process group# s session leader# l multi-threaded