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, execvpethey 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