前面一篇文章讲到了nfs4_pnfs_get_layout()最终调用struct pnfs_export_operations结构中的layout_get()创建一个新的layout,这篇文章中就详细讲讲file layout中layout的创建过程。file layout中的pnfs_export_operations定义如下:
const struct pnfs_export_operations pnfs_dlm_export_ops = {
.layout_type = nfsd4_pnfs_dlm_layouttype,
.get_device_info = nfsd4_pnfs_dlm_getdevinfo,
.get_device_iter = nfsd4_pnfs_dlm_getdeviter,
.layout_get = nfsd4_pnfs_dlm_layoutget,
};
因此,file layout中最终调用了nfsd4_pnfs_dlm_layoutget()创建新的layout。这个函数不复杂,因为pNFS正在实现过程中,还没有整合进标准的内核代码,因此很多地方只考虑了最基本的情况,nfsd4_pnfs_dlm_layoutget()中很多数据都是固定的。首先看一个数据结构struct pnfs_filelayout_layout,这个数据结构定义如下:
// 这是一个file layout的数据结构.
struct pnfs_filelayout_layout {
// layout类型,这里当然是file layout了。
u32 lg_layout_type; /* response */
// 数据分发方式,DENSE还是SPARSE.
u32 lg_stripe_type; /* response */
// COMMIT请求是提交给MDS还是DS.
u32 lg_commit_through_mds; /* response */
// file layout中每个Stripe Unit的大小
u64 lg_stripe_unit; /* response */
// pattern在文件中的起始位置
u64 lg_pattern_offset; /* response */
// 客户端应该首先向哪个DS组传输数据.
u32 lg_first_stripe_index; /* response */
// deviceid
struct nfsd4_pnfs_deviceid device_id; /* response */
// 应答报文中文件句柄数量
u32 lg_fh_length; /* response */
// 这是返回给客户端的文件句柄,客户端向DS传输数据时使用这组文件句柄.
struct knfsd_fh *lg_fh_list; /* response */
};
这个数据结构中包含了LAYOUTGET应答报文中的各种信息,nfsd4_pnfs_dlm_layoutget()的作用就是填充这个数据结构,然后将数据封装进LAYOUTGET应答报文中,这个函数代码如下:
static enum nfsstat4 nfsd4_pnfs_dlm_layoutget(struct inode *inode, // 这是文件索引节点,就是获取这个文件的layout信息.
struct exp_xdr_stream *xdr,
const struct nfsd4_pnfs_layoutget_arg *args, // 这是LAYOUTGET的参数
struct nfsd4_pnfs_layoutget_res *res) // 这是LAYOUTGET的返回值
{
struct pnfs_filelayout_layout *layout = NULL;
struct knfsd_fh *fhp = NULL;
int index;
enum nfsstat4 rc = NFS4_OK;
dprintk("%s: LAYOUT_GET\n", __func__);
/* DLM exported file systems only support layouts for READ */
// file layout目前只支持读操作
// DLM是distributed lock manager的缩写,这是一种分布式锁机制,GFS文件系统依赖这种锁机制实现数据同步.
if (res->lg_seg.iomode == IOMODE_RW)
return NFS4ERR_BADIOMODE;
// 第一个I/O操作应该发给哪个DS组.
index = dlm_ino_hash(inode);
dprintk("%s first stripe index %d i_ino %lu\n", __func__, index,
inode->i_ino);
if (index < 0)
return NFS4ERR_LAYOUTUNAVAILABLE;
res->lg_seg.layout_type = LAYOUT_NFSV4_1_FILES; // file_layout.
/* Always give out whole file layouts */
// file layout目前只能返回这个文件的layout,与用户请求的layout范围无关.
res->lg_seg.offset = 0; // 偏移是0
res->lg_seg.length = NFS4_MAX_UINT64; // 最大长度
/* Always give out READ ONLY layouts */
res->lg_seg.iomode = IOMODE_READ; // 只能是读方式.
layout = kzalloc(sizeof(*layout), GFP_KERNEL);
if (layout == NULL) {
rc = NFS4ERR_LAYOUTTRYLATER;
goto error;
}
/* Set file layout response args */
layout->lg_layout_type = LAYOUT_NFSV4_1_FILES; // file layout.
layout->lg_stripe_type = STRIPE_SPARSE; // 目前固定支持SPARSE方式
layout->lg_commit_through_mds = false; // 将COMMIT请求提交给DS
// 计算Stripe Unit的大小
layout->lg_stripe_unit = get_stripe_unit(inode->i_sb->s_blocksize);
// 只有一个文件句柄,所有的DS使用同一个文件句柄.
layout->lg_fh_length = 1;
// 组装deviceid.
layout->device_id.sbid = args->lg_sbid;
layout->device_id.devid = 1; /*FSFTEMP*/
// 第一个I/O操作应该发给哪个DS组.
layout->lg_first_stripe_index = index; /*FSFTEMP*/
// pattern在文件中的偏移是0.
layout->lg_pattern_offset = 0;
// 为文件句柄分配内存,这个文件句柄供客户端和DS传输数据时使用.
fhp = kmalloc(sizeof(*fhp), GFP_KERNEL);
if (fhp == NULL) {
rc = NFS4ERR_LAYOUTTRYLATER;
goto error;
}
// 下面在组装供客户端和DS传输数据时使用的文件句柄.
memcpy(fhp, args->lg_fh, sizeof(*fhp));
pnfs_fh_mark_ds(fhp);
layout->lg_fh_list = fhp;
/* Call nfsd to encode layout */
// 这是应答消息封装函数,将layout的信息封装进应答报文了.
rc = filelayout_encode_layout(xdr, layout);
exit:
kfree(layout);
kfree(fhp);
return rc;
error:
res->lg_seg.length = 0;
goto exit;
}
看这个函数时可以参考前面介绍file layout的这篇文章:
http://blog.csdn.net/ycnian/article/details/8719051。可以看到,struct pnfs_filelayout_layout结构中大部分字段都是固定值,只有几个值经过了计算,但是这些计算也都很简单。
计算从哪个DS分组开始使用,代码如下:
static int dlm_ino_hash(struct inode *ino)
{
struct dlm_device_entry *de;
u32 hash_mask = 0;
/* If can't find the inode block device in the pnfs_dlm_deivce list
* then don't hand out a layout
*/
// 根据文件系统所在设备的名称查找对应的的dlm_device_entry结构.
de = nfsd4_find_pnfs_dlm_device(ino->i_sb);
if (!de)
return -1; // 没有找到
hash_mask = de->num_ds - 1; // de->num_ds是DS组的数量
return ino->i_ino & hash_mask; // 就是随机选择了一个DS组
}
可以看出,就是随机选择了一个DS分组。
Stripe Unit大小的计算方式如下:
static int get_stripe_unit(int blocksize)
{
// 如果超出了RPC限制,Stripe Unit大小就是数据块大小,
// 每个数据块传输给一个DS组.
if (blocksize >= NFSSVC_MAXBLKSIZE)
return blocksize;
// 结果是blocksize的倍数.
// 在不超过RPC限制的条件下,尽可能将多一些数据块传输给DS组.
return NFSSVC_MAXBLKSIZE - (NFSSVC_MAXBLKSIZE % blocksize);
}
这个函数只生成了一个文件句柄,所有的DS都使用这个文件句柄,文件句柄的组装方式如下:
static inline void pnfs_fh_mark_ds(struct knfsd_fh *fh)
{
BUG_ON(fh->fh_version != 1);
BUG_ON(pnfs_fh_is_ds(fh));
fh->fh_fsid_type += FSID_MAX;
}
memcpy(fhp, args->lg_fh, sizeof(*fhp));
pnfs_fh_mark_ds(fhp);
layout->lg_fh_list = fhp;
从上面这个函数可以看出,DS就使用MDS的文件句柄,只是修改了fsid type。