然后解释这篇文章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使用 structpnfs_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结构完毕后,执行一些收尾工作。