天天看点

NFS系统read调用过程(六)1.创建一个nfs_read_header结构2.初始化nfs_pgio_header3.根据请求的数据量创建多个READ请求4.发起READ请求5.收尾工作

    这篇文章详细讲解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结构
}
           

继续阅读