天天看点

NFS系统read调用过程(五)

    这篇文章接着讲解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结构完毕后,执行一些收尾工作。

继续阅读