Lab: OS-6.S081-Labs

系统调用的使用

系统调用接口:和 unix 类似,但是 xv6 的实现不是 POSIX 完备的,不过 fork、exec、wait 是类似的。

文件描述符:代表一个被打开的 IO 对象,可以是文件、管道、设备

管道 pipes:管道作为内核中的缓冲区,以 fd 的形式暴露给对象,以实现进程间传导。

xv6 操作系统架构

本章构建了 xv6 内核的骨架。对于安全人员,这里是理解特权隔离 (Isolation)攻击面 (Attack Surface) 的起点。如果内核的防御边界(Kernel Boundary)设计得不好,应用层的漏洞就能直接通向 Ring 0。

  1. 隔离机制 (Isolation) 操作系统最核心的任务是在多个程序之间、以及程序与硬件之间建立围墙。
  1. 硬件接口 (Hardware Interface) RISC-V 提供了哪些硬件原语来支持 OS?
  • 特权模式: RISC-V 特权级 - Machine Mode (M) / Supervisor Mode (S) / User Mode (U)。
  • 控制寄存器: RISC-V 关键寄存器 - satp, stvec, sepc, scause 等,掌控着地址转换与中断流向。
  1. 内核入口 (Kernel Entry) 如何从用户程序跳进内核代码?这是系统调用的物理过程。
  1. 进程抽象 (Process in Kernel) 在内核眼里,一个进程长什么样?
  • 进程结构体: struct proc - 进程的元数据 (p->state, p->pagetable, p->trapframe)。
  • 地址空间: 进程内存布局 - 用户栈与内核栈的分离 (User Stack vs Kernel Stack)。
  1. 启动流程 (Booting)
  • 从上电到Shell: xv6启动流程 - 了解 M 态到 S 态的切换,以及第一个进程 init 是怎么来的。

Page tables

本章深入探讨 xv6 如何通过分页机制实现内存隔离 (Memory Isolation)。如果说第二章建立了特权级的围墙,那么第三章则是通过页表为每一道围墙安装了精确到字节的“准入控制系统”。

  1. 映射机制 (Address Translation) 这是从逻辑概念到物理现实的转换。
  • ** Sv39 模式:** RISC-V 内存映射 - 39 位虚拟地址拆分为三级索引(9+9+9)与 12 位页内偏移。
  • 页表项 (PTE): 页表项权限位 - 包含物理页号 (PPN) 以及 等权限标志位,这是系统安全的核心拦截点。
  1. 内核空间布局 (Kernel Address Space) 内核如何安排自己的内存?
  • 直接映射 (Direct Mapping): 内核内存布局 - 大部分内核虚地址等于物理地址,便于直接访问硬件(MMIO)。
  • 安全防御: Guard-Page - 内核栈下方的无效页,利用硬件特性强制拦截栈溢出攻击。
  • 共享区域: Trampoline 与切换 - 位于 的最高端,是用户态切换到内核态的物理跳板。
  1. 物理内存分配 (Physical Memory Management) 内核如何管理那几块物理“砖头”?
  • 自由链表: kalloc 内存分配器 - 物理内存被切分为 字节的页,通过链表进行管理。
  • 原子性与锁: kmem.lock - 确保多核 CPU 同时申请内存时不会发生竞态冲突。
  1. 进程地址空间 (Process Address Space) 在进程眼里,世界是什么样的?
  • 用户布局: 用户内存视图 - 从 开始的代码段、数据段、堆,以及位于顶端的栈和 Trapframe。
  • 地址空间生命周期: exec 与内存重组 - 当进程切换程序时,页表是如何被销毁、重建并填入新数据的。
  1. 核心逻辑实现 (Memory Software Logic) 内核代码如何模拟硬件行为?
  • 查表算法: walk 函数 - C 语言实现的“剥洋葱”逻辑,模拟三级页表查找。
  • 映射维护: mappages - 将物理页“锁死”在虚拟地址上的关键函数。
  • 跨界通信: copyin 与 copyout - 安全地在内核页表与用户页表之间搬运数据。

NOTE

  • va (Virtual Address - 坐标): 它是一个 64 位的数字。它本身不存储数据,它只是一个“地址”,告诉你:“我要找第几号数据”。
  • pagetable (Page Table - 字典本): 它在内存里是一个 4096 字节的物理页。内核把它看作一个包含 512 个格子的数组
  • PTE (Page Table Entry - 字典的条目): 它是数组里的其中一个格子(占 8 字节)。这个格子里面写着两样东西:
    • 下一站的物理地址(去哪找下一本字典或最终的数据页)。
    • 权限位(能不能读?能不能写?)。

Traps与中断机制 (Traps & Interrupts)

Trap 是操作系统控制权的强制夺取机制。它不仅是系统调用 (System Call) 的入口,也是硬件中断 (Interrupt) 和异常 (Exception) 的处理中心。理解 Trap 的核心在于理解上下文切换 (Context Switch) 如何在用户态和内核态之间安全、透明地进行。

  1. 基础设施 (Infrastructure) 在 Trap 发生前,必须预设好的硬件状态和内存结构。
  1. 用户态陷入流程 (User Trap Flow) 这是 Lab 4 的主战场,描述从用户程序跳转到内核的完整路径。
  1. 内核态陷阱 (Kernel Trap)
  1. 关键挑战 (Key Challenges)
  • 页表切换: 如何在切换页表的瞬间保持 PC 指针有效?(见 Trampoline)
  • 死锁风险: 在 Trap 早期甚至不能使用栈。
  1. Trap 整体流程

缺页异常与懒惰策略 (Page Faults & Lazy Evaluation)

Page Fault(缺页异常)不再是需要避免的错误,而是一种强大的系统设计原语。通过故意触发 Page Fault,内核可以在“最后一刻”才分配资源,从而实现极高的效率和灵活性。这章内容直接对应 Lab 5 (Lazy Allocation)Lab 6 (COW)

  1. 核心机制 (The Mechanism) 内核如何捕获并处理“非法”的内存访问?
  1. 懒惰分配 (Lazy Allocation) 这是 Lab 5 的核心。不要急着给内存,等进程真的用了再给。
  • 堆内存: Lazy-sbrk - 只增长 sz 指针,不分配物理页 (kalloc)。
  • 栈内存: Guard-Page - 自动增长的栈与溢出保护。
  1. 写时复制 (Copy-on-Write) 这是 Lab 6 的核心。父子进程共享物理内存,直到一方试图修改。
  • Fork优化: COW-Fork - 将物理页设为只读 (Read-Only),利用 Store Page Fault 触发分离。
  • 引用计数: Reference-Counting - 物理页生命周期的管理难题。
  1. 磁盘与内存映射 (Disk & Memory)
  • 按需分页: Demand-Paging - 执行二进制文件 (exec) 时,不加载代码,运行到了再从磁盘读。
  • 内存映射文件: mmap-机制 - 将文件 I/O 转化为内存读写。

中断与设备驱动 (Interrupts & Device Drivers)

本章揭示了 OS 如何管理龟速的 I/O 设备与光速 CPU 之间的矛盾。 核心机制是异步通知:不要让 CPU 等设备,让设备好了之后“拍一下” CPU。 对于安全人员,这里是侧信道攻击硬件木马的高发区。

  1. 物理接口 (Hardware Interface) 软件如何控制硬件?硬件如何通知软件?
  • 控制方式: MMIO-内存映射IO - 将设备寄存器伪装成内存地址,读写内存即控制硬件。
  • 通知中枢: PLIC-中断控制器 - Platform Level Interrupt Controller,中断信号的路由器。
  1. 驱动架构 (Driver Architecture) 驱动程序的设计哲学:如何处理并发与缓冲。
  1. 实战案例 (Case Studies) xv6 中的两个经典驱动实现。
  • 控制台: UART-驱动详解 - 极其复杂的读写并发逻辑,包含“启动脚”问题。
  • 定时器: Timer-时钟中断 - 操作系统的心跳,调度器的动力源。
  1. 并发挑战 (Concurrency)
  • 死锁陷阱: 中断上下文的锁 - 在中断处理程序中使用自旋锁的致命规则 (Off-Interrupts)。

多核处理与锁机制 (Multiprocessors & Locking)

本章探讨如何在多核 CPU 并发执行时,维护共享数据的一致性。锁 (Lock) 是将并行 (Parallel) 强制转化为串行 (Serial) 的机制。

  1. 核心危机 (The Crisis) 为什么多核会出错?
  • 竞态条件: Race-Condition - 内存访问的时序不确定性导致的数据损坏。
  • 临界区: Critical-Section - 必须原子执行的代码片段。
  1. 硬件基石 (Hardware Primitives) 软件无法单独实现锁,必须依赖硬件提供的原子指令。
  1. 自旋锁实现 (Spinlock Implementation) xv6 中最基础的锁。
  1. 死锁与策略 (Deadlock & Strategy)

根本上说,是通过协议来进行的锁定。基石是硬件电路的 amoswap,可以原子化地修改一个值,但是上层抽象的关键在于遵守这个值,而不是继续通过物理电路保证。即,如果有一个逻辑不获取、释放锁,并不能保证其被原子地执行。


线程切换与调度 (Thread Switching & Scheduling)

本章揭示了操作系统如何实现多任务并发。核心机制是上下文切换 (Context Switch)。 对于内核开发者,理解这里意味着理解**“我是谁”(当前运行在哪个栈上)以及“我在哪”**(PC 指针指向哪里)。

  1. 宏观机制 (The Mechanism) xv6 不允许进程直接互相切换,必须经过中转。
  • 切换模型: 两段式切换模型 - 进程 A 调度器线程 进程 B。
  • 状态保存: struct-context - 内核线程的“存档点”,只保存 Callee-Saved 寄存器。
  1. 核心汇编 (The Assembly) 这是魔法发生的物理地点。
  • 切换函数: swtch函数详解 - 利用 ret 指令实现“偷梁换柱”,瞬间改变 CPU 的栈和指令指针。
  1. 调度逻辑 (Scheduling Loop)
  • 让出 CPU: Yield与Sched - 进程主动放弃 CPU 的流程(状态流转)。
  • 调度器: Scheduler-线程 - 每个 CPU 核独有的无限循环,负责寻找下一个猎物。
  1. 并发深水区 (Deep Concurrency)
  • 跨越边界的锁: p-》lock的传递性 - 为什么进程锁必须在切换前获取,在切换后释放?这是为了防止“幽灵进程”被错误的 CPU 运行。

xv6 不支持用户态的的多线程,且一个进程只对应一个线程,所以进程切换本质上就是内核线程之间的切换。所以只是在内核当中的切换,无需用到 trapframe,直接保存 context 即可。


睡眠与唤醒机制 (Sleep & Wakeup)

这是操作系统中进程协作的核心机制。它允许进程在等待资源(如磁盘读取、管道数据)时主动让出 CPU,从而极大提高系统效率。设计的核心难点在于避免“丢失唤醒”

  1. 核心问题 (The Problem)
  • 忙等待的代价: Busy-Waiting-vs-Sleep - 为什么我们不能一直用 while 循环等待?
  • 竞态危机: Lost-Wakeup-Problem - 经典的并发漏洞:如果在“决定睡觉”和“真的睡着”之间发生了唤醒,进程将永远沉睡。
  1. 解决方案 (The Solution) xv6 通过精妙的锁机制解决了上述危机。
  • 原子睡眠: Sleep函数详解 - 为什么 sleep 需要一把锁参数?它是如何实现“释放锁”与“进入睡眠”的原子性的?
  • 唤醒机制: Wakeup函数详解 - 遍历进程表,寻找灰姑娘(chan)。
  1. 实战模型 (Case Study)
  • 管道通信: Pipe-Coordination - 读写双方如何利用 sleep/wakeup 实现流量控制。
  • 进程回收: Exit与Wait的同步 - 父子进程如何通过 waitexit 进行尸体交接。
  1. 性能陷阱 (Performance Pitfall)
  • 惊群效应: Thundering-Herd - 一个 wakeup 唤醒了 100 个进程,只有一个能抢到资源,其他的怎么办?
  1. Kill调用

文件系统 (File System)

文件系统是操作系统中的数据库。它的核心任务是将磁盘上毫无意义的扇区 (Sectors) 组织成有层次、有名字、可读写的文件 (Files)。设计难点在于性能 (Caching)持久性 (Crash Safety) 之间的平衡。

  1. 物理布局 (On-Disk Layout) 磁盘上的比特是如何排列的?
  • 静态结构: xv6-磁盘布局 - 从 Boot Block 到 Superblock,再到 Inode 和 Data。
  • 元数据管理: Superblock-超级块 - 描述整个文件系统的元信息 (Total blocks, Inode count)。
  1. 核心抽象 (The Abstractions) 文件系统是如何欺骗用户的?
  • 对象抽象: Inode-索引节点 - 文件系统中的“上帝对象”,描述了“文件是什么”以及“文件在哪”。
  • 命名抽象: Directory-目录结构 - 目录本质上只是特殊格式的文件 (name inum)。
  • 地址抽象: Direct与Indirect-Blocks - 如何用有限的 Inode 空间索引巨大的文件?
  1. 缓存与性能 (Buffer Cache) 磁盘太慢,必须加缓存。
  1. 路径解析 (Pathname Lookup)
  • 递归查找: Namei-路径解析 - 从 / 根目录开始,一步步找到目标 Inode 的过程。

崩溃恢复与日志 (Crash Recovery & Logging)

在断电或内核 Panic 的瞬间,内存数据灰飞烟灭。如果文件系统正在进行多步写入(比如新建文件),磁盘就会处于“不一致”的状态。日志系统 (Logging) 旨在保证文件操作的 原子性 (Atomicity)持久性 (Durability)

  1. 核心危机 (The Crisis) 为什么文件系统会损坏?
  • 不一致性: FS-Inconsistency - 多步磁盘操作被打断导致的逻辑错误(如:目录指向了未分配的 Inode)。
  • 根本原因: 磁盘写入不是原子的,也不能回滚。
  1. 解决方案 (The Solution) 如何实现“要么全做,要么全不做”?
  • 核心机制: Write-Ahead-Logging - WAL 协议,数据库和文件系统的通用救命稻草。
  • 事务抽象: Transactions-事务 - begin_opend_op 如何定义原子操作的边界。
  1. xv6 日志实现 (xv6 Implementation) xv6 的日志系统极其简单但有效。
  1. 恢复与优化 (Recovery & Opt)
  • 重启恢复: Replay-回放机制 - 无论因何崩溃,重启时重做日志即可恢复一致性。
  • 性能权衡: Group-Commit-组提交 - 如何合并多个系统调用以减少昂贵的磁盘同步操作。