Contents

计算机操作系统

参考资料

learning outcomes

  • 谈谈操作系统的 整体结构核心功能
  • 进程、线程、调度 的完整生命周期及其关键数据结构(PCB、VMA、inode)
  • 内存管理(分区、分页、伙伴系统、页面置换)与 文件系统(inode、日志、挂载) 的工作原理
  • 中断、I/O 调度、设备驱动 的基本流程
  • 设计 线程池 并正确使用同步原语保证线程安全
  • 说说 死锁、饥饿、实时调度

看完下面的内容再回来尝试一下

操作系统概述

定义

操作系统(Operating System, OS) 是一种 系统软件,位于硬件与用户或应用程序之间
负责 控制、管理 计算机的软硬件资源,并为上层程序提供 统一、抽象且安全的接口

基本功能

  • 进程/线程管理
    负责创建、调度、终止进程/线程,提供同步与通信机制

  • 内存管理
    分配/回收内存、实现虚拟内存、页面置换

  • 文件系统
    抽象存储设备为文件/目录,提供持久化存取

  • 设备管理
    统一管理 I/O 设备,提供驱动程序、缓冲、调度

现代 OS 还会提供 网络栈、系统安全、资源隔离(容器/虚拟化) 等扩展功能

特征

特征 含义
并发性(Concurrency) 多个程序/线程在同一时间段交错执行(时间片轮转等),例如进程/线程调度
并行性(Parallelism) 真正的 同时 执行,依赖多核/多处理器硬件
共享性 资源(CPU、内存、I/O)可被多个执行体同时使用,例如共享内存、文件系统
虚拟化(Virtualization) 物理实体 抽象为 若干逻辑实体(如虚拟内存、虚拟设备),例如页表、虚拟机
异步性(Asynchrony) 事件驱动的处理方式,调用者不必阻塞等待
可移植性 OS 通过硬件抽象层(HAL)实现跨平台,例如 POSIX

“并发”不等同于“并行”。并发是 调度层面的交错,并行是 硬件层面的同时间执行
虚拟化和异步的前提是 并发:只有在并发环境下,才能把资源抽象为逻辑实体或采用非阻塞 I/O

时分复用:不同程序 分时使用 硬件资源,提高资源利用率
空分复用:将物理磁盘虚拟为逻辑磁盘,例如电脑的 C、D 盘等

按使用场景分类

类别 典型系统 主要特征
批处理系统 IBM OS/360 无交互、作业调度为主
分时系统 Unix、VMS 多用户共享 CPU,采用时间片轮转
实时系统 VxWorks、RTLinux 确保硬实时或软实时响应
嵌入式系统 FreeRTOS、Zephyr 资源受限、定制化驱动
通用操作系统 Windows、Linux、macOS 支持图形 UI、网络、丰富的服务

系统结构

内核结构模型

模型 说明 优缺点
单体内核(Monolithic) 所有核心服务(进程管理、文件系统、网络栈等)在同一地址空间 高效、实现简单;缺点是模块间耦合度高,出错风险大
微内核(Microkernel) 只保留最小的进程调度、IPC、基本内存管理,其余服务运行在用户态 高可靠性、易扩展;但 IPC 开销相对大
混合内核(Hybrid) 兼具单体内核的效率和微内核的模块化(如 Windows NT、现代 Linux) 实际使用最广
外核/Exokernel 将硬件抽象最小化,直接把硬件资源暴露给上层库 极端灵活,适合研究实验

Linux 采用 混合内核:核心功能在内核空间,驱动、文件系统等可编译为模块(loadable kernel modules, LKM)

用户态 vs. 内核态

特性 用户态 内核态
访问权限 受限,只能访问用户空间 完全访问硬件、内存
执行指令 只能执行非特权指令 可执行特权指令(I/O、控制寄存器)
切换方式 系统调用、异常、软中断 硬件中断、系统调用返回
开销 需要保存/恢复上下文,开销相对大

系统调用 是用户态程序进入内核态的唯一受控入口,如 read()、write()、fork() 等。

进程 vs. 线程

项目 进程 线程
资源拥有 拥有独立的地址空间、打开的文件描述符、PCB 与同属进程的线程共享地址空间、文件描述符、信号
切换开销 较大(需要切换页表、TLB) 较小(仅切换寄存器、栈)
创建/销毁 较慢(fork() 较快(pthread_create()
适用场景 需要强隔离、不同安全域 需要高并发、共享数据的场景

进程管理

进程的概念与控制块 PCB

进程资源分配和调度的基本单位,是 程序运行时动态实例

Process Control Block 是进程存在的唯一标识,记录进程的状态和运行的信息
典型的 PCB(结构体示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
typedef struct pcb {
    pid_t       pid;            // 进程标识符
    pid_t       ppid;           // 父进程标识符
    int         state;          // 进程状态(就绪、运行、阻塞、终止)
    struct context ctx;         // CPU 上下文(寄存器、PC、SP)
    void       *kstack;         // 内核栈基址
    void       *ustack;         // 用户栈基址
    struct mm_struct *mm;       // 地址空间描述(页表、段表)
    struct files_struct *files; // 打开的文件描述符表
    struct list_head children;  // 子进程链表
    struct list_head sibling;   // 同父进程的兄弟链表
    // … 其他资源(信号、计时器、调度信息) …
} pcb_t;

PCB 中记录的关键字段:

  • PID:唯一标识
  • 状态TASK_RUNNINGTASK_INTERRUPTIBLETASK_UNINTERRUPTIBLETASK_STOPPEDTASK_ZOMBIE
  • 上下文:保存寄存器、程序计数器、栈指针,以便 上下文切换 时恢复
  • 地址空间:指向页表/段表结构
  • 打开的文件:文件描述符表
  • 调度信息:优先级、时间片、调度策略

进程状态模型(五状态 + 挂起)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
+-------------------+
|   创建 (new)      |
+--------+----------+
         |
         v
+--------+----------+          +-----------------+
|   就绪 (ready)    |<--------|   挂起 (suspend)|
+--------+----------+          +-----------------+
         |
+--------v----------+
|   运行 (running) |
+--------+----------+
         |
+--------v----------+
|   阻塞 (blocked) |
+--------+----------+
         |
+--------v----------+
|   终止 (exit)    |
+-------------------+
  • 创建(new):系统为进程分配 PCB,但尚未分配全部资源。
  • 就绪(ready):所有资源(内存、文件句柄)已就绪,等待 CPU。
  • 运行(running):获得 CPU,正在执行指令。
  • 阻塞(blocked):因等待 I/O、信号、同步对象等而主动放弃 CPU。
  • 挂起(suspended):进程已被换出至磁盘(Swap)或被管理员暂停。
  • 终止(exit):进程结束,资源回收,PCB 进入 僵尸(zombie) 状态直至父进程 wait()

进程的创建、执行与终止

系统调用 作用 关键点
fork() 创建子进程,复制父进程的地址空间(写时复制) 返回值:子进程 0、父进程子 PID
execve() 用新程序替换当前进程映像 只保留 PID、打开的文件描述符(可设 FD_CLOEXEC
vfork() exec 设计的轻量 fork,父进程在子进程 exec 前阻塞 用于性能敏感的场景
clone() Linux 特有,可控制子进程共享哪些资源(如线程) clone() + CLONE_VM 实现线程
exit() 结束进程,返回退出码 触发资源回收、父进程 wait
wait()/waitpid() 父进程收集子进程退出状态 防止僵尸进程

上下文切换(Context Switch)

  1. 保存当前进程上下文(寄存器、程序计数器、栈指针)到 PCB。
  2. 切换页表(若进程地址空间不同)。
  3. 调度器选择下一个就绪进程
  4. 恢复新进程上下文,返回到用户态或内核态继续执行。

开销:约 1–5 µs(取决于硬件、TLB 刷新、缓存失效)。
优化:使用 TLB 刷新延迟CPU 亲和性(CPU affinity)等技术。

进程调度层次

调度层次 名称 作用
长程调度(作业调度) job scheduler 决定哪些作业进入系统(系统负载控制),对应 作业管理
中程调度(交换调度) swapper 将进程从主存换出至 Swap,或换回;实现 多道程序
短程调度(CPU 调度) CPU scheduler 从就绪队列挑选进程分配 CPU(时间片轮转、优先级等)

进程同步

四大原则

原则 含义
互斥(Mutual Exclusion) 同一时刻只能有 一个 进程进入临界区
进度(Progress) 当没有进程在临界区时,不阻塞 进入临界区的进程
有界等待(Bounded Waiting) 对每个进程,进入临界区的等待次数 有上限(防止饥饿)
顺序性(Ordering) 对共享资源的所有操作必须 按程序顺序 执行(防止重排)

这四条原则是 正确实现同步 的理论基石。若任意一条被破坏,都可能导致 死锁、饥饿或竞争错误

常用原语

原语 类型 关键特性
互斥锁(Mutex) 阻塞 lock()/unlock(),保证互斥
自旋锁(Spinlock) 非阻塞(自旋) 适用于短临界区,避免上下文切换
读写锁(RWLock) 阻塞 多读者共享,写者独占
信号量(Semaphore) 阻塞/计数 wait()/post(),可实现资源计数
条件变量(Condition Variable) 阻塞 wait() + signal()/broadcast(),配合互斥锁
屏障(Barrier) 阻塞 多线程同步点,全部到达后放行
原子操作(Atomic) 非阻塞 compare_and_swapfetch_add 等硬件指令
内存屏障(Memory Barrier) 非阻塞 防止指令/内存访问重排

实现示例(POSIX)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cv  = PTHREAD_COND_INITIALIZER;
int ready = 0;

void *producer(void *arg) {
    pthread_mutex_lock(&mtx);
    // 产生数据
    ready = 1;
    pthread_cond_signal(&cv);
    pthread_mutex_unlock(&mtx);
    return NULL;
}

void *consumer(void *arg) {
    pthread_mutex_lock(&mtx);
    while (!ready)               // 防止伪唤醒
        pthread_cond_wait(&cv, &mtx);
    // 消费数据
    ready = 0;
    pthread_mutex_unlock(&mtx);
    return NULL;
}

死锁(Deadlock)

类型 描述
死锁 进程之间由于 竞争资源 或者 彼此通信 或者 调度顺序不当 所造成的阻塞现象
饥饿 进程长期得不到资源
死循环 代码存在 BUG
  • 死锁产生的前提(死锁的必要条件)
条件 说明
互斥(Mutual Exclusion) 资源一次只能被一个进程占用
占有且等待(Hold and Wait) 进程已占有至少一个资源,同时请求新的资源
不可剥夺(No Preemption) 已分配的资源只能由占有进程自行释放,不能被强制抢占
循环等待(Circular Wait) 存在进程 → 资源 → 进程的环形等待链,链中的每个进程都在等待下一个进程所占有的资源

只要破坏其中任意一条,就能 预防 死锁。

死锁处理策略

策略 说明 适用场景
预防(Prevention) 通过破坏必要条件(如禁止占有且等待)来根除死锁 系统对响应时间要求不高
避免(Avoidance) 动态检查资源分配是否会导致不安全状态(如 银行家算法 资源种类有限且需求可预测
检测(Detection) 运行时检测死锁(资源分配图、等待图)并触发恢复 资源需求不可预知,且系统能容忍短暂死锁
恢复(Recovery) 终止或回滚进程,抢占资源 检测到死锁后执行
  • 银行家算法(Deadlock Avoidance)

    1. 系统状态
      • Available: 各类资源当前可用数量
      • Max: 每个进程最大需求矩阵
      • Allocation: 已分配矩阵
      • Need = Max - Allocation
    2. 安全性检查:模拟分配后是否仍能满足所有进程的 Need。若能,则系统处于 安全状态,否则拒绝分配。
    3. 实现要点
      • 只在 可抢占 环境下有效(如内存页可回收)
      • 需要 进程提前声明最大需求,不适用于动态需求变化的场景。
  • 死锁检测(资源分配图)

    • 资源分配图(RAG):节点分为 进程(圆)和 资源(方),边表示 请求分配
    • 等待图(Wait‑For Graph, WFG):仅保留进程节点,若 Pi → Pj 表示 Pi 正在等待 Pj 持有的资源。环路即死锁。
  • 恢复策略

    方法 说明
    终止进程 直接杀死一个或多个进程,释放资源(优先终止低优先级或占用资源最多的进程)
    资源抢占 强制回收进程持有的可抢占资源(需要进程能安全回滚)
    回滚 将进程回滚到安全检查点(事务式系统)

进程调度

计算机通过决策决定哪个进程获得 CPU 使用权

  • 长程调度

    • 目标:控制系统负载、实现 多道程序(提高 CPU 利用率)。
    • 策略
      • FCFS(先来先服务):简单但可能导致 短作业延迟
      • 多级队列:将作业按优先级、资源需求划分不同队列。
      • 作业控制块(JCB):记录作业的状态、所需资源、优先级等。
  • 短程调度(CPU 调度)

    进程主动放弃(非抢占),正常结束、调用 exit()sleep()wait()I/O 阻塞 系统强制抢占,时间片耗尽、出现更高优先级进程、I/O 中断、系统调用 sched_yield()

调度机制与常见算法

机制 说明
排队(Queue) 就绪进程排成链表/队列,依据调度算法挑选
委派(Dispatch) 调度器将 CPU 分配 给选中的进程(上下文切换)
上下文切换(Context Switch) 保存/恢复 PCB,切换页表、TLB 等
类别 算法 关键特性 适用场景
非抢占 FCFS(First‑Come‑First‑Served) 按到达顺序,平均等待时间大 批处理系统
非抢占 SJF(Shortest Job First)
(非抢占)
最短作业优先,最小化平均等待时间,但易产生饥饿 作业长度已知的批处理
抢占 RR(Round‑Robin) 时间片轮转,公平性好 分时系统、交互式
抢占 Priority Scheduling(抢占式) 按优先级调度,优先级相同时可采用 RR 实时系统、资源紧张
抢占 Multilevel Queue 多个队列,每队列采用不同算法,队列间有固定优先级 多类作业(交互式 vs. 批处理)
抢占 Multilevel Feedback Queue (MLFQ) 进程可在队列间迁移,动态调节响应时间 兼顾交互性与吞吐量
实时 Rate‑Monotonic (RM) 固定优先级,周期越短优先级越高(硬实时) 硬实时系统
实时 Earliest Deadline First (EDF) 动态优先级,最早截止时间的任务优先(软/硬实时) 动态实时调度
混合 CFS (Completely Fair Scheduler) – Linux 基于红黑树的 虚拟运行时间,近似公平 通用 Linux 系统

调度评价指标

指标 含义
CPU 利用率 CPU 实际工作时间占总时间的比例
吞吐量(Throughput) 单位时间内完成的作业/进程数
周转时间(Turnaround Time) 作业提交到完成的总时间
等待时间(Waiting Time) 作业在就绪队列中等待的时间
响应时间(Response Time) 第一次调度到进程开始执行的时间(交互式系统关键)
公平性(Fairness) 资源分配是否对所有进程均等(避免饥饿)

存储管理

内存的分配与回收

  • 动态分区分配算法

    算法 原理 优点 缺点
    首次适应(First‑Fit) 从低地址向高地址扫描,找到第一个足够大的空闲块 实现简单,搜索时间短 易产生 外部碎片
    最佳适应(Best‑Fit) 在所有空闲块中挑选最小的满足需求的块 减少碎片 需要遍历全部空闲块,搜索时间长
    最差适应(Worst‑Fit) 选取最大的空闲块 产生的大块碎片易合并 仍会产生碎片,且浪费大块空间
    快速适应(Next‑Fit) 类似首次适应,但从上一次分配结束位置继续搜索 对循环负载有优势 与首次适应类似的碎片问题
  • 内存回收的过程
    当回收区出现在空闲区的不同位置的时候,回收区和空闲区是直接合并,还是需要创建新的空闲节点?
    新的空闲区使用的地址是哪一个?

    • 立即合并(Immediate Coalescing):释放块后立即检查前后相邻块是否空闲,若是则合并为更大的块。
    • 延迟合并(Deferred Coalescing):将空闲块挂入空闲链表,等到 分配请求内存回收周期 再统一合并。
    • 合并策略
    • 地址顺序链表:空闲块按起始地址排序,便于检查相邻块。
    • 双向链表:便于向前/向后遍历合并。

    新空闲块的起始地址被释放块的起始地址;若合并后形成更大的块,则该块的起始地址为最左侧块的地址。

虚拟内存与分页

虚拟内存概念

  • 虚拟地址空间:每个进程拥有独立的 32/64 位线性地址空间。
  • 页面(Page):固定大小的块(常见 4 KB、2 MB、1 GB 大页),是虚拟内存管理的基本单元。
  • 页表(Page Table):记录 虚拟页号 ↔ 物理帧号 的映射关系。
  • TLB(Translation Lookaside Buffer):CPU 中的高速缓存,用于存放最近使用的页表项,降低地址转换延迟。

页面置换算法

算法 思路 优点 缺点
FIFO 先进入的页先淘汰 实现简单 Belady 异常(可能更差)
LRU 最近最少使用的页淘汰 接近最优 需要维护访问时间,开销大
Clock(Second‑Chance) 环形指针 + 访问位 近似 LRU,硬件实现友好 仍有一定误差
NRU(Not Recently Used) 定期清除访问位,分四类 简单、适用于 OS 定时器 可能产生误淘汰
随机(Random) 随机淘汰 实现最简 性能不可预测
工作集(Working Set) 维持进程最近 N 条指令的页面集合 减少缺页率、避免抖动 需要额外统计开销

交换(Swapping)与页面置换的关系

  • Swap 区:磁盘上专门用于保存被换出的页面或整个进程的 交换空间
  • 页面置换:当物理内存不足时,页面 被写入 Swap;当页面再次被访问时,触发 缺页异常,内核将其调回内存(可能再置换其他页面)。

Linux 中的 Swap 采用 页面级 交换(默认 4 KB),并通过 swapiness 参数控制换出倾向。

伙伴系统(Buddy System)

  • 原理:将空闲内存块划分为 2 的幂次大小的 伙伴(如 2 KB、4 KB、8 KB…),当分配请求到来时,找最小的足够块;若块太大则 拆分 成两个相同大小的伙伴;释放时若其伙伴也是空闲,则 合并 成更大的块。
  • 优点:快速分配/合并,碎片率低,适合 页框分配(Linux 物理页帧)和 内核对象(Slab)管理。
  • 缺点:只能分配 2 的幂次大小,导致 内部碎片

Linux 的存储管理

常用命令free -hcat /proc/meminfovmstattophtopcat /proc/[pid]/smaps

Buddy 内存管理算法:解决内存外碎片的问题

Swap 空间是 Linux 系统的交换空间
是磁盘的一个分区,在初始化系统时配置的
当内存满时,系统会将一些内存交换至 Swap 空间

Swap 空间 虚拟内存
操作系统概念 进程概念
解决 系统的 物理内存不足问题 解决 进程的 物理内存不足问题

文件管理

文件的抽象层次

  1. 用户视图:文件名、路径、权限、属性(size、mtime、atime)。
  2. VFS(Virtual File System)层:统一的文件系统抽象(struct file, struct inode),实现跨文件系统的统一接口。
  3. 具体文件系统实现:如 ext4、xfs、btrfs、vfat、ntfs,负责磁盘块的布局、目录结构、日志(journal)等。

关键概念

概念 说明
inode 文件元数据结构,存放文件大小、权限、时间戳、指向数据块的指针(直接、间接、双间接、三间接)
目录 特殊文件,内容是 文件名 ↔ inode 编号 的映射
超级块(Superblock) 文件系统的全局信息(块大小、inode 数量、空闲块位图)
文件描述符(fd) 进程打开文件后得到的整数句柄,映射到 文件表项
文件系统挂载(mount) 将文件系统的根目录挂到全局目录树的某个挂载点(如 /mnt/data
日志(Journal) 记录元数据修改的事务日志,防止系统崩溃导致文件系统不一致(ext3/4、xfs)

常见文件系统对比

文件系统 最大文件大小 最大容量 特点
FAT32 4 GB 2 TB 兼容性好,适用于 U 盘、移动设备
NTFS 16 EB 256 TB Windows 主流,支持 ACL、压缩、加密
ext2 2 TB 32 TB 传统 Linux,非日志
ext3 2 TB 32 TB 在 ext2 基础上加入日志
ext4 16 TB 1 EB 延迟分配、extent、校验和、日志
XFS 8 EB 8 EB 高并发、大文件、延迟分配
Btrfs 16 EB 16 EB 子卷、快照、RAID、写时复制(COW)

文件操作系统调用(POSIX)

系统调用 功能 常见参数
open() 打开文件或创建新文件 pathname, flags, mode
read() 从文件读取数据 fd, buf, count
write() 向文件写入数据 fd, buf, count
lseek() 移动文件偏移指针 fd, offset, whence
close() 关闭文件描述符 fd
stat() / fstat() 获取文件属性 pathname / fd, struct stat *buf
unlink() 删除文件 pathname
mkdir() 创建目录 pathname, mode
chmod() 改变权限 pathname, mode
mmap() 将文件映射到进程地址空间 addr, length, prot, flags, fd, offset

文件描述符表:每个进程维护一个 打开文件表(指向全局 系统文件表),实现 共享打开文件(如 fork 后子进程共享同一 fd)。

设备管理与 I/O

设备分类

类别 示例 访问方式
字符设备 键盘、串口、终端、打印机 按字符流顺序读写(/dev/tty*
块设备 硬盘、SSD、U 盘、光盘 按块(sector)随机读写(/dev/sda
网络设备 网卡、虚拟网卡 通过套接字(socket)层抽象
特殊设备 /dev/null/dev/zero/dev/random 虚拟文件系统(tmpfs)提供特殊行为

设备驱动模型(Linux)

  1. 字符设备驱动:实现 file_operationsopen, read, write, ioctl, poll)。
  2. 块设备驱动:实现 block_device_operationssubmit_bio, getgeo),配合 I/O 调度器
  3. 网络设备驱动:实现 net_device_opsndo_start_xmit, ndo_open, ndo_stop)。

驱动注册后会在 /dev 生成对应的 设备文件,用户通过系统调用访问。

中断处理(Interrupt Handling)

  • 为什么需要中断?

    • 异步事件:外设(键盘、网卡)在任意时刻需要 CPU 关注。
    • 提高 CPU 利用率:CPU 不必轮询 I/O,能够在 I/O 完成时立即响应。
    • 实现实时响应:对高优先级硬件事件(如时钟)进行及时处理。
  • 中断的分类

    类型 示例 触发方式
    内部中断(Exception / Fault) 除零、页面错误、非法指令 CPU 在执行指令时检测
    外部硬件中断 键盘、网卡、定时器 设备向 IRQ 线发送信号
    软件中断(System Call) int 0x80syscall 程序显式触发,进入内核态
    可屏蔽中断(Maskable) 大多数外设中断 可被 cli/sti 或 CPU 中断屏蔽位屏蔽
    不可屏蔽中断(NMI) 硬件错误、看门狗 不能被屏蔽,最高优先级
  • 中断处理流程(以 Linux 为例)

    1. 中断请求(IRQ):外设向中断控制器(APIC / PIC)发送信号。
    2. CPU 响应:CPU 通过 中断向量(Interrupt Vector Table)定位对应的 中断服务例程(ISR)
    3. 保存上下文:CPU 自动压栈保存 寄存器、EFLAGS、CS/EIP(或 RIP)等。
    4. 进入中断处理程序
      • Top‑half(硬件中断上下文)完成快速处理(如读取状态寄存器、确认中断、唤醒等待队列)。
      • Bottom‑half(软中断、tasklet、工作队列)在稍后以 进程上下文 完成耗时操作(如磁盘 I/O、网络协议栈处理)。
    5. 恢复上下文:执行 IRET(x86)或 eret(ARM),恢复原先进程的寄存器并返回。

    中断屏蔽:在 ISR 中常使用 local_irq_save/restore 防止同一中断嵌套。
    中断共享:同一 IRQ 线可挂载多个设备,驱动在 ISR 中通过 设备链表 判断是哪一个设备触发。

  • 常见中断相关系统调用 / 接口

    接口 说明
    request_irq() 注册 ISR(Linux)
    free_irq() 释放 IRQ
    enable_irq()/disable_irq() 局部屏蔽/解除屏蔽
    irq_set_affinity() 绑定中断到特定 CPU(NUMA)
    ioctl() 通过系统调用向驱动发送控制命令(如 FIONREAD

I/O 调度(Disk Scheduling)

算法 思路 适用场景
FCFS 按请求到达顺序服务 负载轻、实现简单
SSTF(Shortest Seek Time First) 选择最近磁头位置的请求 减少寻道时间,但可能产生 饥饿
SCAN(Elevator) 磁头在一个方向上服务请求,到达边界后反向 兼顾公平与效率
C‑SCAN(Circular SCAN) 类似 SCAN,但磁头到达边界后回到起点,形成循环 防止方向性饥饿
LOOK / C‑LOOK 与 SCAN/C‑SCAN 类似,但磁头只到达最后一个请求的最远位置再反向 更高效
Deadline / CFQ(Linux) 为 I/O 请求设定截止时间或按进程权重分配时间片 多任务、实时需求

线程与线程池

线程概念

  • 线程(Thread)进程内部的执行流,共享进程的 地址空间、文件描述符、信号处理 等资源。
  • 轻量级进程(LWP):在某些系统(如 Solaris)中,线程被实现为 LWP,内核直接调度。
  • 用户态线程(如 Green Threads)在用户空间调度,不需要内核介入,适用于 I/O 密集型协程框架。

线程的创建与管理(POSIX 示例)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <pthread.h>
#include <stdio.h>

void *worker(void *arg) {
    int id = *(int *)arg;
    printf("Thread %d started\n", id);
    // ... 执行任务 ...
    return NULL;
}

int main(void) {
    const int N = 4;
    pthread_t th[N];
    int ids[N];
    for (int i = 0; i < N; ++i) {
        ids[i] = i;
        pthread_create(&th[i], NULL, worker, &ids[i]);
    }
    for (int i = 0; i < N; ++i)
        pthread_join(th[i], NULL);
    return 0;
}
  • pthread_create → 内核 clone() 系统调用(创建新线程)。
  • pthread_join → 等待线程结束并回收资源。

线程池设计模式

为什么使用线程池?

  • 降低创建/销毁开销:线程的栈、调度信息、内核对象的分配成本较高。
  • 控制并发度:通过固定线程数限制系统资源占用,防止 线程风暴
  • 任务调度解耦:生产者只负责提交任务,消费者(工作线程)负责执行。

典型结构(伪代码)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
+-------------------+        +-------------------+
|   任务生产者      |  --->  |   任务队列 (FIFO) |
+-------------------+        +-------------------+
                                   |
                                   v
+-------------------+        +-------------------+
|   工作线程 1      |  <---  |   线程池管理器    |
+-------------------+        +-------------------+
|   工作线程 2      |
+-------------------+
|   …               |
+-------------------+

关键组件

组件 作用 常用同步原语
Task 包含待执行的函数指针、参数、返回值、状态标记 -
WorkThread 循环从任务队列取任务并执行;支持 平滑退出 pthread_mutex, pthread_cond
ThreadPool 负责创建/销毁工作线程、维护任务队列、提供 submit() 接口 mutex + condition variable
Future / Promise 任务提交后返回的 句柄,可在以后查询结果或阻塞等待 std::future(C++)、concurrent.futures(Python)

线程安全实现(POSIX 示例)

 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
typedef struct {
    pthread_mutex_t mtx;
    pthread_cond_t  not_empty;
    pthread_cond_t  not_full;
    Task **queue;
    int head, tail, size, capacity;
    int stop;               // 线程池关闭标志
} TaskQueue;

/* 入队 */
void enqueue(TaskQueue *q, Task *t) {
    pthread_mutex_lock(&q->mtx);
    while (q->size == q->capacity && !q->stop)
        pthread_cond_wait(&q->not_full, &q->mtx);
    if (q->stop) { pthread_mutex_unlock(&q->mtx); return; }
    q->queue[q->tail] = t;
    q->tail = (q->tail + 1) % q->capacity;
    ++q->size;
    pthread_cond_signal(&q->not_empty);
    pthread_mutex_unlock(&q->mtx);
}

/* 出队 */
Task *dequeue(TaskQueue *q) {
    pthread_mutex_lock(&q->mtx);
    while (q->size == 0 && !q->stop)
        pthread_cond_wait(&q->not_empty, &q->mtx);
    if (q->stop) { pthread_mutex_unlock(&q->mtx); return NULL; }
    Task *t = q->queue[q->head];
    q->head = (q->head + 1) % q->capacity;
    --q->size;
    pthread_cond_signal(&q->not_full);
    pthread_mutex_unlock(&q->mtx);
    return t;
}
  • 生产者 调用 enqueue()消费者(工作线程)循环调用 dequeue()
  • stop 为真时,所有线程安全退出,防止 死锁

常见实现(语言/库)

语言 库 / 框架
C++ std::thread + std::async / Boost.Asio
Java java.util.concurrent.ThreadPoolExecutor
Python concurrent.futures.ThreadPoolExecutormultiprocessing.pool.ThreadPool
Go Goroutine + worker pool(channel + select)
Rust rayontokio(异步)

问题自测

  • 进程状态转换图四大同步原则死锁必要条件
  • 银行家算法 检查安全状态?
  • 解释 TLB Miss、Cache Miss、Page Fault 的区别与处理流程。
  • 比较 FCFS、SJF、RR、MLFQ 的优缺点并给出适用场景。