上一篇我们讲清楚了 Linux 进程的基础概念:程序和进程的区别、内核如何管理进程、进程地址空间,以及 R、S、D、Z、T 等常见状态。
这一篇继续沿着原来的逻辑往下讲:一个新进程到底是怎么启动的?平时查看、结束、后台运行进程,又该用哪些命令?
第四个问题:进程是怎么启动的?从 0 到 1 说清楚
我们知道了进程是什么,那一个新的进程是怎么来的?总不可能凭空冒出来吧?
在 Linux 里,普通用户态进程通常是由已有进程创建出来的。最早的用户态进程一般是 PID 1,也就是 init 或现代系统里常见的 systemd。其他用户态进程大多都可以沿着父子关系追溯到 PID 1。
需要注意:严格来说,PID 1 也是内核启动用户空间时创建出来的;另外,Linux 内核线程和一些特殊进程的创建方式也不完全等同于普通用户进程。对新手理解日常命令来说,先抓住“父进程创建子进程”这个主线就够了。
Linux 创建新进程最经典的模型,就是 fork() 加 exec()。
我们用一个实际场景说:你登录到 Linux 服务器,敲了一个命令想启动程序,比如:
nginx
这个过程大致会发生什么?
- 你登录后的
bash shell 本身就是一个进程。你敲下命令后,shell 收到输入,知道你想启动 nginx。 - shell 调用
fork(),创建一个子进程。这个子进程刚创建出来时,很多信息来自父进程,但它会拥有新的 PID。 - 子进程再调用
exec() 系列函数,把自己原来的程序映像替换成 nginx 的程序代码和运行环境。 nginx 程序开始执行,一个新的进程就运行起来了。- 如果这是前台命令,shell 通常会等待这个子进程结束;如果你在命令后面加了
&,shell 就不会一直等它,命令会作为后台作业运行。
所以可以这样理解:很多 Linux 程序的启动过程,本质上是“先复制出一个子进程,再把子进程替换成要运行的新程序”。
也就是:
wait() / waitpid():父进程等待并回收子进程退出状态。
我们可以用 pstree 命令看一下进程之间的父子关系:
pstree
输出大概像这样:
systemd─┬─NetworkManager───2*[{NetworkManager}] ├─nginx───nginx ├─sshd───sshd───bash───pstree └─systemd-journal
你会看到,很多进程最终都能追溯到 PID 1 的 systemd 或 init。例如你远程登录服务器时,sshd 会创建会话进程,然后启动 shell;你在 shell 里执行 pstree,所以 pstree 又是 shell 的子进程。
第五个问题:日常操作进程,最常用的命令有哪些?我们实操一下
说了这么多原理,我们来点干货。平时操作进程,最常用的几个命令,给大家总结一下,新手也能直接用。
1. 查看进程:ps 和 top 怎么用?
最常用的命令之一是:
ps aux
这个命令会列出系统中当前进程的一个快照。输出大概长这样:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.1 168312 10996 ? Ss 08:00 0:01 /usr/lib/systemd/systemdroot 123 0.0 0.3 82344 24568 ? S 08:01 0:00 nginx: master process nginxroot 456 0.0 0.1 12345 6789 pts/0 R+ 10:23 0:00 ps aux
每一列大概是什么意思?
%CPU:CPU 使用率。注意,ps 里的 %CPU 更像一个统计快照,不一定等同于实时瞬时 CPU;如果要看实时变化,建议用 top。
这里顺便提醒一下:
ps aux
和
ps -aux
最好不要混用。Linux 上很多时候 ps -aux 也能输出类似结果,但从标准和兼容性角度看,推荐使用 ps aux 或者更明确的 ps -ef。
如果想动态查看进程状态,可以用:
top
top 会周期性刷新系统负载、CPU、内存和进程列表。不同系统默认刷新间隔可能不同,常见默认值是几秒一次,也可以在 top 内调整。
常用操作:
排查系统卡慢时,通常第一步就是用 top 或类似工具看:到底是 CPU 打满、内存不足、I/O 等待高,还是某个进程异常。
2. 结束进程:kill、killall 怎么用?
找到你要处理的进程 PID 之后,可以用 kill 给它发送信号。注意,kill 的本质不是“直接杀死”,而是“向进程发送信号”。
常用写法如下:
kill PID
默认发送的是 SIGTERM,也就是 15 号信号。它的意思是告诉进程:“请你正常退出。”这是一种相对温和的方式,进程可以在退出前保存数据、关闭连接、清理临时文件。日常操作建议优先使用这种方式。
如果普通 kill 没效果,再考虑:
kill -9 PID
kill -9 发送的是 SIGKILL,也就是 9 号信号。这个信号不能被进程捕获、阻塞或忽略,内核会强制结束目标进程。
但不建议一上来就用 kill -9,因为它不给进程做清理工作的机会,可能造成临时文件残留、事务中断、数据未刷盘等问题。
还可以按进程名处理:
killall nginx
这会向所有匹配名称的 nginx 进程发送信号。它很方便,但也要小心,因为名字匹配可能影响多个进程。生产环境执行前,建议先用下面的命令确认:
pgrep -a nginx
举个例子:我要结束 PID 为 123 的进程,先温和地尝试:
kill 123
如果确认它无法正常退出,再强制处理:
kill -9 123
3. 把进程放后台运行:& 和 nohup 怎么用?
很多新手会遇到一个问题:
我在 SSH 里启动了一个程序,SSH 一断开,程序也跟着退出了,这是为什么?
原因通常是:这个程序仍然挂在当前 shell 会话或终端上。终端关闭、会话结束时,相关进程可能收到 SIGHUP 信号,或者因为标准输入输出断开而退出。
如果只是想让命令在后台运行,可以在命令后加 &:
./你的程序 &
& 的作用是把程序作为后台作业启动,这样当前终端还能继续输入命令。
但仅仅加 & 不一定能保证你退出 SSH 后程序还继续运行。因为它仍然可能受到会话结束、终端关闭、输出断开等影响。
更常见的写法是:
nohup ./你的程序 > app.log 2>&1 &
这里分开看:
> app.log:把标准输出写到 app.log;2>&1:把标准错误也重定向到标准输出,也就是同一个日志文件。
如果你不手动指定日志文件,nohup 通常会把输出写到 nohup.out,但生产环境更建议明确指定日志路径,方便排查问题。
需要注意:nohup 适合临时任务。如果是长期运行的服务,更推荐用 systemd、Supervisor、容器编排、进程守护工具等方式管理,而不是长期依赖一个手工执行的 nohup 命令。
常见问题解答,你可能也遇到过
最后我们整理几个后台常问的问题,统一解答一下。
Q:为什么我的某个进程占了 100% CPU?怎么回事?
A:原因不一定只有死循环,也可能是程序正在做大量计算、被压测、线程池繁忙、日志过多、GC 异常、加解密任务过重,或者确实遇到了 bug。
排查思路可以这样来:
- 如果是 Java、Go、Python 等服务,再结合对应语言的诊断工具进一步分析;
不要看到 100% CPU 就马上 kill -9。先判断它是在正常忙,还是异常忙。
Q:僵尸进程太多会有什么问题?怎么解决?
A:每个僵尸进程会占用一个 PID 和少量进程表信息。系统 PID 是有限的,如果僵尸进程太多,可能导致新的进程无法创建。
解决方法不是直接杀僵尸进程,因为它已经退出了,普通 kill 对它没用。
正确思路是:
- 用
ps -ef | grep defunct 或 ps aux | grep Z 找到僵尸进程; - 必要时在维护窗口重启相关服务,最后才考虑重启系统。
Q:D 状态的进程为什么杀不掉?怎么处理?
A:D 状态是不可中断睡眠。进程通常卡在内核态 I/O 等待里,信号不会立即生效,所以 kill -9 也可能看起来“杀不掉”。
如果 D 状态进程一直不消失,优先排查:
常用排查命令包括:
dmesg -T | tail -100
iostat -x 1
df -h
mount
如果底层 I/O 恢复,很多 D 状态进程会继续执行或退出。如果存储长时间无法恢复,可能需要重启相关服务,严重时需要重启系统。
Q:前台进程怎么切回后台?
A:如果一个进程正在前台运行,你可以按:
Ctrl + Z
这会把进程暂停,而不是继续在后台跑。
然后输入:
bg
让它在后台继续运行。
如果你想再切回前台,输入:
fg
这个操作常用于临时处理命令行任务。但如果是重要的长期任务,还是建议一开始就规划好运行方式,比如 systemd、tmux、screen 或守护进程管理工具。
写在最后
其实 Linux 进程说来说去,核心逻辑就是:
程序是静态的,进程是程序运行起来后的执行实例;内核通过进程描述符管理每个进程;每个进程有独立的虚拟地址空间;不同进程状态对应不同的运行阶段。
把这些基础概念搞懂了,再去看进程调度、进程通信、查看进程、结束进程、后台运行这些操作,就不会一头雾水了。它们本质上都是建立在同一套进程模型之上的。
日常排查时,你可以先记住这条主线:
把这几条用熟,你就已经能处理大部分 Linux 进程相关的入门问题了。