`
阅读更多
Linux网络文件系统
(NFS)分析
1. 网络文件系统概述 4
1.1 远程文件存取 4
1.2 网络文件系统概述 4
1.3 网络文件系统上层实现 5
1.3.1 Mount安装协议和NFS远程过程 5
1.3.2 访问文件的具体流程 5
1.4 网络文件系统下层实现——远程过程调用(RPC) 6
1.4.1 RPC的概念模型 7
1.4.2 SUN RPC的定义 7
1.4.3 SUN RPC的传输层实现机制 8
1.4.3.1 通信语义 8
1.4.3.2 动态端口映射 9
1.4.4 一次RPC远程调用的具体流程 9
2. Linux的NFS系统框架 11
2.1 源码分析环境 11
2.2 Linux下NFS的体系结构 12
2.3 NFS的Client端 12
2.3.1 Client端的VFS层 13
2.3.2 Client端的RPC层 20
2.3.3 Client端的NFS层 27
2.3.3.1 NFS过程层 27
2.4 NFS的Server端 33
2.4.1 Server端的RPC层 33
2.4.2 Server端的NFS层 37
2.4.3  NFS Server端启动 39
2.4.4 nfsservctl系统调用实现 40
2.4.5 RPC鉴别机制 46
3  Mount安装协议的具体实现 48
3.1 NFS Client端Mount协议数据结构 48
3.2 NFS Client端Mount协议实现 50
3.2.1 Linux根文件系统的NFS Mount协议安装 51
3.2.1.1 nfs_root_setup函数的实现 52
3.2.1.2 nfs_root_mount函数的功能 54
3.2.2 命令行下的动态NFS Mount协议安装 60
3.2.2.1 nfs_read_super函数的实现 60
4 NFS文件操作具体流程 64
4.1 NFS文件协议的数据结构 64
4.2 打开一个NFS文件的具体流程 65
4.2.1 VFS层操作分析 65
4.2.2 NFS层操作分析 67
4.2.3 RPC层操作分析 69
4.2.4 Server端操作分析 69
4.3 读写一个NFS文件的具体流程 70
4.3.1 sys_read函数 71
4.3.2 nfs_file_read函数 72
4.3.3 nfs_readpagenfs_readpage_sync函数 72
5  NFS文件系统FAQ 76
参考文献: 79


1. 网络文件系统概述
1.1 远程文件存取
许多早期的网络系统提供了文件传输(File transfer)服务,它允许用户把某文件的一个副本从一台机器移到另一台机器上。而更多近期的网络则提供了文件存取(File access)服务,即允许某个应用程序从一台远程机器上对某个文件进行存取。
使用远程文件机制对文件进行存取的应用程序,可以同它所要存取的文件驻留在同一台机器上,也可以运行在某台远程机器上。当一个应用程序要存取一个驻留在某台远程机器上的文件时,该程序的操作系统将调用客户机软件,这个客户机软件与远程机器中的服务器联系,执行对该文件执行所请求的操作。与文件传输服务不同,该服务的系统并不立即对整个文件进行传输和存储,而是每次要求传送一小块数据。
为了提供对驻留在某台计算机上的某些或所有文件的远程存取能力,系统管理员必须让该计算机运行一个可以对存取请求进行响应的服务器。这个服务器对每个请求进行检查,以验证该客户是否具有对指定文件进行存取的权利,然后执行所指明的操作,最后向客户机返回一个结果。
1.2 网络文件系统概述
Sun Microsystems公司于1984年推出了一个在整个计算机工业中被广泛接受的远程文件存取机制,它被称为Sun的网络文件系统(Network File System),或者简称为NFS。该机制允许在一台计算机上运行一个服务器,使对其上的某些或所有文件都可以进行远程存取,还允许其他计算机上的应用程序对这些文件进行存取。
它使我们能够达到文件的共享。当使用者想用远端档案时只要用"mount"就可把remote档案系统挂接在自己的档案系统之下,使得远端的文件操作上和本地机器的文件没两样。一个应用程序可以打开(Open)一个远程文件以进行存取,可以从这个文件中读取(Read)数据,向该文件中写入(Write)数据,定位(Seek)到文件中的某个指定位置(开始、结尾或者其他地方),最后当使用完毕后关闭(Close)该文件。并且这些操作都是对编程者透明的,操作方法和对本地文件的操作方法完全一样。
下面的两节我们将继续介绍Sun的网络文件系统的具体实现,这对于了解下一章Linux中NFS的实现提供了很好的理论背景。
1.3 网络文件系统上层实现
1.3.1 Mount安装协议和NFS远程过程
NFS的实现被分离成两个独立的程序来实现,分别是Mount安装协议和NFS远程过程调用。
Mount安装协议是实现文件访问的开端。它的主要功能是获取远程机器上的不同文件系统结构并返回所要访问的文件系统根句柄,作为以后对该文件系统进行操作的根本。
在下一节中我们将介绍PRC(远程过程调用)的基础知识,而关于PRC如何实现NFS的上述两部分功能,我们将在下一章中,结合具体的Linux函数进行分析。相信这样,更能加深这部分知识的理解。
1.3.2 访问文件的具体流程
上面我们看到了NFS文件系统的主要数据结构,以及实现文件系统的两部分重要构成。下面总结一下NFS中怎样实现对一个目标文件的访问。
在NFS中,每次对远程文件系统上的通过一个称之为”文件句柄”(file-handle)的数据结构来实现对远程机器上目标文件的操纵。首先对文件名在本地进行解析。这一过程与传统UNIX中的文件名解析过程类似。即一次解析一个全路径名的一部分。它从分层结构的根及路径的开始出发,重复地从路径中取出下一部分,并找出一个具有该名字的文件或子目录。
在NFS系统中,得到一个目的文件句柄不是一步完成的,而是分多个步骤实现的。首先是由Mount安装协议取得该NFS服务器上的分层文件结构信息,并取得相应文件系统的根句柄。在得到一个远程文件系统的根句柄后,结合本地对文件名字解析的结果,可以调用NFS的远程过程,在当前远程文件系统根句柄下取出各个子目录的文件句柄返回,检查返回的文件句柄,得到最后的所要访问的文件句柄。得到文件句柄的流程图如下所示,它展示了当客户机要在服务器的分层结构中查找一个路径为/a/b/c的文件时,客户机和服务器之间所进行的信息交换。

以后对该文件的各种操作,就通过该文件句柄来实现了。各种操作可以参见前一小节上面所列出的NFS远程过程。

1.4 网络文件系统下层实现——远程过程调用(RPC)
由于NFS的底层实现是采用远程过程调用(RPC)来实现的。因此在这里,我们将先概述一下RPC的基本模型、机制以及SUN RPC的定义。在下一章中,我们将详细介绍NFS的Mount协议以及对文件操作的远程过程是如何用RPC来具体实现的。
1.4.1 RPC的概念模型
远程过程调用模型主要来自于传统编程语言中的过程调用机制。过程调用提供了一个强有力的抽象,它允许程序员将一个程序划分为一些小的、可管理的、易于理解的片段。它可以给出程序执行的概念性模型的简单明了的实现。

RPC使用了和传统程序一样的过程抽象,只是它允许一个过程的边界跨越两台计算机。图一展示了远程过程调用模型如何将一个程序划分为两片,每片在一个单独的计算机上执行。
根据过程执行模型,单个控制线索流经所有过程。计算机从一个主程序开始执行,它将一直继续下去,直到遇到一个过程调用。这个调用使执行转入到某个指定的过程代码并继续执行,直到遇到一个return语句。因此在RPC中,一个远程过程调用把控制权传递给被调用的过程,把调用过程的执行挂起。而远程的服务器则实现这个过程,当执行完毕,它给客户机一个响应,这对应于过程模型的return。使控制权返回给调用者,被调用过程停止执行。这个过程是可嵌套的。
1.4.2 SUN RPC的定义
Sun Microsystems公司定义了一个特定形式的远程过程调用。它称为Sun RPC。它定义了调用者(客户机)发出的调用服务器中的某个远程过程的报文的格式、参数的格式,以及被调用过程返回给调用者的结果的格式。
Sun RPC通过定义一个远程执行环境而扩展了远程过程调用模型。它定义了一个远程程序,把它作为在远程机器上执行的软件的基本单元。每个远程程序对应于我们所设想的一个服务器,它包括一组远程过程以及全局数据。在一个远程程序中的所有过程都能共享它的全局数据。SUN Microsystems公司设计了一种外部数据表示,它规定了在网络传输数据时如何表示成公共形式的数据。Sun的外部数据表示XDR已成为大多数客户机-服务器应用的一个事实上的标准。 RPC远程过程调用的数据传输的数据必须遵循XDR标准。
Sun RPC标准说明,在某个计算机上执行的每个远程程序都必须分配一个唯一的32比特整数,调用者使用该整数来标识这个程序。并且,针对每个远程程序的整数型还有一个版本号,使程序可以不必获得一个新的程序号就能改变某个远程过程的细节。此外,SUN RPC为每一个给定的远程程序中的远程过程分配了一个整数标识符。因此,每个RPC报文通过一个三元组来标识某台给定计算机上的所期望的接受者,这个三元组是:
(prog,  vers,  proc)
在此,prog标识远程程序,vers表示抱文所要发往的程序的版本号,proc表示该远程程序中的某一个远程过程。

1.4.3 SUN RPC的传输层实现机制
1.4.3.1 通信语义
为保证RPC语义的实现,我们必须在良种可能中进行选择。一方面,为尽量使远程过程调用的行为像一个本地过程调用,RPC应该使用一种像TCP这样可靠的运输,而且应该对程序员保证可靠性。另一方面,为允许程序员使用高效率的、无连接的运输协议,远程过程调用机制应当支持用UDP这样的数据报协议进行通信。因为UDP传输的不可靠性,在传输过程中可能因为报文的丢失,使得调用者无法做出应答,而导致远程过程被多次调用。因此,选择UDP作为PRC应用传输协议的程序员,他们所构建的程序必须要能容忍零或多次执行语义。
在我们下一章的Linux源代码分析中,我们将看到,Linux对于RPC远程过程调用的实现是采用UDP作为它的传输层应用协议来进行的。目前的Linux系统中,并没有采用TCP协议来实现网络文件系统的传输层应用。Linux设计者在这方面的考虑应该是基于实现NFS系统传输的高效性,对于TCP协议的支持相信Linux将会在以后实现。
1.4.3.2 动态端口映射
在使用TCP或UDP协议进行远程数据传输的时候,需要指定一个服务器的通信端口号。但是,SUN RPC引出了一个有趣的问题:因为它使用32位比特数来标识远程程序,而UDP和TCP运输协议使用16比特数的协议端口号来标识通讯端点,这就有可能超出协议端口的范围。因此,不可能将RPC程序号直接映射到协议端口号,因此RPC不能象其他通讯协议一样使用分配知名端口的协议。但是尽管RPC程序的潜在数量超过了分配知名端口的能力,但RPC和其他服务没有什么不同。在任意给定时间内,单个计算机仅仅执行少量的远程程序。因此,只要端口分配是临时的,每个RPC程序可以获得一个协议端口号,并且使用这个端口号进行通信。
PRC对于协议端口的获得是通过一个称之为端口映射器的机制来实现的。因为服务器端口映射是临时的,每个 RPC程序在数据传输前可以获得一个协议端口号,并且使用这个传输端口进行通信。
然而,作为发起远程过程调用的客户机程序,除了知道它所希望与之联系的机器地址以及RPC程序号以外,它还必须在开始执行之后获得一个协议端口,否则不能直接联系远程程序。这种端口映射必须是动态的,因为如果机器重启动或者RPC程序再次开始执行,端口可能会改变。
ONC RPC机制包含了一个动态映射服务。提供RPC程序的每台机器维护着一个端口映射数据库,而且提供了一种允许调用者将RPC端口号映射为协议端口的机制。它在每台机器中用一个服务器维护这一个小数据库,这个服务器被称为RPC端口映射器。一个RPC程序一旦注册了自己,其他机器上的调用者就可以通过向端口映射器发送一个请求来找到它的协议端口。
1.4.4 一次RPC远程调用的具体流程
通过以上两小节的讨论,我们对RPC体系的实现从上到下有了一个概念上的认识。这一小节中,将具体说明一次远程过程调用的具体实现,从而加深对这部分实现机制的认识。
一个RPC远程过程调用的流程如下:
1. 需要运行一个远程程序时,本地机器向端口映射器发出注册请求,将一个三元组加到数据库: (RPC程序号,协议端口号,版本号) 并分配给该远程程序一个通信协议端口。
2. 调用方发送RPC查找请求,调用TCP或UDP协议,将请求报文发送到服务器端口映射器的知名端口,在给定一RPC程序号和版本号时查找其协议端口。
3. 端口映射器返回这个指定程序当前正在使用的协议端口号。
4. 调用者在得到了该目标程序正在使用的端口号后,可以直接联系远程程序了。此后,调用方将调用的远程过程的名称、类型、版本号、一些传输的XDR数据结构,进行参数的序列化,构成RPC报文。在调用方和服务器之间实现通信传输。

2. Linux的NFS系统框架
NFS是由客户和服务器共同合作实现的:在客户一边,通过一些核心函数调用来使用远程文件系统;在服务器一边,由NFS服务器监听进程来提供文件数据。主要有两个监听进程moutd和nfsd,其中moutd用来监听客户的安装请求,并发送相应的应答信息,如客户端地址和服务器地址等;而nfsd进程用来监听客户端的读写文件请求并返回相应的文件数据。文件的访问对客户来说是完全透明的,并且NFS可以跨越各种服务器和主机平台进行。同其他文件系统在底层是通过访问磁盘不同,NFS在底层是通过RPC(远程过程调用)协议来实现文件访问的。
NFS的主要优点是可以将占用大量磁盘空间的或用户共享的数据只保存在一个NFS服务器上,其它主机要访问这些数据,只需通过NFS将其安装到本地目录进行透明的访问。所谓透明的访问,是指访问这些文件与访问本地的一般文件的用户界面是一致的,并不需要额外的命令。如下图:
         














2.1 源码分析环境
我们分析的Linux内核代码版本为2.2.5(套装系统为RedHat 6.0)。Linux从2.2.X版本开始,对内核中NFS部分的内容有了重大的改进。增加了很多内容,重写了很多函数,功能有了很大的改进。其中比2.0.34版本的内核的主要改进有:
在内核中增加了NFS的Server端内容。而在2.0.34版本中Server端并不在内核中支持,需要外部Server程序。
将NFS与RPC的实现分开,使RPC可以不仅为NFS提供服务,还可以为其他网络协议提供服务。
显著增强了RPC的功能,增加了如RPC调用的调度、权限验证、端口映射器以及支持同步和异步调用两种方式等功能。
在这一章中,我们将主要介绍Linux下网络文件系统所用到的主要数据结构及他们之间的关系。在下一章开始,我们将详细阐述系统运作的流程。
2.2 Linux下NFS的体系结构
Linux下网络文件系统主要分为两个部分:NFS Client端、NFS Server端,即采用Client-Server体系结构。其中Client 方面主要负责处理用户对远程文件的操作请求,并把请求的内容按一定的包格式从网络发给文件所在的Server方面。而Server方面则接受Client方面的请求,调用本机的VFS的函数进行文件的实际操作,并把结果按一定格式返回给Client方面。而Client方面得到Server的返回结果,把它返回给用户。
这是Linux下网络文件系统的基本体系结构:

2.3 NFS的Client端
由上图所示,Client端从高层到底层由四部分组成:VFS、NFS、RPC、Socket。下面将介绍各个部分的实现以及他们之间的接口。
2.3.1 Client端的VFS层
Linux最大的特点之一是它支持多种文件系统,如:EXT、EXT2、XIA、MINIX、UMSDOS、MSDOS、VFAT、PROC、SMB、NCP、ISO9660、SYSV、HPFS、SFFS和UFS等等,甚至还支持NFS。它之所以能支持这么多的文件系统,是由于它在具体的文件系统上增加了一层抽象层:VFS文件系统。VFS文件系统将独立于具体文件系统的数据和操作集中在自身之中,并通过数据结构中的UNION类型和函数指针将具体的文件系统包容进来。这种分层的概念,使得Linux不仅可以有良好的兼容性,而且也使它有较大的可扩充性。
VFS文件系统是建立在具体文件系统上的一个抽象层次。它必须管理在Linux系统中的每一个具体的文件系统。为此,它维护着众多的数据结构,这些数据结构描述了整个文件系统和实际的安装上的文件系统。其中最主要的数据结构有super_block、inode、file、file_system_type、dentry等。在这些数据结构中,包含了一些与具体文件系统的接口,下面将结合NFS层逐个进行介绍这些数据结构及其和NFS的接口。
NFS的常数和文件模式
定义了六种基本常数来指明协议所用数组的大小。另外,象UNIX一样,NFS假定每个文件或目录一个指明其类型和存取保护的模式(mode)。图23.7列出了NFS模式整数的单个比特及其含义。定义直接对应于UNIX的stat函数的返回值。
  #define NFS_PORT        2049
  #define NFS_MAXDATA     8192
  #define NFS_MAXPATHLEN  1024
  #define NFS_MAXNAMLEN   255
  #define NFS_MAXGROUPS   16
  #define NFS_FHSIZE      32
  #define NFS_COOKIESIZE  4
  #define NFS_FIFO_DEV    (-1)
  #define NFSMODE_FMT     0170000 
  #define NFSMODE_DIR     0040000  //这是个目录;类型是 NFDIR
  #define NFSMODE_CHR     0020000  //这是个字符专有文件;类型应该是NFCHR
  #define NFSMODE_BLK     0060000  //这是个块专有文件:类型应该是NFBLK
  #define NFSMODE_REG     0100000  //这是个普通文件;类型应该是NFREG
  #define NFSMODE_LNK     0120000  //这是个符号连接,类型应该是NFLNK
  #define NFSMODE_SOCK    0140000  //这是个有名的插口,类型应该是NFSOCK
  #define NFSMODE_FIFO    0010000 
NFS的调用返回值
协议定义一个常量枚举类型,被用于报告差错状态。每个远程调用都返回其中的一个值。该协议的集合命名为stat,如下:
    enum nfs_stat {
          NFS_OK = 0,
          NFSERR_PERM = 1,
          NFSERR_NOENT = 2,
          NFSERR_IO = 5,
          NFSERR_NXIO = 6,
          NFSERR_EAGAIN = 11,
          NFSERR_ACCES = 13,
          NFSERR_EXIST = 17,
          NFSERR_XDEV = 18,
          NFSERR_NODEV = 19,
          NFSERR_NOTDIR = 20,
          NFSERR_ISDIR = 21,
          NFSERR_INVAL = 22,      /* that Sun forgot */
          NFSERR_FBIG = 27,
          NFSERR_NOSPC = 28,
          NFSERR_ROFS = 30,
          NFSERR_OPNOTSUPP = 45,
          NFSERR_NAMETOOLONG = 63,
          NFSERR_NOTEMPTY = 66,
          NFSERR_DQUOT = 69,
          NFSERR_STALE = 70,
          NFSERR_WFLUSH = 99
  };
NFS的文件类型:
NFS使用和UNIX相同的基本文件类型。它定义了服务器在指定文件类型时可使用的枚举值。
  enum nfs_ftype {
          NFNON = 0,   //说明不是一个文件
          NFREG = 1,   //一般的数据文件
          NFDIR = 2,    //是一个目录文件
          NFBLK = 3,   //是一个块设备文件
          NFCHR = 4,   //是一个字符设备文件
          NFLNK = 5,   //是一个符号链接
          NFSOCK = 6,
          NFBAD = 7,
          NFFIFO = 8
  };
NFS的文件句柄:
在目前的linux版本中,文件句柄定义为一个32字节长度的数组。在Sun MicroSystem的NFS标准中,文件句柄分为许多个字段,其中有必要提及的是其中的随机生成的一个文件索引结点号。
文件生成号的使用主要出于对网络文件访问安全性的考虑出发。因为一旦客户机知道了远程文件系统服务主机的文件目录结构,以及产生文件句柄的方式,可以很容易地构造出某个指定路径上的文件句柄,并通过该句柄对远程文件系统进行访问。这样,就违背了网络访问的安全性原则,因此在标准的NFS文件系统中,当文件系统服务器收到客户机的访问请求时,除了返回相应文件的文件句柄以外,还动态地随机生成此文件对应的文件生成号,一般是一个32字节的字符串,提供给客户机。这样,以后客户机在访问服务器的文件系统时,通过检查文件句柄结构中所带的文件生成号是否匹配,就可以决定是否是一次合法的访问。这种机制保证了NFS文件系统远程访问的安全性。
  struct nfs_fh {
          char                    data[NFS_FHSIZE];
  };
NFS的远程过程定义:
这里只定义了各个远程过程的程序号,具体每个过程的功能将在后面介绍。
  #define NFS_PROGRAM             100003
  #define NFS_VERSION             2
  #define NFSPROC_NULL            0
  #define NFSPROC_GETATTR         1
  #define NFSPROC_SETATTR         2
  #define NFSPROC_ROOT            3
  #define NFSPROC_LOOKUP          4
  #define NFSPROC_READLINK        5
  #define NFSPROC_READ            6
  #define NFSPROC_WRITECACHE      7
  #define NFSPROC_WRITE           8
  #define NFSPROC_CREATE          9
  #define NFSPROC_REMOVE          10
  #define NFSPROC_RENAME          11
  #define NFSPROC_LINK            12
  #define NFSPROC_SYMLINK         13
  #define NFSPROC_MKDIR           14
  #define NFSPROC_RMDIR           15
  #define NFSPROC_READDIR         16
  #define NFSPROC_STATFS          17
 
  /* Mount support for NFSroot */
  #ifdef __KERNEL__
  #define NFS_MNT_PROGRAM         100005
  #define NFS_MNT_VERSION         1
  #define NFS_MNT_PORT            627
  #define NFS_MNTPROC_MNT         1
  #define NFS_MNTPROC_UMNT        3
  #endif
NFS的文件属性结构:
NFS有一个获得关于某文件的信息的机制。在谈到文件信息时,NFS使用了术语file attribute。结构nfs_fattr描述了NFS所提供的文件属性。
struct nfs_fattr {
        enum nfs_ftype          type;    //文件类型
        __u32                   mode;  //文件保护位
        __u32                   nlink;  
        __u32                   uid;
        __u32                   gid;
        __u32                   size;
        __u32                   blocksize;
        __u32                   rdev;
        __u32                   blocks;
        __u32                   fsid;
        __u32                   fileid;
        struct nfs_time         atime;
        struct nfs_time         mtime;
        struct nfs_time         ctime;
};


file_system_type结构(在fs.h中定义)
在Linux中,当装入一个文件系统模块时,首先要向系统核心注册这个文件系统类型;当卸下一个文件系统模块时,又要向系统核心注销这个文件系统类型。文件系统类型的注册和注销就反映在由结构file_system_type构成的单向链表中——即链表上每一file_system_type节点就反映了一种已注册的文件系统类型的有关信息。它的定义为:
struct file_system_type {
/* 函数指针,当安装此类型的文件系统时,就由VFS调用此例程从设备上将此文件系统的superblock读入内存中 */
struct super_block *(*read_super) (struct super_block *, void *, int);
/* 文件系统类型的名称 */
const char *name;
/* 此文件系统类型的属性 */
int fs_flags;;
/* 指向下一个已注册的文件系统类型 */
struct file_system_type * next;
};
在NFS中,此结构在init_nfs_fs(fs/nfs/inode.c)函数中初始化。其中super_block初始化为指向函数nfs_read_super,负责在mount网络文件系统时读入NSF的SuperBlock信息。fs_flags初始化为0,说明此文件系统不需要设备(~FS_REQUIRES_DEV),需要目录缓存(~FS_NO_DCACHE),需要进行目录缓存的预读(~FS_NO_PRELIM)。
super_block结构(在fs.h中定义)
superblock即文件系统的超级块。它包含了文件系统的许多重要信息,如文件系统的结构、组织、操作以及资源信息等。在super_block数据结构中,主要由两方面和具体文件系统进行接口。以下是super_block与NFS接口的定义:
struct super_block {
/* 其他数据项 */
..................
/* 超级块操作 */
struct super_operations *s_op;
union {
/* 其他文件系统的超级块信息 */
..........................
struct nfs_sb_info nfs_sb;
} u;
};
在网络文件系统中,s_op指向函数表nfs_sops(在fs/nfs/inode.c中定义),而nfs_sb_info(在nfs_fs_sb中定义)的定义为:
struct nfs_sb_info {
/* server信息 */
struct nfs_server       s_server;
/* 远方根节点的NFS文件句柄 */
struct nfs_fh           s_root;
};
inode结构(在fs.h中定义)
文件系统是由单个的文件所构成的,每一个文件都必须由且只能由唯一的inode来标识,即inode包含了此文件所有的关键信息:所在设备、类型、大小、时间属性、在设备上的位置、用户属性等等。在inode数据结构中,主要由两方面和具体文件系统进行接口。以下是inode与NFS接口的定义:
struct inode {
/* 其他数据项 */
..................
struct inode_operations *i_op;
union {
/* 其他文件系统的inode信息 */
..........................
struct nfs_inode_info nfs_i;
} u;
};
在网络文件系统中,i_op指向函数表nfs_file_inode_operations(在fs/nfs/file.c中定义),而nfs_inode_info(在nfs_fs_i中定义)的定义为:
struct nfs_inode_info {
          /* 此数据结构保证命名管道在NFS中能够正常工作 */
          struct pipe_inode_info  pipeinfo;
          /* 各种属性 */
          unsigned short          flags;
          unsigned long           read_cache_jiffies;
          unsigned long           read_cache_mtime;
          unsigned long           attrtimeo;
  /* 未写回页的链表 */
          struct nfs_wreq *       writeback;
};
file结构(在fs.h中定义)
一个文件在打开以后,在系统中就要为它建立起相应的数据结构。主要有两个:一是在当前进程的打开文件描述符中为它分配一项,二是分配一个文件结构(file struct,即FILE结构),并将文件描述符与该结构对应起来。FILE结构再指向该文件的inode。因此file相当于在文件描述符和inode结构中加了一层。在file数据结构中,定义了一个指向文件操作的函数表指针f_op。在nfs中,这个指针指向函数表nfs_file_operations。
2.3.2 Client端的RPC层
在讲NFS层之前,首先介绍一下Linux中RPC的实现方法和具体的数据结构。
如前所述,在Linux的2.2.X版本以后,SUN RPC作为单独的一个模块被独立了出来,它的主要代码位于net/sunrpc目录下面。
远程程序、远程过程
在Linux中,远程程序和远程过程主要是由三个数据结构来维护的。他们是:rpc_program、rpc_version和rpc_procedure。下面是他们的结构定义及结构项解释:
struct  rpc_program { /* 远程程序 */
     char *              name;         /* 远程程序名 */
     u32                number;       /* 程序号 */
     unsigned int         nrvers;        /* 版本个数 */
     struct rpc_version **  version;       /* 指向含有各个版本信息的数组指针 */
     struct rpc_stat *      stats;          /* 统计信息 */
};
struct rpc_version { /* 版本信息 */
     u32                 number;        /* 版本号 */
     unsigned int          nrprocs;        /* 远程过程数*/
     struct rpc_procinfo *   procs;          /* 远程过程数组 */
};
struct rpc_procinfo { /* 远程过程信息 */
     char *                p_procname;     /* 远程过程名 */
     kxdrproc_t            p_encode;       /* XDR译码函数 */
     kxdrproc_t            p_decode;       /* XDR 解码函数 */
     unsigned int           p_bufsiz;       /* 请求缓存大小 */
     unsigned int           p_count;        /* 调用数 */
};
RPC客户结构
Linux将关于RPC客户机的高层信息集中在一个rpc_clnt结构(在sunrpc/clnt.hz中定义)中,
struct rpc_clnt { /* 客户机结构 */
    unsigned int          cl_users;       /* 引用次数 */
    struct rpc_xprt *       cl_xprt;        /* 传输层接口 */
    struct rpc_procinfo *   cl_procinfo;    /* 远程过程 */
    u32                 cl_maxproc; /* 最大远程过程数目 */
    char *               cl_server;      /* Server机器名 */
    char *               cl_protname;   /* 远程程序名 */
    struct rpc_auth *       cl_auth;        /* 验证接口 */
    struct rpc_stat *       cl_stats;       /* 统计数字 */
    unsigned int           cl_softrtry : 1,    /* 软超时 */
                         cl_intr     : 1,  /* 是否可中断 */
                         cl_chatty   : 1,  /* 是否详细说明 */
                         cl_autobind : 1,  /* 是否采用端口映射 */
                         cl_binding  : 1,  /* 是否在映射中 */
                         cl_oneshot  : 1,
                         cl_dead     : 1;
     unsigned int          cl_flags;        /* 客户机属性 */
     unsigned long         cl_hardmax;      /* 最大超时时间 */
     struct rpc_portmap     cl_pmap;         /* 端口映射接口*/
     struct rpc_wait_queue   cl_bindwait;     /* 端口映射等待队列 */
     int                  cl_nodelen;      /* 结点长度 */
     char                 cl_nodename[UNX_MAXNODENAME];
};
传输接口结构
这个结构定义了远程过程调用时实际进行网络传输所需要的数据。
struct rpc_xprt {
        struct rpc_xprt *       link;           /* 所有传输接口组成的列表 */
        struct rpc_xprt *       rx_pending;     /* 接受等待列表 */
        int                  rx_pending_flag;  /* 是否在等待列表中 */

        struct file *           file;            /* 文件结构指针 */
        struct socket *         sock;           /* BSD socket层 */
        struct sock *           inet;           /* INET 层 */

        struct rpc_timeout      timeout;        /* 超时接口 */
        struct sockaddr_in      addr;           /* 服务器地址 */
        int                   prot;         /* IP协议 */

        unsigned long           cong;           /* 现在拥挤情况 */
        unsigned long           cwnd;           /* 拥挤窗口 */
        unsigned long           congtime;      

        struct rpc_wait_queue   sending;        /* 等待发送的请求 */
        struct rpc_wait_queue   pending;        /* 正在发送的请求 */
        struct rpc_wait_queue   backlog;        /* 等待请求槽的请求 */
        struct rpc_wait_queue   reconn;         /* 等待重新连接 */
        struct rpc_rqst *       free;            /* 空闲请求槽 */
        struct rpc_rqst         slot[RPC_MAXREQS];
        unsigned char         connected;       /* TCP: 已经连接 */
        unsigned char         write_space;     /* TCP: 可以发送 */
        unsigned int          shutdown   : 1,   /* 正在关闭 */
                            nocong     : 1,   /* 没有拥塞控制 */
                              stream     : 1,  /* TCP */
                              tcp_more   : 1,  /* 多个记录碎片 */
                              connecting : 1;   /* 正在重新连接 */
        /* TCP答复接收状态 */
        union {  
                u32             header[2];
                u8              data[8];
        }tcp_recm;
        struct rpc_rqst *          tcp_rqstp;
        struct iovec              tcp_iovec[MAX_IOVEC];  /* 接收缓存 */
        u32                     tcp_total;      /* 记录总长度 */
        u32                     tcp_reclen;     /* TCP碎片长度 */
        u32                     tcp_offset;     /* TCP碎片位移 */
        u32                     tcp_copied;     /* 将被拷贝到请求 */
        /* TCP发送数据   */
        struct rpc_iov             snd_buf;        /* 发送缓存 */
        struct rpc_task *           snd_task;        /* 发送中被阻塞的任务 */
        u32                     snd_sent;       /* 已经发送的字节 */

        void                    (*old_data_ready)(struct sock *, int);
        void                    (*old_state_change)(struct sock *);
        void                    (*old_write_space)(struct sock *);
};
Linux的RPC传输接口功能十分强大,能够支持网络拥塞避免和异步调用,它的主要工作流程如下:
当本机一个进程进行远程调用时,它首先为其分配一个请求槽。如果没有空闲槽,就先把它放在储备队列中。
调用者将RPC信息放在一起,用其填充请求结构,然后调用xprt_call()。
Xprt_call()将消息发送出去,并将调用者放在等待队列中。同时,它启动了一个定时器,当包超时的时候自动运行。
如果在超时前有包到达,data_reply 将遍历等待请求队列。如果找到一个XID相同的,将唤醒调用者,并移走定时器。
如果在超时前没有包到达,内核将运行xprt_timer程序。它将调整timeout值或者返回给调用者-ETIMEOUT。
当调用者接到RPC通知应答已到,它应当释放RPC槽并处理应答。如果应答超时,它可以选择或者调整初始的timeout值,或者简单的再调用rpc_call一次。
其中拥塞控制采用的是44BSD中所采用的拥塞控制算法。它实际上是Van  Jacobson迟启动算法。它的主要思想是:当出现重新传输时,拥塞窗口大小减半;否则在下列情况下窗口大小增一:接受到一个应答并且拥塞窗口近期没有被更新。这样可以有效的
而异步调用将在下面的任务结构中予以解释。
远程过程调用请求结构
struct rpc_rqst {
      struct rpc_xprt *       rq_xprt;           /* RPC客户传输接口 */
      struct rpc_timeout      rq_timeout;        /* 超时参数 */
      struct rpc_iov         rq_snd_buf;        /* 发送缓存 */
      struct rpc_iov         rq_rcv_buf;        /* 接收缓存 */
      struct rpc_task *       rq_task;           /* RPC任务 */
      __u32               rq_xid;         /* 请求ID */
      struct rpc_rqst *       rq_next;        /* 下一个请求结构 */
      unsigned char         rq_gotit;       /* 答复接受 */
      unsigned char         rq_damaged;     /* 正在接受 */
      u32                 rq_creddata[2]; /* 鉴别数据 */
      u32                 rq_bytes_sent;  /* 已经传输的字节数 */
};
远程过程调用任务结构
为了同时支持同步和异步远程过程调用,Linux引入了rpc_task这个结构。它有点类似于进程管理的task结构,系统采用一个有限状态自动机对它进行调度。Rpc_task结构是在上层的rpc_client客户结构,以及rpc_xprt接口结构构造好了之后,在真正执行一个rpc远程过程前构造生成的,并放到rpc的任务队列中排队。在执行时以rpc_task结构作为执行远程过程的参数被调用。
所有的任务结构都在一条双向链表中,其中全局变量all_tasks指向链表头。
struct rpc_task {
    struct rpc_task *       tk_prev;        /* 等待队列列表 */
    struct rpc_task *       tk_next;
    struct rpc_task *       tk_next_task;   /* 总的任务列表 */
    struct rpc_task *       tk_prev_task;
    struct rpc_clnt *       tk_client;      /* RPC客户结构指针 */
    struct rpc_rqst *       tk_rqstp;       /* RPC请求结构指针 */
    struct rpc_cred *       tk_cred;      
    int                  tk_status;      /* 最后一次操作结构 */
    struct rpc_wait_queue * tk_rpcwait;     /* rpc等待队列 */
    __u32               tk_proc;     /* 远程过程号 */
     __u32 *             tk_buffer;      /* XDR缓存 */
     void *              tk_argp;        /* 调用参数 */
     void *              tk_resp;        /* 调用返回结果 */
     void                (*tk_callback)(struct rpc_task *);
/* 任务被唤醒时调用的回调函数 */
     void                (*tk_action)(struct rpc_task *);
/* 对异步任务来说,下一个过程指针 */
     void                    (*tk_exit)(struct rpc_task *);
/*  结束异步过程并报告结果给调用过程 */
     void *                  tk_calldata;
     struct timer_list       tk_timer;       /* 内部定时器 */
     struct wait_queue *     tk_wait;      
     unsigned long           tk_timeout;     /* rpc_sleep()超时时间 */
     unsigned short          tk_flags;       /* 各种属性 */
};
前面一个一个列举了NFS RPC层的数据结构,下面我们看一下这些数据结构在整个层中所占的地位及其相互关系,这是他们之间的关系图:

其中rpc_client结构定义了RPC层的高层信息,如远方服务器名、远程过程集合、端口映射器等。而rpc_xprt结构则维护负责在进行RPC调用中,实际进行通信所维护的信息,包括与Berkley Socket的接口、发送请求队列、拥塞控制等等。而rpc_task和rpc_rqst 结构则是针对每一次具体的远程过程调用所维护的结构,每当上层调用rpc_do_call函数时,系统都会建立一个rpc_task结构,由他来负责实际的通讯管理操作。在rpc_task中,有一个关键的成员是tk_action,它是一个函数指针,表示此次任务现在所要调用的函数。在这些函数中,可以将tk_action指向别的函数,从而指明在执行完这个函数后下一步所要作的工作,这就形成了一个有限状态自动机(FSM)。程序可以根据现在的具体状态判断下一步所要作的工作,并置不同的函数指针。而系统将所有的rpc_task结构管理起来,象管理进程一样对这些任务进行调度,如在一些需要等待资源的时候使任务睡眠,然后在资源到达的时候将其唤醒。而每一个任务在实际进行调用时,要创建一个rpc_rqst结构,这个结构实际存储调用所需要的信息。
现在我们将对rpc_do_call函数进行一下介绍,这个函数是RPC层的中心。任何需要进行远程过程调用(RPC)的程序都需要调用这个函数进行实际的过程调用。这是他的函数声明:
int  rpc_do_call(struct rpc_clnt *clnt, u32  proc, void *argp, void *resp,
    int flags, rpc_action func, void *data);
其中proc是调用过程的过程号,argp是传递进去的参数指针,resp是返回的结果指针,flags指明调用方式是同步的还是异步的,func和data分别是过程执行时候的回调函数及其参数。
它的执行流程是,图5是流程图:
1. 调用rpc_init_task创建一个rpc_task结构,并进行一系列初始化;
2. 调用rpc_call_setup对参数和返回结果指针以及tk_action进行初始化,其中tk_action指向函数call_bind;
3. 调用rpc_execute函数执行这个任务。其中会执行tk_action指针指向的函数,即首先执行call_bind;
4. call_bind函数首先要确定服务器的端口号,他会和服务器的portmapper进程进行通讯,并得到端口号,最后设置tk_action函数为call_reserve;

5. call_reserve函数调用xprt_reserve请求一个调用结构(rpc_rqst结构),并设置tk_action函数为call_reserveresult;
6. call_reserveresult得到xprot_reserve返回的结果,设置tk_action函数为call_allocate;
7. 调用call_allocate函数调用rpc_malloc分配调用所需要的缓冲,并设置tk_action函数为call_encode;
8. 调用call_encode对远程过程调用的输入参数进行译码,设置tk_action函数为call_transmit;
9. 调用call_transmit实际传输这个RPC请求,并设置tk_action函数为call_receive;
10. 等待call_receive函数等待接收RPC请求的返回结果。设置tk_action函数为call_status;
11. call_status将根据调用的返回结果决定下一步的行动。如果调用成功,则把tk_action函数设置为call_decode;如果操作超时,将tk_action设置为call_timeout;如果需要重试,则根据返回的结果跳到第6到9步;如果返回信息指示没有连接上,则把tk_action设置为call_reconnect;如果返回信息指示连接被拒绝,则把tk_action函数设置为call_bind;
12. 函数call_timeout负责管理超时,他将不释放调用结构,并把tk_action设置为4到9步的函数,继续进行调用;
13. 函数call_reconnect调用xprt_reconnect对传输结构重新初始化,并把tk_action设置为tk_status;
14. 函数call_decode对返回结果进行译码,置tk_action为空,结束调用。
这里面第3步的rpc_execute负责管理第四步到第十四步这一系列过程。如果是同步调用(NFS采用的是同步调用),他是一个无限循环,不断执行rpc_task结构中的tk_action函数,直到tk_action为空。因此,第四步到第十四步就象一个有限状态自动机,它根据状态的变化不断的跳到各个步骤,直到结束。而如果是异步调用的话,系统将会以象进程调度的方式一样对这些任务进行调度,具体情况请见文件net/sunrpc/sched.c。
2.3.3 Client端的NFS层
这是Client端的中心,它负责接受上层VFS层的接口调用,然后通过RPC调用实现对远程文件系统的存取。
在NFS层中,又可以分为两个子层:NFS过程层与NFS-VFS接口层。其中,NFS过程层主要是为了和RPC接口,它实现了一系列NFS的标准远程过程调用,所有的客户机和服务器之间的通讯都要通过这一层。
2.3.3.1 NFS过程层
根据SUN公司建立的NFS协议(参见RFC 1094文档),NFS服务器应该提供含有18个过程的远程程序,这些程序包括了各种对文件的存取和管理工作。为了使Linux NFS客户端能够对各种运行了NFS服务的机器进行文件存取,因此,Linux实现了这些调用。这样,当NFS需要对远方服务器上的文件进行实际存取的时候,只需要简单的调用这些过程,而由这些过程来实现对远方文件的存取。
下面我们先介绍几个基本的数据结构。
nfs_fh结构(在nfs.h中定义)
struct nfs_fh {
char data[NFS_FHSIZE];
};
这个结构定义了NFS私有的文件句柄。在NFS中,一旦某个客户机识别并打开了一个文件后,它需要有一种识别该文件的途径以便进行后续的操作(比如读或写)。而且,当客户机通过服务器的分层结构来搜索某个目录或文件时,它也需要一种识别它们的方法。为了解决这些问题,NFS让服务器给某个打开文件分配了一个唯一的文件句柄,以此作为标识符。当客户机第一次打开某个文件的时候,服务器制造一个句柄,并把该文档发回给客户机。当客户机再对这个文件有操作请求时,就把该句柄发回给服务器。
从客户机的观点来看,文件句柄是一个用来标识文件的32字节的字符串。从服务器的观点来看,文件句柄可以是一个唯一的标识某个文件的任意的字节集合。例如,在Linux中,文件句柄中就含有这个文件的信息,使服务器能迅速的定义定位文件。它的具体格式s,我们将在NFS服务器端进行介绍。
在NFS术语中,文件句柄对客户机是不透明的,也就是说,客户机不能对句柄进行解码或自行制造一个句柄。只有服务器可以创建文件句柄,服务器只能识别它们自己创建的文件句柄。
nfs_server结构(在nfs_fs_sb.h)
这个结构定义了NFS的客户端信息,它放在super_block结构中(参见前面nfs_sb_info结构的定义)。
struct nfs_server {
      struct rpc_clnt *       client;         /* RPC客户端结构指针 */
      int                     flags;          /* 属性信息 */
      int                     rsize;          /* 每次读的字节数 */
      int                     wsize;          /* 每次写的字节数 */
      unsigned int            bsize;          /* 服务器块的大小 */
      unsigned int            acregmin;       /* 缓存超时时间信息 */
      unsigned int            acregmax;
      unsigned int            acdirmin;
      unsigned int            acdirmax;
      char *                  hostname;       /* 远方服务器名称 */
};
下面将对这十八个NFS过程进行介绍,
NFS_NULL(过程0):按照习惯,在任何RPC程序中过程0被称为空,因为它没有任何动作。应用程序可以调用它来测试某个服务器是否响应。
NFS_GETATTR(过程1):客户机调用过程1来得到某个文件的属性,包括保护模式、文件拥有者、大小以及最近存取时间等项。以下是函数声明:
int nfs_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle,
struct nfs_fattr *fattr)
其中nfs_fattr定义了NFS的文件属性结构,以下是它的定义以及解释:
struct nfs_fattr {
enum nfs_ftype        type; /* 文件的类型:目录、块设备、字符设备、符号连接 */
        __u32                mode; /* 文件保护位 */
        __u32                nlink; /* 指向这个文件了硬连接数 */
        __u32                uid; /* 文件拥有者的用户ID */
        __u32                gid; /* 这个文件所属的组 */
        __u32                size; /* 文件的大小(字节数)*/
        __u32                blocksize; /* 每块大小 */
__u32                rdev; /* 如果这个文件是设备文件,它的设备号 */
        __u32                blocks; /* 文件所占的块数 */
        __u32                fsid; /* 这个文件的文件系统id  */
        __u32                fileid; /* 这个文件的id号 */
        struct nfs_time         atime; /* 文件的最后存取时间 */
        struct nfs_time         mtime; /* 文件的最后修改时间 */
        struct nfs_time         ctime; /* 文件的inode修改时间 */
};
NFS_SETATTR(过程2):过程2允许客户机设置文件的某些属性。客户机不能设置所有属性(例如fsid、rdev、fileid等)。如果调用成功,将会返回改变后文件的属性。以下是函数声明:
int  nfs_proc_setattr(struct nfs_server *server, struct nfs_fh *fhandle,
                        struct nfs_sattr *sattr, struct nfs_fattr *fattr);
其中,nfs_sattr结构定义了可以修改的文件属性,定义如下:
struct nfs_sattr {
      __u32                mode; /* 文件保护位 */
      __u32                uid; /* 文件拥有者的id */
      __u32                gid; /* 文件所属的组 */
      __u32                size; /* 文件大小 */
       struct nfs_time        atime; /* 文件的最后存取时间 */
       struct nfs_time        mtime; /* 文件的最后修改时间 */
};
NFS_ROOT(过程3):此过程在NFS3中已经不存在,被安装协议取代。
NFS_LOOKUP(过程4):此过程在一个目录中搜索某个文件。如果成功,则返回的值由该文件的属性及其句柄组成。以下是函数声明:
int nfs_proc_lookup(struct nfs_server *server, struct nfs_fh *dir, const char *name,
                     struct nfs_fh *fhandle, struct nfs_fattr *fattr);
其中,name是搜索的文件名,dir是搜索所在的目录,fhandle和fattr则是返回值。
NFS_READLINK(过程5):过程5允许客户机把符号连接的值读出来。以下是函数声明:
int  nfs_proc_readlink(struct nfs_server *server, struct nfs_fh *fhandle,
                      void **p0, char **string, unsigned int *len,
                      unsigned int maxlen);
其中输入值fhandle是符号连接文件的句柄,返回res是路径字符串,len是路径字符串长度,maxlen是最大长度。
NFS_READ(过程6):过程6允许客户机从某个文件中读出数据。服务器如果操作成功,返回结果包含了所需要的数据及该文件的属性;如果操作失败,则状态值包含了一个差错代码。下面是函数声明:
int nfs_proc_read(struct nfs_server *server, struct nfs_fh *fhandle, int swap,
                           unsigned long offset, unsigned int count,
                           void *buffer, struct nfs_fattr *fattr);
其中fhandle是文件句柄,offset是读文件的起始位置,count是读取的字节数,而buffer是实际存放数据的地方。
NFS_WRITECACHE(过程7):此过程在NFS3中已经不存在。
NFS_WRITE(过程8):过程8和过程6相似,它允许客户向一个远程文件写入数据。调用成功返回文件的属性,否则包含一个错误代码。下面是函数声明:
int nfs_proc_write(struct nfs_server *server, struct nfs_fh *fhandle, int swap,
                         unsigned long offset, unsigned int count,
                         const void *buffer, struct nfs_fattr *fattr);
其中fhandle是文件句柄,offset是写文件的起始位置,count是写入的字节数,而buffer是实际存放数据的地方。
NFS_CREATE(过程9):客户机调用过程9在一个指定目录创建一个文件。该文件不能存在,否则该调用将返回差错。调用如果成功,将返回新文件的句柄及其属性。下面是函数说明:
int nfs_proc_create(struct nfs_server *server, struct nfs_fh *dir,
                         const char *name, struct nfs_sattr *sattr,
                         struct nfs_fh *fhandle, struct nfs_fattr *fattr);
其中dir是创建文件的目录句柄,name是创建文件的名字,sattr是指定创建文件的属性,而fhandle和fattr则是返回的创建文件的句柄及其属性。
NFS_REMOVE(过程10):客户机调用过程10来删除一个已经存在的文件。该调用返回一个状态值。该状态值指示此操作是否成功。下面是函数说明:
int nfs_proc_remove(struct nfs_server *server, struct nfs_fh *dir,
const char *name);
其中dir是文件所在的目录句柄,name则是被删除文件名。
NFS_RENAME(过程11):客户机调用过程11为一个文件改名。由于参数使客户机可以指定文件的新的名字和新的目录,所以rename操作就对应着UNIXC的mv命令。NFS保证rename在服务器上是原子操作(也就是说,它的执行不会被中断)。对原子性的保证十分重要,因为它意味着知道安装好文件的新名才能把旧名删除。因此,在rename操作过程中,文件不会看上去象丢失了一样。下面是函数说明:
int nfs_proc_rename(struct nfs_server *server,
                 struct nfs_fh *old_dir, const char *old_name,
                 struct nfs_fh *new_dir, const char *new_name);
NFS_LINK(过程12):过程12允许客户机形成一个到已存在文件的硬连接。NFS保证,如果一个文件有多个连接,那么无论用哪条连接对该文件进行存取,文件的可视属性都是一致的。下面是函数说明:
Int nfs_proc_link(struct nfs_server *server, struct nfs_fh *fhandle,
                         struct nfs_fh *dir, const char *name);
其中handle是硬连接文件句柄,dir和name则定义了连接的文件。
NFS_SYMLINK(过程13):过程13创建一个符号连接。参数指明了一个目录句柄、要创建的文件名以及作为该符号连接内容的字符串。下面是函数说明:
int nfs_proc_symlink(struct nfs_server *server, struct nfs_fh *dir,
                         const char *name, const char *path,
                         struct nfs_sattr *sattr);
NFS_MKDIR(过程14):过程14创建一个目录。如果调用成功,则服务器返回新目录的句柄及其属性。下面是函数说明:
int nfs_proc_mkdir(struct nfs_server *server, struct nfs_fh *dir,
                         const char *name, struct nfs_sattr *sattr,
                         struct nfs_fh *fhandle, struct nfs_fattr *fattr);
其中dir是创建目录的父目录,name是创建目录的名称,sattr是初始属性,fhandle和fattr是返回结果。
NFS_RMDIR(过程15):客户机调用过程15来删除一个目录。正如在UNIX中一样,一个目录被删除以前必须是空的。下面是一函数说明:
int nfs_proc_rmdir(struct nfs_server *server, struct nfs_fh *dir,
const char *name);
其中dir是删除目录的父目录,name是删除目录的名称。
NFS_READDIR(过程16):客户机调用过程16从一个目录中读取其中的目录项。输入参数包含目录句柄、魔饼和要读取的最大字符数。在最初的调用中,客户机指明了一个含零的魔饼,让服务器从目录的最开始读起。返回的值包括零个或多个目录项的链表。当每一次成功的调用返回以后,链表中的每一个目录项都包含一个文件名、该文件的唯一标识符、一个给出该文件在目录中的位置的魔饼,以及一个指向链表中下一项的指针。
为了读取目录中的目录项序列,客户机必须从NFSPROC_READDIR开始,传给它一个参数为零的魔饼和一个等于它的内部缓冲区大小的字符记数。服务器返回的目录项的个数将尽可能占满该缓冲区所能存放的数量。客户机循环扫描链表中的每一项,分别处理每一个文件名。如果返回值表明客户机已经到达目录的末尾,它便停止处理。否则,客户机用最后一项的魔饼再一次向服务器发起调用,从而得到更多的目录项。客户机继续读取目录项直到该目录的结尾。下面是函数说明:
int nfs_proc_readdir(struct nfs_server *server, struct nfs_fh *fhandle,
                         u32 cookie, unsigned int size, __u32 *entry);
其中entry是缓冲池,size是其大小,cookie是返回的魔饼,fhandle是目录的句柄。
NFS_STATFS(过程17):过程17允许客户机得到驻留有某个文件的文件系统的信息。返回结果包括以下信息:指明最优传输大小(即在read或write请求中的数据长度,这个长度可以产生最优的传输率)、存储设备的数据块大小、设备的块数、当前未使用的块数以及非特权用户可用的未使用块数。下面是函数说明:
int nfs_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle,
                         struct nfs_fsinfo *info);
其中nfs-fsinfo定义了文件系统的信息结构,定义如下:
struct nfs_fsinfo {
        __u32          tsize; /* 最优传输大小 */
        __u32          bsize; /* 存储设备的数据块大小 */
        __u32          blocks; /* 设备的块数 */
        __u32          bfree; /* 当前未使用的块数 */
__u32          avail; /* 非特权用户可用的未使用块数*/
};
2.4 NFS的Server端
Linux从2.2.X版本以后在内核中增加了对NFS Server端的支持。下面将对Server端的RPC层、NFS层加以介绍。而Socket层和VFS层由于比较简单,就穿插在这两个层中加以介绍,而不单独列出。
2.4.1 Server端的RPC层
在NFS的Server端,是通过建立一个RPC的服务来实现的。而一个RPC的服务实际上是一个daemon程序。它在后台运行,检测有没有到达的消息。如果有消息到达,就接收并且处理这个消息。一般这个daemon程序有一个或多个传输套接字和它联系在一起,然后它建立多个线程在这些套接字上等待着消息的到达。现在,Linux只支持一个远程程序一个deamon,还不支持多个daemon。
这是RPC服务的结构定义:
struct svc_serv {
       struct svc_rqst *       sv_threads;     /* 服务线程 */
       struct svc_sock *       sv_sockets;    /* 挂起的套接字 */
       struct svc_program *    sv_program;   /* RPC程序 */
       struct svc_stat *       sv_stats;       /* RPC统计数字 */
       unsigned int            sv_nrthreads;   /* 服务线程的个数 */
       unsigned int            sv_bufsz;       /* 数据报缓冲池大小 */
       unsigned int            sv_xdrsize;     /* XDR缓冲池大小 */
       struct svc_sock *       sv_allsocks;    /* 所有的套接字 */ 
       char *                  sv_name;        /* 服务名 */
};
其中,svc_rqst结构定义了一个服务线程的基本内容。下面是它的定义:
struct svc_rqst {
       struct svc_rqst *       rq_prev;        /* 空闲线程链表指针 */
       struct svc_rqst *       rq_next;
       struct svc_sock *       rq_sock;        /* 套接字 */
       struct sockaddr_in      rq_addr;        /* 地址 */
       int                     rq_addrlen; 94
       struct svc_serv *       rq_server;      /* RPC服务结构指针 */
       struct svc_procedure *  rq_procinfo;    /* RPC过程信息 */
       struct svc_cred         rq_cred;        /* 鉴别信息 */
       struct sk_buff *        rq_skbuff;      /* 高速接收inet缓冲 */
       struct svc_buf          rq_defbuf;      /* 缺省缓冲 */
       struct svc_buf          rq_argbuf;      /* 参数缓冲池 */
       struct svc_buf          rq_resbuf;      /* 返回结果缓冲池 */
       u32                     rq_xid;         /* 传输id */
       u32                     rq_prog;        /* 远程程序号 */
       u32                     rq_vers;        /* 远程版本号 */
       u32                     rq_proc;        /* 远程过程号 */
       u32                     rq_prot;        /* IP协议 */
       unsigned short          rq_verfed  : 1, /* 应答已经被确认 */
                             rq_userset : 1, /* auth->setuser执行正确 */
                             rq_secure  : 1, /* 安全端口 */
                             rq_auth    : 1; /* 检查客户机 */
       void *                  rq_argp;        /* 译码信息 */
       void *                  rq_resp;        /* xdr结果 */
      struct svc_client *     rq_client;      /* RPC client信息 */
      struct svc_cacherep *   rq_cacherep;    /* 缓冲信息 */
      struct wait_queue *     rq_wait;        /* 同步 */
};
其中,svc_buf是一段缓冲池,在它里面存储了RPC的请求和应答消息。每一个Server线程都有这样一个缓冲。下面是它的定义:
struct svc_buf {
       u32 *                   area;   /* 分配的内存 */
       u32 *                   base;   /* RPC数据报的开始 */
       int                     buflen; /* 整个缓冲池的大小 */
       u32 *                   buf;    /* 当前读写位置*/
       int                     len;    /* 当前读写长度*/
      /* 附加信息 */
      struct iovec            iov[RPCSVC_MAXIOV];
       int                     nriov;
};
其中,area和base一般是一样的,但是当线程进行快速读写时(见svc_rqst结构的sk_buff)它们是不一样的。另外,iovec数组是用来放一些附加信息的,这些信息将不会被拷贝到RPC应答缓冲池中去,但是它被直接送到网络上。这个结构主要是NFS_READ操作用的,但是READLINK和READDIR也会使用这个缓冲池。另外,在Server的接收端,有时我们需要直接处理UDP的碎片,这时iovec也用来放这些碎片。
为了实现Server对Socket的绑定,RPC的Server端在Berkley Socket上有套了一层,加了一个svc_socket结构,定义如下:
struct svc_sock {
      struct svc_sock *       sk_prev;        /* 准备好的svc_socket的链表 */
      struct svc_sock *       sk_next;
      struct svc_sock *       sk_list;        /* 所有的svc_socket */
      struct socket *         sk_sock;        /* berkeley socket 层 */
      struct sock *           sk_sk;          /* INET层 */
      struct svc_serv *       sk_server;      /* 对应这个socket的serv结构 */
      unsigned char           sk_inuse;       /* 使用计数 */
      unsigned char           sk_busy;        /* 排队/接收 */
      unsigned char           sk_conn;        /* 连接/等待 */
      unsigned char           sk_close;       /* dead or dying */
      int                     sk_data;        /* 等待的数据 */
      unsigned int            sk_temp : 1,    /* 临时sockett */
                            sk_qued : 1,    /* 是否在调用serv->sk_sockets */
                            sk_dead : 1;    /* 是否已经关闭 */
      int                     (*sk_recvfrom)(struct svc_rqst *rqstp);
      int                     (*sk_sendto)(struct svc_rqst *rqstp); 36
      int                     sk_reclen;      /* 记录长度 */
      int                     sk_tcplen;      /* 现在读长度 */
      struct svc_rqst *          sk_rqstp;        /* 用于调试用 */
};
对于每个服务器来说,它都维护了一个列表,声明本机的哪些路径可以被哪些客户机器mount成NFS文件系统。在linux中,这个文件列表放在/etc/exports中,这是exports文件的一个例子:
/mnt/eris/local apollo(rw)
/mnt/eris/remote  horse(ro)
这声明/mnt/eris/local可以被apollo机器以读写方式所mount,/mnt/eris/remote可以被horse机器所mount,但是只读方式。
对于表项中每一个或几个与这个NFS服务器相连的客户机,系统都会维护一个svc_client结构,这个结构定义了客户机的信息以及向这个客户机输出的表项。结构声明如下:
struct svc_client {
struct svc_client * cl_next; /* client链表 */
char cl_ident[NFSCLNT_IDMAX]; /* 标识符 */
int cl_idlen; /* 标识符长度 */
int cl_naddr; /* 地址个数 */
struct in_addr cl_addr[NFSCLNT_ADDRMAX]; /* 地址 */
struct svc_uidmap * cl_umap;
struct svc_export * cl_export[NFSCLNT_EXPMAX]; /* 输出表项信息 */
};
输出表项的声明如下:
struct svc_export {
struct svc_export * ex_next; /* export链表 */
char ex_path[NFS_MAXPATHLEN+1]; /* 路径名 */
struct svc_export * ex_parent; /* 父export表 */
struct svc_client * ex_client; /*  svc_client结构 */
int ex_flags; /*  属性,如只读或读写 */
struct dentry * ex_dentry; /* 输出所在的目录的dentry */
kdev_t ex_dev; /* 输出所在的dev号 */
ino_t ex_ino;
uid_t ex_anon_uid;
gid_t ex_anon_gid;
};
2.4.2 Server端的NFS层
Server端的NFS层主要实现了一个RPC的服务,然后通过这个RPC服务来对网络进行监视,当接受到适当的消息时,再调用适当的过程对它进行处理。
刚才在client端说明了在客户机和服务器通讯的时候,需要一个nfs_fh作为文件句柄,现在先介绍一下在NFS服务器端这个文件句柄的内部表示。在linux下它是由一个svc_fh结构定义的,声明如下:
typedef struct svc_fh {
struct knfs_fh fh_handle; /* 传给客户机的文件句柄*/
struct dentry * fh_dentry; /* 验证过的dentry */
struct svc_export * fh_export; /* export表指针 */
size_t fh_pre_size; /* 操作前大小 */
time_t fh_pre_mtime; /* 操作前的mtime */
time_t fh_pre_ctime; /* 操作前的ctime */
unsigned long fh_post_version;/* 操作后的inode版本 */
unsigned char fh_locked; /* 加锁的inode */
unsigned char fh_dverified; /* 检查过的dentry */
} svc_fh;
其中第一项fh_handle是提供给客户机的文件句柄,它是一个64字节信息,主要包括以下内容:
struct nfs_fhbase {
struct dentry * fb_dentry; /* dentry魔饼 */
__u32 fb_ino; /* inode号 */
__u32 fb_dirino; /* 目录inode号 */
__u32 fb_dev; /* 设备号 */
__u32 fb_xdev; /* export表的设备号 */
__u32 fb_xino; /* export表的inode号 */
};
在Linux中,在系统内核中提供了一个系统调用nfsservctl,这个系统调用可以对Server端进行控制,它的格式是:
nfsservctl(int   cmd,   struct   nfsctl_arg  *argp,  union  nfsctl_res *resp);
分享到:
评论

相关推荐

    深入理解Linux网络文件系统.pdf

    深入理解Linux网络文件系统.pdf

    Linux网络文件系统 (NFS)分析

    此文档详细介绍了Linux网络文件系统 (NFS),对于想从事文件系统,或者存储开发的同志来说,是一份不可多得的好资料,希望对你们有用。

    Linux网络文件系统(NFS)分析.doc

    Linux网络文件系统(NFS)分析.doc

    Linux网络文件系统(NFS)分析

    Sun Microsystems公司于1984年推出了一个在整个计算机工业中被广泛接受的远程文件存取机制,它被称为Sun的网络文件系统(Network File System),或者简称为NFS。该机制允许在一台计算机上运行一个服务器,使对其上...

    Linux网络文件系统分析pdf版

    Linux网络文件系统分析pdf版 浙大论文,79页,很全的。

    Linux网络文件系统分析(NFS)

    NFS的毕业设计,关于网络文件系统的详细描述。请欢乐下载。

    linux文件系统基础知识

    NFS : 网络文件系统,由SUN发明,主要用于远程文件共享 MS-DOS : MS-DOS文件系统 VFAT : Windows 95/98 操作系统采用的文件系统 FAT : Windows XP 操作系统采用的文件系统 NTFS : Windows NT/XP 操作系统...

    Linux文件管理Linux的文件系统

     NFS: 网络文件系统 3.Linux中的文件分类 (1)普通文件 文本文件:采用ASCII编码方式,可编辑,可修改 二进制:不可查看,不可修改 (2)目录文件 存放的内容是目录中的文件名和子目录名 (3)设备文件...

    Linux下NFS网络文件系统设定及管理 - NFS服务

    Linux下NFS网络文件系统设定及管理 - NFS服务 Linux下NFS网络文件系统设定及管理 - NFS服务

    linux下文件管理系统的研究与实现

    Linux操作系统是现在流行的一种免费的...在研究了Linux文件系统及网络传输的原理基础上系统主要实现了对文件的管理,包括创建、删除、重命名、复制、移动和文件权限的修改的功能,以及对字符的搜索和对文件的网络传输。

    Linux网络编程

    内容包含Linux系统概述、Linux编程环境、Linux文件系统简介、Linux下的进程和线程、TCP/IP协议族、应用层网络服务程序、TCP编程、主机信息获取、数据IO复用、UDP编程、高级套接字、套接字选项、原始套接字、服务器...

    嵌入式Linux文件系统及其存储机制分析

    构建适用于嵌入式系统的Linux文件系统,必然会涉及到两个关键点,一是文件系统类型的选择,它关系到文件系统的读写性能、尺寸大小;另一个就是根文件系统内容的选择,它关系到根文件系统所能提供的功能及尺寸大小。...

    Linux下NFS网络文件系统设定及管理

    Linux下NFS网络文件系统设定及管理.doc

    Linux系统网络接口配置文件及相关工具

    网络接口(interface)是网络硬件设备在操作系统中的表示方法,在Linux操作系统中配置网络接口,一般是通过网络配置工具实现的,但最终目的还 是通过网络配置工具来达到修改与网络相关的配置文件而起作用的。由于Linux...

    Linux网络编程 Linux网络编程.TXT

    Linux网络编程 Linux网络编程 Linux网络编程

    linux网络操作系统课件

    第一章 Linux系统基础  第二章 Linux文件系统结构  第三章 实用命令介绍  第四章 文本编辑程序  第五章 关于SHELL  第六章 系统管理与网络服务 

Global site tag (gtag.js) - Google Analytics