需求

操作系统有 4 个重要的功能:文件管理、内存管理、外设管理和进程管理。因此,有必要深究一下操作系统管理进程的方式。

在计算机中,进程和操作系统的视角是不对等的。利用虚拟化技术,操作系统让进程产生一种幻象:进程占据了物理机中所有的计算资源。但实际上,操作系统可能同时运行许多进程。

理解这种不对等视角对理解进程管理有很大的帮助。操作系统就像一个花花公子,同时和许多妹子 date,而每个妹子都认为自己是这个花花公子的唯一。

进程的视角

进程的用户区虚地址空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
--------------------
| null |
-------------------- 0x401000
| 代码段 |
-------------------- 0x404000
| 数据段 |
-------------------- 0x405000
| 只读数据段 |
-------------------- 0x406000
| bss 段 |
--------------------
| 堆 |
--------------------
| null |
-------------------- 0x7ff000
| 栈 |
-------------------- 0x800000

一个程序的典型内存布局如上图所示,不同程序的逻辑段长度可能不同,比如代码段可能是 0x8000 或者 0xC000 的长度,但逻辑分布是不变的。

  • 代码段存放了程序定义的所有函数,比如 main0printf 等。
  • 数据段 存放了带初值的全局变量;
  • 只读数据段 存放了常量;
  • bss 段存放了不带初值的全局变量,你可能会在学习高程的时候有疑问,凭什么不带初值的全局变量会初始化为 0,实际上这是操作系统对程序分配内存时所做的工作,对 bss 段清 0;
  • 存放了通过 mallocnew 分配的动态内存;
  • 用来存放函数的局部变量,注意到栈从内存地址的最高端开始,这样方便堆和栈此消彼长地动态利用所有剩余的内存空间。

进程的完整虚地址空间

前面讲的是进程的用户区,在低地址,存放应用程序。而高地址区存放操作系统内核。

在 Unix V6++ 系统中,用户区是 $[0, 0\text{x}800000)$,核心区是 $[0\text{xC}0000000, 0\text{xC}0400000)$

在核心区中,会存放代码、全局变量,内核堆,页表区以及 User + 核心栈。

虚地址空间

看到许多不熟悉的标识符,不要紧,下面会逐个讲解。

操作系统的视角

进程管理

在操作系统的视角中,为了维护一个进程的信息,需要如下三个部分:PCB,应用程序,内核。而一个进程私有的部分是:PCB应用程序核心栈

应用程序前面已经讲解过了,就是一个程序的代码段、数据段等一堆逻辑段信息,分配了虚拟内存空间。没什么难度。


PCB 由两个内容组成:Process[n] + user。

Process 叫进程基本控制块,属于核心区中的全局变量部分,常驻内存。Process 是一个数组,每一项会存放进程的一些状态信息,如 PID、优先级、运行状态、一些指针等等。它必须常驻内存,否则操作系统就失去了对进程的控制。

user 结构存放了进程使用的寄存器保存区、文件描述符表、当前工作目录等信息。


核心栈存放了执行系统调用所需的子程序栈帧。

接下来你可能又有许多疑问,比如,页表区是什么?在虚地址空间中,每个进程在内核区都有代码、全局变量、页表区这些公共部分,会不会造成重复?这就是映射的魅力,看上去每个进程都有一个副本,实际上指针指向了同一个地方。

物理地址映射

虚拟内存是幻象,看上去每个程序都能独占无限的地址空间,实际上还是要映射到有限的物理内存中。

我觉得可以这样理解物理内存的语义划分。系统区存放了所有进程需要共用的信息,用户区的代码段存放了同一个程序的不同副本——同一程序不同进程之间可以共用的信息,如代码段和只读数据段(当然 Unix V6++ 把只读数据段放到了可交换区),可交换部分则存放了每个进程自己的内容,无法共享的内容。

这里有一个特殊的地方,核心空间页表的最后一项映射到了 p_addr 的起始位置,这是当前虚拟空间所对应进程 PPDA 区的首地址。因为确实是核心空间,但是却是可交换的,所以有这样一个孤立的映射逻辑。

物理内存中的进程图像

注意到有两个指针,x_caddrp_addr

前者在 Text 结构中,属于内核的全局变量部分,和 Process 数组一样,表示 core address of text,指向共享代码段在物理内存中映射的位置。

后者在 Process 数组中,指向进程的可交换部分物理内存的首地址。

进程组成

总之,在映射的时候,需要把逻辑结构打散,一一对应到物理结构中。上帝的归上帝,凯撒的归凯撒。例如下面这个例子。

一个地址映射的例子

Unix V6++ 系统,进程 PB 有 3 页代码段,1 页数据段,1 页 bss 段,2 页堆栈。x_caddr = 0x600000(代码段,物理内存首地址),p_addr = 0x800000(可交换部分,物理内存首地址)。

求进程 PB 的页表。


解:

虚拟页号 物理页号
0x401 0x600
0x402 0x601
0x403 0x602
0x404 0x801
0x405 0x802
null
0x7fe 0x803
0x7ff 0x804
null
0xC000 0
0xC001 1
0xC03fe 0x3fe
0xC03ff 0x800

分析一下,首先,从 0x4010x403 是代码段,从 x_caddr 开始映射。0x404 是数据段,0x405 是 bss 段,应该从 p_addr 指向的位置开始,但是由于 0x800 留给了 0# 进程,因此从 0x801 开始。

之后,从 0xC000 开始是系统区,前 1023 个页面按照递增的顺序映射,最后 0# 进程的 PPDA 映射到 p_addr 指向的位置。

一个类比

程序就像是和花花公子 date 的女孩子,而花花公子就是操作系统。在女孩子眼中,她是花花公子的唯一,因为在她目之所及的所有地方,她能够使用花花公子的全部资源与时间(虚拟内存空间)。但花花公子不一样,他需要通过 PCB 记录每个女孩子的信息,她是谁?她高兴吗?她在心中的优先级如何?上次聊天进行到了哪里?

在花花公子的秘密日记本中,process 数组是必须存在的,因为它记录了每个女孩子的信息。如果花花公子很忙,和女孩子说“过一会再联系(swap)”,那必须在 process 中知道女孩子被 swap 到了什么地方,不然就丢失了这一信息。

相比之下,核心栈和 user 结构可以 swap 走,只要能根据 process 重新得到它们便没问题。

你会说,其实女孩子也知道花花公子有个日记本(进程的虚拟内存空间包含系统区),会不会他的秘密暴露了?不会的,因为处在用户态的女孩子没有权限翻阅花花公子的日记本,只有在内核态时才有可能访问内核空间。内核态的女孩子已经被花花公子迷住了,早就忘记了日记本的事情。

如果觉得我总结的不好,那就看看 AI 的解读吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
The "playboy" (the kernel)

Always keeps a little black book (proc[] table):

Who all the girls (processes) are,

Their status (happy, waiting, sleeping, angry zombie 👻),

Their priority (VIP vs casual date),

And where to find them if they’ve gone home (swapped out).

This book never leaves his pocket — always in memory.

---

The "girls" (applications/processes)

Each girl thinks: “I’m the only one.”

She only sees her own virtual space (text, data, stack).

She doesn’t know she’s just an entry in the playboy’s proc[] book.

---

Dating in action (system calls)

When the girl needs attention, she reaches for the phone line → that’s the kernel stack.

The playboy answers and looks at the user profile (the u-area) to remember her preferences:

What files she has open,

What directory she’s in,

What signals she can handle,

Etc.

---

Swapping

If the playboy is too busy, he says: “Wait at home, I’ll call you later.”

That means: the girl (process image + u-area) is swapped to disk.

But her entry in the black book (proc[]) remains, so he never forgets she exists.

---

Why the girl never sees the black book

If each girl knew about the other entries in proc[], the illusion would break: they’d realize they’re just one of many!

Likewise, processes cannot see proc[], because that’s kernel-private state.

They only see their own “relationship view”: text, data, user stack, plus whatever kernel exposes through system calls.