LAYOUTGET(一)

本文探讨了在NFS文件系统中,LAYOUTGET请求的触发时机和作用。当客户端请求数据时,若缓存页不连续则需发起READ请求。在满足特定条件时,会执行filelayout_pg_init_read()函数触发LAYOUTGET请求,用于获取布局信息。pnfs_layout_hdr结构用于存储客户端的layout,而pnfs_update_layout()函数判断并决定是否需要获取新的layout。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    现在可以讲解LAYOUTGET请求了,首先我们需要回忆一下NFS文件系统中读数据的过程,看看LAYOUT请求是什么时候触发的。NFS文件系统中,每个缓存页关联了一个数据结构struct nfs_page。同时客户端还定义了一个数据结构struct nfs_pageio_descriptor,这个结构中包含了一个链表。当客户端向服务器请求数据时,会把缓存页挂载到这个数据结构的链表中,添加到这个链表中的缓存页中的数据必须是连续的。如果一个新的缓存页和链表中的数据不连续,那么就需要先向服务器发起READ请求,更新链表中的缓存页。当获取数据后这些缓存页从链表中摘除,这时就可以添加新的缓存页了。将一个缓存页添加到链表中的函数是nfs_pageio_do_add_request(),这个函数的代码如下:

static int nfs_pageio_do_add_request(struct nfs_pageio_descriptor *desc,
                                     struct nfs_page *req)
{
        if (desc->pg_count != 0) {
                struct nfs_page *prev;

                prev = nfs_list_entry(desc->pg_list.prev);
                if (!nfs_can_coalesce_requests(prev, req, desc))
                        return 0;
        } else {
                if (desc->pg_ops->pg_init)
                        desc->pg_ops->pg_init(desc, req);
                desc->pg_base = req->wb_pgbase;
        }
        nfs_list_remove_request(req);
        nfs_list_add_request(req, &desc->pg_list);
        desc->pg_count += req->wb_bytes;
        return 1;
}
    前面的文章中已经分析过这个函数了,可以参考这篇文章: http://blog.csdn.net/ycnian/article/details/8526734。当desc->pg_count==0时,也就是链表中没有缓存页时会执行函数desc->pg_ops->pg_init(desc, req)。前面的文章中没有分析这个函数,因为在不使用pNFS的情况下这个函数为空,所以当时直接跳过了。那么在使用pNFS的情况下这个函数是什么呢?如果采用的是file layout,desc->pg_ops的定义如下:

static const struct nfs_pageio_ops filelayout_pg_read_ops = {
        .pg_init = filelayout_pg_init_read,
        .pg_test = filelayout_pg_test,
        .pg_doio = pnfs_generic_pg_readpages,
};

这时就会执行到函数filelayout_pg_init_read(),这个函数就发起了LAYOUTGET请求。不过现在还不能讲解layout的获取过程,我们首先需要弄清楚layout在客户端的存放方式。客户端,每个layout用数据结构struct pnfs_layout_segment表示,每个layout表示文件中的一段数据。用户可能申请了文件中多个数据段的layout,因此同一个文件中所有的layout构成了一个链表,这个链表保存在数据结构struct pnfs_layout_hdr中。用户还可能访问了多个文件,每个文件对应一个struct pnfs_layout_hdr结构,因此客户端就有多个struct pnfs_layout_hdr结构,这些pnfs_layout_hdr构成了一个链表,保存在struct nfs_server中。

struct nfs_server {
        ......
        // 这是客户端使用的layout类型,比如file layout, block layout, object layout.
        struct pnfs_layoutdriver_type  *pnfs_curr_ld;
        // 这是一个链表,链表中的数据结构是struct pnfs_layout_hdr
        struct list_head        layouts;
        ......
}

不是pnfs_layout_hdr还保存在nfs_inode结构中了。

struct nfs_inode {
        ......
        struct pnfs_layout_hdr *layout; // 保存了这个文件中所有的layout.
        ......
}

struct pnfs_layout_hdr的定义如下:

struct pnfs_layout_hdr {
        atomic_t                plh_refcount;   // 这个数据结构的引用计数.
        // 这个字段负责将pnfs_layout_hdr链接到nfs_server->layouts中
        struct list_head        plh_layouts;   /* other client layouts */
        // 这是CB_LAYOUTRECALL中使用的字段,将pnfs_layout_hdr链接到一个临时链表中,
        // 表示其中的layout需要回收了.
        struct list_head        plh_bulk_recall; /* clnt list of bulk recalls */
        // 这是一个链表头,这个链表中保存的是struct pnfs_layout_segment.
        struct list_head        plh_segs;      /* layout segments list */
        // LAYOUTGET请求获取的stateid保存在这里.
        nfs4_stateid            plh_stateid;
        // 这是客户端正在发起的LAYOUTGET请求的计数
        atomic_t                plh_outstanding; /* number of RPCs out */
        // 这是一个阻塞标志位,如果设置了这个标志位,就不能继续发送LAYOUTGET请求了.
        unsigned long           plh_block_lgets; /* block LAYOUTGET if >0 */
        // 这是更新plh_stateid时使用的一个字段,如果获取的stateid中的序列号低于
        // 这个值就不更新plh_stateid了.
        u32                     plh_barrier; /* ignore lower seqids */
        // 这是一个时间戳
        unsigned long           plh_retry_timestamp;
        // 这是一些标志位
        unsigned long           plh_flags;
        loff_t                  plh_lwb; /* last write byte for layoutcommit */
        // 这里保存的是用户信息.
        struct rpc_cred         *plh_lc_cred; /* layoutcommit cred */
        // 这是对应文件的索引节点.
        struct inode            *plh_inode;
};
struct pnfs_layout_segment的定义如下:

struct pnfs_layout_segment {
        // 链接到链表pnfs_layout_hdr->plh_segs中
        struct list_head pls_list;
        // 这是供临时链表使用的一个字段,负责将这个pnfs_layout_segment
        // 存放到一个临时链表中。
        struct list_head pls_lc_list;
        // 这是layout在文件中的数据范围和访问方式.
        struct pnfs_layout_range pls_range;
        atomic_t pls_refcount;          // 这个数据结构的引用计数
        unsigned long pls_flags;        // 一些标志位
        struct pnfs_layout_hdr *pls_layout;     // 指向了对应的pnfs_layout_hdr结构.
};

struct pnfs_layout_range {
        u32 iomode;     // 访问方式  IOMODE_READ or IOMODE_RW
        u64 offset;     // layout在文件中的起始位置
        u64 length;     // layout长度
};

现在可以接着看filelayout_pg_init_read()了,这个函数很简单,直接调用filelayout_pg_init_read()更新指定范围内layout的信息。

static void
filelayout_pg_init_read(struct nfs_pageio_descriptor *pgio,
                        struct nfs_page *req)
{
        BUG_ON(pgio->pg_lseg != NULL);

        if (req->wb_offset != req->wb_pgbase) {
                /*
                 * Handling unaligned pages is difficult, because have to
                 * somehow split a req in two in certain cases in the
                 * pg.test code.  Avoid this by just not using pnfs
                 * in this case.
                 */
                nfs_pageio_reset_read_mds(pgio);
                return;
        }
        pgio->pg_lseg = pnfs_update_layout(pgio->pg_inode,
                                           req->wb_context,
                                           0,
                                           NFS4_MAX_UINT64,
                                           IOMODE_READ,
                                           GFP_KERNEL);
        /* If no lseg, fall back to read through mds */
        if (pgio->pg_lseg == NULL)
                nfs_pageio_reset_read_mds(pgio);
}

    下面是pnfs_update_layout(),这个函数有点长,但是逻辑不复杂。这个函数的主要作用是检查是否需要获取layout,如果需要就调用send_layoutget()发起LAYOUTGET请求。那么什么情况下不需要获取layout呢?主要包括下面几种情况:

(1)客户端没有设置pNFS,也就是nfs_server->pnfs_curr_ld == NULL。

(2)文件内容很短,且读取的数据量也很少。这种情况下就没必要使用pNFS了,直接将数据发送给MDS就可以了。

(3)客户端正在清理这个文件中的layout,那就不要申请新的layout了。

(4)最近几次LAYOUTGET请求失败了,那也就不要申请layout了,即使申请的化估计也会失败。

(5)客户端已经申请了文件指定范围的layout了。

这个函数的代码如下:

// 这是更新layout的函数,这个函数检查本地是否已经获取了文件中指定范围的layout
// 如果没有就向MDS发起LAYOUTGET请求.
struct pnfs_layout_segment *
pnfs_update_layout(struct inode *ino,           // 这是文件的索引节点结构
                   struct nfs_open_context *ctx,        // 用户信息
                   loff_t pos,                  // layout在文件中的起始位置
                   u64 count,                   // layout长度
                   enum pnfs_iomode iomode,     // 操作类型,读还是写   IOMODE_READ or IOMODE_RW
                   gfp_t gfp_flags)             // 这是分配内存时使用的标志位
{
	// 这个数据结构应该表示文件中的一段数据
        struct pnfs_layout_range arg = {
                .iomode = iomode,       // 操作类型  IOMODE_READ or IOMODE_RW
                .offset = pos,          // layout在文件中的起始位置
                .length = count,        // layout长度
        };
        unsigned pg_offset;
        struct nfs_server *server = NFS_SERVER(ino);    // 取出nfs_server结构.
        struct nfs_client *clp = server->nfs_client;    // 然后取出nfs_client结构.
        struct pnfs_layout_hdr *lo;
        struct pnfs_layout_segment *lseg = NULL;
        bool first = false;

	// 检查客户端是否设置了layout
        if (!pnfs_enabled_sb(NFS_SERVER(ino)))
                goto out;
        // OK,现在确定启用了pNFS.

	// 这种情况下读写请求执行发送给MDS就可以了,因此也不需要更新layout了.
        if (pnfs_within_mdsthreshold(ctx, ino, iomode))
                goto out;

	// 下面就需要更新layout,读写请求需要发送给DS.
        spin_lock(&ino->i_lock);
	// 取出对应于这个文件的pnfs_layout_hdr结构,如果不存在就创建一个。
        lo = pnfs_find_alloc_layout(ino, ctx, gfp_flags);
        if (lo == NULL) {
                spin_unlock(&ino->i_lock);
                goto out;	// 创建pnfs_layout_hdr过程出错了,就是分配内存出错了,直接退出.
        }

        /* Do we even need to bother with this? */
	// 标志位NFS_LAYOUT_BULK_RECALL表示客户端正在清理这个文件中的layout
	// 因此就不能发起LAYOUTGET请求了。
        if (test_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags)) {
                dprintk("%s matches recall, use MDS\n", __func__);
                goto out_unlock;  
        }

        /* if LAYOUTGET already failed once we don't try again */
        // 客户端会记录最近几次LAYOUTGET请求的结果,如果最近几次LAYOUTGET请求都失败了,
        // 那么这次LAYOUTGET请求失败的可能性也非常大,干脆就不发起LAYOUTGET请求了
        if (pnfs_layout_io_test_failed(lo, iomode))
                goto out_unlock;
        /* Check to see if the layout for the given range already exists */
        // 检查是否已经获取过这个数据端的layout了,如果已经获取了,直接退出就可以了.
        lseg = pnfs_find_lseg(lo, &arg);        // pnfs_layout_segment
        if (lseg)
                goto out_unlock;        // 太好了,直接退出了.

        // 还没有获取指定范围数据的layout信息,需要我们处理.
	// 进一步检查是否可以发起LAYOUTGET请求
        if (pnfs_layoutgets_blocked(lo, 0))
                goto out_unlock;	// 条件不满足,还是不能发起LAYOUTGET请求.
	// 现在可以发起LAYOUTGET请求了,先增加正在发起的LAYOUTGET请求的计数.
        atomic_inc(&lo->plh_outstanding);

	// 说明这是文件中第一个layout
        if (list_empty(&lo->plh_segs))
                first = true;

        spin_unlock(&ino->i_lock);
        if (first) {            // 将lo链接到链表 nfs_server->layouts中
                /* The lo must be on the clp list if there is any
                 * chance of a CB_LAYOUTRECALL(FILE) coming in.
                 */
                spin_lock(&clp->cl_lock);
                BUG_ON(!list_empty(&lo->plh_layouts));
		// 链接到nfs_server->layouts中.
                list_add_tail(&lo->plh_layouts, &server->layouts);
                spin_unlock(&clp->cl_lock);
        }
        // arg.offset是layout在文件中的起始位置,pg_offset是以缓存页对齐的偏移值.
        // PAGE_CACHE_MASK = (~(PAGE_SIZE-1))
        pg_offset = arg.offset & ~PAGE_CACHE_MASK;
        if (pg_offset) {
                arg.offset -= pg_offset;        // 这是以缓存页为边界对齐的位置
                arg.length += pg_offset;        // 这是现在需要请求的数据量
        }       // layout的起始位置需要以缓存页对齐
        if (arg.length != NFS4_MAX_UINT64)
		// 请求的数据量也需要是缓存页长度的整数倍.
                arg.length = PAGE_CACHE_ALIGN(arg.length);

	// 这个函数发起了LAYOUTGET请求,创建了一个新的pnfs_layout_segment.
        lseg = send_layoutget(lo, ctx, &arg, gfp_flags);
	// LAYOUTGET请求结束了,现在需要减少plh_outstanding计数了.
        atomic_dec(&lo->plh_outstanding);
out_put_layout_hdr:
        pnfs_put_layout_hdr(lo);        // 这里减少lo的引用计数了,因为前面增加了引用计数.
out:
        dprintk("%s: inode %s/%llu pNFS layout segment %s for "
                        "(%s, offset: %llu, length: %llu)\n",
                        __func__, ino->i_sb->s_id,
                        (unsigned long long)NFS_FILEID(ino),
                        lseg == NULL ? "not found" : "found",
                        iomode==IOMODE_RW ?  "read/write" : "read-only",
                        (unsigned long long)pos,
                        (unsigned long long)count);
        return lseg;
out_unlock:
        spin_unlock(&ino->i_lock);
        goto out_put_layout_hdr;
}
下篇文章中继续讲解send_layoutget()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值