Chinaunix首页 | 论坛 | 博客
  • 博客访问: 13333
  • 博文数量: 1
  • 博客积分: 61
  • 博客等级: 民兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-25 17:28
文章分类

全部博文(1)

文章存档

2012年(1)

我的朋友
最近访客

分类: LINUX

2012-02-25 17:31:05

本文翻译自同名论文,论文地址为:


论文作者为:
Russel Sandberg
David Goldberg
Steve Kleiman
Dan Walsh
Bob Lyon

1 介绍

Sun网络文件系统(NFS)提供透明的远程文件系统访问。与很多其他UNIX下实现的网络文件系统不同,NFS被设计为可非常方便地被移植到其他操作系统和机器架构。它使用XDR标准(External Data Representation),通过一种极其和系统无关的方式,描述NFS内部的通信协议。同时它使用了RPC(Remote Procedure Call)来简化协议的定义、实现和维护。

为了使用一种对用户透明的方式将NFS加入UNIX 4.2内核,我们决定在内核中加入一个新的接口,以便将通用的文件系统操作和特定的文件系统实现分隔开。这个“文件系统接口”包含两个部分:虚拟文件系统(VFS)接口定义了可以对一个文件系统执行的操作,而虚拟节点(VNODE)接口定义了可以对一个文件系统中的文件执行的操作。这个新的接口让我们可以像为内核增加新设备驱动一样实现和安装新的文件系统。

这篇论文中我们将会讨论内核文件系统接口的设计和实现,以及NFS虚拟文件系统的设计和实现。我们将描述一些有趣的设计要点以及它们是怎样被解决的,并且提出当前实现的缺点。最后我们将会给出未来系统优化的一些方向和思路。

2 设计目标

NFS被设计为使在一个由不同机器组建成的网络环境中共享文件系统资源这个任务更加简单。我们的目标是在不对现有的程序进行修改甚至重编译的情况下,使远程文件系统的文件可以通过类UNIX文件操作接口的方式在本地访问。同时,我们希望远程文件的访问速度能够与本地文件的访问速度具有可比性。

NFS整体的设计目标如下:

机器和操作系统无关性
使用的协议必须与UNIX系统无关,这样NFS Server就可以为各种不同类型的客户端提供文件访问服务。协议也应该足够简单,以便可以在PC这样的低端机器上实现。
故障恢复
当客户端挂载(mount)不同服务器上的远程文件系统时,客户端能够非常方便地从服务器故障中恢复是非常重要的。
透明访问
我们希望NFS让应用程序使用和访问本地文件一样的方式访问远程文件,而不需要进行额外的路径传递、特殊函数库链接或者重新编译。应用程序应该无法感知到当前使用的文件是在本地或是在远程文件系统上。
保持客户端UNIX操作语义
需要对远程文件实现和本地文件相同的UNIX文件操作语义。
合理的性能
如果NFS的性能不能比rcp等网络工具更快,即使它更易于使用,用户也不会考虑使用。我们的设计目标是让NFS的性能与Sun网络磁盘协议(Sun Network Disk protocol)一样快,或者相当于本地磁盘80%的性能。

3 基本设计

NFS的设计由三个主要部分组成:协议设计、服务器设计和客户端设计。

协议设计

NFS协议使用Sun远程过程调用(RPC)机制实现。正如函数调用让应用程序简化一样,RPC简化了远程服务的定义、组织和实现。NFS协议被定义为一系列的过程,过程的参数和返回值,以及过程的功能。远程过程调用是同步的,也就是说客户端会阻塞,直到服务器完成调用功能并且返回结果。这使得RPC非常容易使用,因为它与本地函数调用的使用方式一样。

NFS使用无状态的协议。各个过程调用的参数包含了所有完成本次调用所需要的信息,服务器不维护任何过去的请求状态。这样的设计使得故障恢复非常简单:当服务器崩溃时,客户端重发NFS请求,直到请求响应为止,服务器不需要做任何的故障恢复工作。客户端崩溃时,客户端和服务器都不需要做任何的恢复工作。如果服务器端保留了状态,恢复会变得更加困难:客户端和服务器都需要可靠地检测故障。服务器需要检测客户端的故障以便它可以丢弃故障客户端的任何状态;客户端需要检测服务器的故障,以便它可以重建服务器的状态。

使用无状态的协议避免了复杂的故障恢复,简化了协议。如果客户端只需要重发请求直到获得响应,不会因为服务器故障而出现数据丢失。实际上客户端无法区分一个从故障中恢复的服务器,和一个响应迟缓的服务器。

Sun的远程过程调用被设计为传输无关的。新的传输协议可以在不影响上层协议代码的基础上被“接入”RPC实现中。NFS使用UDP和IP协议作为传输层。因为UDP是一个不可靠的数据报协议,数据包有可能在传输过程中丢失,但是因为NFS协议是无状态的,并且NFS协议是幂等的,客户端可以通过不断重试来完成请求。

NFS过程调用中最常使用的参数是一个称为“文件句柄”(fhandle或者fh)的结构体,它由服务器提供并且由客户端使用来引用一个远程文件。fhandle对客户端是一个黑盒子,客户端不会查看这个结构体中的数据,而只会在文件操作完成的时候使用。

NFS协议过程的概要在下面给出。完整的规范请参见Sun Network Filsystem Protocol。

null() 返回()
不执行任何操作,有两个作用:相当于到服务器的ping,以及检测客户端和服务器间的RTT(Round Trip Time)。
lookup(dirfh, name) 返回(fh, attr)
返回目录中一个指定文件的fhandle和属性信息。
create(dirfh, name, attr) 返回(newfh, attr)
创建一个新文件并且返回它的fhandle和属性信息。
remove(dirfh, name) 返回(status)
从指定目录中删除文件。
getattr(fh) 返回(attr)
返回文件属性信息。这个调用类似于一个stat调用。
setattr(fh, attr) 返回(attr)
设置一个文件的mode,uid,gid,size,access time,modify time属性。将文件大小设置为0相当于对文件调用truncate。
read(fh, offset, count) 返回(attr, data)
从文件的offset偏移处开始,返回最多count个字节的数据。read操作也返回文件的属性信息。
write(fh, fh, offset, count, data) 返回(attr)
向文件offset偏移处开始,写入count字节的数据,返回写操作完成后的文件属性信息。
rename(dirfh, name, tofh, toname) 返回(status)
将dirfh目录中名为name的文件,重命名为tofh目录中名为toname的文件。
link(dirfh, name, tofh, toname) 返回(status)
在tofh目录中创建名为toname的链接,指向dirfh目录中的name文件。
symlink(dirfh, name, string) 返回(status)
在dirfh目录中创建一个名为name的符号链接。服务器不解释string的具体内容,而只是简单将其保存起来并且与符号链接文件相关联。
mkdir(dirfh, name, attr) 返回(fh, newattr)
在dirfh目录中创建名为name的目录,并返回其fhandle和属性信息。
rmdir(dirfh, name) 返回(status)
从dirfh中删除名为name的空目录。
readdir(dirfh, cookie, count) 返回(entries)
从dirfh目录返回组多count字节的目录项信息。每个目录项信息包含了一个文件名,文件id,和一个由服务器解释的指向下一个目录项的指针cookie。cookie的作用是在后续readdir操作中从一个指定的位置返回目录项信息。cookie为0的readdir调用从目录的第一个目录项开始返回。
statfs(fh) 返回(fsstats)
返回块大小、空闲块数等文件系统的信息。

lookup、create和mkdir这三个返回新fhandle的调用,都需要传入一个fhandle作为参数。远端文件系统的根目录的fhandle,是通过另外一个调用来获得的。MOUNT调用传入一个目录路径名,返回该目录的fhandle。该调用成功返回的前提是,客户端有该目录所在文件系统的访问权限。引入这个独立的调用的原因是,这样做更容易引入新的文件系统访问权限检查机制,同时分离了协议中与操作系统相关的部分。注意MOUNT调用是唯一一个向服务器传送UNIX文件路径名的地方。在其他操作系统中,MOUNT调用可以在不改变NFS协议的前提下替换。

NFS协议和RPC是在XDR构建的。XDR定义了string、integer、union、boolean和array等基本数据类型的大小、字节顺序和对齐方法。复杂的结构体可以由基本数据类型组成。使用XDR不但实现了协议的机器和语言的无关性,也使协议易于定义。RPC调用的参数和返回值是使用XDR数据定义语言定义的,看起来与C语言的声明非常相似。


服务器设计

上面已经提到NFS的服务器是无状态的,当处理一个NFS请求时,所有已经修改的数据必须在结果返回之前提交到存储设备上。对于部署在UNIX系统上的NFS服务器来说,修改文件系统的请求必须在将所有已修改数据提交到磁盘之后才能返回。以write请求为例,这意味着,不单已修改的数据块,任何已修改的间接块和包含inode信息的块都需要提交到存储设备上。

另一个需要引入NFS服务器端UNIX系统的修改是,为inode增加一个版本号,为superblock增加一个文件系统id。这些额外的数值使NFS服务器可以将inode号、inode版本号和文件系统id作为一个文件的fhandle。inode版本号是必要的,因为NFS服务器有可能已经分发了一个fhandle,而这个fhandle中inode号对应的文件后面被删除了,并且被分配给了一个新的文件时,当旧的fhandle在请求中传入时,NFS服务器必须能够根据inode版本号确定这个inode号现在引用了另外一个文件。inode版本号必须在每次inode被释放时递增。

客户端设计

客户端提供了到NFS的透明接口。为了实现对远程文件的透明访问,必须在不改变路径名的前提下定位远端文件。一些基于UNIX实现的远端文件访问方案使用host:path的形式命名远端文件。这种方法无法实现真正的透明访问,因为现有应用程序的路径解析逻辑需要进行修改。

NFS并没有采用文件地址的后期绑定(late binding)方案,而是使用目录挂载的机制,使NFS服务器主机名查找和文件地址绑定操作仅在将远端文件系统挂载到本地目录时执行一次。这种方案的好处在于,客户端只需要在挂载操作执行时才需要处理NFS服务器的主机名。这种机制也使NFS服务器可以通过检查客户端的证书信息来限制对文件系统的访问。这种方法的缺点是,文件系统在挂载之前,客户端无法访问该远端文件系统。

对单台机器中不同的已挂载文件系统的透明访问,是通过新的文件系统接口实现的。每种“文件系统类型”支持两个集合的操作:虚拟文件系统(VFS)接口定义了对整个文件系统进行操作的函数,而虚拟节点(VNode)接口定义了对文件系统上的单个文件进行操作的函数。图1展示了文件系统接口的概要图以及NFS如何使用它。


图1


文件系统接口设计

VFS接口是通过一个包含对整个文件系统操作的接口函数集合的结构体来实现的。同样的,VNode接口是一个包含对单个文件操作的接口函数集合的结构体。内核中每个已挂载的文件系统都对应一个VFS结构,每个活跃的文件或目录节点都对应一个VNode结构。使用这样的抽象数据类型实现使kernel可以用相同的方式对不同文件系统
及其上的文件和目录进行操作。

每个VNode包含了一个指向其父VFS的指针和一个指向其挂载文件系统的VFS指针。这意味着一个文件系统树中所有的目录节点都可以作为另一个文件系统的挂载点。VFS的root操作返回一个已挂载文件系统的根VNode。这个操作被内核的路径解析流程用于桥接各个挂载点,它使用root操作来获取一个已挂载文件系统的根节点,而不是仅仅是记录一个指针。已挂载文件系统的VFS也包含了一个指向其挂载点所在VNode的反向指针,以便包含“..”的路径名可以跨越挂载点进行解析。

除了VFS和VNode操作外,每个文件系统类型必须提供mount和mount_root操作,实现挂载一般和根文件系统的功能。文件系统接口定义的操作如下所示:

文件系统操作
mount(varies)                                挂载文件系统的系统调用
mount_root()                                 将文件系统挂载为根文件系统
VFS操作
unmount(vfs)                                 文件系统卸载
root(vfs) 返回 (vnode)                       返回文件系统根VNode
statfs(vfs) 返回(fsstatbuf)                  返回文件系统统计信息
sync(vfs)                                    将延迟写的块提交到存储介质
VNode操作
open(vnode, flags)                           标记一个文件打开了
close(vnode, flags)                          标记一个文件关闭了
rdwr(vnode, uio, rwflags, flags)             读或者写一个文件
ioctl(vnode, cmd, data, rwflag)              执行IO控制操作
select(vnode, rwflag)                        执行select操作
getattr(vnode) 返回(attr)                    返回文件属性信息
access(vnode, attr)                          设置文件属性信息
lookup(dvnode, name) 返回(vnode)             在目录中查找文件名
create(dvnode, name, attr, excl, mode) 返回(vnode)  创建一个文件
remove(dvnode, name)                         删除目录中的文件
link(vnode, todvnode, toname)                创建到文件的链接
rename(dvnode, name, todvnode, toname)       重命名文件
rmdir(dvnode, name)                          删除一个目录
readdir(dvnode) 返回(entries)                读取目录项
symlink(dvnode, name, attr, to_name)         创建一个符号链接
readlink(vp) 返回(data)                      读取符号链接的数据
fsync(vnode)                                 提交文件的脏块
inactive(vnode)                              标记vnode非活跃并执行清理
bmap(vnoe, blk) 返回(devnode, mappedblk)     映射块号
strategy(bp)                                 读写文件系统块
bread(vnode, blockno) 返回(buf)              读一个块
brelse(vnode, buf)                           释放一个块buffer

注意很多vnode的函数与NFS的协议一一对应。其他如open、close和ioctl则不是。bmap、strategy、bread和brelse函数用来执行块缓存的读和写操作。

内核中的路径遍历操作是通过将路径拆分成各个目录分量,并在各个分量对应的vnode上执行lookup操作完成的。咋一看每次只处理一个目录分量比传入整个路径并返回目标vnode更耗费时间。这样做的主要原因是任何一个目录分量都可以是另一个文件系统的挂载点,并且挂载信息保存在vnode中。在NFS文件系统中,传递整个路径要求服务器必须记录每个客户端的挂载点,以便确定在哪里断开路径,这破坏了服务器的无状态特性。每次查找一个分量的低效性,可以通过引入目录vnode缓存来缓解。

 
4 实现
 
NFS的实现工作开始于1984年。实现的第一步工作是修改4.2内核,加入新的文件系统接口。到六月份我们已经让第一个版本的VNode内核运行起来。我们对这个额外的接口带来的开销做了基准测试,结果表明在大部分情况下新旧实现的差别是微乎其微的,在最坏的情况下内核因为引入了新接口而变慢了大概2%。为内核增加新接口的大部分工作在于查找和修复内核中直接使用inode的地方,以及隐含使用inode结构及磁盘布局的代码。
 
内核代码中只有文件系统的一些流程需要重写以使用vnode。namei这个历程实现路径的查找功能,我们将其改成使用vnode的lookup方法,并且对其原来使用全局状态的代码进行了清理。direnter这个例程实现新增目录项的功能(例如,由create,rename使用),使用了由namei设置的全局状态,所以也需要进行修改。direnter中对目录重命名时进行目录锁定的操作也需要进行修改,因为inode锁定已经不在这一层实现,vnode不会被锁定。
 
为了避免对活跃的vnode和VFS结构体的数量设置上限,我们在内核中引入了一个内存分配器,以便这两个结构体以及其他的结构体可以动态地分配和释放。
 
一个新的系统调用getdirentries,被加入内核,实现从不同的文件系统读取目录项。4.2版本系统的readdir库函数进行了修改,使用这个新的系统调用,因此应用程序不需要重写。但是这个修改意味着,原来使用了readdir的应用程序需要重新链接。
 
从3月份开始,用户态的RPC和XDR库被移植到了内核。6月份,我们已经实现了内核到用户态和内核到内核的RPC调用。我们在RPC的性能调优上花了差不多一个月的时间,使内核到内核的null RPC调用的RTT达到8ms。性能调优的过程中,我们对内核的UDP和IP代码进行了几个性能提升优化。
 
当内核的RPC和vnode功能实现后,NFS的实现工作就剩下写XDR函数实现NFS协议、在内核中实现NFS服务器和实现一个将vnode操作转换为NFS远程过程调用的文件系统接口。NFS内核在8月中旬第一次成功运行。这是我们对vnode接口做了一些修改,允许NFS服务器执行同步写操作。这是必要的,因为未提交到存储介质的数据缓存是“用户状态”的一部分。
 
MOUNT协议首先是通过NFS协议实现的,后来才将它独立出来,实现为一个用户态的RPC服务。MOUNT服务器是一个用户态的守护进程,当一个mount请求被处理时,这个进程自动启动。该进程会检查文件/etc/exports,文件中包含了导出的文件系统列表和可以导入这些文件系统的客户端的列表。如果客户端有导入权限,mount守护进程执行getfh系统调用,将路径转换为fhandle,返回给客户端。
 
在客户端,mount命令被修改为可接受文件系统类型和挂载选项字符串等额外的参数。文件系统类型使mount命令可以挂载任何类型的文件系统。挂载选项字符串用于给不同的文件系统传递可选标志。例如,NFS允许两种不同的mount方式,soft mount和hard mount。一个hard mount的文件系统会在服务器宕机时不停地重试,而一个soft mount的文件系统会在检测到服务器宕机之后一段时间放弃重试并且返回错误。soft mount的问题在于大多数UNIX程序通常不会非常好地处理系统调用的返回值,因此在服务器宕机时经常会遇到一些奇怪的现象。另一方面,一个hard mount的文件系统永远不会因为服务器宕机而失败,它可能导致进程挂起一段时间,但是数据不会丢失。
 
除了mount服务器,我们还实现了NFS服务器守护进程。这些守护进程是用户态的进程,负责向内核发起nfsd系统调用,并且永远不会返回。这为内核的NFS服务器提供了一个用户态的上下文,以便服务器可以执行sleep操作。类似的,客户端的block I/O守护进程,是一个用户态的进程,驻留在内核中处理异步块IO请求。因为RPC请求是阻塞的,我们需要一个用户态的上下文来等待read-ahead和write-behind请求完成。这些守护进程为在内核中处理并行同步请求提供了一个临时的解决方案,未来我们希望使用内核的轻量级进程机制处理这些请求。
 
NFS开发组在9月份开始使用NFS,并在后续的6个月中对其性能进行优化提升,开发管理工具集,让NFS更易于安装和使用。NFS的优势在这时马上体现出来了,从下图的df输出可以看到,一个无盘工作站可以访问大于1GB的磁盘!

 
 
5 系统实现难点
 
在NFS的开发过程中,有多个设计上的难点被解决了。其中最困难的一个是决定要如何使用NFS。过多的灵活性会导致过多的混乱。
 
根文件系统
 
当前的NFS实现不允许将共享的NFS目录作为根文件系统使用。这里有多个与共享的根文件系统相关的问题,我们暂时没有时间去解决。例如,众所周知,机器相关的文件存储在根文件系统中,太多的程序使用这些文件。共享一个根文件系统意味着共享/tmp和/dev目录。共享/tmp目录会存在问题,因为应用程序使用他们的进程id来创建临时文件,而进程id在两台机器间不是唯一的。共享/dev目录需要一个远程设备访问系统。我们正在考虑通过使对设备节点的操作限制在本地,来允许对/dev的共享访问。这个简单解决方案的问题是,很多应用程序用特殊的方式使用了设备节点的所有者和权限信息。
 
因为每个客户端的根文件系统都使用了私有存储(真实的磁盘或者ND),我们可以将机器专用的文件从共享文件系统中移动到一个叫/private的目录,并创建指向这个目录文件的符号链接。/usr/lib/crontab和/usr/adm这样的文件和目录需要移动到/private目录。这使得客户端可以仅使用本地的/etc和/bin目录,而/usr和其他文件系统可以远程挂载。
 
文件系统命名
 
NFS服务器导出了整个文件系统,但是客户端可以将远程文件系统的任何一个子目录挂载在本地文件系统之上或者另一个远程文件系统之上。实际上,一个远程文件系统可以被挂载多次,甚至可以被挂载在自身的另外一个拷贝之上!这意味着客户端可以通过将文件系统挂载在不同位置为文件系统起不同的名字。为了减少混淆我们在每台机器上使用了一系列预先挂载的基本文件系统,让用户在其上增加其他的文件系统。当然这只是一种外部策略,NFS中并没有机制强制这样做。用户的home目录被挂载在/usr/servername。这好像和我们的目标有冲突,因为服务器的主机名现在是路径的一部分,但是实际上这些目录可能已经被命名为/usr/1,/usr/2之类的名字。使用服务器主机名只是为了简便。
 
凭证、认证和安全
 
 
并发访问和文件锁定
 
 
UNIX文件Open语义
 
 
系统时间差
 
 
性能
 
 
下一步计划
 
 
6 结论
 
 
7 感谢
 
 
8 引用
阅读(3010) | 评论(1) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~

guzheng23313142012-03-20 17:07:50

期待下文~~