上篇文章讲到了MDS中LAYOUTGET的处理过程,当MDS处理完毕后就会将数据封装到应答消息中返回给客户端。这篇文章中我们讲讲客户端接收到应到报文后的处理。以前的文章讲到过客户端用struct pnfs_layout_segment表示一个layout,LAYOUTGET的作用就是获取文件的layout,因此当客户端接收到LAYOUTGET应答报文后会创建一个pnfs_layout_segment结构,然后解析应答报文,用应答报文中的数据填充这个结构。这个处理过程是在函数pnfs_layout_process()中完成的,客户端执行完LAYOUTGET请求后会马上调用这个函数。
struct pnfs_layout_segment *
pnfs_layout_process(struct nfs4_layoutget *lgp)
{
// 先取出文件索引节点中的pnfs_layout_hdr结构.
struct pnfs_layout_hdr *lo = NFS_I(lgp->args.inode)->layout;
struct nfs4_layoutget_res *res = &lgp->res; // 这是LAYOUTGET返回的信息
struct pnfs_layout_segment *lseg;
struct inode *ino = lo->plh_inode;
int status = 0;
/* Inject layout blob into I/O device driver */
// 调用alloc_lseg()创建一个pnfs_layout_segment结构,这个函数可能发起了GETDEVICEINFO请求.
lseg = NFS_SERVER(ino)->pnfs_curr_ld->alloc_lseg(lo, res, lgp->gfp_flags);
// 现在已经分配完,并且设置好了.
if (!lseg || IS_ERR(lseg)) {
if (!lseg)
status = -ENOMEM;
else
status = PTR_ERR(lseg);
dprintk("%s: Could not allocate layout: error %d\n",
__func__, status);
goto out;
}
spin_lock(&ino->i_lock);
if (test_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags)) {
dprintk("%s forget reply due to recall\n", __func__);
goto out_forget_reply;
}
if (pnfs_layoutgets_blocked(lo, 1) ||
pnfs_layout_stateid_blocked(lo, &res->stateid)) {
dprintk("%s forget reply due to state\n", __func__);
goto out_forget_reply;
}
/* Done processing layoutget. Set the layout stateid */
// 根据LAYOUTGET的返回值设置layout的stateid.
pnfs_set_layout_stateid(lo, &res->stateid, false);
init_lseg(lo, lseg); // lseg指向了lo
lseg->pls_range = res->range; // 这是layout中数据的范围.
pnfs_get_lseg(lseg); // 增加lseg引用计数.
pnfs_layout_insert_lseg(lo, lseg); // 将lseg添加到lo中.
// 设置return on close标志位
if (res->return_on_close) {
set_bit(NFS_LSEG_ROC, &lseg->pls_flags);
set_bit(NFS_LAYOUT_ROC, &lo->plh_flags);
}
spin_unlock(&ino->i_lock);
return lseg;
out:
return ERR_PTR(status);
out_forget_reply:
spin_unlock(&ino->i_lock);
lseg->pls_layout = lo;
NFS_SERVER(ino)->pnfs_curr_ld->free_lseg(lseg); // 出错了,需要释放这个lseg.
goto out;
}
这个函数涉及到了file layout中的两个函数alloc_lseg()和free_lseg()。alloc_lseg()的作用是创建一个
pnfs_layout_segment结构,并根据LAYOUTGET应答报文中的数据填充这个结构中的字段,这是主要的处理函数。free_lseg()是当pnfs_layout_process()出错后删除刚创建的pnfs_layout_segment的函数。file layout中这两个函数定义如下:
static struct pnfs_layoutdriver_type filelayout_type = {
......
.alloc_lseg = filelayout_alloc_lseg,
.free_lseg = filelayout_free_lseg,
......
};
我们只讲alloc_lseg(),这个函数代码如下:
static struct pnfs_layout_segment *
filelayout_alloc_lseg(struct pnfs_layout_hdr *layoutid,
struct nfs4_layoutget_res *lgr,
gfp_t gfp_flags)
{
struct nfs4_filelayout_segment *fl;
int rc;
struct nfs4_deviceid id; // 设备id.
dprintk("--> %s\n", __func__);
fl = kzalloc(sizeof(*fl), gfp_flags); // 分配内存
if (!fl)
return NULL; // 分配内存出错了.
// 解码LAYOUTGET应答报文,根据应答报文中的数据填充fl.
rc = filelayout_decode_layout(layoutid, fl, lgr, &id, gfp_flags);
// filelayout_check_layout()是一个检查函数,这个函数检查了很多信息,
if (rc != 0 || filelayout_check_layout(layoutid, fl, lgr, &id, gfp_flags)) {
_filelayout_free_lseg(fl);
return NULL;
}
return &fl->generic_hdr;
}
这个函数很短,逻辑很清晰,但是需要介绍一个数据结构struct nfs4_filelayout_segment。前面一直说pnfs_layout_segment结构表示一个layout,其实这个数据结构不足以表示一个完整的layout,pnfs_layout_segment中只包含了layout的基本信息,比如layout在文件中的起始位置、layout长度、layout访问方式等信息。但是各种类型的layout还有一些特殊数据需要保存。对于file layout来说,表示layout的数据结构是struct nfs4_filelayout_segment,pnfs_layout_segment是nfs4_filelayout_segment中的一部分。
struct nfs4_filelayout_segment {
struct pnfs_layout_segment generic_hdr; // 这是各种类型的layout通用的数据.
u32 stripe_type; // STRIPE_SPARSE 或者 STRIPE_DENSE
u32 commit_through_mds; // COMMIT请求是提交给MDS还是提交给DS.
u32 stripe_unit; // Stripe Unit 大小
u32 first_stripe_index; // 第一个I/O请求发送给哪个DS组.
u64 pattern_offset; // 这是pattern在文件中的起始位置.
// 这个数据结构中保存了DS的地址,这个结构中的数据需要通过GETDEVICEINFO请求获取.
struct nfs4_file_layout_dsaddr *dsaddr; /* Point to GETDEVINFO data */
unsigned int num_fh; // LAYOUTGET请求返回的文件句柄的数量
struct nfs_fh **fh_array; // 这是一组文件句柄,供DS使用.
};
这个数据结构中的字段应该很清晰了,基本上就是LAYOUTGET应答报文中的数据,但是dsaddr除外,这个字段存储的是layout的Stripe信息和DS的地址,LAYOUTGET请求中没有获取这些信息,这些信息是在GETDEVICEINFO中获取的,后面会讲解GETDEVICEINFO请求。现在filelayout_alloc_lseg()就很容易理解了,这个函数包含三个步骤:
步骤1:调用kzalloc()创建一个新的nfs4_filelayout_segment结构。
步骤2:调用filelayout_decode_layout()解析LAYOUTGET应答报文,填充nfs4_filelayout_segment中的各个字段。
步骤3:调用filelayout_check_layout()检查nfs4_filelayout_segment结构中的数据是否有效。因为MDS返回的数据不一定有效。如果数据无效客户端就不能使用这个layout。对于我们来说,这个函数最主要的作用是检查了nfs4_filelayout_segment中dsaddr中的信息是否有效。如果无效,这个函数会向MDS发起GETDEVICEINFO请求获取DS的地址。
static int
filelayout_check_layout(struct pnfs_layout_hdr *lo,
struct nfs4_filelayout_segment *fl, // 这里面包含了解码后的数据
struct nfs4_layoutget_res *lgr, // 这是LAYOUTGET的返回值
struct nfs4_deviceid *id, // deviceid
gfp_t gfp_flags)
{
struct nfs4_deviceid_node *d;
struct nfs4_file_layout_dsaddr *dsaddr;
int status = -EINVAL;
struct nfs_server *nfss = NFS_SERVER(lo->plh_inode); // 取出nfs_server结构.
dprintk("--> %s\n", __func__);
/* FIXME: remove this check when layout segment support is added */
// 客户端目前只支持整个文件的layout.
if (lgr->range.offset != 0 ||
lgr->range.length != NFS4_MAX_UINT64) {
dprintk("%s Only whole file layouts supported. Use MDS i/o\n",
__func__);
goto out;
}
// pattern的起始位置也必须是0,必须从文件头开始.
if (fl->pattern_offset > lgr->range.offset) {
dprintk("%s pattern_offset %lld too large\n",
__func__, fl->pattern_offset);
goto out;
}
// 目前Stripe Unit大小必须是缓存页长度整数倍.
if (!fl->stripe_unit || fl->stripe_unit % PAGE_SIZE) {
dprintk("%s Invalid stripe unit (%u)\n",
__func__, fl->stripe_unit);
goto out;
}
/* find and reference the deviceid */
// 这个函数在检查deviceid.
d = nfs4_find_get_deviceid(NFS_SERVER(lo->plh_inode)->pnfs_curr_ld,
NFS_SERVER(lo->plh_inode)->nfs_client, id);
if (d == NULL) {
// 没找到,发起GETDEVICEINFO请求.
dsaddr = filelayout_get_device_info(lo->plh_inode, id, gfp_flags);
if (dsaddr == NULL)
goto out;
} else
dsaddr = container_of(d, struct nfs4_file_layout_dsaddr, id_node);
/* Found deviceid is unavailable */
// 设备现在不可使用.
if (filelayout_test_devid_unavailable(&dsaddr->id_node))
goto out_put;
fl->dsaddr = dsaddr; // 这里面是设备信息.
// 这是第一个使用的stripe编号. 超出范围了.
if (fl->first_stripe_index >= dsaddr->stripe_count) {
dprintk("%s Bad first_stripe_index %u\n",
__func__, fl->first_stripe_index);
goto out_put;
}
// SPARSE方式下允许所有的ns class使用同一个文件句柄。如果不使用同一个文件句柄,则必须每个DS组一个文件句柄.
if ((fl->stripe_type == STRIPE_SPARSE && fl->num_fh > 1 && fl->num_fh != dsaddr->ds_num) ||
// DENSE方式下,每个DS组必须有自己的文件句柄,不允许使用同一个文件句柄.
(fl->stripe_type == STRIPE_DENSE && fl->num_fh != dsaddr->stripe_count)) {
dprintk("%s num_fh %u not valid for given packing\n",
__func__, fl->num_fh);
goto out_put;
}
// nfss->rsize是客户端设置的每次读操作过程中可以传输的数据量
// nfss->wsize是客户端设置的每次写操作过程中可以传输的数据量
if (fl->stripe_unit % nfss->rsize || fl->stripe_unit % nfss->wsize) {
dprintk("%s Stripe unit (%u) not aligned with rsize %u "
"wsize %u\n", __func__, fl->stripe_unit, nfss->rsize,
nfss->wsize);
}
status = 0;
out:
dprintk("--> %s returns %d\n", __func__, status);
return status;
out_put:
nfs4_fl_put_deviceid(dsaddr);
goto out;
}
我们在下篇文章中介绍nfs4_find_get_deviceid()和filelayout_get_device_info()两个函数以及GETDEVICEINFO请求的处理过程。