TCP/IP详解 卷一 网络文件系统
发表于:2025-10-29 | 分类: 学习
网络文件系统

概述

NFS(网络文件系统),它为客户程序提供透明的文件访问。NFS的基础是Sun RPC:远程过程调用。

客户程序使用NFS不需要做什么特别的工作,当NFS内核检测到被访问的文件位于一个NFS服务器时,就会自动产生一个访问该文件的RPC调用。

Sun远程过程调用

大多数的网络程序设计都是编写一些调用系统提供的函数来完成特定的网络操作的应用程序。例如,一个函数完成TCP的主动打开,另一个完成TCP的被动打开,一个函数在一个TCP连接上发送数据,另一个设置特定的协议选项(如激活TCP的keepalive定时器)。

远程过程调用RPC(Remote ProcedureCall)是一种不同的网络程序设计方法。客户程序编写时只是调用了服务器程序提供的函数。这只是程序员所感觉到的,实际上发生了下面一些动作。

  1. 当客户程序调用远程的过程时,它实际上只是调用了一个位于本机上的、由RPC程序包生成的函数。这个函数被称为客户残桩(stub)。客户残桩将过程的参数封装成一个网络报文,并且将这个报文发送给服务器程序。
  2. 服务器主机上的一个服务器残桩负责接收这个网络报文。它从网络报文中提取参数,然后调用应用程序员编写的服务器过程。
  3. 当服务器函数返回时,它返回到服务器残桩。服务器残桩提取返回值,把返回值封装成一个网络报文,然后将报文发送给客户残桩。
  4. 客户残桩从接收到的网络报文中取出返回值,将其返回给客户程序。

网络程序设计是通过残桩和使用诸如插口或TLI的某个API的RPC库例程来实现的,但是用户程序—客户程序和被客户程序调用的服务器过程—不会和这个API打交道。客户应用程序只是调用服务器的过程,所有网络程序设计的细节都被RPC程序包、客户残桩和服务器残桩所隐藏。

一个RPC程序包提供了很多好处。

  1. 程序设计更加容易,因为很少或几乎没有涉及网络编程。应用程序设计员只需要编写一个客户程序和客户程序调用的服务器过程。
  2. 如果使用了一个不可靠的协议,如UDP,像超时和重传等细节就由RPC程序包来处理。这就简化了用户应用程序。
  3. RPC库为参数和返回值的传输提供任何需要的数据转换。例如,如果参数是由整数和浮点数组成的,RPC程序包处理整数和浮点数在客户机和服务器主机上存储的不同形式。这个功能简化了在异构环境中的客户和服务器的编码问题。
Sun RPC

Sun RPC有两个版本。一个版本建立在插口API基础上,和TCP和UDP打交道。另一个称为TI-RPC的(独立于运输层),建立在TLIAPI基础上,可以和内核提供的任何运输层协议打交道。

下图是使用UDP时,一个RPC过程调用报文的格式。:

IP首部和UDP首部是标准的首部,UDP首部以下是RPC程序包定义的部分。

事务标识符(XID)由客户程序设置,由服务器程序返回。当客户收到一个应答,它将服务器返回的XID与它发送的请求的XID相比较。如果不匹配,客户就放弃这个报文,等待从服务器返回的下一个报文。每次客户发出一个新的RPC,它就会改变报文的XID。但是如果客户重传一个以前发送过的RPC(因为它没有收到服务器的一个应答),重传报文的XID不会修改。

调用(call)变量在过程调用报文中设置为0,在应答报文中设置为1。当前的RPC版本是2。接下来三个变量:程序号、版本号和过程号,标识了服务器上被调用的特定过程。

证书(credential)字段标识了客户。有些情况下,证书字段设置为空值;另外一些情况下,证书字段设置为数字形式的客户的用户号和组号。服务器可以查看证书字段以决定是否执行请求的过程。验证(verifier)字段用于使用了DES加密的安全RPC。尽管证书字段和验证字段是可变长度的字段,它们的长度也作为字段的一部分被编码。

接下来是过程参数(procedure parameter)字段。参数的格式依赖于远程过程的定义。

既然使用的是UDP协议,UDP数据报的大小减去验证字段以上所有字段的长度就是参数的大小。如果使用的不是UDP而是TCP,因为TCP是一个字节流协议,没有记录边界,所以没有固定的长度。为了解决这个问题,在TCP首部和XID之间增加了一个4字节的长度字段,告诉接收者这个RPC调用由多少字节组成。这也使得一个RPC调用报文在必要时可以用多个TCP段来传输

下图展示一个RPC应答报文的格式:

当远程过程返回时,服务器残桩将这个报文发送给客户残桩。

应答报文中的XID字段是从调用报文的XID字段复制而来。应答字段设置为1,以区别于调用报文。如果调用报文被接受,状态字段设置为0(如果RPC的版本号不为2,或者服务器不能鉴别客户的身份,调用报文可能被拒绝)。安全的RPC使用验证字段来标识服务器。

如果远程过程调用成功,接受状态字段置为0。一个非零的值可能表示一个不合法的版本号或者一个不合法的过程号。如果使用的不是UDP而是TCP,如同RPC调用报文一样,在TCP首部和XID字段之间插入一个4字节的长度字段。

XDR:外部数据表示

外部数据表示XDR(eXternal Data Representation)是一个标准,用来对RPC调用报文和应答报文中的值进行编码。这些值包括RPC首部字段(XID、程序号、接受状态等)、过程参数和过程结果。采用标准化的方法对这些值进行编码使得一个系统中的客户可以调用另一个不同架构的系统中的一个过程。

XDR定义了很多数据类型以及它们如何在一个RPC报文中传输的具体形式(如比特顺序,字节顺序等)。发送者必须采用XDR格式构造一个RPC报文,然后接收者将XDR格式的报文转换为本机的表示形式。XDR支持的其他数据类型包括无符号整数、布尔类型、浮点数、定长数组、可变长数组和结构。

端口映射器

包含远程过程的RPC服务器程序使用的是临时端口,而不是知名端口。这就需要某种形式的“注册”程序来跟踪哪一个RPC程序使用了哪一个临时端口。这个注册程序被称为端口映射器(portmapper)。

端口映射器本身必须有一个知名端口:UDP端口111和TCP端口111。端口映射器也就是一个RPC服务器程序。它有一个程序号(100000)、一个版本号(2)、一个TCP端口111和一个UDP端口111。服务器程序使用RPC调用向端口映射器注册自身,客户程序使用RPC调用向端口映射器查询。端口映射器提供四个服务过程:

  1. PMAPPROC_SET。一个RPC服务器启动时调用这个过程,注册一个程序号、版本号和带有一个端口号的协议。
  2. PMAPPROC_UNSET。RPC服务器调用此过程来删除一个已经注册的映射。
  3. PMAPPROC_GETPORT。一个RPC客户启动时调用此过程。根据一个给定的程序号、版本号和协议来获得注册的端口号。
  4. PMAPPROC_DUMP。返回端口映射器数据库中所有的记录(每个记录包括程序号、版本号、协议和端口号):
    在一个RPC服务器程序启动,接着被一个RPC客户程序调用的过程中,进行了以下一些步骤:
    1. 一般情况下,当系统引导时,端口映射器必须首先启动。它创建一个TCP端点,并且被动打开TCP端口111。它也创建一个UDP端点,并且在UDP端口111等待着UDP数据报的到来。
    2. 当RPC服务器程序启动时,它为它所支持的程序的每一个版本创建一个TCP端点和一个UDP端点(一个给定的RPC程序可以支持多个版本。客户调用一个服务器过程时,说明它想要哪一个版本)。两个端点各自绑定一个临时端口(TCP端口号和UDP端口号是否一致无关紧要)。服务器通过RPC调用端口映射器的PMAPPROC_SET过程,注册每一个程序、版本、协议和端口号。
    3. 当RPC客户程序启动时,它调用端口映射器的PMAPPROC_GETPORT过程来获得一个指定程序、版本和协议的临时端口号。
    4. 客户发送一个RPC调用报文给第3步返回的端口号。如果使用的是UDP,客户只是发送一个包含RPC调用报文的UDP数据报到服务器相应的UDP端口。服务器发送一个包含RPC应答报文的UDP数据报到客户作为响应。

如果使用的是TCP,客户对服务器的TCP端口号做一个主动打开,然后在建立的TCP连接上发送一个RPC调用报文。服务器作为响应,在连接上发送一个RPC应答报文。

NFS协议

使用NFS,客户可以透明地访问服务器上的文件和文件系统。这不同于提供文件传输的FTP。FTP会产生文件一个完整的副本。NFS只访问一个进程引用文件的那一部分,并且NFS的一个目的就是使得这种访问透明。这就意味着任何能够访问一个本地文件的客户程序不需要做任何修改,就应该能够访问一个NFS文件。

NFS是一个使用Sun RPC构造的客户服务器应用程序。NFS客户通过向一个NFS服务器发送RPC请求来访问其上的文件。尽管这一工作可以使用一般的用户进程来实现—即NFS客户可以是一个用户进程,对服务器进行显式调用。而服务器也可以是一个用户进程—因为两个理由,NFS一般不这样实现。首先,访问一个NFS文件必须对客户透明。因此,NFS的客户调用是由客户操作系统代表用户进程来完成的。第二,出于效率的考虑,NFS服务器在服务器操作系统中实现。如果NFS服务器是一个用户进程,每个客户请求和服务器应答(包括读和写的数据)将不得不在内核和用户进程之间进行切换,这个代价太大。

下图显示了一个NFS客户和一个NFS服务器的典型配置:

图中有很多地方需要注意:

  1. 访问的是一个本地文件还是一个NFS文件对于客户来说是透明的。当文件被打开时,由内核决定这一点。文件被打开之后,内核将本地文件的所有引用传递给名为“本地文件访问”的框中,而将一个NFS文件的所有引用传递给名为“NFS客户”的框中。
  2. NFS客户通过它的TCP/IP模块向NFS服务器发送RPC请求。NFS主要使用UDP,最新的实现也可以使用TCP。
  3. NFS服务器在端口2049接收作为UDP数据报的客户请求。尽管NFS可以被实现成使用端口映射器,允许服务器使用一个临时端口,但是大多数的实现都是直接指定UDP端口2049。
  4. 当NFS服务器收到一个客户请求时,它将这个请求传递给本地文件访问例程,后者访问服务器主机上的一个本地的磁盘文件。
  5. NFS服务器需要花一定的时间来处理一个客户的请求。访问本地文件系统一般也需要一部分时间。在这段时间间隔内,服务器不应该阻止其他的客户请求得到服务。为了实现这一功能,大多数的NFS服务器都是多线程的—即服务器的内核中实际上有多个NFS服务器在运行。具体怎么实现依赖于不同的操作系统。既然大多数的Unix内核不是多线程的,一个共同的技术就是启动一个用户进程(常被称为nfsd)的多个实例。这个实例执行一个系统调用,使自己作为一个内核进程保留在操作系统的内核中。
  6. 同样,在客户主机上,NFS客户需要花一定的时间来处理一个用户进程的请求。NFS客户向服务器主机发出一个RPC调用,然后等待服务器的应答。为了给使用NFS的客户主机上的用户进程提供更多的并发性,在客户内核中一般运行着多个NFS客户。同样,具体实现也依赖于操作系统。Unix系统经常使用类似于NFS服务器的技术:一个叫作biod的用户进程执行一个系统调用,作为一个内核进程保留在操作系统的内核中。

大多数的Unix主机可以作为一个NFS客户,一个NFS服务器,或者两者都是。

在客户能够访问服务器上的文件系统之前,NFS客户主机必须调用安装守护程序。

加锁管理程序和状态监视器允许客户锁定一个NFS服务器上文件的部分区域。这两个程序独立于NFS协议,因为加锁需要知道客户和服务器的状态,而NFS本身在服务器上是无状态的

  1. 文件句柄

    NFS中一个基本概念是文件句柄(filehandle)。它是一个不透明(opaque)的对象,用来引用服务器上的一个文件或目录。不透明指的是服务器创建文件句柄,把它传递给客户,然后客户访问文件时,使用对应的文件句柄。客户不会查看文件句柄的内容—它的内容只对服务器有意义。

    每次一个客户进程打开一个实际上位于一个NFS服务器上的文件时,NFS客户就会从NFS服务器那里获得该文件的一个文件句柄。每次NFS客户为用户进程读或写文件时,文件句柄就会传给服务器以指定被访问的文件。

    一般情况下,用户进程不会和文件句柄打交道—只有NFS客户和NFS服务器将文件句柄传来传去。

  2. 安装协议

    客户必须在访问服务器上一个文件系统中的文件之前,使用安装协议安装那个文件系统。一般情况下,这是在客户主机引导时完成的。最后的结果就是客户获得服务器文件系统的一个文件句柄。

  3. NFS过程

    尽管NFS被设计成可以在不同的操作系统上工作,而不仅仅是Unix系统,但是一些提供Unix功能的过程可能不被其他操作系统支持
    下面列出了NFS服务器提供的15个过程:

    1. GETATTR。返回一个文件的属性:文件类型(一般文件,目录等)、访问权限、文件大小、文件的属主者及上次访问时间等信息。
    2. SETATTR。设置一个文件的属性。只允许设置文件属性的一个子集:访问权限、文件的属主、组的属主、文件大小、上次访问时间和上次修改时间。
    3. STATFS。返回一个文件系统的状态:可用空间的大小、最佳传送大小等。例如Unix的df命令使用此过程。
    4. LOOKUP。查找一个文件。每当一个用户进程打开一个NFS服务器上的一个文件时,NFS客户调用此过程。
    5. READ。从一个文件中读数据。客户说明文件的句柄、读操作的开始位置和读数据的最大字节数(最多8192个字节)。
    6. WRITE。对一个文件进行写操作。客户说明文件的句柄、开始位置、写数据的字节数和要写的数据。
    7. CREATE。创建一个文件。
    8. REMOVE。删除一个文件。
    9. RENAME。重命名一个文件。
    10. LINK。为一个文件构造一个硬链接。硬链接是一个Unix的概念,指的是磁盘中的一个文件可以有任意多个目录项(即名字,也叫作硬链接)指向它。
    11. SYMLINK。为一个文件创建一个符号链接。符号链接是一个包含另一个文件名字的文件。大多数引用符号链接的操作(例如,打开)实际上引用的是符号链接所指的文件。
    12. READLINK。读一个符号链接。即返回符号链接所指的文件的名字。
    13. MKDIR。创建一个目录。
    14. RMDIR。删除一个目录。
    15. READDIR。读一个目录。例如,Unix的ls命令使用此过程。

    这些过程有一个前缀NFSPROC_。

  4. UDP还是TCP

    NFS最初是用UDP写的,所有的厂商都提供了这种实现。最新的一些实现也支持TCP。TCP支持主要用于广域网,它可以使文件操作更快。NFS已经不再局限于局域网的使用

  5. TCP上的NFS

    使用TCP与UDP相比有以下不同:

    1. 当服务器主机进行引导时,它启动一个NFS服务器,后者被动打开TCP端口2049,等待着客户的连接请求。这通常是另一个NFS服务器,正常的NFSUDP服务器在UDP端口2049等待着进入的UDP数据报。
    2. 当客户使用TCP安装服务器上的文件系统时,它对服务器上的TCP端口2049做一个主动打开。这样就为这个文件系统在客户和服务器之间形成了一个TCP连接。如果同样的客户安装同样服务器上的另一个文件系统,就会创建另一个TCP连接。
    3. 客户和服务器在它们连接的两端都要设置TCP的keepalive选项,这样双方都能检测到对方主机崩溃,或者崩溃然后重启动。
    4. 客户方所有使用这个服务器文件系统的应用程序共享这个TCP连接。
    5. 如果客户检测到服务器已经崩溃,或者崩溃然后重启动(通过收到一个TCP差错“连接超时”或者“对方复位连接”),它尝试与服务器重新建立连接。客户做另一个主动打开,为同一个文件系统请求重新建立TCP连接。在以前连接上超时的所有客户请求在新的连接上都会重新发出。
    6. 如果客户机崩溃,那么当它崩溃时正在运行的应用程序也要崩溃。当客户机重新启动时,它很可能使用TCP重新安装服务器的文件系统,这将导致和服务器的另一个连接。客户和服务器之间针对同一个文件系统的前一个连接现在打开了一半(服务器方认为它还开着),但是既然服务器设置了keepalive选项,当服务器发出下一个keepalive探查报文时,这个半开
      着的TCP连接就会被中止。
  6. 等幂过程

    如果一个RPC过程被服务器执行多次仍然返回同样的结果,那么就把它叫作等幂过程(Idempotent Procedure)。NFS的读过程是等幂的。

    等幂的NFS过程是:GETATTR、STATES、LOOKUP、READ、WRITE、READLINK和READDIR。不是等幂的过程是:CREATE、REMOVE、RENAME、LINK、SYMLINK、MKDIR和RMDIR。SETATTR过程如果不用来截断文件,一般是等幂的。

  7. 第3版的NFS

    第3版与第2版有以下区别:

    1. V2中的文件句柄是32字节的固定大小的数组。在V3中,它变成了一个最多为64个字节的可变长度的数组。在XDR中,一个可变长度的数组被编码为一个4字节的数组成员个数跟着实际的数组成员字节。这样在实现时减少了文件句柄的长度,例如Unix只需要12个字节,但又允许非Unix实现维护另外的信息。
    2. V2将每个READ和WRITE RPC过程可以读写的数据限制为8192个字节。这个限制在V3中取消了,这就意味着一个UDP上的实现只受到IP数据报大小的限制(65535字节)。这样允许在更快的网络上读写更大的分组。
    3. 文件大小以及READ和WRITE过程开始偏移的字节从32字节扩充到64字节,允许读写更大的文件。
    4. 每个影响文件属性值的调用都返回文件的属性。这样减少了客户调用GETATTR过程的次数。
    5. WRITE过程可以是异步的,而在V2中要求同步的WRITE过程。这样可以提高WRITE过程的性能。
    6. V3中删去了一个过程(STATFS),增加了七个过程:ACCESS(检查文件访问权限)、MKNOD(创建一个Unix特殊文件)、READ DIR PLUS(返回一个目录中的文件名字和它们的属性)、FSINFO(返回一个文件系统的静态信息)、FSSTAT(返回一个文件系统的动态信息)、PATH CONF(返回一个文件的POSIX.1信息)和COMMIT(将以前的异步写操作提交到外存中)。
上一篇:
容器
下一篇:
TCP/IP详解 卷一 SMTP:简单邮件传送协议