跳到主要内容

1. 简介 (Introduction)

Sun 的 NFS 协议 (NFS Protocol) 提供了跨网络对共享文件系统的透明远程访问. NFS 协议被设计为独立于机器, 操作系统, 网络架构和传输协议. 这种独立性是通过在外部数据表示 (eXternal Data Representation, XDR) 之上构建的远程过程调用 (Remote Procedure Call, RPC) 原语实现的. NFS 版本 2 协议的实现存在于各种机器上, 从个人计算机到超级计算机. NFS 协议的初始版本在网络文件系统协议规范 [RFC1094] 中指定. 初始实现的描述可以在 [Sandberg] 中找到.

支持的 MOUNT 协议执行特定于操作系统的功能, 允许客户端将远程目录树附加到本地文件系统中的某个点. 挂载过程还允许服务器通过导出控制向受限的一组客户端授予远程访问权限.

锁管理器 (Lock Manager) 在 NFS 环境中使用时提供文件锁定支持. 网络锁管理器 (Network Lock Manager, NLM) 协议将文件锁定固有的有状态方面隔离到单独的协议中.

有关上述协议及其实现的完整描述可以在 [X/OpenNFS] 中找到.

本文档的目的是:

  • 指定 NFS 版本 3 协议.

  • 通过注释和对预期实现的描述来描述协议的语义.

  • 指定 MOUNT 版本 3 协议.

  • 简要描述 NLM 版本 3 协议和 NLM 版本 4 协议之间的变化.

规范性文本是对 RPC 过程及其参数和结果的描述, 它定义了线上协议 (over-the-wire protocol) 以及这些过程的语义. 描述实现实践的材料有助于理解协议规范, 并描述了一些可能的实现问题和解决方案. 不可能描述所有实现, 因此最常使用 UNIX 操作系统的 NFS 版本 3 协议实现来提供示例. 鉴于此, 实现讨论不具有线上协议描述本身的权威性.

1.1 NFS 版本 3 协议的范围 (Scope of the NFS version 3 protocol)

此次 NFS 协议修订解决了新的需求. 对支持更大文件和文件系统的需求促使扩展以允许 64 位文件大小和偏移量. 该修订通过添加对在服务器上进行访问检查的支持来增强安全性. 性能修改有三种类型:

  1. 通过在每个操作上返回文件属性, 减少了给定文件操作集的线上数据包数量, 从而减少了获取修改属性的调用次数.

  2. NFS 版本 2 协议中写操作的同步定义造成的写吞吐量瓶颈已通过添加支持得到解决, 使 NFS 服务器可以执行不安全写入 (unsafe writes). 不安全写入是指在操作返回之前尚未提交到稳定存储的写入. 本规范定义了一种以可靠方式将这些不安全写入提交到稳定存储的方法.

  3. 对传输大小的限制已放宽.

RPC 中支持协议多版本的能力将允许 NFS 版本 3 协议的实现者定义与现有已安装的 NFS 版本 2 协议实现基础提供向后兼容性的客户端和服务器.

这里描述的扩展代表了现有 NFS 协议的演进, [Sandberg] 中描述的 NFS 协议的大多数设计特性仍然保留. 有关此修订引入的变化的更详细摘要, 请参阅第 11 页的"NFS 版本 2 协议的变化".

1.2 有用的术语 (Useful terms)

在本规范中, "服务器 (server)" 是向网络提供资源的机器; "客户端 (client)" 是通过网络访问资源的机器; "用户 (user)" 是登录到客户端的人; "应用程序 (application)" 是在客户端上执行的程序.

1.3 远程过程调用 (Remote Procedure Call)

Sun 远程过程调用规范提供了到远程服务的面向过程的接口. 每个服务器提供一个程序, 即一组过程. NFS 服务就是这样一个程序. 主机地址, 程序编号, 版本号和过程编号的组合指定一个远程服务过程. 服务器可以通过使用不同的协议版本号来支持程序的多个版本.

NFS 协议被设计为不需要其较低层的任何特定可靠性级别, 因此它可能在许多底层传输协议上使用. NFS 服务基于 RPC, 它提供了高于较低级别网络和传输协议的抽象.

本文档的其余部分假设 NFS 环境在 Sun RPC 之上实现, Sun RPC 在 [RFC1057] 中指定. 完整的讨论可在 [Corbin] 中找到.

1.4 外部数据表示 (External Data Representation)

外部数据表示 (eXternal Data Representation, XDR) 规范提供了一种在网络上表示一组数据类型的标准方法. 这解决了在不同的通信机器上字节顺序, 结构对齐和数据类型表示不同的问题.

在本文档中, RPC 数据描述语言 (RPC Data Description Language) 用于指定 NFS 服务器提供的每个 RPC 服务过程的 XDR 格式参数和结果. RPC 数据描述语言类似于 C 编程语言中的声明. 添加了一些新的构造. 符号:

string  name[SIZE];
string data<DSIZE>;

定义了 name, 它是一个 SIZE 字节的固定大小块, 以及 data, 它是一个最多 DSIZE 字节的可变大小块. 这种符号表示固定长度数组和具有可变数量元素直到固定最大值的数组. 未指定大小的可变长度定义意味着该字段没有最大大小.

判别联合 (discriminated union) 定义:

union example switch (enum status) {
case OK:
struct {
filename file1;
filename file2;
integer count;
}
case ERROR:
struct {
errstat error;
integer errno;
}
default:
void;
}

定义了一个结构, 其中网络上的第一项是名为 status 的枚举类型. 如果 status 的值是 OK, 则网络上的下一项将是包含 file1, file2 和 count 的结构. 否则, 如果 status 的值是 ERROR, 则网络上的下一项将是包含 error 和 errno 的结构. 如果 status 的值既不是 OK 也不是 ERROR, 则结构中没有更多数据.

XDR 类型 hyper 是 8 字节 (64 位) 量. 它的使用方式与整数类型相同. 例如:

hyper          foo;
unsigned hyper bar;

foo 是 8 字节有符号值, 而 bar 是 8 字节无符号值.

尽管存在 RPC/XDR 编译器可以从 RPC 数据描述语言输入生成客户端和服务器存根, 但 NFS 实现不需要使用它们. 任何提供与 XDR 定义的数据规范网络顺序等效编码和解码的软件都可以用于与其他 NFS 实现互操作.

XDR 在 [RFC1014] 中描述.

1.5 身份验证和权限检查 (Authentication and Permission Checking)

RPC 协议在每次调用中都包含一个用于身份验证参数的槽. 身份验证参数的内容由服务器和客户端使用的身份验证类型确定. 服务器可以同时支持几种不同风格的身份验证. AUTH_NONE 风格提供空身份验证, 即不传递身份验证信息. AUTH_UNIX 风格在每次调用时提供 UNIX 风格的用户 ID, 组 ID 和组. AUTH_DES 风格提供基于网络范围名称的 DES 加密身份验证参数, 通过公钥方案交换会话密钥. AUTH_KERB 风格提供基于网络范围名称的 DES 加密身份验证参数, 通过 Kerberos 密钥交换会话密钥.

NFS 服务器通过从每个远程请求中的 RPC 身份验证信息获取凭据来检查权限. 例如, 使用 AUTH_UNIX 风格的身份验证, 服务器在每次调用时获取用户的有效用户 ID, 有效组 ID 和组, 并使用它们来检查访问权限. 使用用户 ID 和组 ID 意味着客户端和服务器共享相同的 ID 列表或进行本地用户和组 ID 映射. 对于未实现一致的用户 ID 和组 ID 空间的站点, 服务器和客户端必须就从用户到 uid 和从组到 gid 的映射达成一致. 实际上, 这种映射通常在服务器上执行, 遵循静态映射方案或用户在挂载时从客户端建立的映射.

AUTH_DES 和 AUTH_KERB 风格的身份验证基于网络范围的名称. 它通过在 AUTH_DES 的情况下使用 DES 加密和公钥, 在 AUTH_KERB 的情况下使用 DES 加密和 Kerberos 密钥 (和票据) 来提供更高的安全性. 同样, 服务器和客户端必须就网络上特定名称的身份达成一致, 但名称到身份的映射比 AUTH_UNIX 中的 uid 和 gid 映射更独立于操作系统. 此外, 由于身份验证参数是加密的, 恶意用户必须知道另一个用户的网络密码或私钥才能伪装成该用户. 类似地, 服务器返回的验证器也是加密的, 因此伪装成服务器需要知道网络密码.

NULL 过程通常不需要身份验证.

1.6 设计理念 (Philosophy)

本规范定义了 NFS 版本 3 协议, 即客户端访问服务器的线上协议. 该协议为服务器的文件资源提供了一个明确定义的接口. 客户端或服务器实现该协议, 并提供本地文件系统语义和操作到 NFS 版本 3 协议中定义的那些的映射. 根据给定环境支持 NFS 版本 3 协议中定义的所有操作和语义的程度, 实现可能在不同程度上有所不同. 尽管存在实现并用于说明 NFS 版本 3 协议的各个方面, 但协议规范本身是客户端如何访问服务器资源的最终描述.

由于 NFS 版本 3 协议被设计为独立于操作系统, 因此它不一定与任何现有系统的语义匹配. 服务器实现应尽最大努力支持该协议. 如果服务器无法支持特定的协议过程, 它可以返回错误 NFS3ERR_NOTSUP, 表示不支持该操作. 例如, 许多操作系统不支持硬链接的概念. 无法支持硬链接的服务器应该响应 LINK 请求返回 NFS3ERR_NOTSUP. FSINFO 在属性位图中描述了最常见的不受支持的过程. 或者, 服务器可能本身不支持给定的操作, 但可以在 NFS 版本 3 协议实现中模拟它以提供更强大的功能.

在某些情况下, 服务器可以支持协议描述的大部分语义但不是全部. 例如, fattr 结构中的 ctime 字段给出文件属性最后一次修改的时间. 许多系统不保留此信息. 在这种情况下, 服务器可以通过返回最后修改时间来代替 ctime 来模拟它, 而不是不支持 GETATTR 操作. 服务器在模拟属性信息时必须小心, 因为可能对客户端产生副作用. 例如, 许多客户端使用文件修改时间作为其缓存一致性方案的基础.

NFS 服务器是简单的, NFS 客户端是智能的. 客户端完成将服务器提供的通用文件访问转换为对应用程序和用户有用的文件访问方法所需的工作. 在上面给出的 LINK 示例中, 从服务器收到 NFS3ERR_NOTSUP 错误的 UNIX 客户端将执行必要的恢复, 以使应用程序看起来链接请求已成功或返回合理的错误. 一般来说, 客户端负担恢复责任.

NFS 版本 3 协议假设无状态服务器实现. 无状态意味着服务器不需要维护有关其任何客户端的状态即可正常运行. 无状态服务器在崩溃时比有状态服务器具有明显的优势. 对于无状态服务器, 客户端只需重试请求直到服务器响应; 客户端甚至不需要知道服务器已崩溃. 有关更多评论, 请参阅第 99 页的"重复请求缓存".

为了有用, 服务器保存非易失性状态: 存储在文件系统中的数据. NFS 版本 3 协议中关于将修改数据刷新到稳定存储的设计假设减少了可能发生数据丢失的故障模式数量. 通过这种方式, NFS 版本 3 协议实现可以容忍瞬态故障, 包括网络的瞬态故障. 一般来说, NFS 版本 3 协议的服务器实现无法容忍稳定存储本身的非瞬态故障. 然而, 存在试图解决此类问题的容错实现.

这并不是说 NFS 版本 3 协议服务器不能维护非关键状态. 在许多情况下, 服务器将维护有关先前操作的状态 (缓存) 以提高性能. 例如, 客户端 READ 请求可能触发将文件的下一个块预读到服务器的数据缓存中, 以预期客户端正在进行顺序读取, 并且下一个客户端 READ 请求将从服务器的数据缓存而不是从磁盘满足. 服务器上的预读通过将服务器磁盘 I/O 与客户端请求重叠来提高性能. 这里的重点是预读块对于正确的服务器行为不是必需的. 如果服务器崩溃并丢失其读缓冲区的内存缓存, 则重启时恢复很简单 - 客户端将继续从服务器磁盘检索数据的读操作.

NFS 协议中的大多数数据修改操作都是同步的. 也就是说, 当数据修改过程返回到客户端时, 客户端可以假设操作已完成, 并且与请求关联的任何修改数据现在都在稳定存储上. 例如, 同步客户端 WRITE 请求可能导致服务器更新数据块, 文件系统信息块和文件属性信息 - 后者信息通常称为元数据 (metadata). 当 WRITE 操作完成时, 客户端可以假设写入数据是安全的并丢弃它. 这是服务器无状态特性的非常重要的一部分. 如果服务器在返回到客户端之前没有将脏数据刷新到稳定存储, 客户端将无法知道何时可以安全地丢弃修改的数据. 以下数据修改过程是同步的: WRITE (稳定标志设置为 FILE_SYNC), CREATE, MKDIR, SYMLINK, MKNOD, REMOVE, RMDIR, RENAME, LINK 和 COMMIT.

NFS 版本 3 协议在与 COMMIT 过程结合使用 WRITE 过程时引入了服务器上的安全异步写入. COMMIT 过程提供了一种方法, 用于客户端将先前异步 WRITE 请求中的数据刷新到服务器上的稳定存储, 并检测是否需要重新传输数据. 请参阅第 49 页的 WRITE 和第 92 页的 COMMIT 的过程描述.

LOOKUP 过程由客户端用于遍历多组件文件名 (路径名). 每次调用 LOOKUP 用于解析路径名的一个段. 将 LOOKUP 限制为单个段有两个原因: 很难为分层文件名标准化通用格式, 并且客户端和服务器可能对路径名到文件系统有不同的映射. 这意味着客户端必须在文件系统附加点处断开路径名, 或者服务器必须了解客户端的文件系统附加点. 在 NFS 版本 3 协议实现中, 客户端使用挂载构建层次结构来构造分层文件名空间. 支持实用程序 (如 Automounter) 提供了一种管理文件名空间的共享一致映像的方法, 同时仍由客户端挂载过程驱动.

客户端可以以不同的方式执行缓存. NFS 版本 2 协议的一般做法是实现基于时间的客户端-服务器缓存一致性机制. 预计 NFS 版本 3 协议实现将使用类似的机制. NFS 版本 3 协议具有一些显式支持, 以附加属性信息的形式消除显式属性检查. 然而, 不需要缓存, 协议也没有定义任何缓存策略. NFS 版本 2 协议和 NFS 版本 3 协议都不提供维护严格客户端-服务器一致性的手段 (并且, 通过暗示, 跨客户端缓存的一致性).

1.7 与 NFS 版本 2 协议的变化 (Changes from the NFS Version 2 Protocol)

ROOT 和 WRITECACHE 过程已被删除. 定义了 MKNOD 过程以允许创建特殊文件, 从而消除了 CREATE 的重载. 协议不定义也不规定客户端上的缓存, 但已向协议添加了附加信息和提示, 以允许实现缓存的客户端更有效地管理其缓存. 影响文件或目录属性的过程现在可以在操作完成后返回新属性, 以优化用于验证属性缓存的后续 GETATTR. 此外, 修改目标对象所在目录的操作会返回目录的旧属性和新属性, 以允许客户端实现更智能的缓存失效过程. ACCESS 过程在服务器上提供访问权限检查, FSSTAT 过程返回有关文件系统的动态信息, FSINFO 过程返回有关文件系统和服务器的静态信息, READDIRPLUS 过程除了返回目录条目外还返回文件句柄和属性, PATHCONF 过程返回有关文件的 POSIX pathconf 信息.

以下是 NFS 版本 2 协议和 NFS 版本 3 协议之间重要变化的列表.

文件句柄大小 (File handle size)

文件句柄已从 32 字节的固定数组增加到最多 64 字节的可变长度数组. 这解决了对稍大文件句柄大小的一些已知需求. 文件句柄从固定长度转换为可变长度, 以减少不使用全部 64 字节长度的系统的本地存储和网络带宽需求.

最大数据大小 (Maximum data sizes)

READ 和 WRITE 过程中使用的数据传输的最大大小现在由 FSINFO 返回结构中的值设置. 此外, FSINFO 还返回首选传输大小. 协议不对最大传输大小施加任何人为限制.

文件名和路径名现在指定为可变长度的字符串. 实际长度限制由客户端和服务器实现适当确定. 协议不对长度施加任何人为限制. 提供错误 NFS3ERR_NAMETOOLONG 以允许服务器向客户端返回指示, 表明它收到了一个太长而无法处理的路径名.

错误返回 (Error return)

在某些情况下, 错误返回现在返回数据 (例如, 属性). nfsstat3 现在定义了服务器可以返回的完整错误集. 不允许其他值.

文件类型 (File type)

文件类型现在包括用于特殊文件的 NF3CHR 和 NF3BLK. 这些类型的属性包括 UNIX 主设备号和次设备号的子字段. 现在为文件系统中的套接字和 FIFO 定义了 NF3SOCK 和 NF3FIFO.

文件属性 (File attributes)

blocksize (文件中块的大小, 以字节为单位) 字段已被删除. mode 字段不再包含文件类型信息. size 和 fileid 字段已从四字节整数扩展为八字节无符号整数. 主设备号和次设备号信息现在在不同的结构中呈现. blocks 字段名称已更改为 used, 现在包含文件使用的总字节数. 它也是一个八字节无符号整数.

设置文件属性 (Set file attributes)

在 NFS 版本 2 协议中, 可设置的属性由文件属性结构的子集表示; 客户端通过将相应字段设置为 -1 来指示不修改哪些属性, 从而重载了一些无符号字段. 设置文件属性结构现在对每个字段使用判别联合来告诉是否或如何设置该字段. atime 和 mtime 字段可以设置为服务器的当前时间或客户端提供的时间.

LOOKUP

LOOKUP 返回结构现在包括搜索目录的属性.

ACCESS

添加了 ACCESS 过程以允许显式的线上权限检查. 这解决了许多服务器实现中超级用户 ID 映射功能的已知问题 (由于 root 用户的映射, 在读取或写入文件时可能会发生意外的权限被拒绝错误). 这也消除了 NFS 版本 2 协议中的假设, 即对文件的访问完全基于 UNIX 风格的模式位.

READ

回复结构包括一个布尔值, 如果在 READ 期间遇到文件结束, 则该值为 TRUE. 这允许客户端正确检测文件结束.

WRITE

beginoffset 和 totalcount 字段已从 WRITE 参数中删除. 如果需要, 回复现在包括一个计数, 以便服务器可以写入少于请求的数据量. 在参数中添加了一个指示符, 用于指示客户端所需的缓存同步级别.

CREATE

为独占创建常规文件添加了独占标志和创建验证器.

MKNOD

添加了此过程以支持特殊文件的创建. 这避免了重载 CREATE, 就像某些 NFS 版本 2 协议实现中所做的那样.

READDIR

READDIR 参数现在包括一个验证器以允许服务器验证 cookie. cookie 现在是 64 位无符号整数, 而不是 NFS 版本 2 协议中使用的 4 字节数组. 这将有助于减少互操作性问题.

READDIRPLUS

添加了此过程以在扩展目录列表中返回文件句柄和属性.

FSINFO

添加了 FSINFO 以提供有关文件系统的非易失性信息. 回复包括首选和最大读取传输大小, 首选和最大写入传输大小, 以及声明是否支持链接或符号链接的标志. 还返回 READDIR 过程回复的首选传输大小, 服务器时间粒度以及是否可以在 SETATTR 请求中设置时间.

FSSTAT

添加了 FSSTAT 以提供有关文件系统的易失性信息, 供 Unix 系统 df 命令等实用程序使用. 回复包括以字节为单位指定的文件系统的总大小和可用空间, 文件系统中的文件总数和可用文件槽数, 以及文件系统修改之间的时间估计 (用于缓存一致性检查算法).

COMMIT

COMMIT 过程提供与异步 WRITE 操作一起使用的同步机制.