

不知道你有没有过这样的经历:
刚接触 Linux 的新手,跟着教程敲了个 ps aux,出来一屏幕密密麻麻的内容。教程说“这就是系统正在跑的进程”,你点点头假装听懂了,但转头还是想问:进程到底是个啥?它和程序有啥不一样?为什么有的进程会占满 CPU?为什么杀进程有时候还杀不掉?
如果你也有这些疑问,那这篇文章正好帮你把 Linux 进程讲明白。我们先从最基础的概念讲起,一步步拆解进程的本质。看完这一篇,你至少能搞懂三个问题:
ps、top 里看到的 R、S、D、Z、T 到底是什么意思?很多新手刚入门,最容易搞混的就是程序和进程,我们先把这个关系理清楚。
举个生活里的例子:你下载了一个微信的安装包,安装到电脑里,这个躺在你硬盘里的微信,就是程序。
程序本质上是一堆存放在磁盘上的二进制代码、配置文件和资源文件。它是静态的,占的是磁盘空间,不跑起来的时候,它不会主动做任何事情。
什么时候它变成进程呢?
当你双击打开微信,系统把它加载到内存里,给它分配 CPU 时间、内存、文件描述符等资源,让它开始处理消息、网络连接和界面操作时,这个正在运行的微信,就是一个进程。
这么说是不是就好理解了?总结一下两者的区别:
一个程序也可以对应多个进程。
比如你多次运行同一个脚本,或者同一个 Web 服务启动了多个 worker 进程,这些进程运行的可能是同一份程序代码,但每个进程都有自己的 PID、运行状态和资源信息。现代浏览器的多个标签页、渲染进程、插件进程也经常采用多进程模型,但具体是否“一页一个进程”要看浏览器实现和配置,不能简单地一概而论。
理解了基本概念,我们再往深挖一点:Linux 系统里,进程本质上是什么东西?
Linux 是一个多任务操作系统。你可以一边跑 Web 服务,一边跑数据库,还能远程登录服务器查日志,看起来这些任务都在“同时运行”。
但对单个 CPU 逻辑核来说,同一时刻通常只能执行一个任务流。Linux 之所以能让我们感觉很多程序在同时运行,是因为内核会让不同进程在 CPU 上快速切换:这个进程运行一小会儿,切到下一个进程,再切到另一个进程。切换速度足够快,我们就感觉它们在同时工作。
为了管理这么多进程,Linux 内核必须给每个进程做一套“信息登记”。否则它就不知道哪个进程跑到哪了、占了哪些资源、该不该继续调度。
所以在 Linux 里,理解进程时最关键的两个概念是:
很多人没听过 PCB。PCB 是操作系统教材里常见的说法,意思是 Process Control Block,进程控制块。
在 Linux 内核里,和 PCB 对应的核心数据结构通常叫 task_struct,也可以理解为 Linux 的“进程描述符”。内核会用它记录每个进程的关键信息,我们挑几个最重要的说:
task_struct 直接记录或关联到其他内核结构。简单说,没有这些进程描述信息,内核就没法管理进程。你可以把 task_struct 理解成进程在内核里的“档案袋”。
很多新手会问:
为什么一个进程崩溃了,不会把别的进程也带崩?为什么每个进程都好像拥有一大片连续内存?
这就是虚拟地址空间在起作用。
每个进程都有自己独立的虚拟地址空间。简单说,Linux 给每个进程画了一个“独立小房间”:进程看到的是自己的虚拟地址,而不是直接操作真实的物理内存。
以 32 位系统为例,一个进程理论上可以看到 4GB 左右的虚拟地址范围,但实际可供用户程序使用的范围会受到内核空间和用户空间划分的影响,例如常见的 3G/1G 划分。64 位系统的虚拟地址空间更大,但也不是说物理内存真的有那么大。
真正使用内存时,内核会通过页表把虚拟地址映射到物理内存。没真正用到的地址,不一定会马上分配物理内存。
这么做有什么好处呢?
隔离性更好每个进程默认只能访问自己的地址空间,不能随便读写别的进程的内存。所以一个普通进程崩溃,通常不会直接把其他进程也带崩。
内存管理更方便程序不需要关心物理内存到底在哪。哪怕物理内存上是零散的,进程看到的虚拟地址也可以是连续的,开发和管理都更方便。
支持更灵活的内存使用操作系统可以结合按需分配、内存映射、写时复制、交换空间等机制,让内存使用更灵活。但要注意,虚拟地址空间很大,不代表系统一定能真正提供那么多可用内存。
所以你看,进程并不是“代码跑起来”这么简单。它是内核管理的一个执行实体,有编号(PID)、有档案(进程描述符)、有自己的地址空间,也有自己的资源和状态。
我们经常在 ps 或者 top 命令里看到进程状态,比如 S、R、Z 这些字母。很多人不知道它们是什么意思,其实它们表示的是进程当前处于什么阶段。
Linux 里的进程状态不止下面这些,但对新手来说,先掌握这几个最常见的就够用了。
我们再说说平时最容易遇到的几个特殊状态。
D 状态一般出现在进程等待某些不可中断的内核态操作时,最常见的是磁盘 I/O、NFS 网络存储、块设备、驱动层调用等。
比如一个进程要从磁盘读取大文件,或者访问的 NFS 存储突然无响应,进程可能就会卡在 D 状态。这个状态下,信号不会马上生效,所以你就算执行 kill -9 PID,进程也不一定立刻消失。
准确地说,不是 kill -9 没发出去,而是进程还卡在不可中断等待里,内核要等它从这个等待中返回,才有机会处理相关退出逻辑。
如果系统里出现大量 D 状态进程,大概率要优先排查 I/O 问题,比如:
进程退出后,父进程需要通过 wait() 或 waitpid() 读取子进程的退出状态,并让内核清理最后的进程记录。
如果子进程已经退出,但父进程一直没有回收它,那么这个子进程就会变成僵尸进程。
那僵尸进程有害吗?
少量僵尸进程一般影响不大,因为它已经不运行了,也不再占用真正的 CPU 和大量内存。它主要占用的是 PID 和一小部分进程表记录。
但如果系统里大量产生僵尸进程,就可能导致 PID 被耗尽,新的进程无法创建,这就会变成严重问题。
怎么处理僵尸进程?
普通的 kill 命令对僵尸进程没用,因为它已经退出了。正确思路是:
生产环境里不要一上来就随便杀父进程,因为父进程可能是重要服务。应该先确认影响范围,再决定是重启服务、修复程序,还是在维护窗口处理。
孤儿进程和僵尸进程刚好相反。
如果父进程先退出了,子进程还在运行,这个子进程就变成了孤儿进程。
孤儿进程不会没人管。Linux 会让它被 PID 1,或者某个被设置为子进程收割器的进程接管。之后等它退出时,会由新的父进程负责回收。
所以孤儿进程本身并不一定是问题。它是否需要处理,主要看它是不是你期望继续运行的服务或任务。
到这里,我们已经把 Linux 进程的基础部分理清楚了:程序是静态文件,进程是运行中的执行实例;内核通过进程描述符管理进程;每个进程有自己的虚拟地址空间;不同进程状态代表了它当前的运行阶段。
下一篇我们继续往下看:一个进程到底是怎么启动的?日常排查时,ps、top、kill、nohup 又应该怎么用?

END




