深入理解Lustre文件系统-第6篇 OST和obdfilter

/* Sigh -really, this is an OSS, the _server_, not the _target_ */

static intost_setup(struct obd_device *obd, obd_count len, void *buf)

{ ... }

from Lustre source tree b16

如果我们正确地理解了上述注释,Lustre源码树lustre/ost和所有的以ost_开头的函数名可能都应该作为服务器(OSS)函数。

6.1OSS和OST

OST以内核模块的形式加载。它和obdfilter紧密合作,完成了服务器/OST端的大部分工作。在这两层中,OSS是交换层(switch layer),或者薄层(thin layer),它解释从Portal RPC来的请求,为请求做准备,然后将请求传递到obdfilter做进一步处理。在接下来的讨论中,我们集中讨论它的两个方面:初始建立和交换结构,它们分别由ost_setup()和ost_handle()完成。

初始建立

  • 首先,OST检查OSS的线程数是否确定了。如果没有,则根据CPU和内存计算最小线程数,确保最大和最小线程数之间有四倍的动态范围。

oss_min_threads= num_possible_cpus() * num_physpages >> (27 - CFS_PAGE_SHIFT);

if(oss_min_threads < OSS_THREADS_MIN)

oss_min_threads = OSS_THREADS_MIN;

/* Insure a 4xrange for dynamic threads */

if(oss_min_threads > OSS_THREADS_MAX / 4)

oss_min_threads = OSS_THREADS_MAX / 4;

oss_max_threads= min(OSS_THREADS_MAX, oss_min_threads * 4 + 1);

为了得到OST的obd设备,使用了如下的函数调用:

struct ost_obd*ost = &obd->u.ost;

  • 然后服务器端初始化RPC服务,如下:

ost->ost_service= ptlrpc_init_svc( , , , , , , ost_handle, , , , "ll_ost");

这个函数返回指向结构体ptlrpc_service的指针。这里需要指出的一个重要的事情是,我们已经提供了一个处理函数ost_handle。一旦像下面所示的一样,服务启动了,Portal RPC将向这个处理函数派遣(dispatch)请求来做进一步处理。这是下一节中的内容。

  • prtrpc线程开始,如下:

rc =ptlrpc_start_threads(obd, ost->ost_service);

重复执行类似的调用序列,创建ost create线程,而服务处理函数设置为ost->ost_create_service。这个流程还为创建ost io线程而重复执行,而服务处理函数设置为ost->ost_io_service。

最后,ping驱逐(eviction?)服务开始了。

派遣(dispatching)

处理函数使用一个输入参数struct ptlrpc_request *req,而它大部分由请求的类型驱动。解码请求的类型是通过将req->rq_reqmsg(它指向结构体lustre_msg)传递到一个由Portal RPC提供的帮助函数lustre_msg_get_opc()来完成的。所以派遣的结构类似于:

swtich(lsutre_msg_get_opc(req->rq_reqmsg)) {

case OST_CONNECT:

...

rc = target_handle_connect(req, ost_handle);

break;

case OST_CREATE:

...

rc = ost_create(req->rq_export, req, oti);

break;

case OST_WRITE:

...

rc = ost_brw_write(req, oti);

RETURN (rc);

case OST_READ:

...

rc = ost_brw_read(req, oti);

RETURN(rc);

}

意外处理包括可能出现的恢复,这种恢复可能在除了OST_CONNECT之外的任何请求中出现。另外,我们需要通过检查req->rq_export是否为NULL来检查连接是否从未知客户端而来。

6.2 OSS目录布局

这节介绍当登录到一个OST节点上时,你将在磁盘中观察到那些东西。现在为止,磁盘中的文件系统最有可能是ldiskfs。这意味着后端数据实际上以普通文件的方式存储,以一种Lustre特有的方法组织着:

组号

在OST的顶层目录下是以各组命名的子目录。这种布局容许了集群MDS的存在,而每个组对应一个MDS。就目前来说,只使用一个MDS,所以只有第零组是有效的。

对象ID

在每个组里,创建了32个子目录。对每个文件对象,它的最后五位用来表明这个文件应该放置在哪个子目录中。这里,文件名是对象ID。

6.3 obdfilter

obdfilter设备是在OST服务初始化的时候创建的。对每个OST我们有一个相对应的obdfilter设备。对每个客户端连接,obdfilter创建一个输出口(export)作为输出的管道。所有的输出口都维护在一个全局哈希表中,哈希关键字称为UUID,在Figure 10和11中都给出了。Portal RPC层使用UUID来快速确定到来的请求应当送去哪个输出口(和obdfilter设备)。另外,每个obdfilter设备维护一个它服务的输出口链表。这种关系在Figure 10中画出来了。

obdfilter提供了如下函数:

  • 处理创建请求,该请求可能是来自于MDS的对文件数据对象的请求。
  • 处理读取和限额如请求,该请求来自于OSC客户端。
  • 处理连接和断开连接请求,该请求是来自于低层Portal RPC层的,对已建立好的输出口和输入口的请求。
  • 处理销毁请求(牵涉到客户端和MDS两方面)。

6.3.1文件删除

销毁协议如下所述。首先,客户端决定删除一个文件,而这个请求传送到了MDS。MDS检查了EA分条,并用llog产生了一个事务日志。这个日志包含如下:<从OST1中unlink对象1,从OST2中unlink对象2,等等>。然后MDS将布局和事务日志传输到客户端。客户端得到这个日志,并与每个OST(实际上是obdfilter)联系,删除(unlink)每个文件对象。一旦所有在MDS上的的删除(unlink)llog记录都已经被告知,那么文件删除过程就结束了。

6.3.2文件创建

正如早先在第5节所讲的,所有的请求都由OST和obdfilter一起处理。现在我们来理顺处理创建请求的流程。处理请求的第一部分是在ost_create()进行了如下操作:

1. 准备回复消息的大小。这由两个记录组成,所以需要两块缓冲。第一个记录是为portalrpc body准备的,而第二个是为ostreply body准备的。

__u32 size[2] ={ sizeof(struct ptlrpc_body), sizeof(*repbody)};

2. 从原始请求中取得一个请求体的指针,并在需要的时候进行字节交换。

struct ost_body*body = lustre_swab_reqbuf(req, REQ_REC_OFF,

sizeof(*body),lustre_swab_ost_body);

最后一个参数是swab处理函数,只有在需要交换的时候才调用它。客户端的请求使用本地字节序,里面包含一个预先商量好的魔数。服务器端读取这个魔数,并用以确定是否需要进行交换。

3. 进行实际的空间分配,并填入初步的头信息。

rc =lustre_pack_replay(req, 2, size, NULL);

repbody =lustre_msg_buf(req->rq_repmsg, REPLY_REC_OFF,sizeof(*repbody));

在第一个调用之后,req->rq_repmsg()指向新分配的空间。第二个调用为回复体缓冲设置了开始地址的repbody。

4. 最后,它用与请求体完全一样内容填充回复体,然后传给obdfilter做进一步处理。

memcpy(&repbody->oa,&body->oa, sizeof(body->oa));

req-rq_status =obd_create(exp, &repbody->oa, NULL, oti);

对于创建请求,obdfilter的入口点是通过filter_create():

static intfilter_create(struct obd_export *exp, struct obdo *oa ..).

我们忽略了牵涉到struct lov_stripe_md **ea和struct obd_trans_info *oti的处理过程,这是因为前者是遗留代码,将来不大可能使用。

1. 首先,保存当前上下文,并指定客户端的上下文为它自己的操作上下文。这是为让线程在想要访问后端文件系统时,为其确定必须的信息。这就像一个sandbox(?),限制在处理客户端请求时,服务线程的reach(?)。它为服务线程存储“文件系统根”和“当前工作目录”(当然,不是从客户端取得,而是取决于我们正在哪个个OST上工作)。

obd =exp->exp_obd;

put_ctxt(&saved,&obd->obd_lvfs_ctxt, NULL);

2. 如果请求的目的是重新创建一个对象,那么我们取消所有加在重建对象上的extent锁,取消方法是要求所有加在对象上的锁调用filter_recreate()来做实际的工作。否则,我们按照正常的重建对象的流程执行。重建的原因是,从概念上讲,当MDS指示OST创建一个对象,OST并不单单创建一个对象,而是创建多个已指定了对象ID的对象。创建的这一组对象的磁盘大小是零。这样做的目的是,当下一次MDS回复客户端创建新文件的请求时,它不用再向OST发送请求,就能为客户端呈现布局信息。通过查看每个OST中预先创建的对象池,MDS就可能已经拥有所有用来回复客户端所需的信息。

if(oa->o_valid & OBD_MD_FLFLAGS) &&

(oa->o_flags & OBD_FL_RECREATE_OBJS)){

rc =ldlm_cli_enqueue_local(obd->obd_namespace, &res_id, ... );

rc = filter_recreate(obd, oa);

ldlm_lock_deref(&lockh, LCK_PW);

} else {

rc = filter_handle_precreate(exp, oa,oa->o_gr, oti);

}

这里,从预先创建处理函数中返回的rc要么是一个负数,表明错误,要么是一个非负数,表明创建的文件数目。

3. 现在,我们更近一点看看预先创建函数:

当客户端通过一个预先创建对象ID与一个OST联系时,OST知道这个对象ID现在被激活了。但是这里出现一个问题:如果MDS挂了,它关于预先创建对象的信息就过时了。为了解决这个冲突,当MDS重启时,它检查未使用的预先创建对象的记录,向OST发送请求,删除那些对象(删除孤儿)。obdfilter接收这些请求,跳过那些实际上已经被使用(但是没有和MDS自己的记录同步)的对象,将剩下的对象删除。这就是filter_handle_precreate()在第一部分中所需要做的:

if((oa->o_valid & OBD_MD_FLFLAGS) &&

(oa->o_flags & OBD_FL_DELORPHAN)) {

down(&filter->fo_create_lock);

rc = filter_destroy_precreated(exp,oa,filter);

...

} else {

rc = filter_precreate(obd, oa, group, &diff);

...

}

4. 最终,创建请求传递到fsfilt,由VFS调用完成。这个进程稍后将经过更多的步骤,例如取得父亲索引节点,事务创建等等。

rc =ll_vfs_create(dparent->d_inode, dchild,

S_IFREG | S_ISUID | S_ISGID | 0666, NULL);

本文章欢迎转载,请保留原始博客链接http://blog.csdn.net/fsdev/article

你可能感兴趣的:(深入理解Lustre文件系统-第6篇 OST和obdfilter)