操作系统:期中复习

Last updated on November 20, 2025 am

本文为 SJTU-CS3601 操作系统课程的期中复习,主要复习内容为操作系统概述、内存管理

操作系统概述及结构

  • 操作系统的功能
    • 从硬件角度:管理硬件、对硬件进行抽象
    • 从应用角度:服务应用、管理应用

操作系统内核架构

  • 宏内核:内核所有模块运行在内核态,具备直接操作硬件的能力

    • 优点:拥有丰富的沉淀和积累
    • 缺点
      • 安全性与可靠性问题:模块之间没有很强的隔离机制
      • 实时性支持:系统太复杂导致无法做最坏情况时延分析
      • 系统过于庞大而阻碍创新
      • 向上向下的扩展:很难去剪裁/扩展一个宏内核系统支持从 KB 级别到 TB 级别的场景
      • 硬件异构性:很难长期支持一些定制化的方式去解决一些特定问题
      • 功能安全:无法通过汽车安全完整性认证
      • 信息安全:单点错误会导致整个系统出错
      • 确定性时延:目前依然不确定是否能支持确定性时延
  • 微内核

    • 设计原则:最小化内核功能
      • 将内核功能拆分到用户态,成为独立的“服务”
      • 内核仅保留极少功能,为这些服务提供通信
    • 优点
      • 易于扩展:直接添加一个用户进程即可为操作系统增加服务
      • 易于移植:大部分模块与底层硬件无关
      • 更加可靠:在内核模式运行的代码量大大减少
      • 更加安全:即使存在漏洞,服务与服务之间存在进程粒度隔离
      • 更加健壮:单个模块出现问题不会影响到系统整体
    • 缺点
      • 性能较差:内核中的模块交互由函数调用变成了进程间通信
      • 生态欠缺:尚未形成像 Linux 一样具有广泛开发者的社区
      • 重用问题:重用宏内核操作系统提供兼容性,带来新问题
  • 外核:不提供硬件抽象;不管理资源,只管理应用

    • 应当由应用来尽可能地控制对硬件资源的抽象
    • 将对硬件的抽象以库(LibOS)的形式提供,不同应用可使用不同的 LibOS
    • 优点
      • OS 无抽象,能在理论上提供最优性能
      • 应用对计算有更精确的实时等控制
      • LibOS 在用户态更易调试,调试周期更短
    • 缺点
      • 对计算资源的利用效率主要由应用决定
      • 定制化过多,导致维护难度增加
  • 多内核:通过多内核来管理异构多核设备

    • 思路
      • 默认的状态是划分而不是共享
      • 维持多份状态的 copy 而不是共享一份状态
      • 显式的核间通信机制
    • 设计
      • 在每个 core 上运行一个小内核
      • OS 整体是一个分布式系统
      • 应用程序依然运行在 OS 之上

ARM 汇编

  • 寄存器x1 - x30 是 31 个 64 位寄存器,w0 - w30 是其低位的 31 个 32 位寄存器

基础指令

  • 寄存器之间的数据搬移mov dst, src
    • src:立即数或寄存器
    • dst:寄存器
  • 算术指令

  • 移位指令

  • 逻辑运算指令

  • 修改过的寄存器:对操作数进行移位或位扩展

    • 无符号扩展:uxtb, uxth, uxtw (Zero-extend single-word / half-word / byte)
    • 符号扩展:sxtb, sxth, sxtw (Sign-extend single-word / half-word / byte)
  • 访存指令


  • 寻址模式


分支指令

  • 条件码:包含 N(Negative)、Z(Zero)、C(Carry)、V(Overflow)

    • 设置
      • 带有 s 后缀的算术或逻辑运算指令(如 subsadds
      • 比较指令:cmp (操作数之差)、cmn (操作数之和)、tst (操作数相与)
        • cmp src1, src2:计算 src1 - src2
  • 跳转条件

  • 跳转指令

    • 直接分支指令:以标签对应的地址作为跳转目标
      • 无条件分支指令:b \<label>
      • 有条件分支指令:bcond \<label>,如 beqbneble
    • 间接分支指令:以寄存器中的地址作为跳转目标
      • br reg,如 br x0
  • for 循环翻译实例

  • while 循环翻译方法及实例

函数调用

  • 函数调用指令
    • bl label (直接调用,调用函数)
    • blr Rn (间接调用,调用函数指针)
    • 功能:将返回地址存储在链接寄存器 LR (即 x30 寄存器),并跳转到被调用者的入口地址
  • 函数返回指令
    • ret (不区分直接调用与间接调用)
    • 功能:跳转到链接寄存器 LR 中的返回地址
  • SP(Stack Pointer)寄存器:栈指针,指向栈顶(低地址)
  • FP (Frame Pointer) 寄存器:帧指针,即 x29 寄存器
  • 标准的函数首尾操作
1
2
3
4
5
stp x29, x30, [sp, #-32]!
mov x29, sp
...
ldp x29, x30, [sp], #32
ret
  • 参数传递
    • 调用者使用 x0 - x7 寄存器传递前 8 个参数
    • 第 8 个之后的参数,按声明顺序从右到左压到栈上,被调用者通过 SP + 偏移量访问
    • 被调用者使用 x0 寄存器传递返回值

  • 寄存器保存
    • 调用者保存:x9 - x15
      • 调用者:在调用前按需进行保存,在返回后进行恢复
      • 被调用者:可以随意使用
    • 被调用者保存:x19 - x28
      • 被调用者:在使用前进行保存,在返回前进行恢复
      • 调用者:这些寄存器的值在函数调用前后不会改变

  • 函数调用实例

特权级

  • 常见寄存器

  • 系统指令

    • mrs/msr:从系统寄存器读取值/向系统寄存器写入值
    • svc/eret:特权级切换和返回
  • 特权级切换的时机

    • 同步异常:执行当前指令触发异常
      • 第一类:用户程序主动发起系统调用(svc 指令)
      • 第二类:非主动,如用户程序意外访问空指针
    • 异步异常:CPU 收到中断信号
      • 从外设发来的中断,如屏幕点击、鼠标、收到网络包
      • CPU 时钟中断,如定时器超时

特权级切换过程

  • 处理器在切换过程中的任务
    1. 将发生异常事件的指令地址保存在 ELR_EL1 中
    2. 将异常事件的原因保存在 ESR_EL1
    3. 将处理器的当前状态(即 PSTATE)保存在 SPSR_EL1
    4. 栈寄存器不再使用 SP_EL0(用户态栈寄存器),开始使用 SP_EL1
    5. 修改 PSTATE 寄存器中的特权级标志位,设置为内核态
    6. 找到异常处理函数的入口地址,并将该地址写入 PC,开始运行操作系统
  • 操作系统在切换过程中的任务:将属于应用程序的 CPU 状态保存到内存中,用于之后恢复应用程序继续运行,包括:
    • 通用寄存器 x0 - x30(只需要保存这个)
    • 特殊寄存器,主要包括 PC、SP 和 PSTATE
    • 系统寄存器,包括页表基地址寄存器等

系统调用的优化

  • vDSO:内核将一部分数据通过只读的形式共享给应用,允许应用直接读取
  • Flex-SC:允许应用以“向某一块内存页写入请求”的方式发起系统调用,并通过轮询来等待系统调用完成
    • 内核独占一个 CPU 核心,通过轮询来等待用户的请求,然后执行系统调用,并将返回值写入同一块内存页

常见的进程接口

  • fork():父进程创建新的子进程,调用一次返回两次
    • 返回值:子进程为 0,父进程为子进程 PID
    • 语法:pid_t fork(void);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "csapp.h"
int main()
{
pid_t pid;
int x = 1;
pid = Fork();
if (pid == 0) { /* child */
printf("child : x=%d\n", ++x);
exit(0);
}
/* parent */
printf("parent: x=%d\n", --x);
exit(0);
}
  • execve():加载和运行

    • 只调用一次,且永远不会返回(仅仅在运行报错的时候,返回调用程序)
    • 语法:int execve(const char *filename, const char *argv[], const char *envp[]);
  • waitpid():等待子进程终止后,内核回收子进程

    • 语法:pid_t waitpid(pid_t pid, int *status, int options);
      • pid>0 :等待集合中只有 pid 子进程
      • pid=-1:等待集合包括所有子进程
      • options=0
        • 挂起调用进程,等待集合中任意子进程终止
        • 如果等待集合中有子进程在函数调用前已经终止,立刻返回
        • 返回值是导致函数返回的终止子进程 pid
        • 该终止子进程被内核回收
  • 进程的终止:进程终止后,内核不会立刻销毁该进程,而是以终止态存在,等待父进程回收

    • 僵尸进程:终止状态下还未被回收的进程
      • 如果父进程在自己终止前没有回收僵尸子进程,内核会安排 init 进程回收这些子进程

ELF 文件格式

ELF 格式

  • ELF 头部:通常用于存元数据
  • 节头部表:每一个节都有一个节头部(节头部表的一项)描述
  • ELF 字符串表(.strtab):记录一系列 C 风格字符串,表示符号名或节名
  • 用以调试的节
    • .debug:调试符号表,包括变量、typedef、C 源文件
    • .line:C 源文件的行数与 .text 节中指令的映射
  • 代码和数据节
    • .text:代码
    • .rodata:只读数据,包括不可修改的常量数据
    • .data:初始化的全局变量和静态变量
    • .bss:未初始化的全局变量和静态变量(不占文件空间,运行时分配内存)

内存地址翻译

  • 64 位虚拟地址解析:
    • [63:48] 16-bit:全 0(应用程序地址)或全 1(内核地址)
    • [47:39] 9-bit:0 级页表索引
    • [38:30] 9-bit:1 级页表索引
    • [29:21] 9-bit:2 级页表索引
    • [20:12] 9-bit:3 级页表索引
    • [11:0] 12-bit:页内偏移
  • 页表基地址寄存器:TTBR0_EL1 和 TTBR1_EL1
    • MMU 根据虚拟地址第 63 位选择使用哪一个
    • 应用程序(地址首位为 0)使用 TTBR0_EL1,操作系统(地址首位为 1)使用 TTBR1_EL1
  • 页表大小:每个页表页占用一个物理页,对 64 位的地址空间:
    • 页大小为 4K
    • 每个页表页中有 512 个页表项
    • 页表项为 8 字节

  • 页描述符 / 表描述符 / 块描述符:PFN,即物理页号
    • 页描述符指向 4K 页,表描述符指向下一级页表,块描述符指向大页
    • 有效的 0-2 级页表项,第 1 位为 0 表示 PFN 指向大页,第 1 位为 1 表示 PFN 指向下一级页表

  • 大页的优缺点
    • 好处
      • 减少 TLB 缓存项的使用,提高 TLB 命中率
      • 减少页表的级数,提升遍历页表的效率
    • 弊端
      • 未使用整个大页而造成物理内存资源浪费
      • 增加管理内存的复杂度

页表初始化

  1. 设置 TTBR0 页表:将低地址虚拟内存映射到物理内存,使当前低地址代码能正常运行
  2. 设置 TTBR1 页表:将高地址虚拟内存映射到物理内存,使得内核能切换到高地址运行
  3. 将页表的物理地址写入 TTBR0 和 TTBR1
  • TTBR0_EL1:虚拟地址 = 物理地址
  • TTBR1_EL1:虚拟地址 = 物理地址 + OFFSET
  1. 将 SCTLR_EL1 的某些位置 1,开启页表

页表管理

  • 直接映射:虚拟地址 = 物理地址 + 固定偏移

    • 操作系统使用,映射全部物理内存,在启动时填写
    • 好处:能在内核地址空间中很方便地在物理地址和虚拟地址之间进行转换
  • 立即映射:在初始化虚拟地址空间时,直接在进程页表中添加各虚拟内存区域的映射

    • 在进程运行期间,OS 接收到创建虚拟内存区域的请求后,立刻在页表中为该区域中添加映射
    • 问题:物理内存资源浪费、非必要时延
  • 延迟映射:先记录为进程分配的虚拟内存区域,但不分配物理内存

    • 当进程实际访问某个虚拟页时,CPU 触发缺页异常,OS 在缺页异常处理函数中添加映射
    • VMA (vm_area_struct) 结构体:Linux 中记录进程已分配的虚拟内存区域,通过平衡树数据结构组织

    • 根据 VMA 判断缺页异常的合法性:
      • 根据 ESR 信息判断是否为缺页异常
      • 访问的虚拟地址存放在 FAR_EL1
      • FAR_EL1 中的值不落在 VMA 区域内,则为非法
      • 反之,则分配物理页,并在页表中添加映射
    • 优势:节约内存资源
    • 劣势:缺页异常导致访问延迟增加
    • 取得平衡的方式:采用预先映射策略、主动告知 OS 提前填写映射
  • 改变虚拟内存区域的接口

    • mmap:新增一段虚拟内存区域
    • brk:扩大或缩小堆区域
  • 虚拟内存扩展功能

    • 写时拷贝(copy-on-write)
      • 实现:以只读的方式共享一段物理内存,在修改并触发访问权限异常时拷贝
      • 优点:节约物理内存、性能加速(fork

    • 内存去重:OS 定期在内存中扫描发现具有相同内容的物理页面,执行去重
    • 内存压缩:当内存资源不充足时,选择一些“最近不太会使用”的内存页进行数据压缩

伙伴系统

  • 基本思想:将物理内存划分成连续的块,以块为基本单位进行分配
    • 每个块都由一个或多个连续的物理页组成,物理页的数量必须是 2 的 n 次幂
  • 物理页/连续物理页的分配:当一个请求需要分配 mm 个物理页时,伙伴系统将寻找一个大小合适的块
    • 该块包含 2n2^n 个物理页,且满足 2n1<m2n2^{n−1} < m \le 2^n
    • 大的块可以分裂成两半,这两个小块互为伙伴
  • 物理页/连续物理页的释放:分配器会找到其伙伴块
    • 互为伙伴的两个块的物理地址仅有一位不同,而且块的大小决定是哪一位
    • 若伙伴块也处于空闲状态,则将这两个伙伴块进行合并,形成一个大一号的空闲块

  • 资源利用率:外部碎片程度降低;内部碎片依然存在
  • 分配性能:分配和合并的时间复杂度均为常数时间

SLAB 分配器

  • 目标:快速分配小内存对象,因为内核中的数据结构大小远小于 4K(例如 VMA)
  • SLUB 分配器的思路:OS 频繁分配的对象大小相对比较固定;避免外部碎片
    • 从伙伴系统获得大块内存(名为 slab)
    • 对每份大块内存进一步细分成固定大小的小块内存进行管理
    • 块的大小通常是 2n2^n 个字节(一般来说,3n<123 \le n < 12
  • SLUB 设计:对于每个固定块大小,SLUB 分配器都会使用独立的内存资源池进行分配(采用 best fit 定位资源池)
    • 1. 初始化资源池:把从伙伴系统得到的连续物理页划分成若干等份(slot)
    • 2. 空闲链表:采用空闲链表区分是否空闲,当分配时直接分配一个空闲 slot
    • 3. 分配与释放
      • 分配
          1. 定位到大小最合适的资源池
          1. 从 slab 中取走 Next_Free 指向的第一个 slot
      • 释放:将 Next_Free 指针指向待释放内存(slot)
        • 如何找到 Next_Free:根据待释放内存地址计算 slab 起始地址
        • 在物理页结构体中要记录所属 slab 信息
    • 4. 新增 slab:当某个资源池中的 slab 已经分配完,再从伙伴系统分配一个slab
    • 5. 资源池内组织多个 slab:引入两个指针
      • Current 指向一个 slab,并从其中分配
      • 当 Current slab 全满,则从 Partial 链表中取出一个放入 Current
      • 释放后,若某个 slab 不再全满,则加入 partial
      • 释放后,若某个 slab 全空则可还给伙伴系统

换页机制

  • 换页机制的代价
    • 优势:突破物理内存容量限制
    • 劣势:缺页异常+磁盘操作导致访问延迟增加
    • 如何取得平衡:预取机制 (Prefetching),预测接下来进程要使用的页,提前换入

换页策略

  • OPT 策略(理想):优先换出未来最长时间内不会再访问的页面
  • FIFO 策略:优先选择最先换入的页进行换出
    • OS 维护一个队列,每换入一个物理页就把其页号加到队尾,因此最先换进的物理页号总是处于队头位置
  • Second Chance 策略:FIFO 策略的一种改进版本,为每一个物理页号维护一个访问标志位
    • 如果访问的页面号已经处在队列中,则置上其访问标志位
    • 换页时查看队头:1)无标志则换出;2)有标志则去除并放入队尾,继续寻找
  • LRU 策略:优先选择最久未被访问的页面
    • OS 维护一个链表,在每次内存访问后,OS 把刚刚访问的内存页调整到链表尾端;每次都选择换出位于链表头部的页面
  • 时钟算法策略:将换进物理内存的页号排成一个时钟的形状
    • 有一个针臂,指向新换进内存的页号的后一个;同时,为每个页号维护一个访问位
    • 当物理页被访问时, 把访问位设成 T
    • 需要换页时,OS 从针臂所指的页号开始检查
      • 如果是 T,则置成 F
      • 如果是 F,则换出该页

参考资料

本文参考上海交通大学并行与分布式系统研究所(IPADS)操作系统课程 CS3601 华志超老师的 PPT 课件整理。

部分图片来源于上海交通大学程序语言与编译原理课程 CS2612 曹钦翔老师的讲义。


操作系统:期中复习
https://cny123222.github.io/2025/11/05/操作系统:期中复习/
Author
Nuoyan Chen
Posted on
November 5, 2025
Licensed under