NFS是一种跨操作系统的协议,Linux客户端可以挂载Windows服务器中的文件系统,Mic客户端也可以挂载Solaris服务器中的文件系统。Linux中,NFS客户端和NFS服务器端是分别开发的,客户端的程序位于fs/nfs/中,目前由NetApp的Trond Myklebust维护;服务器端的程序位于fs/nfsd中,由RedHat的J. Bruce Fields维护。前面几篇文章讲解了服务器端的处理过程,接下来讲讲客户端的数据结构和处理流程。
1.文件系统超级块 nfs_server
这是文件系统超级块结构中与NFS文件系统本身相关的字段,也就是struct super_block结构中的字段s_fs_info。NFS文件系统与其他文件系统一样,使用前也需要先挂载,挂载NFS文件系统时也会在内存中创建一个超级块结构struct super_block。由于NFS中的文件位于服务器端,所以NFS文件系统的挂载过程比其他文件系统挂载过程复杂一些,后面的文章可能会讲解这个挂载过程,这篇文件中先讲讲文件系统超级块中的这个数据结构struct nfs_server。
struct nfs_server {
// 这个结果所属的NFS客户端
struct nfs_client *nfs_client;/* shared client and NFS4 state */
// 一个nfs_client结构包含多个nfs_server结构,这些nfs_server结构组成一个链表。
// client_link指向链表中相邻的元素
struct list_headclient_link;/* List of other nfs_server structs
* that share the same client
*/
// 系统中所有的nfs_server结构构成了一个链表,链表头是网络命名空间net_fs中的字段nfs_volume_list
// master_link指向链表中相邻的元素
struct list_headmaster_link;/* link in master servers list */
// RPC客户端,当发起RPC请求时需要使用这个客户端。
struct rpc_clnt *client;/* RPC client handle */
// RPC客户端,当发起与ACL相关的RPC请求时使用这个客户端。
struct rpc_clnt *client_acl;/* ACL RPC client handle */
// NFS中文件锁相关的数据结构,NFSv2和NFSv3依靠NLM实现文件锁,NFSv4本身实现了文件锁功能.
struct nlm_host*nlm_host;/* NLM client handle */
// IO统计信息
struct nfs_iostats __percpu *io_stats;/* I/O statistics */
// 文件系统的backing_dev_info结构
struct backing_dev_infobacking_dev_info;
// 这是写操作中正在向服务器写入的数据,以数据页为单位
atomic_long_twriteback;/* number of writeback pages */
// 这是挂载NFS文件系统时指定的各种挂载选项
intflags;/* various flags */
// 这个字段保存了服务器的功能,如是否支持链接文件、是否支持ACL等,
// 这是通过GETATTR请求获取的。
unsigned intcaps;/* server capabilities */
// READ请求中RPC报文传输数据的最大值,如果需要读取的数据量超过rsize,
// 就需要发起多个READ请求
unsigned intrsize;/* read size */
unsigned intrpages;/* read size (in pages) */
// WRITE请求中RPC报文传输数据的最大值,如果需要写入的数据量超过wsize,
// 就需要发起多个WRITE请求
unsigned intwsize;/* write size */
unsigned intwpages;/* write size (in pages) */
// 如果WRITE请求中的数据量超出了wsize,必须分割成多个WRITE请求,
// 同时一定要保证分割后每个WRITE请求中的数据量是wtmult的倍数.
unsigned intwtmult;/* server disk block size */
// READDIR请求中最多可以包含的数据量.READDIR请求的目的是读取目录中文件和子目录的信息,
// 由于目录中可能包含很多文件,当这些信息超出dtsize的限制时,需要发起多个READDIR请求。
unsigned intdtsize;/* readdir size */
// NFS服务器使用的端口号
unsigned shortport;/* "port=" setting */
// NFS服务器中数据块的大小
unsigned intbsize;/* server block size */
// 普通文件缓存最小超时时间
unsigned intacregmin;/* attr cache timeouts */
// 普通文件缓存最大超时时间
unsigned intacregmax;
// 目录缓存最小超时时间
unsigned intacdirmin;
// 目录缓存最大超时时间
unsigned intacdirmax;
// 文件系统中文件名称的最大长度
unsigned intnamelen;
// 执行mount命令时添加的附加参数
unsigned intoptions;/* extra options enabled by mount */
// 支持FS-Cache缓存 这也是一个挂载选项.
#define NFS_OPTION_FSCACHE0x00000001/* - local caching enabled */
struct nfs_fsidfsid; // fsid,跟文件系统有关.
// 文件的最大长度
__u64maxfilesize;/* maximum file size */
// 服务器端的时间精度,当客户端通过SETATTR请求修改文件时间时,服务器只能识别到这个精度。
struct timespectime_delta;/* smallest time granularity */
// 文件系统的挂载时间
unsigned longmount_time;/* when this fs was mounted */
// 文件系统的设备号
dev_ts_dev;/* superblock dev numbers */
#ifdef CONFIG_NFS_FSCACHE
// 下面两个字段是和FS-Cache缓存相关的字段,后面的文件中会讲解FS-Cache缓存.
// 这是fscache中使用的key
struct nfs_fscache_key*fscache_key;/* unique key for superblock */
// nfs_server结构的facache
struct fscache_COOKIE*fscache;/* superblock COOKIE */
#endif
// 这是pNFS中使用的一个数据块大小,而且只有block layout这种方式的pNFS才使用这个变量,
// file layout和object layout没有用到这个变量,一般来说就是服务器端数据块的大小。
u32pnfs_blksize;/* layout_blksize attr */
#if IS_ENABLED(CONFIG_NFS_V4) // 下面是NFSv4中用到的字段
// 支持的属性信息 这是NFS服务器支持的文件属性信息比特位,每个bit代表一种属性。
// NFSv4支持的属性可以参见RFC3530
u32attr_bitmask[3];/* V4 bitmask representing the set
of attributes supported on this
filesystem */
// 这是与文件属性变化相关的属性比特位,表示服务器端支持这些属性变化。
u32cache_consistency_bitmask[2];
/* V4 bitmask representing the subset
of change attribute, size, ctime
and mtime attributes supported by
the server */
// 这是与ACL相关的属性标志位
u32acl_bitmask;/* V4 bitmask representing the ACEs
that are supported on this
filesystem */
// 这是文件句柄的过期原因
u32fh_expire_type;/* V4 bitmask representing file
handle volatility type for
this filesystem */
// 这是pNFS类型的数据结构,包含了pNFS相关的操作函数。pNFS包含三种类型:
// block layout、file layout、object layout
struct pnfs_layoutdriver_type *pnfs_curr_ld; /* Active layout driver */
// 这是一个等待队列,上面关联了一些RPC任务.
struct rpc_wait_queueroc_rpcwaitq;
// 这是block layout中使用的一个字段,block layout中这个字段的类型是struct block_mount_id
void*pnfs_ld_data;/* per mount point data */
/* the following fields are protected by nfs_client->cl_lock */
// 这棵红黑树中保存的数据结构是struct nfs4_state_owner,
// 这是和NFSv4状态管理相关的一个数据结构.
struct rb_rootstate_owners;
#endif
// NFSv4是一种有状态的协议,这是与open相关的一个编号
struct idaopenowner_id;
// NFSv4是一种有状态的协议,客户与lock相关的一个编号
struct idalockowner_id;
// 这是一个链表,链表中保存的是空闲不用的nfs4_state_owner结构.
struct list_headstate_owners_lru;
// 这是pNFS用到的一个字段,每个文件需要一个pnfs_layout_hdr结构,
// layouts是一个链表头,链接了这个文件系统中所有的pnfs_layout_hdr结构.
struct list_headlayouts;
// 这是delegation相关的链表,链接了这个文件系统中所有的delegation.
struct list_headdelegations;
// 删除客户端的方法 nfs_destroy_server()
void (*destroy)(struct nfs_server *);
// nfs_server结构的引用计数
atomic_t active; /* Keep trace of any activity to this server */
/* mountd-related mount options */ // 下面是与MOUNT协议相关的选项
// NFSv2和NFSv3依靠MOUNT协议挂载NFS文件系统,MOUNT服务和NFS服务可以位于不同的主机上,
// 下面几个字段是为MOUNT服务设置的。
struct sockaddr_storagemountd_address; // MOUNT服务器地址
size_tmountd_addrlen; // NFS服务器地址的长度
u32mountd_version; // MOUNT协议版本号
unsigned shortmountd_port; // MOUNT协议使用的端口号
unsigned shortmountd_protocol; // 传输层协议
};
2.NFS客户端 nfs_client
struct nfs_client {
// 这个nfs_client结构的引用计数
atomic_tcl_count;
// 这是pNFS中使用的一个字段,记录了这个客户端中有多少文件系统在使用pNFS
atomic_tcl_mds_count;
// NFS客户端目前的状态,现在定义了三个状态.
intcl_cons_state;/* current construction state (-ve: init error) */
// 客户端已经初始化完毕,可以使用了
#define NFS_CS_READY0/* ready to be used */
// 客户端正在初始化
#define NFS_CS_INITING1/* busy initialising */
// 客户端正在初始化session,这是NFSv4.1中的状态
#define NFS_CS_SESSION_INITING2/* busy initialising session */
// NFS资源状态,标记了客户端可以使用的功能
unsigned longcl_res_state;/* NFS resources state */
// 开启了CALLBACK机制
#define NFS_CS_CALLBACK1/* - callback started */
// 开启了IDMAP机制
#define NFS_CS_IDMAP2/* - idmap started */
// 开启了RENEW机制
#define NFS_CS_RENEWD3/* - renewd started */
// 停止更新状态,不需要执行RENEW请求了
#define NFS_CS_STOP_RENEW4/* no more state to renew */
// 这是NFSv4.1中的功能,表示需要发起GET_LEASE_TIME请求,获取状态过期时间
#define NFS_CS_CHECK_LEASE_TIME5/* need to check lease time */
// 下面是一些标志位信息
unsigned longcl_flags;/* behavior switches */
// 不要使用预留的端口号
#define NFS_CS_NORESVPORT0/* - use ephemeral src port */
// 这个标志位影响RPC中的标志位RPC_CLNT_CREATE_DISCRTRY
// 当RPC请求超时后会重新发送,RPC_CLNT_CREATE_DISCRTRY表示重发RPC请求前先断开连接。
#define NFS_CS_DISCRTRY1/* - disconnect on RPC retry */
// NFS服务器地址,包括了IP地址和端口号。
struct sockaddr_storagecl_addr;/* server identifier */
// NFS服务器地址长度 cl_addr的长度
size_tcl_addrlen;
// NFS服务器名称
char *cl_hostname;/* hostname of server */
// 一个系统中包含多个nfs_client结构,这些结构构成了一个链表,链表头是nfs_net->nfs_client_list
// cl_share_link指向链表中相邻的元素
struct list_headcl_share_link;/* link in global client list */
// 一个nfs_client结构包含多个nfs_server结构,这些nfs_server结构组成一个链表。
// cl_superblocks字段是这个链表的链表头
struct list_headcl_superblocks;/* List of nfs_server structs */
// 这代表一个RPC客户端,这个客户端中包含RPC远端调用信息,
// 当客户端发起与nfs_server无关的RPC请求时(如GET_LEASE_TIME)需要使用这个RPC客户端
struct rpc_clnt *cl_rpcclient;
// 这个NFS客户端对应的操作函数集合,如nfs_v2_clientops、nfs_v3_clientops、nfs_v4_clientops
const struct nfs_rpc_ops *rpc_ops;/* NFS protocol vector */
// 传输层协议
intcl_proto;/* Network transport protocol */
// 这个客户端对应的文件系统,如nfs_v2、nfs_v3、nfs_v4
struct nfs_subversion *cl_nfs_mod;/* pointer to nfs version module */
// 次版本号 NFSv4包含两个版本NFSv4.0和NFSv4.1
u32cl_minorversion;/* NFSv4 minorversion */
// 当发起与nfs_server无关的RPC请求时使用的用户信息
struct rpc_cred*cl_machine_cred;
#if IS_ENABLED(CONFIG_NFS_V4) // 下面是NFSv4中的字段
// cl_clientid和cl_confirm是在nfs4_init_clientid()中初始化的.
// 这是NFSv4中的参数,cl_clientid代表一个NFS客户端
// cl_clientid和cl_confirm是SETCLIENTID请求中返回的数据,代表了一个NFS客户端.
u64cl_clientid;/* constant */
// 这是一个验证信息,客户端可以根据这个信息判断服务器是否重启了。
// 如果服务器重启了,客户端就要进行一些恢复操作.
nfs4_verifiercl_confirm;/* Clientid verifier */
// 这是NFS客户端的状态
unsigned longcl_state;// 状态信息定义在 nfs4_fs.h
// 这是保护NFSv4状态的自旋锁
spinlock_tcl_lock;
// 这是clientid的有效时间,一般设置为90秒。
unsigned longcl_lease_time;
// cientid最后一次更新时间,当时间超过cl_last_renewal+cl_lease_time就认为clientid无效了。
unsigned longcl_last_renewal;
// 这个任务负责周期性检查clientid是否有效,一般间隔是clientid有效期限的2/3.
// 这个延迟任务的处理函数是nfs4_renew_state() nfs4renewd.c
struct delayed_workcl_renewd;
// 这是一个RPC任务等待队列
struct rpc_wait_queuecl_rpcwaitq;
/* idmapper */ // 这是id映射相关的数据结构
struct idmap *cl_idmap;
/* Our own IP address, as a null-terminated string.
* This is used to generate the mv0 callback address.
*/
charcl_ipaddr[48]; // 这是NFS客户端的地址.
// 这是NFSv4.0中的字段,一台客户端可以挂载多个NFS文件系统,cl_db_ident是一个编号,
// 用来区分这些nfs_client结构
u32cl_cb_ident;/* v4.0 callback identifier */
// NFSv4是一种有状态的协议,cl_mvops中保存的是状态管理相关的函数集合.
const struct nfs4_minor_version_ops *cl_mvops;
/* The sequence id to use for the next CREATE_SESSION */
// 这是NFSv4.1中CREATE_SESSION使用的一个编号,记录了下次CREATE_SESSION过程中使用的seqid
u32cl_seqid;
/* The flags used for obtaining the clientid during EXCHANGE_ID */
// 这是NFSv4.1中EXCHANGE_ID请求中使用的标志位,这些标志位定义在nfs4.h中
u32cl_exchange_flags;
// 这是NFSv4.1中的session,通过CREATE_SESSION请求设置这个字段。
struct nfs4_session*cl_session;/* shared session */
// cl_serverowner、cl_serverscope、cl_implid是和服务器范围相关的数据结构
struct nfs41_server_owner *cl_serverowner;
struct nfs41_server_scope *cl_serverscope;
struct nfs41_impl_id*cl_implid;
#endif /* CONFIG_NFS_V4 */
#ifdef CONFIG_NFS_FSCACHE
// fscache中的COOKIE信息
struct fscache_COOKIE*fscache;/* client index cache COOKIE */
#endif
struct net*cl_net; // 网络命名空间
};
3.文件索引节点 nfs_inode
这是NFS文件系统中文件索引节点的数据结构,这个结构的定义如下:
struct nfs_inode {这几个数据结构都很大,包含的信息也很多,现在只是简单列出了每个字段的函数,以后会对其中的某些字段进行详细说明。客户端执行mount命令挂载NFS文件系统时主要的工作就是创建并初始化nfs_client和nfs_server结构。由于这里面的信息太多了,因此涉及到很多操作,而且NFSv3和NFSv4的处理过程也有些差别。以后抽时间再说说这个挂载过程。 最后说说nfs_client和nfs_server的关系。这两个名称起的有歧义,开始看代码的时候我一直以为这两个结构是并列关系,nfs_client保存客户端的信息,nfs_server保存服务器端的信息,后来才发现不是这样的。nfs_server是NFS文件系统中的超级块结构,一般来说,每挂载一个文件系统就要创建一个超级块,就需要一个nfs_server结构。比如 mount -t nfs -o vers=3 192.168.6.144:/tmp/nfs/root1 /tmp/root1 mount -t nfs -o vers=3 192.168.6.144:/tmp/nfs/root 2 /tmp/root2 客户端挂载了192.168.6.144中两个文件系统,因此需要创建两个nfs_server结构,每个nfs_server结构对应一个文件系统。但是这两个文件系统还是有一些共性,因此将其中共性的内容提取出来,填充到nfs_client结构中,这两个nfs_server共享同一个nfs_client结构。两个nfs_server共享同一个nfs_client结构需要具备三个条件:(1)NFS服务器地址相同,都是192.168.6.144;(2)传输层协议相同,默认采用的都是TCP协议;(3)NFS协议版本相同,都是NFSv3。这个检查工作在函数nfs_match_client()中完成。NFS解析挂载时指定的参数信息,和系统中已经创建的nfs_client进行比较,如果满足上面三个条件,就使用这个nfs_client结构。如果不满足条件,就创建一个新的nfs_client结构。 还有一点需要注意,客户端可以多次挂载同一个文件系统,比如 mount -t nfs -o vers=3 192.168.6.144:/tmp/nfs/root1 /tmp/root1 mount -t nfs -o vers=3 192.168.6.144:/tmp/nfs/root1 /tmp/root2 将一个文件系统挂载到了/tmp/root1和/tmp/root2两个目录下,这种情况下其实两次挂载只创建了一个nfs_server结构,因为两次挂载使用的参数完全相同。NFS会将挂载过程中使用的参数和系统中已有的nfs_server结构进行比较,如果相同就使用这个nfs_server结构,如果不相同就创建一个新的nfs_server结构,这个比较过程是在函数nfs_compare_super()中完成的,感兴趣可以看看。
/*
* The 64bit 'inode number'
*/
__u64 fileid;// 文件索引编号
/*
* NFS file handle
*/
struct nfs_fhfh; // 这就是从服务器端请求来的文件句柄
/*
* Various flags
*/
// NFS文件索引节点使用的各种标志位
unsigned longflags;/* atomic bit ops */
// 表示文件的各种缓存信息是否有效
unsigned longcache_validity;/* bit mask */
/*
* read_cache_jiffies is when we started read-caching this inode.
* attrtimeo is for how long the cached information is assumed
* to be valid. A successful attribute revalidation doubles
* attrtimeo (up to acregmax/acdirmax), a failure resets it to
* acregmin/acdirmin.
*
* We need to revalidate the cached attrs for this inode if
*
*jiffies - read_cache_jiffies >= attrtimeo
*
* Please note the comparison is greater than or equal
* so that zero timeout values can be specified.
*/
unsigned longread_cache_jiffies;// 这是文件属性的更新时间
unsigned longattrtimeo;// 文件属性有效时间
// 这是设置attrtimeo的时间戳,记录了attrtimeo最后修改的时间。
unsigned longattrtimeo_timestamp;
// 这是和文件属性相关的一个计数
unsigned longattr_gencount;
/* "Generation counter" for the attribute cache. This is
* bumped whenever we update the metadata on the
* server.
*/
// 这是与文件修改属性相关的一个计数
unsigned longcache_change_attribute;
// 这里保存了用户对这个文件的访问权限.
struct rb_rootaccess_cache;// 这是一棵红黑树,保存的是nfs_access_entry结构
// 这是一个链表,链表中存放的也是nfs_access_entry结构.
struct list_headaccess_cache_entry_lru;
// 这是一个lru链表
struct list_headaccess_cache_inode_lru;
#ifdef CONFIG_NFS_V3_ACL // 与ACL相关的两个数据结构
struct posix_acl*acl_access;
struct posix_acl*acl_default;
#endif
/*
* This is the COOKIE verifier used for NFSv3 readdir
* operations
*/
// READDIR请求中用到的结构
__be32COOKIEverf[2];
unsigned longnpages;
// 这里字段与COMMIT请求有关
// WRITE操作中如果没有把数据写入服务器磁盘中,就会将缓存页链接到这里
struct nfs_mds_commit_info commit_info; // 这里保存了需要提交的nfs_page请求
/* Open contexts for shared mmap writes */
// 这是一个链表头,链表中保存的数据结构是nfs_open_context,包含了用户打开文件的信息.
struct list_headopen_files;
/* Number of in-flight sillydelete RPC calls */
atomic_tsilly_count;
/* List of deferred sillydelete requests */
struct hlist_headsilly_list;
wait_queue_head_twaitqueue;
#if IS_ENABLED(CONFIG_NFS_V4)
struct nfs4_cached_acl*nfs4_acl;// NFSv4中ACL的数据结构
/* NFSv4 state */
// 这是一个链表,链表中的数据结构是nfs4_state,与NFSv4状态有关.
struct list_headopen_states;
// 这是文件中的delegation
struct nfs_delegation __rcu *delegation;
fmode_t delegation_state;// delegation的读写类型
// 与NFSv4状态相关的信号量
struct rw_semaphorerwsem;
/* pNFS layout information */
// 这是pNFS中的数据结构,保存了已经获取的layout信息
struct pnfs_layout_hdr *layout;
#endif /* CONFIG_NFS_V4*/
/* how many bytes have been written/read and how many bytes queued up */
__u64 write_io;// 已经写的数据量
__u64 read_io;// 已经读的数据量
#ifdef CONFIG_NFS_FSCACHE
struct fscache_COOKIE*fscache;// 这是文件索引节点的fscache结构
#endif
struct inodevfs_inode;// 这是通用inode节点的数据结构
};