上一篇文章中我们以REMOVE请求为例讲解了NFS请求的处理过程,其中提到了文件句柄的概念,NFS需要根据文件句柄查找一个文件,这篇文章中我们就来聊聊文件句柄。在普通的文件系统中,我们用文件索引节点编号(ino)表示一个文件。ino就是一个数字,ino保存在磁盘中,整个文件系统中任何两个文件的ino都不相同,因此给定一个ino,我们就能找到对应的文件。当使用NFS文件系统时就出现问题了,我们无法通过文件索引编号找到对应的文件。下面的例子中我们将一个文件系统挂载在另一个文件系统之上导出了。
mount /dev/sdb1 /tmp/nfs/root/mount
/tmp/nfs/root 192.168.0.0/16(sec=sys,rw,sync)
/tmp/nfs/root/mount 192.168.0.0/16(nohide,sec=sys,rw,sync)
当客户端执行 mount -t nfs nfs_server:/tmp/nfs/root /tmp/mnt后,客户端挂载了服务器端的两个文件系统/tmp/nfs/root和/tmp/nfs/root/mount。
因此,当NFS客户端给出一个文件索引节点编号时,服务器端无法确定到底是哪个文件系统中的索引编号,也就无法找到对应的文件。为了区分不同的文件系统,NFS用文件句柄标识一个文件,文件句柄中既包含了服务器端文件系统的信息,也包含了文件的信息。服务器端解析客户端传递过来的文件句柄,定位客户端请求的文件。对NFS客户端来说,文件句柄是透明的,客户端不关心文件句柄的构成方式,也不对文件句柄进行解析。只需要将文件句柄传递给服务器端就可以了。服务器端可以向文件句柄中加入任何信息,只要保证能根据文件句柄查找到对应的文件就可以了。
1.文件句柄的数据结构
NFS不同版本对文件句柄的长度进行了不同的限制,NFSv2中文件句柄长度固定为32字节。NFSv3中文件句柄长度可变,但是不能超过64字节。NFSv4中文件句柄长度可变,但是不能超过128字节。Linux中,服务器端一个文件句柄的数据结构如下:
struct knfsd_fh {
// 文件句柄的长度
unsigned int fh_size; /* significant for NFSv3.
* Points to the current size while building
* a new file handle
*/
union {
struct nfs_fhbase_old fh_old;
__u32 fh_pad[NFS4_FHSIZE/4];
struct nfs_fhbase_new fh_new;
} fh_base; // 文件句柄内容
};
knfsd_fh由两个字段构成:fh_size表示文件句柄的实际长度,fh_base表示文件句柄的内容。文件句柄的构成方式有三种:fh_old、fh_pad、fh_new,这里我们只讲解fh_new的构成方式。在这种方式中,文件句柄用数据结构nfs_fhbase_new表示。
struct nfs_fhbase_new {
__u8 fb_version; // 1 /* == 1, even => nfs_fhbase_old */
__u8 fb_auth_type;
__u8 fb_fsid_type;
__u8 fb_fileid_type;
__u32 fb_auth[1];
/* __u32 fb_fsid[0]; floating */
/* __u32 fb_fileid[0]; floating */
};
fb_version表示版本号,固定为1。
fb_auth_type表示文件句柄是否经过了MD5校验,0表示没有经过校验,1表示进行了校验处理。目前的实现方式中固定为0。
fb_fsid_type表示fsid的构成方式,fsid表示一个文件系统。
fb_fileid_type表示fileid的构成方式,fileid表示一个文件。
fb_auth 如果fb_auth_type为1,fb_auth表示文件句柄MD5校验值。如果fb_auth_type为0,则没有这个字段。
fb_fsid 是一个文件系统的标识,这个字段的长度可变,根据fb_fsid_type进行设置。
fb_fileid 是一个文件的标识,这个字段的长度也可变,根据fb_fileid_type进行设置。
fb_fsid_type表示文件系统的标识方式,Linux定义了八种方式,这八种方式定义在文件fs/nfsd/nfsfh.h中。
enum nfsd_fsid {
FSID_DEV = 0, // 4字节设备编号 + 4字节文件系统根节点索引编号
FSID_NUM, // 如果用户在导出文件系统时设置了fsid,则是这种方式,每个文件系统用一个fsid表示。 4字节fsid.
FSID_MAJOR_MINOR, // 这种方式已经废弃了.
FSID_ENCODE_DEV, // 4字节设备编号(经过编码了) + 4字节文件系统根节点索引编号
FSID_UUID4_INUM, // 4字节文件系统根设备索引节点 + 4字节UUID
FSID_UUID8, // 8字节UUID
FSID_UUID16, // 16字节UUID
FSID_UUID16_INUM, // 8字节文件系统根节点编号 + 16字节UUID
};
fb_fsid_type是管理员配置/etc/exports文件时设置的,函数set_version_and_fsid_type()会根据配置情况挑选一种合适的标识方式,我们在后面会讲解这个函数。
fb_fileid_type表示文件的标识方式,Linux定义了很多种
enum fid_type {
/*
* The root, or export point, of the filesystem.
* (Never actually passed down to the filesystem.
*/
FILEID_ROOT = 0,
/*
* 32bit inode number, 32 bit generation number.
*/
FILEID_INO32_GEN = 1,
/*
* 32bit inode number, 32 bit generation number,
* 32 bit parent directory inode number.
*/
FILEID_INO32_GEN_PARENT = 2,
/*
* 64 bit object ID, 64 bit root object ID,