# 第 49 章实验:NFS 设计要点的小型模拟 NFS 需要真正的服务器和挂载权限,不适合在课堂上随手做。这里用一个**单进程、输出确定**的 C 程序 `nfs_sim.c` 模拟 NFSv2 服务器的关键行为,帮助你把书上的四个论点"跑一遍"。 ## 编译与运行 ```bash cd labs/49-nfs make ./nfs_sim # 运行全部 4 个实验 ./nfs_sim 2 # 只运行第 2 个 ``` ## 四个实验与该观察到什么 1. **无状态协议 vs 有状态协议**(对应原书 Figure 49.5 与 49.4 节) 客户端的打开文件表保存 `fd -> (文件句柄, 当前偏移)`,每个 `READ` 都带着句柄和偏移。 服务器在第一次 `READ` 之后崩溃重启,后续 `READ` 照常成功——服务器根本不需要记得任何东西。 对比假想的有状态协议(`OPEN` 返回服务器端描述符 `sfd`),崩溃后服务器不认识 `sfd=7`,读取失败。 2. **幂等性**(Figure 49.6 的第 3 种情况:回复丢失) 同一请求被服务器执行两次: - `WRITE(fh, offset=2, ...)`:结果与执行一次完全相同 → 幂等,重试安全; - 假想的 `APPEND`:数据被追加两次 → 不幂等(这正是 NFS 协议里没有"追加"而是让客户端给出绝对偏移的原因); - `MKDIR`:第一次成功,重试返回 `EEXIST`,客户端被告知"失败",而操作其实成功了(原书的例子); - `REMOVE`:同理,重试得到 `ENOENT`。 3. **文件句柄中的世代号(generation number)** 客户端 A 缓存了 `foo` 的句柄;B 删除 `foo` 并创建新文件,重用了同一个 inode 号。 服务器检查世代号时,A 用旧句柄读取得到 `ESTALE`;不检查时,A 读到了别人的文件内容。 4. **服务器端写缓冲的陷阱**(49.11 节) "先回复、后落盘"时,第二个块在服务器崩溃中丢失,但客户端已被告知成功,最终文件是 `aaaa / yyyy / cccc`(`<--- oops`)。"先落盘、再回复"时,崩溃导致没有回复,客户端超时重试,最终 `aaaa / bbbb / cccc`。 ## 为什么 - NFSv2 的首要目标是**简单快速的服务器崩溃恢复**。无状态 + 幂等操作让客户端能用一种统一的办法—— **超时后重试**——同时处理请求丢失、服务器宕机、回复丢失三种情况。 - 服务器必须在回复 `WRITE` 成功前把数据提交到稳定存储,否则"客户端重试"这一恢复手段就失效了。 NetApp 等厂商用带电池的内存(battery-backed memory)和专门的文件系统布局来让这件事变快。