首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

NFS 文件系统源代码剖析

NFS 文件系统源代码剖析

NFS 文件系统概述
NFS(Network File System,网络文件系统)是一种基于网络的文件系统。它可以将远端服务器文件系统的目录挂载到本地文件系统的目录上,允许用户或者应用程序像访问本地文件系统的目录结构一样,访问远端服务器文件系统的目录结构,而无需理会远端服务器文件系统和本地文件系统的具体类型,非常方便地实现了目录和文件在不同机器上进行共享。虽然 NFS 不是唯一实现这个功能的文件系统,但它无疑是最成功一个。
NFS 的第一个版本是 SUN Microsystems 在 20 世纪 80 年代开发出来的,至今为止,NFS 经历了 NFS,NFSv2,NFSv3 和 NFSv4 共四个版本。现在,NFS 最新的版本是 4.1,也被称为 pNFS(parallel NFS,并行网络文件系统)。
前四个版本的 NFS,作为一个文件系统,它几乎具备了一个传统桌面文件系统最基本的结构特征和访问特征,不同之处在于它的数据存储于远端服务器上,而不是本地设备上,因此不存在磁盘布局的处理。NFS 需要将本地操作转换为网络操作,并在远端服务器上实现,最后返回操作的结果。因此,NFS 更像是远端服务器文件系统在本地的一个文件系统代理,用户或者应用程序通过访问文件系统代理来访问真实的文件系统。
众所周知的是,NFS 的客户端在访问远端服务器文件系统时,既需要通过服务器获得文件的属性信息,还需要通过服务器获得文件的数据信息,这使得 NFS 天然地具备将文件的属性信息和数据信息分离在不同服务器上进行访问的特性,于是最后一个版本 NFS4.1/pNFS,将 Lustre/CephFS/GFS 等集群文件系统的设计思想引入到自身中,成为一个具有里程碑意义的 NFS 版本。它使得 NFS 的数据吞吐的速度和规模都得到了极大提高,为 NFS 的应用带了更为广阔的空间。
NFS 之所以备受瞩目,除了它在文件共享领域上的优异表现外,还有一个关键原因在于它在 NAS 存储系统上应用。NAS 与 DAS 和 SAN 在存储领域的竞争中,NFS 发挥了积极的作用,这更使得 NFS 越来越值得关注。
NFSv3 源代码结构
相比之前的两个版本,NFSv3 是一个较为稳定和成熟的 NFS 版本,而之后的 NFSv4 除了在安全和性能上有所提高外,还在网络连接中加入了状态属性,因此显得复杂一些。在此,本文以 NFSv3 为例来剖析 NFS 文件系统的源代码结构,所用源码来自 Linux 2.4.9 内核。
按照 NFS 文件系统的设计与实现,NFS 文件系统主要分为三个部分:The Protocol(网络协议),Client Side(NFS 客户端)和 Server Side(NFS 服务器)。NFS 客户端提供了接口,保证用户或者应用程序能像访问本地文件系统一样访问 NFS 文件系统,NFS 服务器作为数据源,为 NFS 客户端提供真实的文件系统服务,而网络协议则使得 NFS 客户端和 NFS 服务器能够高效和可靠地进行通信。NFS 网络协议使用的是 RPC(Remote Procedure Call,远程过程调用)/XDR(External Data Representation,外部数据表示)机制,因此本文将剖析的重点放在 NFS 客户端和 NFS 服务器上。
Client Side 源代码
Client Side 的头文件在 include/linux/ 下面,C 文件在 fs/nfs 下面。
    dir.c/file.c/inode.c/symlink.c/unlink.c:与文件操作相关的系统调用
    read.c/write.c/flushd.c:文件读写
    mount_clnt.c/nfs_root.c:将 NFS 文件系统作为 root 目录的相关实现
    proc.c/nfs2xdr.c/nfs3proc.c/nfs3xdr.c:网络数据交换
与文件操作相关的系统调用都在 struct file_operations,struct inode_operations 这两个数据结构里面定义。文件的读操作 nfs_file_read 和写操作 nfs_file_write 被单独提出来,因为文件读写性能将直接关系到文件系统的成败,本文在后面会重点阐述其实现。
Server Side 源代码
Server Side 的头文件在 include/linux/nfsd 下面,C 文件在 fs/nfsd 下面。
    auth.c/lockd.c/export.c/nfsctl.c/nfscache.c/nfsfh.c/stats.c:导出目录的访问管理
    nfssvc.c:NFS 服务 deamon 的实现
    vfs.c:将 NFS 文件系统的操作转换成具体文件系统的操作
    nfsproc.c/nfsxdr.c/nfs3proc.c/nfs3xdr.c:网络数据交换
导出目录的访问管理主要解决网络文件系统实现面临的几个重要问题,包括目录导出服务,外部访问的权限控制,多客户端以及客户端与服务器的文件并发操作等。
一个典型例子:rename 的调用过程
在 NFS 文件系统的文件操作中,除了 read 和 write 操作考虑到性能因素,专门使用了缓存机制外,其它的操作基本上都是同步完成的。本文以 rename 为例来进行说明,如下图所示。首先用户或者应用程序开始调用文件操作,经过系统调用 sys_rename,到达虚拟文件系统层 vfs_rename,然后交给 NFS 文件系统 nfs_rename 来处理。NFS 文件系统无法操作存储介质,它调用 NFS 客户端函数 nfs3_proc_rename 和 NFS 服务器函数 nfsd3_proc_rename 进行通信,把文件操作转发到 NFS 服务器的虚拟文件系统层 vfs_rename,最后调用具体的文件系统如 ext2 的函数 ext2_raname,完成文件重命名。

图 1. rename 调用过程
图 1. rename 调用过程
与传统文件系统相同点
在阐述 NFS 文件系统与传统桌面文件系统的相同点之前,我们首先简要回顾一下 Linux 操作系统上文件系统的体系结构。按照 Linux 文件系统剖析的划分,Linux 文件系统从上至下主要由虚拟文件系统层,特定文件系统层和页高速缓存层三部分组成,如下图所示。当然,这种划分并不是一定的,例如在执行直接 I/O 调用时,是不需要进行页高速缓存的,另外,对于块设备的读写,进行页高速缓存之后还会有通用块层和 I/O 调度层的处理。

图 2. 文件系统体系结构
图 2. 文件系统体系结构
用户或者应用程序通过统一的系统调用接口对文件系统进行操作,然后系统调用进入虚拟文件系统层,虚拟文件系统根据文件系统类型,调用特定文件系统的操作函数。对用户和应用程序来说,由于接口完全相同,因此用户感觉不到差异,应用程序也可以无缝地移植到 NFS 文件系统上。Linux 通过一组对象对文件系统的操作,这组对象是 superblock(超级块对象),inode(索引节点对象),dentry(目录项对象)和 file(文件对象),如下图所示。所有文件系统都支持这些对象,正是因为它们,VFS 层可以对 NFS 和其它文件系统一视同仁,只管调用这些对象的数据和函数指针,把具体的文件系统数据布局和操作都留给特定的文件系统来完成。

图 3. VFS 对象
图 3. VFS 对象
NFS 与其它文件系统一样,向内核声明和注册自己的文件系统类型。
   
static DECLARE_FSTYPE(nfs_fs_type, "nfs", nfs_read_super, FS_ODD_RENAME);
... ...
module_init(init_nfs_fs)
module_exit(exit_nfs_fs)

同样,NFS 也需要根据自己的文件类型设置相应的文件操作函数。如果是正规文件,需要设置 inode 操作函数,file 操作函数,以及 address_space 操作函数;如果是目录文件,需要设置 inode 操作函数,file 操作函数;如果是链接,则只需设置 inode 操作函数。
   
static void
nfs_fill_inode(struct inode *inode, struct nfs_fh *fh, struct nfs_fattr *fattr)
{
     ... ...
     inode->i_op = &nfs_file_inode_operations;
     if (S_ISREG(inode->i_mode)) {
          inode->i_fop = &nfs_file_operations;
          inode->i_data.a_ops = &nfs_file_aops;
     } else if (S_ISDIR(inode->i_mode)) {
          inode->i_op = &nfs_dir_inode_operations;
          inode->i_fop = &nfs_dir_operations;
     } else if (S_ISLNK(inode->i_mode))
          inode->i_op = &nfs_symlink_inode_operations;
     else
          init_special_inode(inode, inode->i_mode, fattr->rdev);
     ... ...
}

与传统文件系统不同点
与内存文件系统,闪存文件系统和磁盘文件系统这些本地文件系统最大的不同在于,NFS 文件系统的数据是基于网络,而不是基于存储设备的,因此 NFS 文件系统在设计自己的 inode 和 superblock 数据结构,以及实现文件操作函数时,无需考虑数据布局情况。同样是因为基于网络,NFS 文件系统的权限控制和并发访问的要求比本地文件系统更高,读写的缓存机制也大大有别于本地文件系统。
superblock 和 inode
清单 1. NFS 的 superblock 定义
   
struct rpc_clnt *   client;     /* RPC 客户端句柄 */
struct nfs_rpc_ops *   rpc_ops;    /* RPC 客户端函数向量表 */
int     flags;     /* 标识信息 */
unsigned int    rsize;     /* 每次读请求的最小数据量 */
unsigned int    rpages;     /* 每次读请求的最小数据量(以页为单位)*/
unsigned int    wsize;     /* 每次写请求的最小数据量 */
unsigned int    wpages;     /* 每次写请求的最小数据量(以页为单位)*/
unsigned int    dtsize;     /* 每次读目录信息的最小数据量 */
unsigned int    bsize;     /* NFS 服务器端的块大小 */
unsigned int    acregmin;    /* 正规文件在缓存中驻留的最小允许时间 */
unsigned int    acregmax;    /* 正规文件在缓存中驻留的最大允许时间 */
unsigned int    acdirmin;    /* 目录文件在缓存中驻留的最小允许时间 */
unsigned int    acdirmax;    /* 目录文件在缓存中驻留的最大允许时间 */
unsigned int    namelen;    /* NFS 服务器端的主机名称最大长度 */
char *     hostname;    /* NFS 服务器端的主机名称 */
struct nfs_reqlist *   rw_requests;    /* 异步读写请求队列信息 */
继承事业,薪火相传
返回列表