进程的组成

这一节有两个层面,考试都会碰:

  • 对外:系统里那么多 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 中的调度信息:进程私有 → 属于

疑问点:中断向量存放在何处,是否属于进程上下文

  1. 下列内容中,不属于进程上下文的是( )。 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数据段是
未初始化全局 / staticBSS否(只记大小)
字符串字面量 / 取过地址的 const只读数据段是
代码正文段是

考点

  • 中断向量不属于进程上下文(高频选择题)
  • 进程映像与进程上下文的区别
  • 五个内存段各放什么,BSS 的特殊性
  • PCB 按状态组织成多条队列,阻塞队列按事件分开

链接