1 文件系统深入要解决什么
在终端执行 printf 'ready\n' > state.txt,随后执行 cat state.txt,看到内容正确,能够证明什么?它至少说明这次读取能够取得刚才写入的数据,却不能单凭这个现象证明突然断电后仍然能读到它。程序、内核内存和存储设备之间还有不同阶段。文件系统深入要解决的核心问题,就是把“程序交出了数据”“其他进程可以读到”“故障后仍能恢复”这几种承诺分开,并找到与承诺对应的接口和证据。
设想主机上有个采集进程,每隔 1 秒读取网络接口统计,把累计值保存到状态文件,重启后接着计算差值。管理员通过 shell 查看文件,监控进程同时读取它。这里既有写入后的可见性问题,也有重启恢复问题。如果更新时先把旧文件截成空文件,再慢慢写入新内容,读者可能看到空文件或半段文本;如果写完就向上游回报“保存完成”,异常断电又可能把状态退回旧版本。这是不同故障,不能用同样的补救解释。
我们把本课的故障边界分为 3 类。进程退出或被终止时,内核通常仍在运行,已经进入页缓存的数据可能继续写回。操作系统崩溃时,内存里的未完成状态可能丢失。整机断电时,还要依赖设备正确兑现刷写请求。越过不同边界,需要不同证据;用终止进程来模拟整机断电,会得到过于乐观的结论。

文件系统管理的不只是“文件内容放在哪里”。它还组织名字、目录、权限、空间分配和修改顺序。应用看到的是路径与文件描述符,文件系统还要把这些操作变成可以恢复的存储状态。某个接口成功返回,承诺范围通常比日常语言里的“保存”更窄。先问接口承诺了什么,再问当前文件系统和设备能否实现这个承诺,排查才有可靠起点。
本课以 Linux 上的普通本地文件为主要实验对象。网络文件系统、容器挂载和虚拟磁盘也可以沿用这里的提问方式,但它们会增加服务端、宿主机等边界,不能直接套用本地实验结论。我们暂不展开磁盘布局、内核实现或复杂恢复算法,只建立足以指导日常运维和应用保存逻辑的模型:名字指向对象,写入可能先进入缓存,持久化需要明确的同步与顺序。
2 inode、目录与日志/写序
路径 /srv/collector/state.json 看起来像文件本身,实际包含逐级查找的名字。目录维护名字到文件对象的关联;在常见 Linux 文件系统里,inode 保存文件类型、权限、所有者、大小以及定位数据所需的信息。文件名属于目录项,并不直接存放在 inode 里。因此“名字变化”和“内容变化”是两类操作,保存流程必须分别考虑它们的持久化。
可以在临时目录里执行 printf 'v1\n' > a、ln a b,再用 ls -li a b 查看 inode 号和链接数。硬链接让 2 个名字关联同一个文件对象,通过任意名字修改内容,另一个名字也能读到变化。inode 号需要结合文件系统识别,不能把它当作跨设备、跨时间永不重复的全局标识。文件删除后,编号以后可能被重新使用。
打开文件后,进程拿到的是文件描述符。之后即使有人重命名或删除原来的名字,已打开的描述符仍可能访问原来的对象,直到相关引用释放。因此替换配置文件后,某个长期运行的进程仍读取旧内容,并不一定是缓存失效;它可能一直持有旧文件。日志轮转时也常见同类现象:旧名字消失,写日志的进程却继续占用旧对象的空间。应先确认进程是否重新打开文件,再决定是否需要重载服务。

文件系统更新往往涉及多个部分:为内容安排空间、更新文件大小、记录名字变化。如果断电恰好发生在中间,恢复时不能把随意完成的一组修改当成完整结果。日志型文件系统会先记录用于恢复的某些修改信息,再按自身规则提交。这里的“日志”是文件系统的恢复机制,与应用的运行日志不同;它也不自动保证应用最近写出的每个字节已经持久化。
需要区分结构一致性和应用数据完整性。系统重启后能挂载目录、没有明显损坏,只能说明文件系统结构在一定程度上可用。某个业务文件仍可能停留在旧版本,应用约定的多文件更新也可能只完成部分。不同文件系统的日志模式和默认写序不同,不能因为看到“支持日志”就跳过 fsync。本课只要求理解日志帮助恢复结构和顺序,具体记录哪些块、如何提交留待进阶。
保存 1 个小状态文件时,常用流程是在同一目录创建临时文件,完整写入,调用文件的 fsync,再用原子重命名替换目标,最后对父目录调用 fsync。文件同步处理新内容;目录同步处理名字关联的持久化。临时文件与目标放在同一目录,也避免跨文件系统重命名失败。跨文件系统的移动可能退化成复制与删除,不再具有相同语义。
这里“原子替换”的意思,是并发查找目标名字的读者通常看到旧对象或新对象,不应看到替换过程中的半个名字状态;已经打开旧文件的读者仍可继续读旧对象。它不等于整个保存流程已经抗断电,也不等于多个文件会一起切换。只有把可见性的切换点与持久化的完成点分别标出来,才能准确描述保存协议。
3 页缓存与写回
普通文件的缓冲写入通常先修改内核中的页缓存。对普通文件调用 write,成功返回的字节数说明这部分数据已被接口接受;程序还要处理短写,即返回值小于请求长度的情况。此时同机后续读取往往能从缓存看到新内容,存储设备却可能尚未完成写入。因此“读回校验成功”适合验证内容生成或接口使用,不能独自证明断电持久性。
应用本身还可能有用户态缓冲。例如 Python 的文件对象和 C 的标准输入输出库可能先把数据保存在进程内存中。调用 flush 通常只推动这一层缓冲交给操作系统;随后仍需要合适的同步调用来请求持久化。若先执行 fsync,再把用户态剩余数据 flush 出去,后半段内容并没有包含在前面的同步范围中。接口顺序是语义的一部分。
内核把已经修改、尚需写回的缓存页称为脏页。它会根据时间、内存压力和系统策略安排后台写回;压力较大时,前台写入也可能被拖慢。于是同样大小的 write 有时很快,有时停顿较久。某次写入耗时短,常常只是暂时借用了内存;某次耗时长,也不能直接推断设备坏了。要把应用写入、内存脏页和块设备活动放到同一时间线上观察。

在 Linux 上,可以用 grep -E '^(Dirty|Writeback):' /proc/meminfo 观察系统范围的脏页和正在写回的数据量。这里的值包含其他进程活动,不是当前程序的独占账单。如果系统安装了 vmstat 或 iostat,可以观察写出量和设备延迟随时间的变化。这些数据有助于解释卡顿,却不能替代某个文件的同步成功记录,更不能证明某条业务记录已经落盘。
fsync(fd) 用于请求同步该文件的数据和必要元数据,并等待接口所承诺的完成。fdatasync(fd) 侧重数据及读取这些数据所必需的元数据;例如文件大小变化仍可能需要同步。它不是“忽略所有元数据”的捷径。close 释放描述符,可能报告错误,但普通的关闭成功不等价于 fsync 成功。全局 sync 会影响更广范围,也不适合作为每条记录成功的专属证据。
同步间隔涉及性能与可恢复进度。假设程序每秒生成 40 条记录,每 5 秒同步 1 批,那么有约 200 条记录可能处于上次完成同步之后。这个数只是稳定生成、按时完成同步的简化估算;队列积压、同步超时和突发流量都会使风险窗口变化。即使选择批量同步,也应让调用方知道哪些记录只被接受,哪些已经跨过持久化边界。
滑块展示同步批次怎样改变待确认量,而不是预测真实设备吞吐。缩短间隔会增加同步调用频率,可能降低批处理收益;延长间隔则增加未完成确认的记录。实际选择需要测量延迟分布和最坏积压,并结合可以接受的恢复点。若产品把每次返回都定义为“已持久”,就不能仅为了降低延迟而把回包提前到同步之前。
4 动手:观察一次 fsync 路径
下面在独立临时目录里保存 1 个小状态文件。实验需要 Linux、Python 3 和 strace,选用支持文件及目录同步的本地文件系统。若没有 strace,仍可阅读程序理解顺序,但不能声称已经观察到系统调用。此实验不需要管理员权限,也不需要断电、卸载磁盘或修改主机缓存参数。请把实验目录视作临时数据,命令会输出它的位置。
work=$(mktemp -d)
printf '实验目录:%s\n' "$work"
strace -o "$work/trace.log" \
-e trace=openat,write,fsync,rename,renameat,renameat2,close \
python3 - "$work" <<'PY'
import os
import sys
root = sys.argv[1]
temp = os.path.join(root, "state.tmp")
target = os.path.join(root, "state.json")
with open(temp, "xb") as f:
f.write(b'{"sample": 42}\n')
f.flush()
os.fsync(f.fileno())
os.replace(temp, target)
directory = os.open(root, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(directory)
finally:
os.close(directory)
print(target)
PY
这个示例用独占创建避免覆盖已有临时文件,只适合没有并发写入者的教学目录。生产程序应使用安全的唯一临时名,明确文件权限,并在失败后处理残留。替换已有文件还可能改变权限、所有者或扩展属性,不能假定新文件自动继承旧文件的全部属性。若保存的是敏感配置,应在发布新名字之前设置并检查所需属性。
执行 cat "$work/state.json" 检查文本,再执行 grep -E 'state|write\(|fsync\(|rename' "$work/trace.log" 查看关键调用。跟踪里会包含解释器启动、模块加载和标准输出等活动,需要根据路径、文件描述符以及先后关系找出目标流程。描述符是进程内可复用的数字,不能看到某次 fsync(3) 就认定它一定同步目标文件,应先找对应的打开记录。

预期能够找到临时文件打开、内容写入、文件 fsync 成功、重命名成功,以及目录 fsync 成功。Python 的 os.replace 可能表现为 rename 或相应变体,具体取决于平台和调用方式。关键是顺序与返回结果。系统调用返回 0 说明该接口报告成功;若出现 -1 和错误码,则必须把它视作失败证据,不能只依据最终文件可读就宣布保存完成。
动画中请分别观察“读者何时看到新版”和“程序何时可以确认持久保存”。前者发生在替换名字时,后者要等目录同步成功。若最终同步失败,新文件可能已经对读者可见,程序却没有足够依据确认断电后的状态。这种不确定结果应该进入错误处理或重试协议,不能把失败提示简单解释成“肯定没有修改”。
这段跟踪展示调用意图、顺序和内核返回值,无法直接观察设备内部是否正确兑现刷写。不要通过清空缓存或终止进程来补上这个证据缺口。受控故障恢复测试需要隔离环境、已知存储条件和可比较的恢复结果,属于后续工作。当前实验的完成标准是能够解释每个同步对象,指出成功回报的位置,并识别任意关键调用失败时尚未兑现的承诺。
5 快照、配额与挂载选项
文件系统能力往往通过挂载点呈现。同样的路径形式,背后可能是本地磁盘、内存文件系统、网络挂载或容器叠加层。排查之前先执行 findmnt -T "$work" -o TARGET,SOURCE,FSTYPE,OPTIONS,确认实验目录落在哪个挂载点。必要时使用 df -h "$work" 查看空间、df -i "$work" 查看 inode 使用情况。工具输出是某个时刻的观察,并不保证下一次写入仍有足够资源。
快照保存某个时间点的视图,常用于快速回退或创建备份来源,但它是否包含应用所需的一致状态,取决于创建时机。假设应用先写数据文件,再写索引文件,快照恰好夹在中间,即使快照本身可读,应用仍可能无法使用这组文件。应该通过应用提供的检查点、暂停写入或其他已支持的协调方式建立一致边界,再创建快照,并记录恢复步骤。
快照也不自动等于独立备份。如果快照与原数据依赖同一存储资源,设备故障或误操作仍可能同时影响它们。更实际的问题是“从这个快照恢复后,应用能恢复到哪个已确认进度”。先定义恢复点,再讨论复制和保存位置。具体的写时复制实现、快照树结构和块引用计数不属于本课范围。
配额控制某个用户、组或项目可以占用的资源。df 显示还有剩余空间,写入仍可能因为配额耗尽而失败,常见错误是 EDQUOT。普通空间耗尽常表现为 ENOSPC,inode 耗尽也可能让新文件创建失败。大量小文件的主机会出现字节容量尚多、却无法新增文件的情况。排查要同时看空间、inode 和适用配额,不能看到“磁盘未满”便结束调查。

挂载选项会改变行为,但要先知道选项针对什么。ro 表示只读;文件系统检测到严重错误后也可能转成只读,此时写入可能返回 EROFS。noatime 主要影响访问时间更新,并不把所有写入改成持久写。sync 等选项会改变写入完成行为,也可能带来明显开销,不宜把它当成无需修改应用的万能修复。涉及日志或设备刷新语义的选项,应查阅当前文件系统的文档再调整。
容器中的相同命令还要结合挂载来源解释。程序写在临时层、内存挂载或宿主机持久目录,生命周期可能完全不同;在容器里成功调用同步,也不能凭空赋予临时卷长期保存策略。网络挂载则还涉及客户端与服务端的承诺。遇到特殊文件系统不支持某类同步时,应保留错误并重新设计或限定支持范围,不能静默跳过后继续宣称相同保证。
运维记录至少应包含路径所在挂载点、文件系统类型、关键选项、存储来源和适用配额。这样,当同步延迟突然升高或保存开始失败时,才能判断是程序顺序改变、空间资源变化,还是底层环境迁移。环境描述不是附录里的背景资料,它直接参与“保存完成”这句话的适用条件。
6 常见坑:以为 write 即持久
最常见的误判是把 write 成功、文件可读、文件关闭以及持久化成功连成同义词。它们分别描述接受数据、读取可见性、描述符生命周期和故障恢复承诺。排查时要拆开记录:写了多少字节,用户态是否已经刷新,同步哪个描述符,返回什么错误,最后何时向调用方确认。只有“保存失败”这句话,往往不足以定位问题。
另一个坑是直接覆盖目标文件。以截断方式打开会先破坏旧内容,之后进程若退出,读者就可能得到空文件或不完整文本。即使稍后打算调用 fsync,也无法让此前看到半段内容的读者回到过去。小文件的完整版本替换可以使用同目录临时文件流程;持续追加日志有不同的恢复需求,需要定义完整记录边界和尾部检查,不能机械照搬整文件替换。
错误也未必在最初写入时出现。延迟分配和后台写回意味着空间不足或输入输出错误可能在后续调用才暴露。同步失败必须沿着调用链向上传递;记录错误后仍返回“保存成功”,会把可恢复的问题变成错误承诺。关闭文件时也要关注错误,但不要因此省略先前必要的同步。每个接口的错误都应结合它所在阶段解释。

对于 ENOSPC,检查目标挂载点的容量、inode 和写入增长;对于 EDQUOT,检查适用配额;对于 EACCES,回到路径各级权限、目录写权限和进程身份;对于 EROFS,检查挂载状态及系统错误记录。EIO 表示更底层的输入输出问题,需要结合系统日志和存储状态调查。错误码提供方向,却不能单独证明是哪块设备或哪个组件损坏。
删除大日志后空间没有立即下降,也容易被误判成文件系统没有工作。若进程仍持有已删除对象的描述符,空间可能要等引用释放后才回收。具备适当权限时,可用 lsof +L1 辅助查看已删除但仍打开的文件;若工具不可用,也可结合 /proc/<pid>/fd 的链接判断。应使用应用支持的重开日志机制,避免为了回收空间随意终止关键进程。
最后,要警惕“再加一次重试”掩盖不确定结果。重命名已经成功而目录同步失败时,当前可见状态可能已经改变;调用方重试可能覆盖更新的版本。带版本号、单写入者约束和可识别的操作编号,有助于判断重复请求。锁与并发控制解决谁能更新的问题,fsync 解决持久化边界的问题;只完成其中 1 项,另一项仍可能使状态出错。
7 案例:未 fsync 的「已保存」丢失
某台 Linux 采集主机把网络接口的累计接收字节数写入 checkpoint.json,文件包含采样时间、接口名和计数值。进程每 2 秒写入临时文件,关闭后重命名为目标文件,随即输出“已保存”。运维人员多次通过 shell 读回检查,看到的都是最新内容,因此认为保存逻辑可靠。后来主机断电,重启后文件恢复到较早版本,监控图中出现异常差值。
调查先明确事实边界:应用日志里的“已保存”证明执行到了打印位置;断电前读回证明当时内容可见;重启后的文件证明恢复结果较旧。它们并不互相矛盾。代码使用了临时文件和替换,减少了并发读到半段内容的机会,却没有对文件或目录执行同步。“原子更新名字”被错误地解释成“新版本已经耐受断电”。
随后在隔离目录里跟踪同样的保存路径,观察到写入、关闭、重命名和打印,没有对应的同步调用。这是协议缺口的直接证据。它支持“程序没有建立所宣称的持久化保证”,但无法仅凭 1 次事故还原断电瞬间所有缓存层的状态。调查报告应保留这一区别,避免把推测写成已经观测到的设备行为。
动画比较 2 条路径。原路径在重命名后就确认,故障可能使最新内容或名字变更没有保留下来;具体恢复为旧版、缺失还是其他允许状态,取决于文件系统和环境,不能保证总是整齐地退回某个版本。修正路径先同步临时文件,再替换并同步父目录,最后确认。若任意阶段失败,则保留错误及操作版本,不产生“已持久”的成功事件。
修复还涉及采集逻辑本身。网络接口的累计计数可能在主机重启、接口重建或计数源重置后下降,即使文件完全持久,也不能直接用新值减旧值得到有效流量。检查点应带上采样来源和重启标识等必要上下文;当计数回退或身份变化时,重新建立基线。文件系统保证字节的恢复,应用仍负责解释恢复出的字节。
对外状态改为“已接受”和“已持久”这 2 个明确阶段。只有完成约定同步后,才推进已持久的检查点版本。若同步耗时过长,采集进程可以继续收集到有界队列,但需要暴露积压,并在达到上限时选择背压或明确丢弃策略。不能让无限增长的内存队列把持久化停滞隐藏起来。
验证分为接口顺序检查、失败路径检查和受控恢复检查。前者确认同步对象与顺序;中间部分确认同步失败不会产生成果回报;恢复检查则在隔离环境里比较最后确认版本与重启结果。普通进程退出测试可以验证清理与恢复逻辑,却不能替代断电测试。案例的可迁移结论是:应让成功事件紧跟它承诺的边界,而不是紧跟最方便打印日志的位置。
8 把持久化语义写进数据契约
“保存到文件”不是足够完整的接口说明。数据契约至少需要回答:返回时数据到达什么阶段,能够承受哪类故障,哪些对象作为完整更新单位,以及失败后调用方可以怎样重试。对于单个配置文件,可以约定返回成功意味着新内容与目标名字已按支持环境完成同步;对于批量采集,可以约定先接受,再通过版本游标报告已持久进度。
更新单位必须具体。若配置分散在 3 个文件里,对每个文件分别调用 fsync,并不能自动让 3 个文件一起更新成功。初学阶段可以优先把需要共同发布的小状态放进同一个完整文件,或使用应用已经支持的事务存储。不要临时拼凑多个重命名,就把它写成跨文件原子事务。更复杂的提交协议留到具备相应背景之后。

契约还要覆盖并发。示例临时文件流程默认只有 1 个写入者;如果多个进程同时保存,最后替换名字的进程可能覆盖其他进程刚写好的版本。需要明确由哪个进程负责写入、是否使用锁,以及版本是否允许倒退。读取方也应明确每次重新打开目标文件,还是长时间持有描述符。否则发布完成后仍有人读取旧对象,容易被错误归因于同步失效。
建议把错误结果分成可明确判定未发布、已发布但持久性未确认,以及已经完成约定的持久确认。这里的划分服务于恢复决策,不能只靠一个布尔值含糊表达。操作若在返回前失去连接,调用方可能不知道结果;通过可查询的版本号或操作编号,可以在重试前确认当前状态。幂等性要依据更新内容设计,不能因为文件写入看起来可以重复执行就自动成立。
观测指标应直接对应这些承诺。可记录写入错误数、同步错误数、同步耗时分布、最近已持久版本、未确认队列长度和检查点年龄。仅有磁盘使用率不足以发现“空间充足但所有同步都失败”的情况。日志可以记录操作编号、阶段和错误码,避免写入敏感文件正文;即使日志也发生丢失,应用仍应能从目标文件及版本规则恢复判断。
支持环境同样属于契约。写清本地文件系统假设、是否需要目录同步、允许的挂载类型,以及容器卷由谁负责保留。如果未来迁移到网络文件系统,应重新核对服务端同步语义和故障边界。测试通过的环境清单比“任何磁盘都可靠”更有用,因为它给运维变更提供了可以复核的条件。
最后写出恢复入口:启动时检查目标文件格式和版本,识别残留临时文件,确认哪个版本已经发布,再恢复采集。不要看到临时文件时间较新就自动覆盖正式文件,它可能只写了一部分,也可能从未完成确认。恢复规则应与写入协议一起设计,并说明遇到未知状态时如何停止推进、保留证据和请求人工处理。
9 从文件系统走向 namespaces 与 cgroups(S29)
到这里,可以沿着“进程缓冲、页缓存、文件同步、名字替换、目录同步、成功回报”解释一次保存,也能说明哪一步影响读者可见性,哪一步对应故障恢复。继续学习操作系统与网络时,这个边界意识比记住某个命令更重要。任何看似简单的成功返回,都需要问清对象、完成条件以及失败后由谁恢复。
下一课 namespaces 与 cgroups 会把“谁在同一台主机上共享哪些资源”拆开:命名空间决定进程看见的挂载点、网络栈与进程树,控制组决定 CPU、内存与 I/O 配额。文件系统视角仍然有用:容器里的路径可能指向不同挂载命名空间中的对象;同一路径名在宿主机与容器中可能对应不同的 inode。cgroup 的 memory 上限又会改变页缓存与回收压力,使本课讨论的写回与同步时延在受限环境中表现不同。
shell 同样会影响观察结论。命令退出码为 0 表示该命令按自身规则成功,不自动表示整条业务链持久完成。管道中某个环节失败,需要结合各阶段返回值和 pipefail 等已有知识解释。将输出重定向到文件只是改变字节去向;重定向本身不会替程序补上事务或同步协议。把 shell 退出码、应用成功事件和文件系统同步结果分开,才能构建有效的自动化检查。
可以用 3 个问题自检。为什么修改后立刻读取成功,断电后仍可能丢失?为什么文件已经同步,替换名字之后还要考虑父目录?为什么终止写进程不能充分模拟整机断电?如果能够分别从缓存边界、目录项持久化和故障范围回答,就已经抓住本课主线。若回答只剩“系统会自动处理”,应回到实验时间线标出尚未完成的步骤。
进阶时再学习不同文件系统的数据模式、存储设备缓存承诺、完整的故障注入和多对象事务。当前阶段优先做到保存顺序明确、同步错误不丢、运行环境可描述、恢复版本可判断。这些工作能够直接改善真实主机上的配置保存、采集检查点和日志运维,也为 S29 讨论隔离边界与资源限额时如何解释挂载与内存行为打下基础。
放进方案里
为采集主机写方案时,把检查点保存列为独立协议:由 1 个写入进程生成完整状态,内容含格式版本、采样来源、重启标识、采样时间与累计值。在目标目录创建唯一临时文件,写完并刷新用户态缓冲,成功同步文件后替换正式名字,再同步父目录。全部完成才发布“已持久”事件,同时推进版本游标。并发读者每次重新打开正式路径,避免长期持有旧对象。
方案中的成功标准要能检查:系统调用跟踪应显示目标文件和父目录的同步顺序;写入、替换或同步失败时,不得推进已持久版本。若名字已替换而最终同步失败,应标记结果未确认,保留操作编号,通过查询当前版本和恢复规则处理重试。不能把错误处理统一写成“再次保存”,也不能把终止进程后的可读结果当成抗断电验收。
部署记录写明挂载点、文件系统类型、关键选项、卷生命周期及配额。监控同步耗时、同步错误、检查点年龄和未确认队列长度;队列必须有上限,并明确触发背压后的行为。预留空间不足和 inode 耗尽的排查入口,对输入输出错误保留系统侧证据。调整同步批次时,同时更新允许丢失窗口和确认语义。
恢复流程先验证正式文件格式、来源与版本,识别计数重置,再决定是否接续差值计算。残留临时文件只作为调查材料,未经协议判断不得提升为正式版本。最后安排隔离环境中的恢复演练,比较故障前最后确认的版本与恢复结果,并验证快照恢复后应用能继续运行。这样,“已保存”就成为带有条件、证据和恢复办法的承诺。
10 小结:下一步 namespaces 与 cgroups(S29)
路径是名字,inode 对应文件对象,描述符保存进程对对象的引用。页缓存使读写高效,也让可见与持久之间存在距离。可靠保存需要匹配场景的写序、同步对象、错误处理和恢复约定,原子重命名只是其中的组成部分。
下一步进入 namespaces 与 cgroups:弄清挂载、网络与进程视图如何被隔离,以及 CPU、内存限额如何改变本课讨论的缓存与同步行为。保留本课的提问习惯:谁接受了数据,谁确认了完成,哪类故障仍可能改变结果,以及恢复时依据哪个版本作判断。