从layout到extent的演变史
client端在读/写文件时,先从server端申请layout,才能向磁盘读/写文件,写文件时又有extent,这又是如何一个演变过程,余决心把这个过程搞得清清楚楚,不知不止。
Clients和Storage Devices传输数据时需要使用专门的存储协议,因为client直接从storage device读取数据。RFC定义了三种存储协议:file layout(RFC5661)、block layout(RFC5663)、object layout(RFC5664)。
我们的pnfs是block layout类型。
layout
什么是layout
layout翻译过来是布局,那布局是什么。
在rfc5663文档中是这样写的:
A pNFS layout for this block/volume class of storage is responsible for mapping from an NFS file (or portion of a file) to the blocks of storage volumes that contain the file.
layout是文件内指定区域的数据是如何分布到存储设备上的。可以这样理解,client要从存储设备上读取文件,client首先要找到在哪个存储设备上哪个位置分布,client向server发送请求,server管理layout信息,server将layout信息给client,client拿到layout信息后去读文件内容。client不需要理解layout信息内容,而是把layout信息传递给布局驱动layout driver来解释和处理,由layout driver根据layout完成文件数据到物理设备的读写。这里就到了我想知道的extent。
布局也可以理解为一种授权,拿到布局信息,才能读取文件内容。
layout的相关操作
LAYOUTGET:从元数据服务器MDS那里请求一个布局,用于读写文件指定区域。请求中会给定所请求的文件的文件句柄,要读写的逻辑区域的偏移和长度,以及请求的类型(读请求或者写请求);
LAYOUTCOMMIT:客户端将布局修改提交到元数据服务器。客户端需要保证在布局提交之前,数据已经写入到存储设备之中;
LAYOUTRETURN:客户端用此操作来归还已经获得的布局信息。归还完毕后,客户端不得再使用此布局信息来访问存储设备。归还的布局信息可能是之前获得的布局信息的一个子集。LAYOUTGET操作的返回值中有一个标志,叫做关闭时归还,用于指示客户端在文件关闭时即归还所得布局;
CB_LAYOUTRECALL:服务器用此操作来召回已经授权给客户端的布局,当数据发生改变,server阻止client使用旧的布局来从存储设备上访问文件。
client用layout读取文件过程
涉及文件读写的操作分为两部分,一部分是元数据服务器的,另一部分是客户端的。元数据服务器上主要包括分配资源和更新文件映射;客户端上的操作主要包括:获取布局、进行读写IO和提交布局修改。
当客户端需要读写文件时,它先以pNFS协议将请求发送到元数据服务器,然后元数据服务器上的pNFS服务进程会向底层存储系统(负责管理元数据的文件系统)请求文件的布局信息。底层存储系统将布局信息返回给pNFS服务进程,然后pNFS服务进程将结果按pNFS协议的标准回复给客户端。客户端将收到的布局信息传给layout driver,最后layout driver根据布局信息完成数据读写。
server的底层文件系统负责管理文件布局?
那么server端底层的文件系统是如何管理的呢?
NFS是一种网络文件系统,它必须依附于一种实际的文件系统之上(比如EXT3、JFS等)。如果底层的文件系统支持pNFS,则这种文件系统必须实现struct pnfs_export_operations中定义的方法。
struct pnfs_export_operations {
/* Returns the supported pnfs_layouttype4. */
int (*layout_type) (struct super_block *);
/* Encode device info onto the xdr stream. */
int (*get_device_info) (struct super_block *,
struct exp_xdr_stream *,
u32 layout_type,
const struct nfsd4_pnfs_deviceid *);
int (*layout_get) (struct inode *,
struct exp_xdr_stream *xdr,
const struct nfsd4_pnfs_layoutget_arg *,
struct nfsd4_pnfs_layoutget_res *);
/* Commit changes to layout */
int (*layout_commit) (struct inode *,
const struct nfsd4_pnfs_layoutcommit_arg *,
struct nfsd4_pnfs_layoutcommit_res *);
/* Returns the layout */
int (*layout_return) (struct inode *,
const struct nfsd4_pnfs_layoutreturn_arg *);
};
这个数据结构定义了MDS中layout相关的函数。
代码中的layout
layout相关的数据结构
Pnfs支持三种类型中的哪一种layout类型。三种类型layout编号如下:
enum pnfs_layouttype {
LAYOUT_NFSV4_FILES = 1,
LAYOUT_OSD2_OBJECTS = 2,
LAYOUT_BLOCK_VOLUME = 3,
};
layout编号从1开始,0是一个保留序号,不允许出现编号为0的layout类型。
和pnfs_layouttype相关的数据结构是pnfs_layoutdriver_type
/* Per-layout driver specific registration structure */
struct pnfs_layoutdriver_type {
const u32 id; /* id取值1、2、3三种layouttype */
const char *name;
struct layoutdriver_io_operations *ld_io_ops;
struct layoutdriver_policy_operations *ld_policy_ops;
};
client端调用set_pnfs_layoutdriver函数,根据id设置pnfs支持的layout类型。当前每个pnfs只支持一种类型的layout。设置server->pnfs_curr_ld,pnfs当前使用的layout类型。
我们这个是支持block layout,对函数set_pnfs_layoutdriver做了一些修改,设置pnfs支持的layout类型时block layout,调用bl_initialize_mountpoint,为挂载点遍历可用的设备链表(发现只是修改了原pnfs的函数bl_set_layoutdriver)。
初始化结构体block_mount_id
struct block_mount_id {
struct super_block *bm_sb; /* back pointer */
spinlock_t bm_lock; /* protects list */
struct list_head bm_devlist; /* holds pnfs_block_dev */
};
bm_sb为传递的超级快,初始化链表等,这个应该是mount时调用的,mount时会指出挂载点,为挂载点找到设备,后面在挂载点的操作创建文件、删除文件等读写,都是需要设备的,挂载点只是个名称,最后写还是要写入到设备上。
跑偏了回来,layout在client端是如何存放的?
client端,每个layout用数据结构struct pnfs_layout_segment表示,每个layout表示文件中的一段数据。用户可能申请了文件中多个数据段的layout,因此同一个文件中所有的layout构成了一个链表,这个链表是在数据结构struct pnfs_layout_type的segs链表中。用户还可能访问了多个文件,每个文件对应一个struct pnfs_layout_type结构,struct pnfs_layout_type又在struct nfs_inode结构中,client端会有多个pnfs_layout_type结构,在nfs_inode结构中有链表lo_inodes,表示有layout的的nfs_inode链表,链表头在struct nfs_client有cl_lo_inodes链表。
struct pnfs_layout_type {
int refcount;
atomic_t lretcount; /* Layoutreturns outstanding */
atomic_t lgetcount; /* Layoutgets outstanding */
struct list_head segs; /* layout segments list */
int roc_iomode; /* iomode to return on close, 0=none */
seqlock_t seqlock; /* Protects the stateid */
nfs4_stateid stateid;
void *ld_data; /* layout driver private data */
};
struct nfs_inode {
/* Inodes having layouts */
struct list_head lo_inodes;
struct pnfs_layout_type layout;
};
struct pnfs_layout_segment {
struct list_head fi_list;
/* layout在文件中的数据范围和访问方式 */
struct nfs4_pnfs_layout_segment range;
struct kref kref;
bool valid;
struct pnfs_layout_type *layout;
u8 ld_data[]; /* layout driver private data */
};
struct nfs4_pnfs_layout_segment {
u32 iomode; /* 访问模式 */
u64 offset; /* layout在文件中的起始位置 */
u64 length; /* layout的长度 */
};
这样仅仅是把代码中layout的数据结构清晰化了,但函数实际执行时时什么情况并不清晰,下面通过具体函数来看layout。
先写到这里。
参考:
[1] http://www.rfc-editor.org/rfc/rfc5663.txt rfc5663主要针对块存储
[2] http://blog.csdn.net/ycnian/article/details/8732856