Linux Process & Signals
fork, exec, signals, job control, /proc, ulimits, and cgroups v2
fork() ┌──────────────────────────┐ execve() │ copy-on-write clone │ Parent ──────────────► │ new PID, same address │ (PID 1 = init/systemd)│ space until write │ └──────────────┬────────────┘ │ execve() replaces image Child process ├─ PID (unique) ├─ PPID (parent's PID) ├─ PGID (process group) ├─ SID (session) └─ State: R S D T Z X
Process LifecycleCore

Every process is created by forking an existing one, then (usually) replacing its image via exec. PID 1 (systemd or init) is the ancestor of all user processes.

Process states

StateCodeMeaning
Running / RunnableROn CPU or in the run queue, ready to run.
Interruptible sleepSWaiting for an event (I/O, signal, timer). Can be woken by a signal.
Uninterruptible sleepDWaiting for I/O that cannot be interrupted (e.g., disk, NFS). Signals are ignored. High D-state count indicates I/O pressure.
StoppedTPaused by SIGSTOP or a debugger (ptrace).
ZombieZExited but not yet reaped by parent (wait() not called). Consumes a PID entry only.
DeadXBeing removed; not visible in ps.

fork / exec / clone

C
// fork: duplicate the calling process
pid_t pid = fork();
if (pid == 0) {
    // child: execve replaces the process image
    execve("/bin/ls", args, envp);
} else {
    // parent: wait for child to avoid zombie
    waitpid(pid, &status, 0);
}

// clone: low-level syscall behind fork() and pthread_create()
// Flags control which resources are shared:
// CLONE_VM (share memory), CLONE_FS (share cwd/root),
// CLONE_FILES, CLONE_SIGHAND, CLONE_THREAD (same TGID)

Zombie and orphan processes

Shell
# Find zombie processes
ps aux | awk '$8 == "Z"'
ps -eo pid,ppid,stat,comm | grep ' Z'

# Find the parent of a zombie and investigate
ps -o pid,ppid,stat,comm -p <zombie-pid>
# If parent is stuck, killing/restarting it lets init reap zombies

# Orphan process — parent died; reparented to PID 1 (init/systemd)
cat /proc/<pid>/status | grep PPid
Process IdentityCore

Each process carries several IDs that control its relationship to other processes, its session, and its privileges.

ID types

IDMeaning
PIDUnique process identifier.
PPIDParent's PID. Used to build the process tree.
PGIDProcess group ID. Processes in the same group can be signalled together (kill -PGID).
SIDSession ID. A session contains one or more process groups. A terminal is associated with one session.
TGIDThread group ID. All threads of a process share the same TGID (= PID of the main thread).
UID / EUIDReal and effective user ID. EUID is used for permission checks; can differ via setuid binaries.
GID / EGIDReal and effective group ID.
Shell
# Show full process tree with IDs
ps -eo pid,ppid,pgid,sid,uid,comm --forest

# Show IDs of current shell
echo "PID=$$  PPID=$PPID  PGID=$(ps -o pgid= $$)  SID=$(ps -o sid= $$)"

# Show all threads of a process
ps -eLf | grep <pid>
ls /proc/<pid>/task/       # one dir per thread
/proc FilesystemFilesystem

/proc is a virtual filesystem exposing kernel data structures as files. Each process has a directory /proc/<PID> with detailed runtime information.

Per-process /proc/PID entries

PathContents
/proc/PID/cmdlineFull command line (null-separated args).
/proc/PID/environEnvironment variables (null-separated).
/proc/PID/statusHuman-readable stats: Name, State, Pid, PPid, Threads, VmRSS, …
/proc/PID/statMachine-readable stats used by ps/top (41+ fields).
/proc/PID/mapsMemory mappings — virtual address ranges, permissions, mapped files.
/proc/PID/smapsExtended maps with RSS/PSS/private/shared per region.
/proc/PID/fd/Symlinks to every open file descriptor.
/proc/PID/fdinfo/Flags and position for each fd.
/proc/PID/net/Network state from the process's network namespace.
/proc/PID/cgroupcgroup membership for each hierarchy.
/proc/PID/oom_scoreCurrent OOM killer score (higher = more likely to be killed).
/proc/PID/oom_score_adjWritable bias (-1000 to 1000) added to oom_score.
Shell
# Command line of a process
tr '\0' ' ' < /proc/<pid>/cmdline; echo

# Memory usage breakdown
grep -E 'VmRSS|VmSize|VmSwap|Threads' /proc/<pid>/status

# Open files
ls -la /proc/<pid>/fd

# Find deleted files held open (common cause of disk-full)
ls -la /proc/*/fd 2>/dev/null | grep deleted

# Memory maps
cat /proc/<pid>/maps

# Stack trace (if kernel supports it)
cat /proc/<pid>/stack
SignalsSignal

Signals are asynchronous notifications sent to a process. The kernel delivers them by interrupting the process between instructions. Each signal has a default action; processes can override it with a handler (except SIGKILL and SIGSTOP).

Common signals

SignalNo.Default actionCommon use
SIGHUP1TerminateReload config (daemons often catch this)
SIGINT2TerminateCtrl+C — interrupt from terminal
SIGQUIT3Core dumpCtrl+\ — quit with core dump
SIGILL4Core dumpIllegal instruction
SIGTRAP5Core dumpDebugger breakpoint (ptrace)
SIGABRT6Core dumpabort() — assertion failure
SIGBUS7Core dumpMisaligned memory access
SIGFPE8Core dumpFloating-point / division by zero
SIGKILL9TerminateCannot be caught or ignored
SIGSEGV11Core dumpInvalid memory access
SIGPIPE13TerminateWrite to closed pipe/socket
SIGALRM14Terminatealarm() timer expiry
SIGTERM15TerminateGraceful shutdown request (default of kill)
SIGCHLD17IgnoreChild state change (exit/stop)
SIGCONT18ContinueResume a stopped process
SIGSTOP19StopCannot be caught or ignored
SIGTSTP20StopCtrl+Z — terminal stop (catchable)
SIGUSR1/210/12TerminateUser-defined; application-specific semantics
SIGKILL vs SIGTERM: Always try SIGTERM first and give the process time to clean up. SIGKILL bypasses the process entirely — the kernel cleans up, but open transactions, locks, and temp files may be left in a bad state.
Sending SignalsSignal
Shell
# Send SIGTERM (graceful shutdown) — default
kill <pid>
kill -15 <pid>
kill -TERM <pid>

# Force kill — use only if SIGTERM fails after a timeout
kill -9 <pid>
kill -KILL <pid>

# Reload config
kill -HUP <pid>

# Send to whole process group (negative PID)
kill -TERM -<pgid>

# By name
pkill nginx                        # SIGTERM to all matching
pkill -9 -x nginx                  # exact name match, SIGKILL
pkill -HUP -u www-data nginx       # match user + name

# killall — by exact name
killall -TERM gunicorn

# Show all signal names
kill -l

trap in shell scripts

Bash
cleanup() { echo "cleaning up..."; rm -f /tmp/my-lockfile; }
trap cleanup EXIT          # always run on exit
trap cleanup INT TERM      # also on Ctrl+C and kill
trap '' HUP                # ignore SIGHUP
Job ControlShell
Shell
# Run in background
my-command &
jobs                     # list background jobs
fg %1                    # bring job 1 to foreground
bg %1                    # resume stopped job in background

# Ctrl+Z suspends foreground process (sends SIGTSTP)

# disown — detach job from shell (won't get SIGHUP when shell exits)
my-command &
disown %1

# nohup — immune to SIGHUP; redirects stdout/stderr
nohup my-command > /var/log/my.log 2>&1 &

# setsid — create new session (fully detach from terminal)
setsid my-command &

# Run in new process group
set -m   # enable monitor mode (job control) in scripts
Resource LimitsResources

Resource limits (rlimits) cap per-process resource consumption. Each limit has a soft value (current enforcement) and a hard value (ceiling a process can raise its soft limit to).

Common limits

Limitulimit flagDefaultCommon fix
Open files-n1024Raise to 65536+ for servers/databases
Max processes-uvariesRaise for apps that fork heavily
Stack size-s8MBRaise for deep recursion; lower for thread safety
Core file size-c0 (disabled)Set to unlimited for debugging crashes
Virtual memory-vunlimitedCap for memory-leaking processes
Shell
# Show current limits
ulimit -a

# Raise open file limit for current shell session
ulimit -n 65536

# Check limits of a running process
cat /proc/<pid>/limits

# Persistent limits in /etc/security/limits.conf
*    soft nofile 65536
*    hard nofile 131072
www-data soft nproc  2048

# Set limits when launching a process (prlimit)
prlimit --nofile=65536:131072 -- my-server

# Set limits in a systemd service
# [Service]
# LimitNOFILE=65536
# LimitNPROC=4096
cgroups v2Resources

Control groups (cgroups) limit, account for, and isolate resource usage of process groups. cgroups v2 uses a unified hierarchy (one tree, multiple controllers). systemd uses cgroups v2 slices to enforce per-service limits.

Key controllers

ControllerLimits
cpucpu.weight (relative shares), cpu.max (quota/period for hard cap)
memorymemory.max (hard limit), memory.high (soft throttle), memory.swap.max
ioio.max (IOPS/bandwidth per device), io.weight
pidspids.max (prevent fork bombs)
Shell
# Inspect cgroup hierarchy
systemd-cgls
cat /proc/<pid>/cgroup

# Show resource usage for a service
systemctl status my-service     # shows CPU/memory at bottom
systemd-cgtop                   # live per-cgroup resource usage

# Set memory limit on a service (systemd)
systemctl set-property my-service MemoryMax=512M

# Or in the unit file [Service] section:
# MemoryMax=512M
# CPUQuota=50%
# TasksMax=100

# Create a cgroup manually (v2)
mkdir /sys/fs/cgroup/my-group
echo "512M" > /sys/fs/cgroup/my-group/memory.max
echo <pid> > /sys/fs/cgroup/my-group/cgroup.procs
Cheat SheetReference

Process inspection

ps aux --forest
pstree -p
top / htop
cat /proc/<pid>/status

Signals

kill -TERM <pid> — graceful
kill -9 <pid> — force
kill -HUP <pid> — reload
pkill -f pattern

/proc quick hits

tr '\0' ' ' < /proc/PID/cmdline
ls /proc/PID/fd | wc -l — fd count
cat /proc/PID/limits
cat /proc/PID/oom_score

Resource limits

ulimit -n 65536 — open files
cat /proc/PID/limits
prlimit --nofile=65536 -- cmd
Persistent: /etc/security/limits.conf

Job control

Ctrl+Z suspend, bg resume
nohup cmd & — HUP immune
disown %1 — detach from shell
setsid cmd — new session

cgroups v2

systemd-cgls
systemd-cgtop
systemctl set-property svc MemoryMax=1G
cat /proc/PID/cgroup