Every Process Has a PID and a Parent
Linux organizes every running program as a process with a unique numeric PID and a parent PID, forming a tree that starts at PID 1 (systemd on modern systems). Knowing how to inspect that tree, signal individual processes, schedule recurring jobs, and tune priority is fundamental sysadmin work.
This article is a working reference for Linux process management: viewing processes, sending signals, foreground/background control, scheduling with cron and at, and the priority knobs (nice, renice, ionice).
Viewing Processes — ps
ps # your processes in this terminal
ps aux # ALL processes (BSD-style)
ps -ef # ALL processes (System V-style)
ps -eLf # threads visible
ps aux | grep nginx # filter for nginx processes
ps -p 1234 # specific PID
ps -ppid 1234 # children of PID 1234
ps --sort=-%mem | head # top memory consumers
ps --sort=-%cpu | head # top CPU consumers
Two flavors of ps survive: BSD (ps aux) and System V (ps -ef). Either works on Linux. The output looks slightly different but conveys the same data.
top, htop, btop
top # default interactive viewer
htop # nicer UI, mouse support (install separately)
btop # the prettiest, modern Rust-based
Inside top:
P— sort by CPUM— sort by memoryk— kill a process (prompts for PID)r— renice a process1— show per-CPU breakdownq— quit
Process Trees — pstree
pstree # full tree
pstree -p # with PIDs
pstree alice # only alice's processes
pstree 1234 # tree starting from PID 1234
Useful when debugging “who spawned this orphan?” questions.
Signals — kill Doesn’t Just Kill
The kill command sends signals to processes. The signal you send determines what happens.
| Signal | Number | Default action |
|---|---|---|
SIGHUP |
1 | Reload config (in well-behaved daemons) |
SIGINT |
2 | Interrupt (what Ctrl-C sends) |
SIGQUIT |
3 | Quit + core dump |
SIGTERM |
15 | Polite termination — default for kill |
SIGKILL |
9 | Forceful kill — cannot be caught or ignored |
SIGSTOP |
19 | Pause (cannot be caught) |
SIGCONT |
18 | Resume from stop |
kill 1234 # send SIGTERM (default)
kill -15 1234 # explicit SIGTERM
kill -9 1234 # SIGKILL (force)
kill -HUP 1234 # send SIGHUP (config reload)
kill -l # list all signal names
killall nginx # by name (every match)
pkill -f "python.*serve.py" # by command-line regex
Try SIGTERM first, give it a few seconds, then SIGKILL only if needed. SIGKILL doesn’t let the process clean up its file handles, network sockets, or temp files. It’s the right tool for hung processes, the wrong tool for the first attempt.
Foreground, Background, and Job Control
command & # run in background
jobs # list background jobs
fg # bring most-recent to foreground
fg %2 # bring job 2 to foreground
bg %2 # resume job 2 in background
Ctrl-Z # suspend the foreground job
disown %1 # detach from shell so it survives logout
nohup command & # immune to hangup signal at logout
setsid command # run in a new session
For long-running commands you want to survive logout, the modern answer is tmux or screen — persistent terminal sessions you can detach from and reattach to. Use tmux new -s work, then Ctrl-b d to detach, tmux attach -t work to come back.
Scheduling — cron and at
cron — recurring jobs
crontab -e # edit your cron file
crontab -l # list it
crontab -r # delete it (be careful!)
# Cron syntax: minute hour day-of-month month day-of-week command
# 0 3 * * * run at 3 AM every day
# */5 * * * * every 5 minutes
# 0 9-17 * * 1-5 every hour 9-17, weekdays only
0 2 * * * /usr/local/bin/backup.sh
*/15 * * * * /usr/bin/check-health.sh
Always use full paths in cron. The cron environment is minimal — $PATH is short, no shell aliases, often no $HOME. Test your cron command exactly as cron will: env -i /bin/sh -c '/your/command'.
at — one-shot future
echo "backup-now.sh" | at 03:00 tomorrow
atq # list scheduled at jobs
atrm 5 # delete at job 5
systemd timers — modern alternative
systemctl list-timers
systemctl status backup.timer
Timer units are configured as pairs (backup.service + backup.timer). They survive reboots cleanly, log to journald, and integrate with the rest of systemd. The right choice for new infrastructure on systemd-based systems.
Priority — nice, renice, ionice
nice -n 10 ./big-job.sh # lower CPU priority (range -20 to +19)
renice -n 5 -p 1234 # change priority of running PID
ionice -c 3 ./big-io.sh # idle I/O class
nice -n -5 ./important.sh # raise priority (root only for negative)
Niceness is counter-intuitive: higher number = nicer = lower priority. Default is 0; range is -20 (highest) to +19 (lowest). Only root can lower the niceness (raise priority).
Common Pitfalls
- SIGKILL as the first reach. Try SIGTERM first; processes that catch SIGTERM clean up properly. SIGKILL is the kick-down-the-door option.
- Cron silently failing. Cron emails errors by default but if mail isn’t configured, errors disappear. Always redirect:
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 $PATHin cron. The cron environment doesn’t have your shell’s PATH. Use absolute paths or setPATH=...at the top of the crontab.nohupdoesn’t imply background.nohuphandles the SIGHUP signal at logout, but you still need&to background the process.- Killing a parent doesn’t kill children. They get re-parented to PID 1 (init/systemd). To kill a process group:
kill -- -PGID(note the leading negative). killallon macOS vs Linux. Different semantics across platforms. On Linux,killall namekills all processes matching name; on Solaris, it killed all processes period. Triple-check before running on unfamiliar systems.
Conclusion
Five habits:
htopopen in a tmux pane during any non-trivial workload.- Try SIGTERM, wait 5 seconds, then SIGKILL only if needed.
- Cron jobs always: full paths, output redirection,
2>&1. - For long workloads,
tmuxor systemd-units, notnohup. - Use
niceandioniceon heavy batch jobs so they don’t starve interactive workloads.
Related Linux Admin troubleshooting
For common errors and fixes related to this topic, see: