进程的组成
这一节有两个层面,考试都会碰:
- 对外:系统里那么多 PCB,OS 怎么把它们组织起来管理?
- 对内:单个进程在内存里长什么样,切换时要保存哪些东西?
第二个层面里藏着全章最容易混的一对词——进程映像和进程上下文。
机制
PCB 的组织方式:链接与索引
OS 手里有几十上百个 PCB,它需要能快速回答”下一个该调度谁""等打印机的有哪些”这类问题。所以 PCB 不是散着放的,而是按状态分组串起来。
两种组织方式:
链接方式——同状态的 PCB 用指针串成一条链。系统维护若干个队列指针:执行指针(指向当前运行的进程,单处理机下只有一个)、就绪队列指针、以及若干条阻塞队列指针。
注意阻塞队列通常不止一条:等打印机的排一条,等磁盘的排一条,等某个信号量的再排一条。这样做的好处是,当打印机被释放时,OS 只需去打印机那条队列的队首取一个唤醒,不必遍历所有阻塞进程去筛选谁在等打印机。这正好解释了 2.1.3 里”释放一台打印机只唤醒一个”是怎么高效实现的。
索引方式——为每种状态建一张索引表,表项指向对应的 PCB。查找快,但维护索引表本身有额外开销。
考试主要考”就绪队列可以按优先级组织成多条”这类细节,以及和多级反馈队列的呼应(见 2.2.5)。
进程在内存里的样子
一个进程的地址空间从低地址到高地址大致是这样:
flowchart TB S["<b>栈</b> 局部变量、函数参数、返回地址<br/>(向低地址增长)"]:::rw G1["↓ ↑"]:::gap H["<b>堆</b> malloc / new 动态分配<br/>(向高地址增长)"]:::rw B["<b>BSS 段</b> 未初始化的全局 / 静态变量<br/>(不占文件体积,装入时清零)"]:::rw D["<b>数据段</b> 已初始化的全局 / 静态变量"]:::rw T["<b>正文段</b> 代码 + 常量(只读)"]:::ro S --- G1 --- H --- B --- D --- T classDef rw fill:#dbeafe,stroke:#2563eb,color:#1e3a5f classDef ro fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef gap fill:none,stroke:none,color:#94a3b8
疑问点:教材的三段划分是否准确
命题:“C 语言编写的程序在使用内存时一般分为三个段:正文段(代码和赋值数据段)、数据堆段和数据栈段。二进制代码和常量存放在正文段,动态分配的存储区存放在数据堆段,临时使用的变量存放在数据栈段。”
该表述是否准确?
该表述基本正确,属于教材的粗粒度三分法(正文段 / 堆 / 栈)。逐句核对:
- “二进制代码和常量存放在正文段” ✅ 常量确实和代码一起放在只读区
- “动态分配的存储区存放在数据堆段” ✅
malloc的空间在堆 - “临时使用的变量存放在数据栈段” ✅ 局部变量在栈
它唯一的粗糙之处是把已初始化和未初始化的全局/静态变量都笼统算进了”正文段(代码和赋值数据段)“。更细的划分会把它们分成数据段和 BSS 段。考试里这两种粒度都出现过,看题目给的选项决定用哪一档。
疑问点:
static与const的存储位置教材所称的”静态数据”是否即 C 语言的
static变量?const常量是否会被直接展开到代码之中,从而不占据独立存储?
static:是的。static 变量和全局变量一样有静态存储期——程序一启动就存在,直到结束才消亡,不随函数调用返回而销毁。它们属于静态数据,去处只看有没有被初始化:
- 初始化成非零值 → 数据段(值要存在可执行文件里)
- 未初始化或初始化为 0 → BSS 段(只在文件里记一个”需要多大”,装入时统一清零,所以不占文件体积)
const:不一定展开到代码里,这是个常见的误解。
- 如果是基本类型的
const,且从没被取过地址,编译器通常做常量折叠,直接把值嵌进指令的立即数字段——此时它确实”没有独立存储”。 - 但只要对它取了地址(
&x),或者它是const数组、结构体,它就必须真实占据存储空间,通常放在只读数据段(.rodata),和字符串字面量在一起。
所以准确的说法是:const 的去处取决于编译器优化和是否需要地址,不能一概而论。 考试层面只需记住”常量在只读区”即可。
进程映像 vs 进程上下文
疑问点:进程上下文与进程映像是否是同一概念
两者不同,且是本节最重要的一对边界。这两个词描述的是同一个进程的两个不同侧面:
进程映像回答的是:“这个进程在内存里占了些什么?”
进程映像 = 程序段 + 数据段 + PCB
它是一张空间快照,强调”内存里躺着哪些东西”。
进程上下文回答的是:“要让这个进程从断点原样跑下去,必须保存和恢复哪些内容?”
进程上下文 = 现场信息(寄存器组、PC、PSW、栈指针)+ 控制信息(PCB 中的调度、资源等信息)
它是一份恢复清单,强调”切换时不存就会丢的东西”。
判断某个东西属不属于上下文,用一个问题就够了:它是不是每个进程各有一份、且切换时不保存就会被下一个进程覆盖?
- 通用寄存器的值:每个进程各有一份,会被覆盖 → 属于上下文
- PC 和 PSW:同上 → 属于
- 用户栈:进程私有 → 属于
- PCB 中的调度信息:进程私有 → 属于
疑问点:中断向量存放在何处,是否属于进程上下文
- 下列内容中,不属于进程上下文的是( )。 A. 进程现场信息 B. 进程控制信息 C. 中断向量 D. 用户栈
答案是 C 中断向量。
用上述判据即可直接判定:中断向量是全系统共享一份的。它是一张固定的表,存放在内存的特定低地址区域(或由中断向量寄存器指向),记录每个中断类型号对应的中断服务程序入口地址。
它由 OS 在初始化时建立,之后所有进程共用,不会因为进程切换而改变,也就没有任何理由去保存和恢复它。所以它不属于任何一个进程的上下文。
其余三项都是进程私有的,都属于。
跨科对照:操作系统与计算机组成原理中的"上下文"
“上下文”一词在两门课中所指的粒度并不相同,需分层区别:
出处 上下文指什么 谁来保存 计组·中断响应 断点(PC)+ 程序状态字 PSW 硬件自动完成 计组·中断服务程序 通用寄存器 软件(服务程序开头压栈) OS·进程切换 全部现场 + PCB 控制信息 OS 的切换例程 三者是同一思想在不同层次上的展开:层次越高,需保存的内容越多,开销越大。这也解释了为什么模式切换比进程切换便宜得多——模式切换只需保存前两档,进程切换还要额外处理页表与 TLB。
边界
映像 ⊃ 上下文?不是包含关系
容易误以为上下文是映像的一部分。实际上两者有交集但互不包含:
- PCB 中的控制信息:既在映像里,也在上下文里
- 程序段(代码):在映像里,不在上下文里(代码是只读的,不会被下一个进程改坏,不需要保存)
- CPU 寄存器的当前值:在上下文里,不在映像里(它在 CPU 里,不在内存里)
所以正确的说法是:映像看内存占用,上下文看切换代价。
BSS 不占文件体积,但占内存
这是个常被问的细节。int arr[1000000]; 作为未初始化全局变量,可执行文件只会记录”BSS 需要 4MB”这一个数字,文件本身不会大 4MB。但程序装入运行时,这 4MB 内存是实打实要分配并清零的。
对照速查
| 进程映像 | 进程上下文 | |
|---|---|---|
| 回答的问题 | 内存里有什么 | 切换时要存什么 |
| 组成 | 程序段 + 数据段 + PCB | 现场信息 + 控制信息 |
| 包含代码段 | ✅ | ❌(只读,不必保存) |
| 包含 CPU 寄存器现值 | ❌(在 CPU 里) | ✅ |
| 中断向量 | ❌ | ❌(全系统共享) |
| 变量 | 去处 | 占文件体积 |
|---|---|---|
| 局部变量 | 栈 | 否 |
malloc 分配 | 堆 | 否 |
已初始化全局 / static | 数据段 | 是 |
未初始化全局 / static | BSS | 否(只记大小) |
字符串字面量 / 取过地址的 const | 只读数据段 | 是 |
| 代码 | 正文段 | 是 |
考点
- 中断向量不属于进程上下文(高频选择题)
- 进程映像与进程上下文的区别
- 五个内存段各放什么,BSS 的特殊性
- PCB 按状态组织成多条队列,阻塞队列按事件分开
链接
- 🏠 返回总览:操作系统第 2 章:进程与线程总览
- ⬅️ 上一节:2.1.1 进程的概念和特征
- ➡️ 下一节:2.1.3 进程的状态与转换
- 📖 名词库:第 2 章名词库