第二章(1) 操作系统——进程与线程(英文标注)
一 .引入和基本概念
2.1 程序
程序是存储在磁盘上的静态指令集合。它是一个文件(.exe、可执行文件),是被动的。
2.2 进程与线程的引入
2.2.1 进程的引入
在早期的单道程序时代,程序顺序执行(串行),具有可再现性(简单来说,同一套输入,多次运行结果一样)。但CPU利用率极低(I/O操作时CPU空闲)。
后来引入了多道程序设计,让多个程序同时驻留内存并发执行。这时问题出现了:程序并发执行时,由于CPU不断切换,程序失去了封闭性,执行结果与执行速度有关,出现了“不可再现性”。
这个问题的本质是“程序”这个概念无法描述“正在执行中”的动态变化。
因此,引入了进程(Process)。
2.2.2 线程的引入
虽然进程实现了并发,但随着时间的推移,发现了以下问题:
-
切换开销巨大:进程是资源拥有者,拥有独立的地址空间。当CPU从一个进程切换到另一个进程时,必须切换整个地址空间、更新页表、刷新TLB(快表),耗时极长。
-
通信成本高昂:进程间内存相互隔离,想要通信必须借助内核IPC(如管道、消息队列),速度慢且复杂。
-
无法满足“内部并发”:比如打开一个浏览器,一边下载一边渲染页面,如果只用进程来做,创建多个进程极其浪费资源。
本质是 进程切换时切换地址空间的成本远远跟不上CPU可能的执行速度。而如果将资源分配与调度执行分离。线程切换无需切换地址空间,开销极小,从而使得并发执行的粒度能够匹配上CPU的高速处理能力。
因此,引入了线程(Thread)。
2.3 进程和线程的概念
| 对比维度 | 程序 (Program) | 进程 (Process) | 线程 (Thread) |
|---|---|---|---|
| 简要介绍 | 静态的指令集(硬盘上的文件) | 动态的执行实例(运行中的程序) | 进程内的执行路径(轻量级运行流) |
| 核心角色 | 提供执行内容和蓝图 | 资源分配的基本单位 | CPU调度的基本单位 |
| 存在形态 | 静态的、被动的 | 动态的、主动的(有生命周期) | 动态的、主动的(依附于进程) |
| 生命周期 | 永久的(文件不删就一直存在) | 临时的(创建而生,终止而亡) | 临时的(随进程创建/消亡,或动态创建/销毁) |
| 资源拥有 | 不拥有任何资源(仅占磁盘空间) | 拥有独立资源(独立地址空间、文件、端口) | 共享进程资源(仅拥有私有栈和寄存器) |
| 内存布局 | 仅有代码段 + 静态数据 | 完整的虚拟地址空间(代码+数据+堆+栈) | 同进程内线程共享地址空间,仅栈独立 |
| 通信方式 | 不涉及通信(无法运行) | 进程间通信(IPC):管道、消息队列、共享内存、Socket(需陷入内核,开销大) | 直接读写共享内存(零拷贝,无需内核介入,极快) |
| 上下文切换 | 不涉及 | 重量级切换(需切换页表、刷新TLB/缓存,耗时约1~10微秒) | 轻量级切换(仅切换寄存器和栈指针,无需切换地址空间,耗时不到1微秒) |
| 系统开销 | 几乎为零(仅为存储) | 创建/撤销开销大(需分配独立资源) | 创建/撤销开销小(仅需分配TCB) |
| 独立性 | 相互独立(静态文件) | 高(拥有独立地址空间,一个进程崩溃通常不影响其他进程) | 低(共享地址空间,一个线程崩溃可能导致整个进程崩溃) |
| 操作系统感知 | 感知不到(当作普通文件管理) | 强烈感知(通过PCB管理) | 看情况(内核级线程感知,用户级线程不感知) |
进程存在的唯一标识——PCB(Process Control Block,进程控制块)

| 字段 | 作用 | 补充 |
|---|---|---|
| pointer(指针) | 指向下一个PCB的地址,用于将多个PCB链接成队列(如就绪队列、阻塞队列)。 | 这就是操作系统把进程挂入“就绪队列”或“阻塞队列”的具体实现方式。 |
| process number(进程号/PID) | 进程的唯一身份证号(整数)。 | 父进程fork()返回的那个数字。 |
| process state(进程状态) | 记录当前进程处于就绪、运行、阻塞、挂起等哪种状态。 | OS根据它决定下一步动作。 |
| program counter(程序计数器/PC) | 记录进程下一条要执行的指令在内存中的地址。 | 上下文切换(Context Switch) 的核心。当进程被踢下CPU(Running→Ready)时,OS把PC值存到这里;当进程再次上CPU时,OS从这里取出PC恢复执行。 |
| registers(寄存器集合) | 保存CPU内部所有通用寄存器、栈指针、状态字的值。 | 进程切走时,寄存器的值必须原封不动存到这里,否则回来时数据全乱了 |
| memory limits(内存界限) | 记录进程占用的内存基址和长度,防止它越界访问其他进程的内存。 | 保证进程间地址空间隔离,这是进程作为“资源拥有者”的关键凭证。 |
| list of open files(打开文件列表) | 记录进程当前打开的所有文件、管道、网络连接的指针。 | 这就是进程“拥有资源”的具体体现。线程共享这个列表,所以线程间打开的文件是共用的。 |
上下文切换(Context Switch):CPU从正在运行的process、thread切换时,保存当前运行现场、加载新任务现场的全过程
2.4 进程的状态

2.4.1 含义
| 状态 | 含义 |
|---|---|
| New(新建态) | 操作系统刚刚收到创建请求,正在分配PCB(进程控制块)和初始资源,但还没允许它进入系统。 |
| Ready(就绪态) | 进程具备了一切运行条件(内存、资源都齐了),在就绪队列里排队等待调度。 |
| Running(运行态) | 进程此刻正占用CPU执行指令。 |
| Waiting/Blocked(阻塞态) | 进程在运行中因资源需要或等待其他进程响应,主动放弃CPU等待。 |
| Terminated(终止态) | 进程结束,操作系统回收它占用的所有资源,PCB即将被销毁。 |
2.4.2 进程控制(process operation)—— 状态切换
原语(Primitive):原语是由若干条机器指令构成、执行过程不可中断的一段内核程序,用于完成特定进程控制功能。(内核底层、原子执行,是实现同步互斥 / 进程管理的基础)
| 转换线 | 触发事件 | 执行的操作(原语) | 执行 |
|---|---|---|---|
| New → Ready | 系统允许进程从外存进入主存 | 创建原语 (Create) (分配PCB、分配初始资源、初始化PCB) |
操作系统内核 (父进程调用 fork(),由内核响应) |
| Ready →Running | 调度程序选中该进程 | 调度原语 (Dispatch/Schedule) (从就绪队列摘出,恢复进程上下文到CPU) |
操作系统调度器 (内核的调度模块) |
| Running → Ready | 时间片用完 / 被高优先级抢占 | 中断处理原语 (Interrupt Handle) (保存当前进程上下文到PCB,将其挂入就绪队列) |
操作系统内核 (时钟中断或I/O中断处理程序强制触发) |
| Running →Waiting | 请求I/O / 等待资源 / wait() |
阻塞原语 (Block/Wait) (保存现场,将进程从运行态移出,挂入阻塞队列) |
进程主动调用 (例如调用 read()、wait()系统调用) |
| Waiting → Ready | I/O完成 / 资源到位 / 事件发生 | 唤醒原语 (Wakeup/Signal) (将PCB从阻塞队列摘下,状态改为就绪,挂入就绪队列) |
操作系统内核 (设备中断处理程序或 signal释放资源时触发) |
| Running→Terminated | 正常结束 / 异常 / 被杀死 | 终止原语 (Exit/Kill) (回收PCB、回收所有内存和资源、通知父进程) |
进程主动调用exit(),或父进程/内核强制kill |
创建原语 (Create) -- 父进程与子进程
- 资源共享(Resource sharing):全部、部分[fork()+写时复制COW]、不共享[(fork()+exec()]
- 执行(Execution):同时执行,父进程会阻塞直到子进程终止
- 进程的创建、程序替换、同步与终止的UNIX/Linux 系统编程代码:
#include <stdio.h>
void main(int argc, char *argv[])
{
int pid;
/*fork another process */
// 创建子进程系统调用fork()
pid = fork();
// 分支1:fork调用失败(内存不足/进程数上限)
if (pid < 0) {
fprintf(stderr, "Fork Failed");
// 标准错误流打印失败信息
exit(-1);
}
// 分支2:pid == 0 代表当前是子进程
else if (pid == 0) {
execlp("/bin/ls", "ls", NULL);
// 加载执行ls命令,替换子进程全部内存镜像
// execlp执行成功后,后续代码永远不会运行;执行失败才会向下执行
}
// 分支3:pid > 0 代表当前是父进程,pid是子进程PID
else {
wait(NULL);
printf("Child Complete");
exit(0);
}
}
2.5 进程的通信 (IPC,Inter-Process Communication)
2.5.1 两个的独立进程,怎么找到对方共同的“内核对象”(管道、共享内存、消息队列)
| 类型 | 代表机制 | 如何找到对象? | 适用范围 |
|---|---|---|---|
| 匿名 (Anonymous) | 匿名管道(pipe()) |
靠继承。父进程通过 fork() 把管道的文件描述符直接传给子进程。 |
仅限于有亲缘关系(父子/兄弟进程)。 |
|
命名 (Named) |
命名管道(FIFO)、共享内存、消息队列、Socket | 靠路径名或Key。进程根据事先约定好的字符串(如 /tmp/my.pipe 或 12345)向内核“申请”打开。 |
任意进程 (无亲缘关系也能通信)。 |
2.5.2 通信方式
| IPC方式 | 通信范围 | 同步/异步 | 数据边界 | 速度排名 | 典型应用 |
|---|---|---|---|---|---|
| 信号 (Signal) | 单机 | 异步 | 无数据负载(仅传信号编号) | 极快 | 进程异常终止、定时器、SIGCHLD |
| 管道 (Pipe) | 匿名(亲缘) / 命名(任意) | 同步阻塞 | 无格式字节流(无边界) | 慢 | Shell命令连接(ls | grep) |
| 消息队列 (MQ) | 任意进程(单机) | 通常异步 | 有边界数据报(按类型/长度取) | 中等 | 异步任务分发、日志收集 |
| 共享内存 (SHM) | 任意进程(单机) | 同步(需配合锁) | 原始内存块(无格式) | 极快 | 数据库缓存区、高速数据交换" |
| 信号量 (Semaphore) | 任意进程(单机) | 同步工具 |
计数器(整数)(非数据通道) |
快 | 保护共享资源(互斥锁/读写锁) |
| 套接字 (Socket) | 跨网络(也可单机) | 同步/异步 | TCP 字节流无边界;UDP 数据报自带边界 | 较慢 | Web服务器、浏览器、分布式系统 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)