进程、作业控制与 systemd

分三层来看:怎么看清现在跑着什么(ps / top / pgrep),怎么控制它(kill / nice / jobs / nohup),以及怎么让它长期可靠地跑着(systemd 服务、定时任务、journalctl 日志)。

共 40 条命令 · 最近更新

查看进程

ps 是快照,top 是实时;找特定进程用 pgrep,看某个进程打开了什么用 lsof。

ps aux

BSD 风格的进程快照:USER 属主、PID 进程号、%CPU / %MEM 占用、STAT 状态、START 启动时间、TIME 累计 CPU 时间、COMMAND 命令行(含参数)。

ps aux | head -1; ps aux --sort=-%cpu | head -11
ps aux --sort=-%mem | head -11
ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head

ps 的 STAT 里常见 R 运行、S 可中断睡眠、D 不可中断(等 I/O)、Z 僵尸、T 停止;带 s 是会话首进程、l 是多线程、+ 在前台进程组。

ps -ef

System V 风格的完整格式,多出 PPID(父进程号)、C(CPU 调度占比)等列,看进程树关系时更好用。

ps -ef | grep nginx
ps -ef --forest | head -40

grep 自己的进程也会出现在结果里,可以用 ps -ef | grep '[n]ginx' 或 pgrep 避免。

pgrep / pkill

按名字或其他属性查找进程号(pgrep),或直接对匹配到的进程发信号(pkill)。-f 匹配整条命令行(不只是进程名),-u 限定用户,-a 输出命令名,-c 只输出数量。

pgrep -a nginx
pgrep -u deploy -a python
pkill -f 'python3 worker.py'
pkill -u deploy -f 'celery'

pkill -f 会匹配整条命令行,模式写得太宽(比如 pkill -f python)容易连自己或不相关的进程一起杀掉,先用 pgrep 确认列表。

pidof

按可执行文件名返回 PID,一行输出、便于脚本取用。找不到时退出码非 0。

pidof nginx
kill -HUP $(pidof nginx)

top

实时进程与资源视图。交互键:P 按 CPU 排序、M 按内存排序、T 按运行时间、1 展开每颗 CPU、c 显示完整命令行、k 杀进程、r 改 nice、f 选列、Shift+H 显示线程、q 退出。

top
top -o %MEM
top -u deploy
top -b -n1 | head -20

top 第一行的 load average 是 1/5/15 分钟的平均运行队列长度,不是 CPU 百分比;要结合 CPU 核数看,单核满载就是 1.00。

htop / atop

htop 是 top 的彩色增强版,支持鼠标、树状视图(F5)、过滤(F4)、直接搜索进程;atop 会记录历史快照,适合事后回溯。两者都需要单独安装。

htop -u deploy
atop -r /var/log/atop/atop_20261019 -b 14:30

lsof

列出打开的文件——在 Linux 里网络连接、设备、管道都算文件,所以它也是查「谁占用着端口 / 谁在用这个文件」的利器。-i 网络,-p 按 PID,-u 按用户,-c 按进程名。

sudo lsof -i :8080
sudo lsof -p $(pgrep -f worker.py)
sudo lsof +D /var/log/app
sudo lsof /var/lib/mysql/ibdata1

删除文件后磁盘空间没释放,通常是进程还持有句柄:lsof | grep deleted 找到它,重启或 kill 之后空间才回来。

pstree

以树状显示进程的父子关系。-p 显示 PID,-a 显示命令行参数。判断「谁启动了这个进程」比 ps 更直观。

pstree -ap | head -30
pstree -p $(pgrep -o systemd)

free

显示内存与交换分区使用情况。-h 人类可读,-m 以 MB 为单位,-s N 每 N 秒刷新。重点看 available 列,而不是 free 列。

free -h
free -m -s 2

buff/cache 是可回收的缓存,不要把 used 高当成内存不足;available 才是「新程序还能申请到多少」。

/proc 里的进程信息

/proc/<pid>/ 是内核暴露的进程视图:cmdline 是完整命令行、environ 是环境变量、cwd 是指向工作目录的链接、fd/ 是打开的文件描述符、limits 是资源限制。

tr '\0' ' ' < /proc/$(pgrep -f worker.py)/cmdline; echo
sudo ls -l /proc/$(pgrep nginx)/cwd
sudo cat /proc/$(pgrep nginx)/limits | grep -i 'open files'

readlink /proc/<pid>/exe 能看到进程实际执行的是哪个文件,排查「是不是跑的老版本」时很有用。

信号与结束进程

kill 发的是信号,不是「杀」。先 TERM 给它机会清理,再考虑 KILL。

kill

向进程发送信号。默认 15(SIGTERM)请求进程正常退出;-9(SIGKILL)强制终结,进程没有机会保存状态或清理临时文件;-1(SIGHUP)通常让服务重新读取配置;-2(SIGINT)等于 Ctrl+C。

kill 12345
kill -HUP $(pidof nginx)
kill -9 12345

kill -9 之后进程占用的端口、锁文件、共享内存可能不会释放,需要手动清理;能 TERM 就不要 KILL。

killall

按进程名结束所有匹配的进程。-i 逐个确认,-s 指定信号,-u 限定用户,-w 等待进程真正结束。注意不同系统上 killall 语义可能不同(Solaris 上是杀全部进程)。

killall -i nginx
killall -s HUP rsyslogd

xargs kill

批量结束一批 PID(比如按条件筛选出来的)。先用不带动作的命令确认列表,再补上 kill。

pgrep -u deploy -f 'celery' | xargs -r kill
ps -eo pid,etime,cmd --sort=-etime | awk '$2 ~ /-/ {print $1}' | head

PID 会被系统复用;从「看到列表」到「执行 kill」之间如果隔了很久,最好重新确认一次。

timeout

给命令设置最长运行时间,超时后发送信号(默认 TERM,-s 可改,-k 再超时后强杀)。脚本里防止某一步卡死时必用。

timeout 30 curl -sI https://example.com
timeout -k 5 60 ./long-task.sh

timeout 退出码 124 表示确实超时了,脚本里可以据此分支处理。

nice / renice

调整进程的调度优先级(-20 最高、19 最低,默认 0)。nice 在启动时指定,renice 调整已在运行的进程。只有 root 能提高优先级(设更小的 nice 值)。

nice -n 10 tar czf backup.tar.gz /data
sudo renice -n 5 -p 12345
ionice -c3 -p 12345

CPU 抢占严重时,比起降优先级,更常见有效的手段是限制 I/O:ionice -c3(idle 级)或 systemd 的 IOWeight。

ulimit

查看或设置当前 shell 及其子进程的资源限制。-a 列出全部,-n 是打开文件数(最常调),-u 是最大进程数,-c 是 core dump 大小。-H 硬限制、-S 软限制。

ulimit -a
ulimit -n 65535
ulimit -Hn

ulimit 只影响当前 shell 及其子进程,对 systemd 服务要用 LimitNOFILE= 设置;改完记得 systemctl daemon-reload。

作业控制与后台运行

终端里的 & / jobs / fg / bg 管的是当前 shell 的作业;要让任务脱离终端长期活着,用 nohup 或交给 systemd。

& 与 jobs / fg / bg

命令末尾加 & 让它在后台运行。jobs -l 列出当前 shell 的作业及 PID,fg %1 把作业 1 调回前台,bg %1 让停止的作业继续在后台跑,Ctrl+Z 把前台任务挂起。

long-task.sh &
jobs -l
fg %1
Ctrl+Z 然后输入 bg

作业是当前 shell 的概念,关掉终端后这些作业通常会收到 SIGHUP 而退出。

nohup

让命令忽略 SIGHUP,从而在退出终端后继续运行。输出默认重定向到 nohup.out,建议自己显式指定日志文件。

nohup ./worker.sh > /var/log/worker.log 2>&1 &
nohup ./sync.sh >/dev/null 2>&1 &

nohup 只解决挂断信号,不解决崩溃重启、开机自启、资源限制——这些是 systemd 的职责。临时任务用 nohup,长期服务用 systemd。

disown / setsid

已经用 & 启动的作业可以用 disown -h %1 摘掉,让它不受 shell 退出影响;setsid 让命令在新会话中运行,彻底脱离当前终端。

disown -h %1
setsid ./daemon.sh < /dev/null > /tmp/daemon.log 2>&1

screen / tmux

终端复用器。会话留在服务器上,断开 SSH 后重新 attach 就能接着看输出,最适合跑长任务和交互式操作。-S 创建命名会话,-ls 列出,attach -t 接回。

tmux new -s deploy
tmux ls
tmux attach -t deploy
screen -S backup / screen -r backup

tmux 里 Ctrl+B 是前缀键:Ctrl+B 然后 d 脱离,c 新窗口,n/p 切换窗口,% 分屏。

wait / && / ;

wait 等后台作业结束;cmd1 && cmd2 前一条成功才执行后一条;cmd1 || cmd2 前一条失败才执行;cmd1 ; cmd2 顺序执行不管结果。set -e 让脚本遇到错误立即退出。

./build.sh && ./deploy.sh
systemctl is-active nginx || systemctl start nginx
set -euo pipefail

systemctl:管理服务

现代发行版上「启动、停止、开机自启、看状态、看日志」都由 systemctl 统一负责。

systemctl start / stop / restart / reload

启动、停止、重启服务,或让服务重新加载配置(reload 不断连接,比 restart 平滑,前提是服务支持)。可以在一条命令里操作多个单元。

sudo systemctl restart nginx
sudo systemctl reload nginx
sudo systemctl restart app-api app-worker

restart 会中断正在处理的请求;能 reload 就别 restart。执行完记得用 status 和 journalctl 确认真的起来了。

systemctl status

查看服务的当前状态:是否 active、主进程 PID、资源占用(CGroup 段)、最近若干行日志。--no-pager 让输出适合管道和复制,-l 不截断长行。

systemctl status nginx --no-pager -l
systemctl is-active nginx; systemctl is-enabled nginx
systemctl is-failed nginx

脚本里判断服务是否正常,用 is-active(输出 active 且退出码 0),不要解析 status 的文本。

systemctl enable / disable

设置或取消开机自启。enable 只是创建符号链接,不会立刻启动服务(要同时启动用 enable --now)。disable 也不会停止正在运行的服务。

sudo systemctl enable --now nginx
sudo systemctl disable nginx
systemctl list-unit-files --state=enabled

enable --now 是「设为开机自启并立即启动」的简写,部署新服务时最常用。

systemctl list-units / list-dependencies

列出已加载的单元,--all 含未激活,--failed 只看失败的;list-dependencies 显示单元的依赖树,排查「为什么这个服务起不来」时能看出被哪个前置依赖卡住。

systemctl --failed
systemctl list-units --type=service --state=running
systemctl list-dependencies nginx.service --reverse

systemctl daemon-reload

重新读取 /etc/systemd/system 下的单元文件。改过 .service 内容、或新增/删除单元文件后必须执行,否则 systemctl 用的还是旧定义。

sudo systemctl daemon-reload
sudo systemctl restart app-api

忘记 daemon-reload 是「改了配置却没生效」最常见的两个原因之一(另一个是服务没 restart)。

systemctl cat / edit / show

cat 打印单元的完整定义(含 drop-in 覆盖文件),edit 生成覆盖片段(推荐用它而不是直接改原文件),show 输出全部生效属性,-p 挑选字段。

systemctl cat nginx.service
sudo systemctl edit app-api
systemctl show nginx -p LimitNOFILE -p MainPID

systemctl edit 生成的覆盖文件放在 /etc/systemd/system/<unit>.d/override.conf,升级软件包时不会被覆盖。

systemctl reset-failed

清除服务的失败状态。服务因为启动过快被 rate limit 限制(start-limit-hit)时,重置后才能再次启动。

sudo systemctl reset-failed app-api
sudo systemctl restart app-api

写一个自己的 service

在 /etc/systemd/system/ 下创建 .service 文件,最少要有 [Unit] Description、[Service] ExecStart 和 Restart、[Install] WantedBy=multi-user.target 三段。

[Unit]
Description=Demo API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/srv/app
EnvironmentFile=-/etc/app/app.env
ExecStart=/srv/app/bin/api --port 8080
Restart=on-failure
RestartSec=3
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now app-api

systemd 服务里的命令必须用绝对路径,PATH 不会被 shell 的 profile 扩展;需要 shell 语法时写 ExecStart=/bin/bash -c '...'。

journalctl:查日志

systemd 把所有服务的标准输出统一收进 journal,并且天然带时间、单元、优先级等结构化字段。

journalctl -u

查看指定服务的日志。-f 持续跟随(相当于 tail -f),-n 100 只看最近 100 行,--no-pager 不进入分页器,-r 倒序(最新的在最前)。

journalctl -u nginx -n 100 --no-pager
journalctl -u app-api -f
journalctl -u app-api --since today

看不到日志时先确认服务是否真的往标准输出写;有些老程序只写自己的日志文件,那就得去看 /var/log 下的文件。

journalctl 时间范围

--since / --until 接受 '2026-10-19 08:00:00'、'today'、'yesterday'、'1 hour ago'、'-30min' 这类写法,组合使用可以精确框定一次故障的时间窗。

journalctl --since '2026-10-19 08:00' --until '2026-10-19 09:00'
journalctl --since '30 min ago' -p err
journalctl -b -1 --no-pager | tail -100

-b 看本次启动以来的日志,-b -1 看上一次启动的日志(排查「重启前发生了什么」的关键手段)。

journalctl -p / -k / _PID

-p 按优先级过滤(emerg/alert/crit/err/warning/notice/info/debug,写 err 表示 err 及以上);-k 只看内核消息(替代 dmesg);-o verbose 显示全部结构化字段,方便按 _PID、_COMM、_UID 精确过滤。

journalctl -p err -b --no-pager
journalctl -k --no-pager | tail -50
journalctl _PID=12345 --no-pager
journalctl _COMM=sshd --since today

journalctl 磁盘占用与清理

journal 会持久化到 /var/log/journal 并持续增长。--disk-usage 看占用,--vacuum-time 按时间清理(如保留 2 周),--vacuum-size 按体积上限清理。

journalctl --disk-usage
sudo journalctl --vacuum-time=2weeks
sudo journalctl --vacuum-size=500M

更彻底的做法是在 /etc/systemd/journald.conf 里设置 SystemMaxUse= 并重启 systemd-journald,让它在写入时就自我限制。

journalctl -xe

看最近日志并附带解释性提示;-x 加上 systemd 对错误消息的补充说明,-e 直接跳到末尾。排查服务启动失败时最顺手的组合。

journalctl -xe --no-pager

dmesg

内核环形缓冲区消息,含硬件、驱动、OOM killer、磁盘错误、网络丢包等。日志量大时按 -T(人类可读时间)、-l err 或 --level 过滤。

sudo dmesg -T | tail -50
sudo dmesg -T --level=err,warn
sudo dmesg -T | grep -i -E 'oom|killed process'

OOM killer 杀掉进程的证据就在 dmesg 里,关键词是 Out of memory 和 Killed process。

定时任务

cron 简单直接,systemd timer 能写日志、有依赖、能补跑;新写的任务优先考虑 timer。

crontab -e / -l / -r

危险

编辑、列出、删除当前用户的定时任务。-u 用户名 可操作其他用户的任务(需 root)。-r 会清空全部任务且没有确认提示。

crontab -l
crontab -e
sudo crontab -u deploy -l

crontab -r 是立即清空,误按没有后悔药;操作前先 crontab -l > ~/crontab.bak 备份。

cron 时间字段

五段式:分 时 日 月 周。* 表示任意,*/5 表示每 5 个单位,1-5 是范围(周一到周五),1,15 是枚举,@daily / @hourly / @reboot 是便捷写法。

*/5 * * * * /srv/app/health.sh >> /var/log/health.log 2>&1
0 3 * * * /srv/backup/run.sh
30 2 * * 0 /srv/report/weekly.sh

cron 的日和周字段是「或」的关系:写成 0 0 1 * 1 会在每月 1 日和每周一都执行,而不是两者的交集。

cron 的常见坑

cron 执行时的环境极其精简:PATH 通常只有 /usr/bin:/bin,没有你登录时的环境变量,工作目录是家目录,也不会加载 .bashrc。

PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
[email protected]
0 3 * * * cd /srv/app && ./backup.sh >> /var/log/backup.log 2>&1

所有命令写绝对路径、显式设置 PATH、把输出重定向到日志文件——三条都做到,cron 任务就不再神秘失败。用 % 时要转义成 \%。

systemd timer

由一对文件组成:.service 描述要做什么,.timer 描述何时触发。比 cron 多了日志(journalctl 可查)、依赖、持久化补跑(Persistent=true)和随机延迟。

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/srv/backup/run.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup every night

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload && sudo systemctl enable --now backup.timer

OnCalendar 的格式是「星期 年-月-日 时:分:秒」,用 systemd-analyze calendar 'Mon *-*-* 09:00' 可以先验证表达式再写进去。

查看定时任务状态

systemctl list-timers 列出所有 timer 的下次触发时间和上次执行结果;at 命令用于一次性延时任务,atq 查看队列。

systemctl list-timers --all
at 23:00 -f /srv/tasks/once.sh
atq