这篇文章详细讲解nfs_generic_pg_readpages()的流程。
1.创建一个nfs_read_header结构
rhdr = nfs_readhdr_alloc();
if (!rhdr) { // 创建rhdr失败了,不能处理desc中的缓存页了,需要释放这些缓存页
desc->pg_completion_ops->error_cleanup(&desc->pg_list);
return -ENOMEM;
}
创建nfs_read_header结构的函数是nfs_readhdr_alloc(),系统为nfs_read_header结构创建了slab缓存,名称是nfs_rdata_cachep,从slab缓存中分配内存。
struct nfs_read_header *nfs_readhdr_alloc(void)
{
struct nfs_read_header *rhdr;
// 从slab中分配内存
rhdr = kmem_cache_zalloc(nfs_rdata_cachep, GFP_KERNEL);
if (rhdr) {
struct nfs_pgio_header *hdr = &rhdr->header; // 这是nfs_pgio_header结构
// 对nfs_pgio_header进行一些初始化
INIT_LIST_HEAD(&hdr->pages); // 这是一个链表,链表中保存的数据结构是nfs_page
INIT_LIST_HEAD(&hdr->rpc_list); // 这是一个链表,链表中保存的数据结构是nfs_read_data
spin_lock_init(&hdr->lock); // 这是一个自旋锁
atomic_set(&hdr->refcnt, 0); // 初始化引用计数为0
}
return rhdr; // 返回新分配的nfs_read_header结构
}
当nfs_read_header创建失败时,就不能向服务器请求数据,填充缓存页了,因此需要进行错误处理,READ操作中,这个处理函数是nfs_async_read_error()。这个函数定义如下:
参数head:这是一个链表,链表中保存的是nfs_page结构,这是需要填充的缓存页。
// 当创建nfs_read_header结构失败,不能继续处理读请求了.
static void
nfs_async_read_error(struct list_head *head)
{
struct nfs_page *req;
while (!list_empty(head)) { // 依次删除链表中的每个缓存页
req = nfs_list_entry(head->next); // 取出链表中一个nfs_page结构
nfs_list_remove_request(req); // 从链表中摘除
nfs_readpage_release(req); // 释放这个缓存页
}
}
这个函数摘除链表中所有的缓存页,然后删除这些缓存页,删除缓存页的处理函数是nfs_readpage_release()。
static void nfs_readpage_release(struct nfs_page *req)
{
// 找到文件的索引节点
struct inode *d_inode = req->wb_context->dentry->d_inode;
if (PageUptodate(req->wb_page)) // 缓存中的数据是最新的
nfs_readpage_to_fscache(d_inode, req->wb_page, 0); // 加入到FS-Cache中
unlock_page(req->wb_page); // 解锁
dprintk("NFS: read done (%s/%Ld %d@%Ld)\n",
req->wb_context->dentry->d_inode->i_sb->s_id, // 文件系统名称
(long long)NFS_FILEID(req->wb_context->dentry->d_inode), // 文件编号
req->wb_bytes, // 缓存中有效数据量
(long long)req_offset(req)); // 偏移值
nfs_release_request(req); // 减少req的引用计数,当计数减为0时释放这个结构.
}
这里又涉及到了FS-Cache缓存。nfs_readpage_release()不仅在这里调用,当READ操作正常结束后也会调用这个函数,因此如果缓存页中的数据有效需要将数据写入FS-Cache缓存中。以后再讲解FS-Cache。
2.初始化nfs_pgio_header
hdr = &rhdr->header;
nfs_pgheader_init(desc, hdr, nfs_readhdr_free);
atomic_inc(&hdr->refcnt);
初始化工作是由nfs_pgheader_init()完成的。
参数desc:这个变量在前面已经初始化好了,包含了缓存页的信息以及各种操作函数。
参数hdr:这是待初始化的变量,nfs_pgheader_init()根据desc中的信息初始化hdr。
变量nfs_readhdr_free:这是释放hdr占用内存的函数,当需要使用hdr占用的内存时调用这个函数。
void nfs_pgheader_init(struct nfs_pageio_descriptor *desc,
struct nfs_pgio_header *hdr,
void (*release)(struct nfs_pgio_header *hdr))
{
// 取出desc中第一个nfs_page结构,但是并没有从desc的链表中删除
hdr->req = nfs_list_entry(desc->pg_list.next);
hdr->inode = desc->pg_inode; // 文件索引节点
hdr->cred = hdr->req->wb_context->cred; // 用户信息
// 请求的数据在文件中的起始位置
hdr->io_start = req_offset(hdr->req); // 这是缓存内容在文件中的起始位置
// 这是请求的数据量
hdr->good_bytes = desc->pg_count; // 这是desc中所有nfs_page中数据量的总和
hdr->dreq = desc->pg_dreq; // NULL 这是直接IO中使用的字段
hdr->release = release; // 这是释放内存的函数,当
hdr->completion_ops = desc->pg_completion_ops; // 设置读操作完成或者出错时的处理函数
if (hdr->completion_ops->init_hdr)
hdr->completion_ops->init_hdr(hdr); // 直接IO中定义了这个函数.
}
3.根据请求的数据量创建多个READ请求
ret = nfs_generic_pagein(desc, hdr);
一般情况下desc缓存页中包含的数据量不会超过RPC的限制,因为向desc中添加缓存页时已经进行了检查。如果desc缓存页中的数据量超出了限制就先处理desc中的请求。这种情况下只需要创建一个READ请求就可以了。但是,如果RPC的限制值小于一个缓存页的大小,desc缓存页中的数据就可以超出RPC限制,这时需要对缓存页中的数据进行分割,创建多个READ请求。nfs_generic_pagein()流程如下:
int nfs_generic_pagein(struct nfs_pageio_descriptor *desc,
struct nfs_pgio_header *hdr)
{
if (desc->pg_bsize < PAGE_CACHE_SIZE) // 每次RPC请求中请求的数据量小于一个缓存页面
// 这种情况下hdr中的请求可能超过了rsize,需要拆分成多个RPC请求,每个RPC请求用一个nfs_read_data表示
return nfs_pagein_multi(desc, hdr);
// 一个READ请求可以处理完所有的数据,只需要创建一个nfs_read_data结构
return nfs_pagein_one(desc, hdr);
}
下面分别介绍这两种情况
情况1:创建多个READ请求
这是通过函数nfs_pagein_multi()实现的,代码流程如下:
nfs_generic_pagein --> nfs_pagein_multi
static int nfs_pagein_multi(struct nfs_pageio_descriptor *desc,
struct nfs_pgio_header *hdr)
{
struct nfs_page *req = hdr->req; // 取出NFS请求
struct page *page = req->wb_page; // 这是请求的缓存页,只有一个缓存页
struct nfs_read_data *data;
size_t rsize = desc->pg_bsize, nbytes; // 取出每次RPC请求中最多可以传输的数据量
unsigned int offset;
offset = 0;
nbytes = desc->pg_count; // 这是数据总量
// 根据请求的数据量创建多个READ请求
do {
// nbytes是请求的总数据量,rsize是每次可以传输的数据量,取二者之间的小值.
size_t len = min(nbytes,rsize); // OK,这次只能传输这么多数据
// 创建一个nfs_read_data结构,每个nfs_read_data表示一个READ请求
data = nfs_readdata_alloc(hdr, 1);
if (!data) {
nfs_pagein_error(desc, hdr); // 创建nfs_read_data过程出错了
return -ENOMEM;
}
data->pages.pagevec[0] = page; // 这是缓存页面
nfs_read_rpcsetup(data, len, offset); // 设置RPC请求的参数和返回值
list_add(&data->list, &hdr->rpc_list); // 链接到链表中
nbytes -= len; // 这是剩余的数据量
offset += len; // 下次数据的起始地址
} while (nbytes != 0);
nfs_list_remove_request(req); // 从desc中移除这个请求
nfs_list_add_request(req, &hdr->pages); // 添加到hdr中
desc->pg_rpc_callops = &nfs_read_common_ops; // 这是RPC请求中使用的函数
return 0;
}
创建nfs_read_data结构的函数如下:
nfs_generic_pagein --> nfs_pagein_multi --> nfs_readdata_alloc
static struct nfs_read_data *nfs_readdata_alloc(struct nfs_pgio_header *hdr,
unsigned int pagecount)
{
struct nfs_read_data *data, *prealloc;
// nfs_read_header中包含一个nfs_read_data结构,如果这个没被占用,直接使用这个就可以了,
// 就不必分配内存了。
prealloc = &container_of(hdr, struct nfs_read_header, header)->rpc_data;
if (prealloc->header == NULL)
data = prealloc;
else
// hdr中包含了多个READ请求,需要分配一个新的nfs_read_data结构
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
goto out; // 分配内存出错了
// 分配pagecount个内存页面指针,这些指针将关联到缓存页
if (nfs_pgarray_set(&data->pages, pagecount)) {
data->header = hdr; // 头部
atomic_inc(&hdr->refcnt); // 增加引用计数,因为增加了一个nfs_read_data结构.
} else { // 分配内存页面指针出错了,需要释放前面申请的内存
if (data != prealloc)
kfree(data);
data = NULL;
}
out:
return data;
}
如果nfs_read_data结构创建过程出错了,需要进行处理,READ操作中这个处理函数是nfs_pagein_error()。由于hdr中可能包含多个nfs_read_data结构,需要释放掉前面已经正确创建的nfs_read_data结构,然后释放所有的缓存页。
nfs_generic_pagein --> nfs_pagein_multi --> nfs_pagein_error
// 创建nfs_read_data结构出错时调用这个函数进行处理,需要使用hdr中所有的nfs_read_data结构.
static void nfs_pagein_error(struct nfs_pageio_descriptor *desc,
struct nfs_pgio_header *hdr)
{
set_bit(NFS_IOHDR_REDO, &hdr->flags); // 设置错误标志位
while (!list_empty(&hdr->rpc_list)) { // 释放hdr中所有的nfs_read_data结构
// 从链表中取出一个nfs_read_data结构
struct nfs_read_data *data = list_first_entry(&hdr->rpc_list,
struct nfs_read_data, list);
list_del(&data->list);
nfs_readdata_release(data); // 释放nfs_read_data结构
}
// 释放所有的缓存页
desc->pg_completion_ops->error_cleanup(&desc->pg_list);
}
释放一个nfs_read_data结构的函数如下:
nfs_generic_pagein --> nfs_pagein_multi --> nfs_pagein_error --> nfs_readdata_release
void nfs_readdata_release(struct nfs_read_data *rdata)
{
struct nfs_pgio_header *hdr = rdata->header;
struct nfs_read_header *read_header = container_of(hdr, struct nfs_read_header, header);
// 减少nfs_open_context的引用计数,当计数减到0时释放这个结构.
put_nfs_open_context(rdata->args.context); // 释放nfs_open_context
if (rdata->pages.pagevec != rdata->pages.page_array)
kfree(rdata->pages.pagevec); // 释放缓存页面指针
if (rdata != &read_header->rpc_data)
kfree(rdata); // 释放nfs_read_data结构占用的内存
else
rdata->header = NULL;
// 由于释放了一个nfs_read_data,需要减少hdr的引用计数
if (atomic_dec_and_test(&hdr->refcnt))
hdr->completion_ops->completion(hdr);
}
释放完nfs_read_data后需要再释放所有的缓存页,这是由error_cleanup进行处理的。READ操作中这个函数是nfs_async_read_error()
参数head:这是一个链表,链表中的数据结构是nfs_page,这是需要释放的缓存页
nfs_generic_pagein --> nfs_pagein_multi --> nfs_pagein_error --> nfs_async_read_error
static void
nfs_async_read_error(struct list_head *head)
{
struct nfs_page *req;
while (!list_empty(head)) { // 依次删除链表中的每个缓存页
req = nfs_list_entry(head->next); // 取出链表中一个nfs_page结构
nfs_list_remove_request(req); // 从链表中摘除
nfs_readpage_release(req); // 释放这个缓存页
}
}
创建好nfs_read_data后需要继续设置READ请求的参数,这是由nfs_read_rpcsetup()实现的
nfs_generic_pagein --> nfs_pagein_multi --> nfs_read_rpcsetup
参数data:表示一个READ请求的数据结构
参数count:这次READ操作中请求的数据量
参数offset:这是数据在缓存页中的偏移位置。如果READ操作中每次传输的数据量小于一个缓存页大小,就需要将一个缓存页拆分成多个READ请求,offset表示本次请求中数据在缓存页中的偏移量
static void nfs_read_rpcsetup(struct nfs_read_data *data,
unsigned int count, unsigned int offset)
{
// 取出链表中第一个nfs_page结构
struct nfs_page *req = data->header->req;
data->args.fh = NFS_FH(data->header->inode); // 文件句柄
data->args.offset = req_offset(req) + offset; // 这是请求的数据在文件中的偏移量
data->args.pgbase = req->wb_pgbase + offset; // 第一个缓存页中存放数据的起始地址
data->args.pages = data->pages.pagevec; // 这是缓存页指针,NFS服务器传输过来的数据存放在这里
data->args.count = count; // 这是请求数据的长度
data->args.context = get_nfs_open_context(req->wb_context);
data->args.lock_context = req->wb_lock_context;
data->res.fattr = &data->fattr; // 这里保存文件的属性信息
data->res.count = count; // 这是期望的返回值,期望读取的数据量.
// 这是READ操作的返回值,1表示读到文件结尾了,0表示没有读到结尾
// 这里先初始化为0,READ操作结束后会修改这个值。
data->res.eof = 0;
nfs_fattr_init(&data->fattr); // 初始化文件属性的数据结构
}
情况2.只需要创建一个nfs_read_data结构
由于每次READ操作中允许传输的数据量大于缓存页的大小,desc中的数据量不会超出READ操作的限制,因此只需要创建一个READ请求就可以了,这是通过函数nfs_pagein_one()实现的。
nfs_generic_pagein --> nfs_pagein_one
static int nfs_pagein_one(struct nfs_pageio_descriptor *desc,
struct nfs_pgio_header *hdr)
{
struct nfs_page *req;
struct page **pages;
struct nfs_read_data *data;
// 取出desc中保存的nfs_page请求.
struct list_head *head = &desc->pg_list; // 这是一个链表,保存的是nfs_page结构
// 设置了data->nfs_page_array结构,分配了内存页面的指针
data = nfs_readdata_alloc(hdr, nfs_page_array_len(desc->pg_base,
desc->pg_count));
if (!data) {
nfs_pagein_error(desc, hdr);
return -ENOMEM;
}
pages = data->pages.pagevec; // 这是内存页面指针
while (!list_empty(head)) { // 这是desc中的链表
req = nfs_list_entry(head->next); // 取出一个nfs_page结构
nfs_list_remove_request(req); // 现在才从desc链表中删除缓存页
nfs_list_add_request(req, &hdr->pages); // 添加到hdr中
*pages++ = req->wb_page; // 指向了缓存页面
}
// desc->pg_count是请求的数据量
nfs_read_rpcsetup(data, desc->pg_count, 0); // 设置RPC请求的参数和返回值
list_add(&data->list, &hdr->rpc_list); // 一个nfs_pgio_header结构可以供多个RPC读请求使用
desc->pg_rpc_callops = &nfs_read_common_ops; // 现在才设置desc中的这个函数集合
return 0;
}
这个函数的流程和nfs_pagein_multi()基本类似,nfs_page_array_len()的作用是根据desc缓存页中的数据量计算缓存页的数量。
nfs_generic_pagein --> nfs_pagein_one --> nfs_page_array_len
参数base:desc中包含多个缓存页,这是第一个缓存页中起始数据在缓存页中的偏移量
参数len:这是desc所有缓存页中数据的总长度
static inline
unsigned int nfs_page_array_len(unsigned int base, size_t len)
{
return ((unsigned long)len + (unsigned long)base + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
由于nfs_page_array_len()包含了第一个缓存页中开始位置到有效数据起始位置的长度,因此nfs_read_rpcsetup()中第3个参数为0。
4.发起READ请求
if (ret == 0)
// 发起READ请求,向服务器请求数据。
ret = nfs_do_multiple_reads(&hdr->rpc_list,
desc->pg_rpc_callops);
hdr->rpc_list是一个链表,这个链表中的数据结构是nfs_read_data,每个nfs_read_data需要发起一次READ请求。nfs_do_multiple_reads()负责处理链表中所有的READ请求。
参数head:这是一个链表,包含了待处理的所有READ请求
参数call_ops:这是READ操作中供RPC使用的函数
static int
nfs_do_multiple_reads(struct list_head *head,
const struct rpc_call_ops *call_ops)
{
struct nfs_read_data *data;
int ret = 0;
// 依次处理每个READ请求
while (!list_empty(head)) {
int ret2;
// 取出一个请求
data = list_first_entry(head, struct nfs_read_data, list);
list_del_init(&data->list); // 从链表中删除
ret2 = nfs_do_read(data, call_ops); // 发起RPC调用,执行READ请求
if (ret == 0)
ret = ret2;
}
return ret;
}
nfs_do_multiple_reads()多次调用nfs_do_read(),这个函数处理一个READ请求。
nfs_do_multiple_reads --> nfs_do_read
static int nfs_do_read(struct nfs_read_data *data,
const struct rpc_call_ops *call_ops)
{
struct inode *inode = data->header->inode;
return nfs_initiate_read(NFS_CLIENT(inode), data, call_ops, 0);
}
nfs_do_read()直接调用了nfs_initiate_read(),这是READ请求实际的处理函数,这个函数根据参数创建了一个RPC任务,发起了READ调用。
nfs_do_multiple_reads --> nfs_do_read --> nfs_initiate_read
int nfs_initiate_read(struct rpc_clnt *clnt,
struct nfs_read_data *data,
const struct rpc_call_ops *call_ops, int flags)
{
struct inode *inode = data->header->inode; // 找到文件的索引节点
int swap_flags = IS_SWAPFILE(inode) ? NFS_RPC_SWAPFLAGS : 0;
struct rpc_task *task; // 这是一个RPC任务的指针
struct rpc_message msg = {
.rpc_argp = &data->args, // 参数
.rpc_resp = &data->res, // 返回值
.rpc_cred = data->header->cred, // 用户信息
};
// 这是创建一个RPC任务时需要的参数信息
struct rpc_task_setup task_setup_data = {
.task = &data->task, // RPC任务
.rpc_client = clnt, // RPC客户端
.rpc_message = &msg, // RPC消息
.callback_ops = call_ops, // RPC回调函数
.callback_data = data, // callback_ops使用的参数
.workqueue = nfsiod_workqueue, // 工作队列
.flags = RPC_TASK_ASYNC | swap_flags | flags,
};
/* Set up the initial task struct. */
// 这个函数设置了READ请求的例程号
NFS_PROTO(inode)->read_setup(data, &msg);
dprintk("NFS: %5u initiated read call (req %s/%lld, %u bytes @ "
"offset %llu)\n",
data->task.tk_pid,
inode->i_sb->s_id,
(long long)NFS_FILEID(inode),
data->args.count,
(unsigned long long)data->args.offset);
task = rpc_run_task(&task_setup_data); // 创建一个RPC任务,然后执行这个任务.
if (IS_ERR(task))
return PTR_ERR(task);
rpc_put_task(task);
return 0;
}
5.收尾工作
当所有的数据都读到客户端后,需要进行一些收尾工作。
if (atomic_dec_and_test(&hdr->refcnt))
hdr->completion_ops->completion(hdr);
READ请求中,这个函数是nfs_read_completion(),这个函数的主要任务是释放内存。
static void nfs_read_completion(struct nfs_pgio_header *hdr)
{
unsigned long bytes = 0;
// 当创建nfs_read_data出错时会设置这个标志位,nfs_pagein_error()已经释放了内存,
// 不需要处理了,直接退出。
if (test_bit(NFS_IOHDR_REDO, &hdr->flags))
goto out;
while (!list_empty(&hdr->pages)) { // 处理每一个NFS请求
struct nfs_page *req = nfs_list_entry(hdr->pages.next); // 取出一个nfs_page结构
struct page *page = req->wb_page; // 取出缓存页
// 读取到文件结尾了,剩余部分填充为0.
if (test_bit(NFS_IOHDR_EOF, &hdr->flags)) {
if (bytes > hdr->good_bytes)
zero_user(page, 0, PAGE_SIZE);
else if (hdr->good_bytes - bytes < PAGE_SIZE)
zero_user_segment(page,
hdr->good_bytes & ~PAGE_MASK,
PAGE_SIZE);
}
bytes += req->wb_bytes; // 加上请求中的数据
// 缓存页中的数据是最新的了
if (test_bit(NFS_IOHDR_ERROR, &hdr->flags)) {
if (bytes <= hdr->good_bytes)
SetPageUptodate(page);
} else
SetPageUptodate(page);
nfs_list_remove_request(req);
nfs_readpage_release(req); // 释放req占用的内存
}
out:
// nfs_readhdr_free(hdr)
hdr->release(hdr); // 释放nfs_pgio_header结构
}