深入理解进程:从硬件到操作系统的进程管理 📅 2026/7/8 2:19:52 这篇想解决一个问题进程到底是个什么东西操作系统凭什么能让一堆程序同时跑起来。我会从硬件一层层往上推推到进程这个抽象为止。一、先搞清楚计算机在干嘛聊进程之前得先知道没有操作系统的计算机长什么样。冯诺依曼结构现代计算机基本都遵循冯诺依曼体系核心就四个部件CPU、存储器、输入、输出。这里不展开讲只说一个后面会反复用到的点——存储层次的速度和容量是反着来的容量 硬盘 内存 CPU 寄存器 速度 硬盘 内存 CPU 寄存器硬盘能装几 TB但读一次要几毫秒CPU 寄存器快到纳秒级但几乎不存东西。中间的内存是个折中。后面会看到操作系统花大量精力就是在抹平这个速度差。CPU 怎么执行程序CPU 其实只会做一件事从内存里取一条指令译码执行写回结果然后取下一条。周而复始。有个寄存器叫PCProgram Counter它存着下一条要执行的指令地址。CPU 眼里没有程序这个概念它就认 PC 指向的那条指令。所谓程序运行起来本质就是 CPU 不断更新 PC、不断取指令执行的过程。这里有个伏笔既然 CPU 只认 PC那操作系统想让 CPU 去执行另一个程序只要改 PC 就行——这就是后面进程切换的雏形。二、操作系统在中间干了什么裸机上直接跑程序会出问题两个程序都想用内存、都想读写硬盘、都想占着 CPU 不放互相踩来踩去。所以需要一个中间人来管资源——这就是操作系统。它干两件事管硬件不让某个程序把资源吃光大家轮流用。给接口程序不用关心硬盘具体怎么转调个open()就能读文件。具体到资源管理操作系统把硬件抽象成几类对象资源管理思路对应模块CPU时间分片轮流用进程调度内存空间划分各占一块内存管理设备驱动屏蔽差异设备管理文件统一成文件概念文件系统下面重点讲 CPU 这条线——它就是进程的来源。三、进程把运行中的程序抽象出来为什么要有进程设想你在单核电脑上同时开着浏览器、音乐播放器、IDE。CPU 只有一颗同一时刻只能执行一条指令那为什么这三个程序看起来都在跑因为操作系统在快速地切换它们浏览器跑一会儿切到音乐跑一会儿再切到 IDE 跑一会儿。切换得足够快人感觉不到就以为是同时。但这里有个问题每个程序都有自己的代码、自己的变量、自己打开的文件、自己执行到哪一行了。如果操作系统要切换就必须把这些现场信息记下来不然切回来就接不上了。进程就是操作系统为了记住一个运行中程序的全部现场而搞出来的一个数据结构。所以进程的定义其实很朴素进程 程序的一次执行过程外加操作系统为它记录的全部上下文信息。程序和进程的区别也就清楚了程序是硬盘上的文件静态的进程是程序被加载到内存跑起来后的动态实体。同一个程序可以同时跑出多个进程比如开三个计算器是三个进程但只有一个 calc.exe 文件。进程控制块PCB操作系统用一个结构体来记录进程的全部信息这个结构体叫PCBProcess Control Block。可以把它理解成操作系统账本上关于某个进程的一页记录。PCB 里大概有这些字段PID每个进程一个唯一编号。任务管理器里看到的那串数字就是它。内存指针进程的代码、数据都在内存里PCB 要记住它们的地址。一个进程的内存布局大致是这样从低地址到高地址┌──────────────┐ 高地址 │ 栈 Stack │ 局部变量、调用栈 ├──────────────┤ │ ↓ 空闲 ↓ │ │ ↑ 空闲 ↑ │ ├──────────────┤ │ 堆 Heap │ new/malloc 出来的 ├──────────────┤ │ 数据段 Data │ 全局变量、静态变量 ├──────────────┤ │ 代码段 Code │ 指令只读 └──────────────┘ 低地址栈往下长堆往上长中间留空。这也是为什么数组下标访问是 O(1)——地址是连续算出来的CPU 直接取。文件描述符表进程打开的每个文件操作系统都给一个编号叫文件描述符fd。PCB 里有一张表记录这个进程打开了哪些 fd。默认每个进程一出生就有三个0标准输入键盘1标准输出屏幕2标准错误屏幕你new FileInputStream(a.txt)操作系统就分配3给它记进表里。close()就是把表里这一行划掉。进程状态进程不是一直占着 CPU 的它会在几种状态间转创建 → 就绪 ⇄ 运行 → 终止 ↑ ↓ ← 阻塞 ←就绪万事俱备排队等 CPU运行正在 CPU 上跑阻塞在等某个事件比如磁盘读完了、网络数据到了这时就算把 CPU 给它也没用它干不了活注意就绪和阻塞的区别就绪是我能跑就差 CPU阻塞是我跑不了在等别的东西。这是个容易混的点。优先级多个进程抢 CPU 时谁先来。音视频通话优先级高后台下载优先级低这样保证不卡。上下文寄存器这是 PCB 最关键的部分之一。进程被切下 CPU 时CPU 寄存器里的值包括 PC要存到这里进程被切上 CPU 时再从这里恢复。没有这个进程切换就接不上。记账信息CPU 用了多少时间、占了多少内存、做了多少次 I/O。用于统计和调度参考。PCB 长什么样用 Java 伪码大概是这样classPCB{intpid;longcodeStart,dataStart,heapPtr,stackPtr;FileDescriptor[]fdTable;intstate;// 就绪/运行/阻塞/...intpriority;longpc;// 程序计数器long[]registers;// 通用寄存器longcpuTimeUsed;longstartTime;longmemoryUsed;}操作系统把所有 PCB 用链表或树组织起来增删查改。进程创建 新建一个 PCB 塞进链表进程结束 从链表摘掉销毁。四、进程调度CPU 怎么分为什么要调度单核 CPU 同一时刻只能跑一个进程。但我们感觉一堆程序在同时跑靠的就是调度操作系统给每个进程分一小段时间片轮流上 CPU切得够快人就不察觉。几种常见调度算法这些算法不必全记理解思路就行FCFS先来先服务谁先来谁先跑跑完才换下一个。公平但长任务会拖死短任务——你取 100 块钱前面那人办 30 分钟贷款你就得干等。SJF短作业优先预计跑得短的先跑平均等待时间最短。问题是一直有短任务来的话长任务永远轮不上饿死。时间片轮转RR每个进程分一个固定时间片用完就排到队尾。这是分时系统的基本做法。时间片太大→响应慢太小→光顾着切换了CPU 干不了正事。现代系统一般取 10~100ms。优先级调度按优先级排队。可能饿死低优先级进程通常配合老化机制等太久就提优先级。多级反馈队列上面几个的综合体现代操作系统实际用的就是这种思路的变种。多个队列不同优先级时间片用完降级等太久升级兼顾响应和吞吐。上下文切换调度落到实处就是切换——把 CPU 从进程 A 换给进程 B。这个过程叫上下文切换步骤是保存 A 的寄存器含 PC到 A 的 PCB把 A 状态改成就绪从 B 的 PCB 读出寄存器恢复到 CPU把 B 状态改成运行CPU 从 B 的 PC 处继续执行这个过程不便宜。直接开销是保存/恢复寄存器、切换页表、刷新 TLB间接开销是切过来之后 CPU 缓存是冷的要重新预热这段预热期命中率低、跑得慢。所以上下文切换不是免费的频繁切换会让 CPU 把时间浪费在切换本身上。这也是为什么线程后来会出现——线程切换比进程轻得多。完整的一次调度流程大致是时钟中断 → OS 介入 → 调度器选下一个进程 → 保存当前/恢复下一个 → 继续时钟中断就是操作系统的心跳每隔一段时间提醒它该看看要不要换人了。五、进程间通信IPC进程之间内存是隔离的A 进程碰不到 B 进程的内存——这是安全设计但也意味着它们要交换数据时没法直接读写对方的内存。操作系统得提供一些受控的通道这就是 IPC。常见的几种管道一个进程写一个进程读单向。Linux 命令行的|就是管道ps aux | grep java里ps写、grep读。共享内存操作系统划一块内存让两个进程都能访问。最快的 IPC因为不用拷贝数据。但既然是共享就涉及同时写怎么办的同步问题一般配信号量用。文件A 写文件B 读文件。简单但慢要落盘。Socket走网络协议栈可以跨机器。最灵活浏览器访问网站就是浏览器进程和服务器进程用 socket 通信。代价是要处理延迟、丢包。信号量不传数据只用来做同步协调——告诉对方我用完了你可以用了。信号一种通知机制比如CtrlC给进程发 SIGINT进程收到就退出。异步的进程不知道什么时候会来。实际开发中同一台机器内首选共享内存要快或管道要简单跨机器只能 socket。六、进程 vs 线程进程是资源分配的单位——每个进程有自己的内存空间、文件描述符表等。但进程有个大问题创建贵、切换贵。要是我只想让程序里的几段代码并行跑没必要每次都开一个全新进程。线程就是在这个背景下出来的一个进程里可以有多条线程它们共享进程的内存和资源但各自有独立的 PC 和栈。所以两者的分工是进程拥有资源内存、文件等线程CPU 调度的实际单位线程切换比进程快因为不用换地址空间、不用刷 TLB。但代价是线程之间共享内存一个线程写坏了全局数据整个进程包括其他线程跟着崩。进程隔离一个进程崩了不影响别的进程但通信成本高。实际工程里两者常常混用进程做隔离边界线程做进程内的并发。七、Java 里的进程Java 里创建子进程主要靠Runtime.exec或ProcessBuilderProcesspnewProcessBuilder(notepad.exe).start();longpidp.pid();// Java 9 可以直接拿 PIDintcodep.waitFor();// 阻塞等子进程结束Process对象本质上是对操作系统进程的一个 Java 侧句柄底层还是走操作系统的 fork/execLinux或 CreateProcessWindows。注意 Java 进程本身也是个进程有自己的 PID。用jps -l能列出当前机器上所有 JVM 进程。八、收尾把整条线捋一遍硬件层面CPU 只认 PC 指向的指令本身没有进程概念裸机上多程序会互相踩所以引入操作系统管资源操作系统为了让多个程序看起来同时跑要快速切换它们切换就得记住每个程序的现场于是抽象出进程 PCBPCB 里存着 PID、内存布局、打开的文件、状态、寄存器现场等调度算法决定谁先上 CPU上下文切换负责实际换人进程隔离带来安全但也带来通信需求于是有 IPC进程太重于是拆出线程共享进程资源进程这个抽象之所以成功在于它把程序运行这件事变成了一个可以被操作系统统一管理的对象——创建、调度、切换、通信、销毁全都标准化了。理解了进程后面再看线程、协程、容器基本都是在这个抽象上做加减法。几个值得想想的问题单核 CPU 上为什么感觉浏览器、音乐、微信在同时跑如果时间片放大到 1 秒你会看到什么现象就绪和阻塞都是没在 CPU 上跑为什么要分两种状态能不能合并上下文切换有开销那是不是线程开得越多越好为什么线程池要有上限共享内存是最快的 IPC那为什么管道、socket 这些慢的方式还在用什么场景下该用哪种同一个程序开三个进程它们共享代码段吗为什么这么设计