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_t type.
    • 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() and getppid() 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 exec function 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.
  • Execution (The exec Family)
    • 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(), and execve(). Suffixes indicate argument structure (list l vs. vector array v), 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() or on_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.
    • _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_SUCCESS maps to , and EXIT_FAILURE maps to a nonzero value.
  • Implicit and External Termination
    • Returning from the main() function implicitly injects an exit() call.
    • Unhandled signals with a default termination action (e.g., SIGTERM, SIGKILL, SIGSEGV) forcefully end the process.
  • Parent Notification
    • Upon termination, the kernel dispatches an asynchronous SIGCHLD signal to the parent process.

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: WNOHANG enforces non-blocking behavior, while WUNTRACED and WCONTINUED report on stopped and resumed children, respectively.
  • Advanced Waiting (waitid())
    • Utilizes idtype_t (P_PID, P_GID, P_ALL) and id_t to specify the target.
    • Populates a siginfo_t structure providing granular diagnostic fields (si_pid, si_uid, si_code, si_status).
  • Resource Usage Waiting (wait3(), wait4())
    • BSD-derived interfaces that operate like waitpid() but additionally populate a rusage structure detailing CPU time consumption, page faults, and context switches.
  • Synchronous Execution (system())
    • Spawns /bin/sh -c <command>, blocks SIGCHLD, ignores SIGINT/SIGQUIT, and waits for the command to return.
    • Vulnerable to environment variable manipulation (e.g., PATH injection), making it highly insecure for privileged processes.

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 exec calls.
    • 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 exec execution.
    • Filesystem ID: Utilized specifically for filesystem access checks, generally mirroring the effective ID.
  • 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.
  • 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 via getpgid(). 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 SIGINT or SIGQUIT) 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:
      1. Invoke fork() and force the parent to exit(). This orphans the child to init and guarantees the child is not a process group leader.
      2. Invoke setsid() to establish a new session, becoming the session leader and severing terminal ties.
      3. Invoke chdir("/") to set the working directory to the root, preventing the daemon from pinning a mounted filesystem.
      4. Close all open file descriptors inherited from the parent environment.
      5. Reopen file descriptors , , and (stdin, stdout, stderr) and redirect them to /dev/null to safely discard standard I/O.
    • The daemon() C library function automates these steps into a single API call.