什么是nfs文件服务器(NFS文件系统协议及解析概要)
本文是第二部分,承接上文,具体请参考本号的历史文章。
2.3 基本数据类型
如下用在其它数据结构中的XDR定义是基本的数据结构和类型。
2.3.1. stat
enum stat {
NFS_OK = 0,NFSERR_PERM=1,NFSERR_NOENT=2,NFSERR_IO=5,NFSERR_NXIO=6,NFSERR_ACCES=13,NFSERR_EXIST=17,NFSERR_NODEV=19,NFSERR_NOTDIR=20,NFSERR_ISDIR=21,NFSERR_FBIG=27,NFSERR_NOSPC=28,NFSERR_ROFS=30,NFSERR_NAMETOOLONG=63,NFSERR_NOTEMPTY=66,NFSERR_DQUOT=69,NFSERR_STALE=70,NFSERR_WFLUSH=99};
“stat”类型作为每次例程调用结果返回。如果值为NFS_OK则说明该次调用成功完成,而且结果是可用的。其它值则说明在服务端例程处理过程中发生了某种错误。错误码继承了UNIX错误码。
NFSERR_PERM
非所有者。调用者没有正确的所有权去执行请求。
NFSERR_NOENT
没有该文件或者目录,指定的文件或者目录不存在。
NFSERR_IO
当执行操作的时候发生了某种硬件错误,比如磁盘错误。
NFSERR_NXIO
没有该设备或者地址。
NFSERR_ACCES
权限被拒绝。调用者对执行的请求操作没有正确的权限。
NFSERR_EXIST
文件存在。指定的文件以及存在。
NFSERR_NODEV
无此设备。
NFSERR_NOTDIR
无此目录。调用者在执行一个目录操作是指定了一个不存在的目录。
NFSERR_ISDIR
是目录。调用者在执行一个非目录操作的时候指定了一个目录。
NFSERR_FBIG
文件太大。操作致使文件大小超过了服务端的限制。
NFSERR_NOSPC
设备无空间。操作致使服务端文件系统达到其最大限制。
NFSERR_ROFS
只读文件系统。企图在一个只读的文件系统执行写操作。
NFSERR_NAMETOOLONG
文件名太长。在一个操作中的文件名太长。
NFSERR_NOTEMPTY
目录非空。试图删除一个非空目录。
NFSERR_DQUOT
磁盘配额已超。在服务器上的客户端配额已超。
NFSERR_STALE
参数中的"fhandle"不可用。文件句柄所关联的文件已经不存在或者已经被撤销。
NFSERR_WFLUSH
请求"WRITECACHE"中涉及的服务端的写缓存需要刷写到磁盘。
2.3.2. ftype
enum ftype {
NFNON = 0,NFREG = 1,NFDIR = 2,NFBLK = 3,NFCHR = 4,NFLNK = 5};
枚举"ftype"给出了文件的类型。NFNON表示为非文件,NFREG表示普通文件,NFDIR表示目录,NFBLK表示块设备,NFCHK表示字符设备,NFLNK表示符号链接。
2.3.3. fhandle
typedef opaque fhandle[FHSIZE];
"fhandle"是在服务器端与客户端传递的文件句柄。所有文件操作通过文件句柄来关联一个文件或者目录。文件句柄包含这能够区分一个独立文件的关键信息。
2.3.4. timeval
struct timeval {
unsigned int seconds;
unsigned int useconds;
};
结构体"timeval"包含着格林威治时间自1970年1月1日零时起的秒和毫秒的数字。用于传递时间和日期信息。
2.3.5. fattr
struct fattr {
ftype type;unsigned int mode;unsigned int nlink;unsigned int uid;unsigned int gid;unsigned int size;unsigned int blocksize;unsigned int rdev;unsigned int blocks;unsigned int fsid;unsigned int fileid;timeval atime;timeval mtime;timeval ctime;};
结构体"fattr" 包含一个文件的属性。
“type”是文件的类型;
“nlink”是该文件硬链接的数量;
“uid”是拥有该文件的用户的识别数字;
“gid”该文件所属组的组识别数字;
“size”是文件以字节为单位的大小;
“blocksize”是文件块以字节为单位的大小;
“rdev”文件的设备号;
“blocks”是文件在磁盘上占用的块数;
“fsid”是包含该文件的文件系统的唯一标识;
“fileid”是文件在文件系统中的唯一标识;
“atime”最后一次访问时间,包括写或者读;
“mtime”最后一次更改时间(写);
“ctime”是文件状态发生变化的时间,如果写文件,文件大小发生变化时也会影响该值。
“mode”是通过一系列位标识的访问模式。需要注意的是文件类型在模式位和文件type中都有定义。这个确实是一个Bug,在以后的版本中将会合并。下面以八进制数字给出每一位的含义。
0040000 代表文件夹; "type" 域应该是NFDIR。
0020000 是一个字符相关的文件; "type" 域应该是 NFCHR。
0060000 是一个块相关的文件; "type" 域应该是 NFBLK.
0100000 是一个常规文件; "type" 域应该是 NFREG.
0120000 是一个符号链接文件; "type"域应该是 NFLNK.
0140000 是一个命名套接字; "type"域应该是 NFNON.
0004000 执行的时候设置用户ID.
0002000 执行的时候设置组ID.
0001000 保存交换文本,甚至在用完之后.
0000400 允许所有者读.
0000200 允许所有者写.
0000100 允许所有者执行或者搜索.
0000040 允许组读.
0000020 允许组写.
0000010 允许组执行或者搜索.
0000004 允许其他用户读.
0000002 允许其他用户读.
0000001 允许其他用户执行或者搜索.
注意: 这些位与使用UNIX中的stat(2)系统调用返回的结果一致。文件类型在模式位和文件type中都有定义。在以后的版本中将会合并。
属性结构体的“rdev”域是操作系统定义的设备表示,在下一个版本的协议中将被移除。
2.3.6. sattr
struct sattr {
unsigned int mode;unsigned int uid;unsigned int gid;unsigned int size;timeval atime;timeval mtime;};
结构体"sattr" 包含可以被客户端设置的文件属性。各个域的含义与上述“fattr”中一致。如果“size”为零,则表示文件应该被截断。如果为-1,则表示应该被忽略。
2.3.7. filename
typedef string filename<MAXNAMLEN>;
"filename"类型用于传输文件名或者路径名组件。
2.3.8. path
typedef string path<MAXPATHLEN>;
类型“path”是一个路径名。服务端认为其是一个没有内部结构的字符串,但客户端认为其实文件系统树种的一个节点的名称。
2.3.9. attrstat
union attrstat switch (stat status) {
case NFS_OK:fattr attributes;default:void;};
结构体 "attrstat" 是普通过程调用的返回结果。其包含一个“status”,如果调用成功,它还会包含被操作文件的属性信息。
2.3.10. diropargs
struct diropargs {
fhandle dir;filename name;};
结构体"diropargs"用于目录操作。"fhandle" "dir" 是要查找文件“name”的目录。目录操作是对目录内部的操作。
2.3.11. diropres
union diropres switch (stat status) {
case NFS_OK:
struct {fhandle file;fattr attributes;} diropok;default:void;};
目录操作的结果通过"diropres"结构体返回。如果调用成功,与该文件关联的文件句柄“file”和“attibutes”将被返回,同时返回“status”。
3 NFS实现规则
NFS协议设计于可以运行不同的操作系统共享文件。但是,由于其在UNIX环境中设计,很多操作与UNIX文件系统的操作非常相像。本节将讨论一些特定的实现细节和语义问题。
3.1. 服务器/客户端的关系
NFS协议设计认为服务器越简单,越通用越好。如果客户端想实现复杂的文件系统语义的情况下,有时服务器端的简化可能会是问题。
例如,有些操作系统允许删除一些正在打开状态的文件。一个进程可以打开一个文件并删除一个文件,并且该文件处于已经被其它进程打开的状态。只要进程保持该文件的打开状态,一个文件能够一直被读写,即使这个文件在文件系统中已经不存在了。对于一个无状态的服务端,实现这些语义是不可能的。客户端可以通过一些技巧,例如在删除的时候重命名,然后在关闭的时候删除等。我们相信服务端为客户端实现大多数文件系统语义提供了足够的功能。
译者注: 在Linux操作系统下是允许一个文件被多个进程同时打开的,如果其中一个进程删除了该文件,而另外一个进程仍然可以进行该文件的读写。其实现的基本原理就是通过“孤儿”文件实现,也就是在后台将文件移动到一个隐藏的位置。但对于Windows操作系统则有互斥的操作,也就是在一个文件正在被读写的过程中,其它进程是不可以删除该文件的。
每一个NFS客户端都潜在的是一个服务端,并且远程文件系统和本地文件系统可以被任意混用。这将导致一些有趣的问题,当一个客户端沿着一个远程文件系统的目录树遍历时,如果达到该服务器的另外一个远程文件系统。允许服务器执行第二次远程装载需要进行循环检测、服务器查找和用户重新验证。然后,我们决定不允许客户端跨越服务器的挂载点。当一个客户端在一个目录中做LOOKUP 操作,而服务端又在该目录挂载了一个文件系统,服务端看到的是底层目录而非挂载目录。
例如,如果服务端有一个文件系统名为“/usr”,并且在其中又将另外一个文件系统挂载在“/usr/src”。如果客户端挂载“/usr”,它将不会看到“/usr/src”的挂载版本。一个客户端可以做与服务端的装载点匹配的远程挂载,以维护与服务端一致的视图。本例中,客户端也可以将“/usr/src”挂载到“/usr”中,即使它们来自同一个服务器。
3.2. 路径名的解释
通常在客户机上解析路径名的规则有一些复杂之处。例如,符号链接可以在不同的客户机上有不同的解释。非UNIX实现的另一个常见问题是路径名“.”的特殊解释,即给定目录的父目录。协议的下一个修订版使用显式标志来指示父级。
3.3. 权限问题
严格来说,NFS协议没有定义服务器使用的权限检查。但是,预料之中的是服务器将使用AUTH_UNIX风格的身份验证作为其保护机制的基础,执行正常的操作系统权限检查。服务端在每次调用中获取客户端的有效“uid”、有效“gid”和组,并使用它们检查权限。这种方法有各种各样的问题,可以用有趣的方式解决。
使用“uid”和“gid”意味着客户机和服务器共享相同的“uid”列表。每个服务器和客户机对必须具有从用户到“uid”和从组到“gid”的相同映射。由于每个客户机也可以是服务器,这往往意味着整个网络共享相同的“uid/gid”空间。AUTH_DES(以及下一版本的nfs协议)使用字符串名称而不是数字,但仍有一些复杂的问题需要解决。
另一个问题是由于通常是有状态的开放操作。大多数操作系统在打开时检查权限,然后在每次读写请求时检查文件是否打开。对于无状态服务器,服务器不知道文件已打开,必须对每个读写调用执行权限检查。在本地文件系统上,用户可以打开一个文件,然后更改权限,这样就不允许任何人接触它,但仍然可以写入该文件,因为它是打开的。相反,在远程文件系统上,写入将失败。为了解决这个问题,服务器的权限检查算法应该允许文件的所有者访问它,而不管权限设置如何。
类似的问题与通过网络从文件调入有关。操作系统通常在打开文件进行请求分页之前检查执行权限,然后从打开的文件中读取块。该文件可能没有读取权限,但打开后并不重要。NFS服务器无法区分正常文件读取和请求页读取之间的区别。要使此工作正常,如果调用中给定的“uid”对文件具有“执行”或“读取”权限,则服务器允许读取文件。
在大多数操作系统中,一个特定的用户(在Unix上,用户ID为零)可以访问所有文件,不管它们拥有什么权限和所有权。服务器上可能不允许此“超级用户”权限,因为任何可以在其工作站上成为超级用户的人都可以访问所有远程文件。在进行访问检查之前,Unix服务器默认将用户ID 0映射到-2。除了不能避免超级用户访问的nfs根文件系统外,这是可以工作的。
3.4. RPC 信息
认证
NFS服务使用AUTH_UNIX, AUTH_DES或AUTH_SHORT风格的认证,除了在NULL例程中。这时AUTH_NONE是被允许的。
传输协议
NFS通常在UDP协议上。
端口
当前NFS协议使用UDP的2049端口,这个并不是官方认可的端口,因此后续版本的协议将采用端口映射机制。
3.5. XDR结构体的大小
下面是协议中的各种XDR结构体的大小,以10进制字节为单位。
/*
* READ或 WRITE请求的最大字节数
*/
const MAXDATA = 8192;
/* 路径名参数的最大字节数 */
const MAXPATHLEN = 1024;
/* 文件名参数的最大字节数 */
const MAXNAMLEN = 255;
/* 通过 READDIR传递的非透明 "cookie"的最大字节数 */
const COOKIESIZE = 4;
/* 非透明文件句柄的最大字节数 */
const FHSIZE = 32;
3.6. 设置RPC参数
各种文件系统参数和选项应该在挂载的时候设置。挂载协议在下面的附录中描述。例如,“软”挂载和“硬”挂载通常都提供。软挂载文件系统在RPC操作失败时返回错误(在给定数量的可选重新传输之后),而硬挂载文件系统将永远继续重新传输。最大传输大小取决于实现。为了在本地网络上进行有效的操作,通常使用8192字节的数据。这可能导致较低级别的碎片(例如在IP级别)。由于某些网络接口可能不允许这样的数据包在低速网络或主机上运行,或通过网关运行,512或1024字节的传输大小通常会提供更好的结果。
客户机和服务器可能需要保存最近操作的缓存,以帮助避免非等幂操作的问题。例如,如果传输协议丢弃删除文件操作的响应,则在重新传输时,服务器可能返回错误代码NFSERR_NOENT,而不是NFS_OK。但是,如果服务器保留上次请求的操作及其结果,它可以返回正确的成功代码。当然,服务器可能在重新传输之间崩溃并重新启动,但是一个小的缓存(甚至一个条目)可以解决大多数问题。
译者注: 对于删除操作,如果服务端已经完成并回复,但客户端接收响应超时,再次发起删除请求。此时服务端已经没有要删除的文件,因此会返回NFSERR_NOENT。但这种结果在用户层面是不应该的,因为用户时期望删除一个存在的文件,而不关心内部的容错。
索引 A. 挂载协议的定义
A.1. 简介
挂载协议独立于NFS协议,但与之相关。它提供了操作系统特定的服务来让NFS运行起来——查找服务器路径名、验证用户身份和检查访问权限。客户机使用挂载协议获取第一个文件句柄,这允许他们进入远程文件系统。
挂载协议与nfs协议分开,以便在不更改nfs服务器协议的情况下插入新的访问检查和验证方法。
注意,协议定义意味着有状态的服务器,因为服务器维护客户机的装载请求列表。装载列表信息对于客户机或服务器的正确运行不是至关重要的。它仅用于建议用途,例如,在服务器停机时警告可能的客户机。
挂载协议的版本1与nfs协议的版本2一起使用。这两个协议之间唯一通信的信息是“fhandle”结构。
A.2. RPC 信息
认证
挂载服务仅仅使用AUTH_UNIX和 AUTH_NONE风格的认证。
传输协议
挂载服务支持UPD和TCP协议。
端口
请参考服务器端口映射,在RFC 1057中有详细的描述, "RPC: Remote
Procedure Call Protocol Specification"。
A.3. XDR结构的大小
下面是协议中的各种XDR结构体的大小,以10进制字节为单位。
/* The maximum number of bytes in a pathname argument. */
const MNTPATHLEN = 1024;
/* The maximum number of bytes in a name argument. */
const MNTNAMLEN = 255;
/* The size in bytes of the opaque file handle. */
const FHSIZE = 32;
A.4. 基本数据类型
本节描述挂载协议中使用的数据类型。在大多数情况下,这些数据类型与NFS协议类似。
A.4.1. fhandle
typedef opaque fhandle[FHSIZE];
"fhandle"是在服务器端与客户端传递的文件句柄。所有文件操作通过文件句柄来关联一个文件或者目录。文件句柄包含这能够区分一个独立文件的关键信息。
这个定义与NFS第二版本中的XDR“fhandle”定义一致,具体请参考"基本数据类型"下的"2.3.3. fhandle"。
A.4.2. fhstatus
union fhstatus switch (unsigned status) {
case 0:
fhandle directory;
default:
void;
}
“fhstatus”是一个枚举类型。如果返回的“status”是0, 则表示调用成功完成,同时会返回一个 文件句柄"directory"。一个非0值表示某种错误。这种情况下,status是一个UNIX错误码。
A.4.3. dirpath
typedef string dirpath<MNTPATHLEN>;
"dirpath" 类型是一个目录路径名。
A.4.4. name
typedef string name<MNTNAMLEN>;
"name"是一个用于做各种名称的任意字符串。
A.5. Server Procedures
如下定义了挂载服务使用的各种RPC例程。
/*
* Protocol description for the mount program
*/
program MOUNTPROG {
/*
* Version 1 of the mount protocol used with
* version 2 of the NFS protocol.
*/
version MOUNTVERS {
voidMOUNTPROC_NULL(void) = 0;fhstatusMOUNTPROC_MNT(dirpath) = 1;mountlistMOUNTPROC_DUMP(void) = 2;voidMOUNTPROC_UMNT(dirpath) = 3;voidMOUNTPROC_UMNTALL(void) = 4;exportlistMOUNTPROC_EXPORT(void) = 5;} = 1;} = 100005;
A.5.1. 不做任何事情
void
MNTPROC_NULL(void) = 0;
该例程不做任何事情。在任何RPC服务中都可以使用,用于测试和计时。
A.5.2. 添加挂载项
fhstatus
MNTPROC_MNT(dirpath) = 1;
如果返回的“status”为0,则 "directory"中包含目录“dirname”的文件句柄。该句柄也可能用在NFS协议中。该例程将在客户端的"dirname"上添加一个新项。
A.5.3. 返回挂载项
struct *mountlist {
name hostname;dirpath directory;mountlist nextentry;};
mountlist
MNTPROC_DUMP(void) = 2;
返回远程已挂载文件系统的列表。"mountlist"包含"hostname"和 "directory"对。
A.5.4. 移除挂载点
void
MNTPROC_UMNT(dirpath) = 3;
移除"dirpath"指定的所有挂载项。
A.5.5. 移除所有挂载项
void
MNTPROC_UMNTALL(void) = 4;
移除本客户端的所有挂载项。
A.5.6. 返回导出列表
struct *groups {
name grname;groups grnext;};
struct *exportlist {
dirpath filesys;groups groups;exportlist next;};
exportlist
MNTPROC_EXPORT(void) = 5;
返回一定数量的导出列表项。每一项包含文件系统名称和允许导入的组列表。文件系统名称在“filesys”中,组名称在“groups”列表中。
注意: 导出列表应该包含文件系统状态的更多信息,例如只读旗标等。
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。