NFS协议最为权威的参考文档自然是RFC文档了。以NFSv3为例,对应的RFC文档是RFC1813。但是当我们初次接触该文档的时候有种找不到北的感觉。以NFSv3的WRITE例程为例,在文档中的说明如下图所示,从该图中似乎看不太明白协议的具体定义。
图1 NFS 写协议描述
其实要理解上述内容并不困难,首先是要结合前面文章介绍的RPC的内容来理解,这样再理解NFS协议的内容就不会太难了。
在RPC协议中我们了解到,RPC实现了一种远程函数调用的机制。也就是客户端在调用基于RPC的函数(存根)时,该函数会被封装成网络消息发送到服务端,并且激活一个服务端的函数(存根)。并且,通常客户端的存根与服务端的存根是一一对应的。
图2 存根函数
RPC实现了函数的远程调用,但是并没有定义函数的参数。而函数的参数则是在NFS中定义的(其实函数ID也是在NFS定义的)。对于图1中的描述,WRITE3args就是写操作的参数,WRITE3resok就是返回值。而NFSPROC2_WRITE=7则是写操作的ID,也就是存根ID。
其实通过NFS协议中的定义也可以看出端倪,如果我们简化一下,其实就是一个函数原型。
WRITE3res= NFSPROC2_WRITE(WRITE3args)
在NFS协议中定义了一组操作,这些操作与文件系统的操作基本上一一对应。比如读数据,写数据,创建文件,查找文件和删除文件等。这样,用户的一个文件操作,基本上就可以从主机端传到存储端来处理。如下图是NFSv3协议的一个子集。
图3 NFS协议子集
针对NFSv3定义的函数集,在内核NFS文件系统中都有对应的实现,如下代码是Linux内核NFS的代码。通过实现代码可以看到内核对NFS协议的实现。
图4 NFS在内核中的实现
介绍到这里,大家应该对NFS协议有了一个感性的认识了。为了让大家更加深切的理解NFS协议以及与RPC协议之间的关系。我们可以通过WireShark抓一下NFS的数据包。以写数据为例,抓获的数据包如下所示。
WireShark是可以识别RPC和NFS协议的。如图所示,我们展开NFS协议的内容,如红色方框和绿色方块所示。其中红色方框中的内容在RPC协议中编码,而绿色方框的内容则在RPC外编码,这些内容其实就是函数的参数,如文件句柄,请求偏移,长度和模式等等,以及要写入的数据。
内核中NFS写数据的流程稍微有点复杂,我们后面会专门介绍。本文我们挑选一个比较简单的函数来介绍,比如创建文件。创建文件的NFS定义如下所示。
我们可以通过WireShark捕获创建文件过程的数据包,从结果可以看到,创建文件包括where(父目录和新文件名),模式和认证信息。这个与协议的定义是完全一致的。
然后我们再结合看一下主机端的代码实现。对于创建文件,其代码实现为nfs3_proc_create。我们看一下该函数对参数的初始化部分。可以看出,这里主要初始化RPC需要的参数,而且参数内容与协议一致(这句话其实是废话)。
最后,该函数会调用nfs3_do_create->rpc_call_sync,rpc_call_sync是RPC的API,前面初始化的参数数据会传给该API,然后RPC会封装为数据包(我们在WireShark抓获的样子),然后发送到服务端。
本文从整体上介绍了一下NFS协议,以及NFS与RPC协议的关系。后面我们将结合更多的实例来介绍NFS协议与代码实现。
领取专属 10元无门槛券
私享最新 技术干货