Hang Zhengyang

系统笔记

按概念组织的笔记,跨 lab 积累。

  • 引用方向:系统笔记可以链接 lab 笔记(作为实践和例子);lab 笔记只为 lab 服务,不反向引用这里。
  • lab 笔记记录:做什么、怎么做、踩了什么坑、测试结果。
  • 系统笔记记录:这个抽象是什么、状态存在哪、为什么这样设计、内核里怎么实现。

目录

# 笔记 内容 主要来自
00 缩写与命名 怎么从名字猜全名;C/xv6/RISC-V/Linux 命令/构建缩写速查 全部
01 进程 用户态/内核边界与 syscall;fork、exec、wait;程序从 main.c 到进程 util
02 文件描述符与文件 fd 是什么;目录与文件名;一条命令的完整一生 util
03 线程,以及 Python / C++ 里的进程与线程 进程 = 资源 + 执行流;调度与 swtch;Linux clone;C++ 数据竞争/atomic/mutex;Python GIL、multiprocessing util + 宿主机实验
04 状态切换、权限与安全 特权级;ecall 进出内核时哪些状态变、谁改;用户输入的检查(copyin、TOCTOU);引用监视器与沙箱性质(seccomp 实验);资源复用前清零 syscall + 宿主机实验
05 页表 为什么要地址翻译;Sv39 三级页表与一次完整翻译;PTE 各位;页表存在哪(直接映射);TLB 与 sfence.vma;xv6 两种地址空间;页表带来的能力(懒分配、COW、共享、保护页);A/D 位;大页的好处和代价(宿主机实测);Linux 对照 pgtbl + 宿主机实验
06 Linux 内核:定义、职责、心智模型与结构 八个角度的定义;14 项职责(抽象、核心结构体、源码、xv6 对应);七个心智模型(服务端、事件驱动、对象图、函数指针表、分层与机制/策略、缓存、并发)加宏内核与微内核;源码树地图与规模;task_struct;开机流程;Kconfig;本机观察;xv6 对照;开发流程与 AI 规定;读码方法 全部 + 本机源码树和运行中的内核

实验代码:code/(Python / C / C++,在宿主 Linux 上跑)。

后续 lab 的概念(页表、trap、调度、锁、文件系统……)按主题新增或并入已有篇目。

怎么建立系统概念的 mental model

跨 lab 的学习方法。写于 util lab 之后(2026-10-08)。

为什么 C++ / Python 的直觉"不管用了"

不是知识作废了,是视角变了。C++ 和 Python 讲的是"一个程序内部":对象、函数调用、调用栈,一条执行流从头走到尾。操作系统讲的是那个世界的外面:好几个程序同时存在,彼此隔离,中间有一个裁判(内核)。

而且旧知识恰好是这一层上面的封装:

熟悉的 底下其实是
Python f = open("x") 内核给一个 fd(整数),文件对象只是包着它
f.close() / with open(...) close(fd)
subprocess.run(["ls"]) fork + exec + wait
subprocess.run("ls > out", shell=True) 启动 sh,sh 做 fork → 重定向 → exec
C++ 段错误 访问非法地址 → CPU 异常 → 内核杀进程
print() 最终是 write(1, ...)
异常 系统调用没有异常,只有返回值 -1

Python 的 os 模块直接暴露了这些调用:os.fork()、os.execvp()、os.pipe()、os.dup2()。可以在宿主 Linux 上用熟悉的语言做实验。

一个核心模型:内核是服务端,进程是客户端

见 用户态与内核的边界。要点:状态存在内核里,用户手里只有一个编号(fd、pid)。想改状态只能发请求(系统调用),内核替你校验、替你办。

一个核心问题:状态存在哪、谁能改、每个操作对它做了什么

遇到新概念先问这三个问题,而不是先背 API。例如进程的两类状态:

进程自己的内存(代码、变量) 内核替它保管的(fd 表、pid、当前目录)
fork 复制一份 复制一份(fd 指向同一批对象)
exec 整个换掉 保持不变
exit 释放 全部关闭

记住这张表,重定向、管道、shell 的设计基本都能推出来。详见 fork 与 exec、fd。

哪些地方确实反直觉(单独记,不要硬套)

  • fork 返回两次,exec 成功时不返回:它们不是普通函数,是"改变世界"的请求。
  • 同一段代码同时有两份在跑:fork 之后有两条执行流。
  • 一个整数代表一个资源:fd 3 在你这里是文件,在别的进程里可能什么都不是。
  • 指针跨进程无意义:每个进程有自己的地址空间。

怎么练

  1. 先预测,再运行。跑之前写下预期输出;预测错的地方就是模型要修正的地方。
  2. 画框和箭头,不要从头到尾读代码。进程是框、fd 表是格子、内核对象在外面、箭头是指向。每执行一次系统调用,重画一次。范例:一条命令的一生。
  3. 只追一条具体路径,比泛读十个概念有效。
  4. 去看内核那一侧。xv6 的好处是神秘的东西只有几十行:fd 就是 struct proc 里的 ofile[16],fork 复制 fd 表就是 proc.c 里两行。
  5. 在宿主 Linux 上观察真实系统:
    • ls -l /proc/self/fd:直接看当前进程的 fd 表。
    • strace -f sh -c 'echo hi > out':看到 clone(Linux 的 fork)、close(1)、openat 拿到 1、execve。
    • strace python3 -c 'print(1)':print 最终就是 write(1, "1\n", 2)。
  6. 接受第一遍是模糊的。课程是螺旋式的:util lab 用,后面的 lab 实现。很多概念要在内核里亲手写过一遍才会真正清楚。