04. The Abstraction: The Process¶
OS 提供了一种 illusion,那就是有无数的 CPU 可以用。
Virtualizing¶
Virtualization 的一个重要手段是 time sharing。
Policies 是操作系统做决策的算法。比如 Scheduling policy 是操作系统决定哪个进程先运行的算法。
Process¶
Process 是操作系统为程序提供的抽象。
重点是深刻理解「程序就是状态机」。如何理解呢:
-
寄存器的值,其中比较特殊的是 PC(程序当前在执行哪个指令)
-
内存的内容:栈,堆,全局变量。(指令存在内存里。)
-
(多线程时)每个线程各自的 PC + 栈
以上整个集合,叫 program state. 在某一个瞬间,只要这些东西都确定了,程序接下来会做什么就是完全确定的。
Process Creation¶
OS 在运行一个进程之前要做很多事情:
-
加载程序的二进制可执行文件 .out 和静态数据到内存
在最古老的操作系统里,OS 在创建进程之前,需要一次性把程序从 disk 加载到内存。
现代操作系统 lazily 加载,在创建进程时只建立虚拟地址空间和页表映射,代码和数据在首次访问时通过 page fault 按需加载到物理内存。
静态数据包括:
.text:程序代码.data:已初始化的全局/静态变量.bss:未初始化的全局/静态变量(OS 会把它们初始化为 0).rodata:只读数据,比如字符串常量 -
OS 为程序的运行时栈分配内存。
OS 会为主线程分配运行时栈,通常在虚拟地址空间里预留一定大小(比如 8 MB)。对于多线程程序,OS 会为每个新线程分配自己的栈。
-
可能为堆分配内存。
OS 通常不会一次性分配整个堆,而是给进程一个初始的 heap 段空间(由 libc 管理)。 当程序调用
malloc()时,可能会触发 OS 系统调用(mmap)来扩展堆。 -
初始化 I/O,打开标准输入,输出,错误
-
跳转到函数入口 _start.
OS 并不知道你程序的 main() 在哪里。
_start是 C runtime(crt0)提供的入口:OS 让程序计数器(PC)指向_start。_start做一些初始化:-
设置 argc、argv、envp
-
初始化全局构造函数(C++)
-
调用
main(argc, argv, envp)注意,main 是由_start调用的! -
调用
exit()回到 OS
OS 怎么知道
_start在哪里呢?操作系统从 ELF 的入口点e_entry开始执行。链接器约定_start放在.text段的最前面。CPU 执行入口点地址对应的指令(一般就是_start)。【后面应该还会学到的。】 -
Process State¶

Data Structures¶
OS 本身也是一个程序,它一个核心数据结构叫 PCB(Process Control Block,进程控制块),每个进程都有一个 PCB。这个维护的数据结构保证了当 CPU 从一个进程切换到另一个进程时,可以完整地保存和恢复执行状态。
一个简化的 PCB 的例子:
typedef struct PCB {
// 1️⃣ 进程标识
int pid; // 进程ID
int ppid; // 父进程ID
char name[32]; // 进程名称
// 2️⃣ 进程状态
enum { NEW, READY, RUNNING, BLOCKED, TERMINATED } state;
// 3️⃣ CPU 上下文(寄存器)
unsigned long pc; // 程序计数器(PC)
unsigned long sp; // 栈指针(SP)
unsigned long regs[16]; // 通用寄存器,数量根据 CPU 架构不同
// 4️⃣ 内存管理信息
void *page_table; // 指向页表或段表
unsigned long heap_start; // 堆起始地址
unsigned long heap_end; // 堆结束地址
unsigned long stack_base; // 栈底
unsigned long stack_limit;// 栈顶
// 5️⃣ 文件和 I/O
int fd_table[16]; // 打开的文件描述符表
// 6️⃣ 调度信息
int priority; // 优先级
unsigned long cpu_time; // 已使用 CPU 时间
} PCB;