现在可以讲解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()。