这篇文章接着讲解nfs_readpages()的流程。上篇文章中,我们讲到了read_cache_pages()将缓存页添加到文件缓存的radix树中,为每个缓存页创建了一个nfs_page结构,然后将这些缓存页链接到nfs_pageio_descriptor结构中。nfs_pageio_descriptor中包含了多个缓存页以及向服务器请求数据的函数集合。接下来就是调用nfs_pageio_descriptor中的函数向服务器请求数据了,这是通过函数nfs_pageio_complete()实现的,这个函数最终调用的是nfs_pageio_descriptor结构中pg_ops字段中的函数pg_doio(),读操作中,这个函数是nfs_generic_pg_readpages()。
static int nfs_generic_pg_readpages(struct nfs_pageio_descriptor *desc)
{
struct nfs_read_header *rhdr;
struct nfs_pgio_header *hdr;
int ret;
// 步骤1 创建一个nfs_read_header结构
rhdr = nfs_readhdr_alloc();
if (!rhdr) { // 创建rhdr失败了,不能处理desc中的缓存页了,需要释放这些缓存页
desc->pg_completion_ops->error_cleanup(&desc->pg_list);
return -ENOMEM;
}
// 步骤2 初始化hdr
hdr = &rhdr->header; // 找到nfs_pgio_header结构
// 根据desc结构中的信息初始化hdr,向hdr中添加了nfs_page结构,
// 设置了hdr->completion_ops、inode、io_start、good_bytes
// nfs_readhdr_free用来释放nfs_read_header结构占用的内存
nfs_pgheader_init(desc, hdr, nfs_readhdr_free);
atomic_inc(&hdr->refcnt); // 增加这个结构的使用计数
// 步骤3 将desc中的缓存页转移到hdr中,设置READ请求的参数和返回值
ret = nfs_generic_pagein(desc, hdr);
if (ret == 0)
// 步骤4 发起READ请求,向服务器请求数据。
ret = nfs_do_multiple_reads(&hdr->rpc_list,
desc->pg_rpc_callops);
// 步骤5 READ请求执行完毕,进行一些收尾工作
if (atomic_dec_and_test(&hdr->refcnt))
hdr->completion_ops->completion(hdr);
return ret;
}
这个程序代码虽然不长,但是内容比较多,一篇文章不一定可以讲清楚。先讲几个数据结构。前面提到过nfs_pageio_descriptor结构中包含了多个缓存页,客户端向服务器发送READ请求,用服务器端返回的数据填充这些缓存页。如果nfs_pageio_descriptor结构中的数据比较多,超出了RPC的限制,则需要发起多个READ请求,每个READ请求用数据结构nfs_read_data表示。
// 这是READ请求中使用的数据结构,每个nfs_read_data结构对应一个READ请求。
struct nfs_read_data {
// nfs_pgio_header中保存了READ请求中的通用信息,多个相关的READ请求可以共享同一个nfs_pgio_header结构.
struct nfs_pgio_header *header;
// 一个nfs_pgio_header结构中包含了多个nfs_read_data结构,每个nfs_read_data结构代表一个READ请求,
// 这些nfs_read_data构成了一个链表,list指向了链表中相邻的元素.
struct list_head list; // 作为指针链接到nfs_pgio_header结构中
struct rpc_task task; // 这是READ请求关联的RPC任务
struct nfs_fattr fattr; /* fattr storage */ // 这里保存了文件的属性信息
struct nfs_readargs args; // 这是READ请求的参数
struct nfs_readres res; // 这是READ请求的返回值
// 这是一个时间戳,记录了READ请求的发起时间.
unsigned long timestamp; /* For lease renewal */
// 这是READ请求完毕后调用的一个函数
int (*read_done_cb) (struct rpc_task *task, struct nfs_read_data *data);
// 这是当读操作出错时使用的一个字段,如果从服务器获取的数据量小于请求的数据量,
// 则设置这个值,再次发起READ请求,获取上次失败的数据。
__u64 mds_offset;
// 这里是缓存页链表指针,从服务器获取的数据填充这些缓存页。
struct nfs_page_array pages; //这里保存了一些缓存页面指针,这些缓存页面指向了nfs_pgio_header结构中的pages
// 这是pNFS中和DS通信的客户端
struct nfs_client *ds_clp; /* pNFS data server */
};
这些READ请求处理的是同一个nfs_pageio_descriptor结构中的数据,具有一些相同的信息,这些相同的信息用数据结构nfs_pgio_header表示,nfs_pageio_descriptor中所有的READ请求共享一个nfs_pgio_header结构中的信息。
// 这是一个I/O操作头的数据结构
struct nfs_pgio_header {
struct inode *inode; // 文件索引节点,请求这个文件中的数据
struct rpc_cred *cred; // 用户信息
// 这是一个链表,链表中的数据结构是nfs_page,这是从nfs_pageio_descriptor结构中转移过来的.
struct list_head pages; // 这里存放的应该是nfs_page结构
// 这是一个链表,链表中存放的是nfs_read_data,每个nfs_read_data表示一个READ请求
struct list_head rpc_list;
atomic_t refcnt; // 这个结构的引用计数
// 这是链表pages中第一个缓存页的指针
struct nfs_page *req; // nfs_page结构,这个结构对应一个缓存页
// 多个WRITE请求中也可以共享一个nfs_pgio_header结构,verf是WRITE请求中使用的一个字段
struct nfs_writeverf *verf;
// 这是文件的layout信息,供pNFS使用
struct pnfs_layout_segment *lseg;
// 这是请求的数据在文件中的起始位置
loff_t io_start;
// 这是RPC操作中使用的函数
const struct rpc_call_ops *mds_ops;
// 这是一个错误处理函数,出错后调用这个函数释放nfs_pgio_header占用的内存
void (*release) (struct nfs_pgio_header *hdr);
// 这是READ请求完毕后执行的一个函数
const struct nfs_pgio_completion_ops *completion_ops;
// 这是直接IO相关的数据结构
struct nfs_direct_req *dreq;
spinlock_t lock; // 保护这个数据结构的自旋锁
/* fields protected by lock */
int pnfs_error; // pNFS中的错误码
int error; /* merge with pnfs_error */
// 实际读取的数据量
unsigned long good_bytes; /* boundary of good data */
unsigned long flags; // 一些标志位
};
如果nfs_pageio_descriptor结构中的数据比较少,只需要一个READ请求,那么就可以使用数据结构nfs_read_header。
struct nfs_read_header {
struct nfs_pgio_header header; // 这是READ操作的通用信息
struct nfs_read_data rpc_data; // 这是一次READ请求的信息
};
如果nfs_pageio_descriptor结构中的数据比较多,需要发起多次READ请求,那么就可以创建一个nfs_read_header结构和多个nfs_read_data结构。
现在可以讲解nfs_generic_pg_readpages的流程了。
(1)创建一个nfs_read_header结构。
(2)初始化其中的nfs_pgio_header结构。
(3)根据nfs_pageio_descriptor结构中的数据创建若干个nfs_read_data结构,将nfs_pageio_descriptor结构中的缓存页分配给每个nfs_read_data结构。
(4)初始化READ请求的参数和返回值,每个nfs_read_data结构发起一次READ请求。
(5)所有的nfs_read_data结构完毕后,执行一些收尾工作。