LinuxBash Scripting

Linux Process Management and Job Control

Master Linux process management: forking, signals, background jobs, wait, kill, ps, and /proc. Lesson 7 of the Linux & Bash Scripting course.

TT
Daniel Brooks
6 min read
Linux Process Management and Job Control

Every command you run is a process. Understanding how the kernel creates and manages processes — through forking, signals, file descriptor inheritance, and process groups — is essential when writing scripts that launch parallel tasks, manage long-running daemons, or need to clean up after themselves reliably.

Previous: Lesson 6 — Error Handling, Traps, and Defensive Scripting


Process Concepts

PIDs, PPIDs, and the Process Tree

Every process has a PID (process ID) and a PPID (parent process ID). When a process forks, the child inherits file descriptors, environment variables, and signal dispositions from the parent.

bash
# Your current shell's PID
echo "Shell PID: $$"

# The PID of the last background command
sleep 10 &
echo "Background job PID: $!"

# View the full process tree
pstree -p $$

# Full process list with hierarchy
ps auxf

Inspecting Processes

bash
# List all processes (BSD-style)
ps aux

# Find a specific process
ps aux | grep nginx

# More targeted: use pgrep
pgrep -la nginx

# Detailed information for a specific PID
ps -p 1234 -o pid,ppid,pcpu,pmem,cmd

# Real-time monitoring
top                # basic
htop               # enhanced (install separately)

/proc — The Process File System

Every running process has a directory under /proc/<PID>/ exposing its state:

bash
cat /proc/$$/status          # Current shell status
cat /proc/$$/cmdline | tr '\0' ' '  # Full command line
ls -la /proc/$$/fd           # Open file descriptors
cat /proc/$$/environ | tr '\0' '\n' | grep PATH  # Environment
cat /proc/meminfo            # System memory
cat /proc/cpuinfo            # CPU details

Background Jobs and Job Control

bash
# Run a job in the background
long_task.sh &

# List background jobs in the current shell
jobs -l

# Bring the most recent background job to the foreground
fg

# Bring a specific job (job number 2) to the foreground
fg %2

# Send a foreground job to the background: Ctrl+Z, then:
bg

# Detach a job so it survives shell exit (immune to SIGHUP)
nohup long_task.sh > /var/log/task.log 2>&1 &

# disown removes the job from the shell's job table
long_task.sh &
disown $!

Running Parallel Tasks and Waiting

bash
#!/usr/bin/env bash
set -euo pipefail

# Launch several tasks in parallel and wait for all
pids=()

for server in web01 web02 web03; do
    ssh "$server" 'systemctl restart nginx' &
    pids+=($!)
done

# Wait for all background jobs and check exit codes
failed=0
for pid in "${pids[@]}"; do
    if ! wait "$pid"; then
        echo "Task (PID $pid) failed" >&2
        (( failed++ )) || true
    fi
done

if (( failed > 0 )); then
    echo "ERROR: ${failed} task(s) failed" >&2
    exit 1
fi

echo "All restarts completed successfully"

Parallel Execution with xargs

bash
# Run a command on 4 servers in parallel
echo -e "web01\nweb02\nweb03\nweb04" \
  | xargs -P 4 -I {} ssh {} 'df -h /'

# Process a list of files in parallel (4 at a time)
find /data -name "*.csv" \
  | xargs -P 4 -I {} python3 process.py {}

Signals

Signals are asynchronous notifications sent to a process. The kernel, terminal, or another process can send them.

Common Signals

SignalNumberDefault ActionTypical Use
SIGTERM15TerminateGraceful shutdown request
SIGINT2TerminateCtrl+C from terminal
SIGKILL9Terminate (uncatchable)Force kill
SIGHUP1TerminateTerminal closed; reload config
SIGSTOP19Stop (uncatchable)Pause process
SIGCONT18ContinueResume paused process
SIGUSR1/210/12TerminateCustom application signals

Sending Signals

bash
# Send SIGTERM (graceful shutdown) to a PID
kill 1234

# Send a specific signal by name or number
kill -SIGTERM 1234
kill -15 1234

# Force kill (cannot be caught or ignored)
kill -9 1234
kill -SIGKILL 1234

# Send a signal to all processes matching a name
pkill -SIGTERM nginx

# Send SIGHUP to reload nginx config (common pattern)
pkill -HUP nginx
# or
nginx -s reload

# Send SIGUSR1 to Apache to rotate logs
pkill -USR1 apache2

Sending Signals to Process Groups

bash
# A negative PID targets the entire process group
# Kill all processes in the process group of PID 1234
kill -TERM -1234

# Kill your own process group (all children)
kill -TERM 0

Managing Long-Running Processes

Check if a Process is Running

bash
is_running() {
    local process_name="$1"
    pgrep -x "$process_name" &>/dev/null
}

if is_running nginx; then
    echo "nginx is up"
else
    echo "nginx is down — restarting..."
    systemctl start nginx
fi

Wait for a Process to Start

bash
wait_for_process() {
    local name="$1"
    local timeout="${2:-30}"
    local elapsed=0

    echo "Waiting for ${name} to start..."
    while ! pgrep -x "$name" &>/dev/null; do
        sleep 1
        (( elapsed++ ))
        if (( elapsed >= timeout )); then
            echo "ERROR: ${name} did not start within ${timeout}s" >&2
            return 1
        fi
    done
    echo "${name} started (PID: $(pgrep -x "$name"))"
}

Process Substitution

Process substitution <(command) connects a command's output to a file descriptor, allowing it to be used where a filename is expected:

bash
# Diff the sorted output of two commands without temp files
diff <(sort file1.txt) <(sort file2.txt)

# Compare the package list on two servers
diff <(ssh web01 'dpkg -l') <(ssh web02 'dpkg -l')

# Read two sorted streams simultaneously
while IFS= read -r line; do
    echo "Line: $line"
done < <(find /var/log -name "*.log" -newer /tmp/last_check)

Monitoring System Resources

bash
# CPU and memory snapshot
top -bn1 | grep "Cpu(s)"
free -h

# Disk I/O statistics
iostat -xz 1 3

# Network connections
ss -tuln          # listening TCP/UDP ports
ss -tunp          # with process info (requires sudo)
netstat -an       # classic alternative

# Per-process resource usage (one snapshot)
ps aux --sort=-%cpu | head -10    # top CPU consumers
ps aux --sort=-%mem | head -10    # top memory consumers

# Open files per process
lsof -p 1234
lsof -i :80        # what's listening on port 80