前面文章我们从应用层面对NFS进行了介绍,接下来的文章我们将进入实现层面。本文首先从整体上对Linux的NFS软件架构进行介绍,然后介绍代码与实际业务逻辑介绍一下NFS的处理流程。
NFS文件系统的架构分析
NFS分布式文件系统是一个客户端-服务端架构(CS架构)。其客户端是Linux内核中的一个文件系统,跟Ext4和XFS类似,它是虚拟文件系统下的一个具体实现。与其它本地文件系统(例如Ext4,XFS或者Btrfs等)的差异在于其数据请求不存储在本地磁盘,而是通过网络发送到服务端进行处理。
如图1是是NFS的整体软件架构,其中左侧是客户端,右侧是服务端。客户端我们称为NFS文件系统,其位于VFS之下,再之下是RPC模块,两者都位于Linux内核之中。
在服务端的NFS主要是指NFSD,它是一个NFS服务。该服务用于接收服务端的请求,处理后通过RPC将处理结果反馈给客户端。而服务端最终还是要将数据存储起来的,在Linux中的NFS服务还是借助的本地文件系统来存储的。因此,在NFS服务端,其数据相关的业务逻辑也会调用到VFS的接口,然后是经过本地文件系统存储在持久化存储上(如磁盘)。
NFS的通信使用的是RPC协议,该协议也是Sun发明的一种网络通信协议。RPC协议位于TCP/IP协议之上,是一个应用层的协议,可以类比http协议。RPC协议进行通信的流程大致如图2所示。
RPC协议称为远程过程调用,类似本地函数调用。因此,RPC首先是在客户端和服务端都要注册处理函数,这些被注册的函数称为存根。这样,当客户端调用某个函数时,比如写数据,RPC服务就会将该请求通过网络传到服务端,然后调用服务端注册的写数据的接口。也就是客户端与服务端是一一对应的。这样在客户端来说,其函数的调用与本地函数调用并没有太大的差异。
Linux NFS代码解析
为了更加清晰的理解NFS的架构,我们以写数据为例来介绍一下NFS文件系统与服务端通信的过程。由于NFS有很多版本,且Linux内核中对所有版本都有实现。为了便于介绍和学习,我们以NFS v3为例进行介绍。
由于NFS基于VFS,因此不可避免的需要实现一套函数指针,并在挂载的时候进行注册。这主要是保证从VFS下来的请求可以转发到NFS文件系统进行处理。如图5是NFS文件系统实现的函数指针集合。
以写数据为例(客户端无缓存,非DIRECT模式),其具体实现的函数为nfs_file_write。当用户通过程序调用write函数时,经过VFS的vfs_write函数,最后调用NFS的nfs_file_write函数。该函数与本地文件系统的逻辑类型,它首先将数据写入缓存中(非DIRECT模式),然后调用generic_write_sync实现缓存数据刷写。
在客户端的具体的写动作由nfs_writepages函数完成,该函数进行若干初始化动作,然后通过RPC将数据发送到服务端。对比本地文件系统,本地文件系统是通过该函数将数据写入磁盘。
服务端向RPC注册了各种回调函数,当接收到客户端的请求时会调用具体的回调函数进行处理。本例将调用nfsd3_proc_write函数。该函数最后调用VFS层的写数据函数,而VFS写数据函数则调用具体文件系统(例如Ext4)的函数完成最终的写数据操作。
NFS分布式文件系统的整个架构和逻辑还是比较清晰的,主要是内核中同时支持了NFSv2、NFSv3和NFSv4多个版本,整体比较复杂,但难度并不是非常大。
本文从比较高的层面介绍了NFS的整体架构和RPC的流程,很多细节没有深入。如果大家有疑惑的地方也没有关系,本专栏后面会逐步对客户端和服务端的软件架构分别进行介绍。