/* 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