Process Management
Process Foundation
A binary is compiled, executable code stored passively on a medium. A process represents the operating system abstraction of that binary in execution, comprising the loaded binary, virtualized memory, kernel resources, an associated security context, and one or more threads. A thread serves as the minimal unit of execution scheduled by the operating system, containing a virtualized processor, a stack, and execution state.
- Process ID (PID): A strictly linearly allocated, unique identifier for a process, represented by the
pid_ttype.- Idle Process: PID , executed when no other processes are runnable.
- Init Process: PID , the first user-space program executed during boot. It initializes the system and adopts orphaned child processes.
- Allocation Limits: Default maximum PID is , configurable via
/proc/sys/kernel/pid_max. Upon reaching the limit, the kernel wraps around and reuses available lower PIDs.
- Parent Process ID (PPID): Identifies the process that spawned a given child process.
- Data Retrieval: The
getpid()andgetppid()system calls return the active process’s PID and PPID, respectively.
To create these active entities, the system utilizes discrete mechanisms that decouple the creation of a new process from the execution of a new binary image.
Process Creation and Execution
Process instantiation requires two distinct stages: duplicating an existing process and replacing its execution context.
- Forking (
fork())- Creates a new child process almost identical to the parent.
- Returns within the child’s execution thread and the child’s PID within the parent’s execution thread.
- The child process inherits the parent’s security contexts and open file descriptors but resets resource statistics and clears pending signals and file locks.
- Copy-on-Write (COW): A lazy optimization technique preventing immediate memory duplication. The parent and child share the address space with read-only page mappings. If either process attempts a write operation, the kernel traps the page fault, duplicates the specific page, and assigns exclusive write access to the modifying process.
- Legacy Forking (
vfork())- An obsolete optimization that suspends the parent process until the child successfully invokes an
execfunction or terminates via_exit(). - Shares the parent’s address space without COW semantics. The child must not modify memory to prevent corrupting the parent’s state.
- An obsolete optimization that suspends the parent process until the child successfully invokes an
- Execution (The
execFamily)- Replaces the current process image, memory mappings, and execution stack with a newly loaded binary.
- Retains the PID, PPID, priority, and open file descriptors, but drops pending signals, memory locks, and custom signal handlers.
- Interfaces: Includes
execl(),execlp(),execle(),execv(),execvp(), andexecve(). Suffixes indicate argument structure (listlvs. vector arrayv), path resolution (p), and custom environment passing (e). - Only
execve()is a true kernel system call; the others are C library wrappers.
Once a newly executed program finishes its workload or encounters a fatal error, it must safely relinquish its resources to the kernel.
Process Termination
Processes conclude execution either through explicit programmatic commands or external forceful events.
- Explicit Termination
exit(int status): A C library function that executes user-space teardown.- Invokes functions registered via
atexit()oron_exit()in Last-In-First-Out (LIFO) order. - Flushes all open standard I/O streams and removes temporary files created by
tmpfile(). - Passes the execution flow to the
_exit()system call.
- Invokes functions registered via
_exit(int status)and_Exit(int status): System calls executing immediate kernel-level resource cleanup, freeing allocated memory, open files, and semaphores.- Status Codes:
EXIT_SUCCESSmaps to , andEXIT_FAILUREmaps to a nonzero value.
- Implicit and External Termination
- Returning from the
main()function implicitly injects anexit()call. - Unhandled signals with a default termination action (e.g.,
SIGTERM,SIGKILL,SIGSEGV) forcefully end the process.
- Returning from the
- Parent Notification
- Upon termination, the kernel dispatches an asynchronous
SIGCHLDsignal to the parent process.
- Upon termination, the kernel dispatches an asynchronous
Terminated processes leave behind a skeletal data structure, entering a dormant state that requires the parent to synchronize and extract the exit status before full destruction.
State Reaping and Synchronization
When a process terminates, it transitions into a “zombie” state. This skeletal remnant retains the exit code and resource usage statistics until the parent retrieves them, preventing the loss of process lifecycle data. If the parent dies before the child, the kernel reparents the zombie to the init process, which routinely reaps it.
- Basic Waiting (
wait())- Blocks the calling process until any child terminates.
- Populates an integer status code analyzable via bitwise macros:
WIFEXITED(status)/WEXITSTATUS(status): Validates normal termination and extracts the passed exit code.WIFSIGNALED(status)/WTERMSIG(status): Validates signal-based termination and extracts the specific signal number.WCOREDUMP(status)identifies if a core file was generated.WIFSTOPPED(status)/WSTOPSIG(status): Identifies processes paused for job control.
- Targeted Waiting (
waitpid())- Allows polling for a specific PID, any child (), any child in the same process group (), or a specific process group ().
- Options:
WNOHANGenforces non-blocking behavior, whileWUNTRACEDandWCONTINUEDreport on stopped and resumed children, respectively.
- Advanced Waiting (
waitid())- Utilizes
idtype_t(P_PID,P_GID,P_ALL) andid_tto specify the target. - Populates a
siginfo_tstructure providing granular diagnostic fields (si_pid,si_uid,si_code,si_status).
- Utilizes
- Resource Usage Waiting (
wait3(),wait4())- BSD-derived interfaces that operate like
waitpid()but additionally populate arusagestructure detailing CPU time consumption, page faults, and context switches.
- BSD-derived interfaces that operate like
- Synchronous Execution (
system())- Spawns
/bin/sh -c <command>, blocksSIGCHLD, ignoresSIGINT/SIGQUIT, and waits for the command to return. - Vulnerable to environment variable manipulation (e.g.,
PATHinjection), making it highly insecure for privileged processes.
- Spawns
A process’s ability to execute commands, access files, or send signals during these lifecycles is bounded by the exact parameters of its security credentials.
Security Contexts (Users and Groups)
Process permissions are dictated by numerical user and group identifiers mapped to system roles.
- Identity Classifications
- Real ID: The user or group who originally invoked the process. Inherited cleanly across
execcalls. - Effective ID: The active identity evaluated by the kernel for permission verifications and resource access.
- Saved ID: A static record of the effective ID established at the moment of an
execexecution. - Filesystem ID: Utilized specifically for filesystem access checks, generally mirroring the effective ID.
- Real ID: The user or group who originally invoked the process. Inherited cleanly across
- Privilege Elevation
- Executing a binary with the set-user-ID (
suid) or set-group-ID (sgid) bit overwrites the process’s effective ID with the ID of the file’s owner, granting elevated privileges during execution.
- Executing a binary with the set-user-ID (
- Context Manipulation
setuid(uid_t uid)/setgid(gid_t gid): Modifies the effective ID. If invoked by a root-privileged process, it simultaneously forces the real and saved IDs to the target value.seteuid(uid_t euid)/setegid(gid_t egid): Modifies only the effective ID. Unprivileged processes may only toggle this to match their existing real or saved IDs.setreuid()/setresuid(): Legacy BSD and HP-UX interfaces for granularly setting real, effective, and saved IDs simultaneously.
While user IDs strictly isolate security permissions, processes additionally require structural grouping to govern terminal interactions and background execution.
Sessions, Process Groups, and Daemons
To manage user logins and job control, processes are organized into a strict hierarchy beyond the parent-child relationship.
- Process Groups
- Collections of interrelated processes (e.g., shell pipeline commands) managed for unified job control.
- Identified by a Process Group ID (PGID), which matches the PID of the process group leader.
- Configured via
setpgid()and retrieved viagetpgid(). Allows signals to be dispatched wholesale to every process within the group.
- Sessions
- Higher-level collections of process groups typically bound to a specific controlling terminal.
- A session contains exactly one foreground process group (which actively receives terminal signals like
SIGINTorSIGQUIT) and zero or more background process groups. - The
setsid()system call creates a new session, assigns the calling process as the session leader, and strips any association with a controlling terminal.
- Daemons
- Background processes completely decoupled from user terminals, generally executing long-running system tasks.
- Daemonization Implementation Steps:
- Invoke
fork()and force the parent toexit(). This orphans the child toinitand guarantees the child is not a process group leader. - Invoke
setsid()to establish a new session, becoming the session leader and severing terminal ties. - Invoke
chdir("/")to set the working directory to the root, preventing the daemon from pinning a mounted filesystem. - Close all open file descriptors inherited from the parent environment.
- Reopen file descriptors , , and (stdin, stdout, stderr) and redirect them to
/dev/nullto safely discard standard I/O.
- Invoke
- The
daemon()C library function automates these steps into a single API call.