Aio即Async IO,AioCompletion即Async Io Completion,也就是Async IO完成时的回调处理制作,librados设计AioCompletion就是为了提供一种机制对Aio完成时结果码的处理。而处理函数则由使用者来实现。
设计
相关类设计
librados设计了两个类:AioCompletion、AioCompletionImpl
类的关系图如下:
从上面可以看出,AioCompletion是librados设计开放的库API,真正的设计逻辑AioCompletionImpl中。
IoctxImpl
该类是pool的上下文信息,一个pool对应一个IoctxImpl对象。librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。它的处理过程如下:
1)把请求封装成ObjectOperation 类(osdc 中的)
2)把相关的pool信息添加到里面,封装成Objecter::Op对像
3)调用相应的函数 objecter- >op_submit 发送给相应的OSD
4)操作完成后,调用相应的回调函数。
AioCompletionImpl
Aio即Async IO,AioCompletion即Async Io Completion,也就是Async IO完成时的回调处理制作,librados设计AioCompletion就是为了提供一种机制对Aio完成时结果码的处理。而处理函数则由使用者来实现。AioCompletion是librados设计开放的库API,真正的设计逻辑在AioCompletionImpl中。
对于AIoCompletion实例的使用都是引用的pc,即AioCompletionImpl,因此具体来说应该是如何包装AioCompletionImpl。这里提一下,librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。而AioCompletionImpl是IO操作的回调,因为对于AioCompletionImpl的包装设计在IoCtxImpl模块中
回调的设计
AioCompletionImpl设计了两种类型的回调,分别用两组成员来表示
(1)callback_complete callback_complete_arg
普通的回调处理函数和参数
(2)callback_safe callback_safe_arg
跟上面的区别就在于在设置和执行回调函数的时候,需要加锁/解锁
同步设计
通过使用线程条件允许使用者阻塞(进入线程条件等待)来等待Aio完成,一般都是执行回调函数之后,发送线程条件信号唤醒阻塞线程。
使用
获取AioCompletion实例
librados开放的创建AioCompletion实例API有两个
librados::AioCompletion *librados::Rados::aio_create_completion()
{
AioCompletionImpl *c = new AioCompletionImpl;
return new AioCompletion(c);
}
librados::AioCompletion *librados::Rados::aio_create_completion(void *cb_arg,
callback_t cb_complete,
callback_t cb_safe)
{
AioCompletionImpl *c;
int r = rados_aio_create_completion(cb_arg, cb_complete, cb_safe, (void**)&c);
assert(r == 0);
return new AioCompletion(c);
}
本质上都一样,只不过对于第二种,它允许使用者直接设置回调和回调参数。使用者也可以通过
librados::AioCompletion::AioCompletion::
系列的接口来设置相关的回调及属性。
举例子:
librbd/Utils.h里面定义了6个根据模版来创建不同的AioCompletion实例的接口
template <typename T>
librados::AioCompletion *create_rados_ack_callback(T *obj) {
return librados::Rados::aio_create_completion(
obj, &detail::rados_callback<T>, nullptr);
}
template <typename T, void(T::*MF)(int)>
librados::AioCompletion *create_rados_ack_callback(T *obj) {
return librados::Rados::aio_create_completion(
obj, &detail::rados_callback<T, MF>, nullptr);
}
...
还有在cls、journal等等其他地方中,总之,就是我们最开始说到的librados::AioCompletion的设计目的,再同librados交互的模块中,如果有需要用到对交互操作结果处理的地方,就需要通过librados::AioCompletion来处理结果码。
创建一个AioCompletion实例还不够,在ceph中,所有的结果码都是包含在Context上下文中,因此对于使用者来说,要将AioCompletion包装在一个上下文实例中,这样就可以在上下文结束的时候,将结果码直接传递给AioCompletion实例的回调函数来处理。
包装AioCompletionImpl的过渡Context
对于AIoCompletion实例的使用都是引用的pc,即AioCompletionImpl,因此具体来说应该是如何包装AioCompletionImpl。这里提一下,librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。而AioCompletionImpl是IO操作的回调,因为对于AioCompletionImpl的包装设计在IoCtxImpl模块中,比如:
struct C_aio_Complete : public Context {
struct C_aio_stat_Ack : public Context {
struct C_aio_stat2_Ack : public Context {
等等。
为什么说是过渡Context?
(1)它不是真正执行AioCompletionImpl的地方
(2)它是负责同osdc交互的Context
每个过渡Context的设计都有些不同的处理逻辑,对于一个过渡Context,再这里我们关注两点: 1. 被谁调用。 2. Context的结束处理finish方法。即:
(1)被谁调用
每个包装AioCompletionImpl之后的Context实例会被封装到osdc/Objecter::Op.finisher中,每个同osdc交互的模块都要通过Objecter模块,而每个操作都会通过Op结构体来表示。每个Op都是同osd交互的基础操作,因此在objecter会处理每个同osd交互Op的返回,在处理osd返回的时候,会调用Op->finisher->complete,进而调用AioCompletionImpl包装Context的finish处理。这就是AioCompletionImpl包装Context被执行的地方。
(2)AioCompletionImpl过渡Context的结束处理函数
绝大部分封装的Context的结束处理函数finish方法中,都将AioCompletionImpl再包装在一个新的Context中,然后将这个新的Context放入finisher队列,而这个新的Context才是真正执行AioCompletionImpl的Context,因此等会儿可以关注下这个新的Context的结束处理函数。另外你可以发现这些新的Context都是设计在librados/AioCompletionImpl中,由此也可以知道,这个Context才是真正执行AioCompletionImpl的地方。
client->finisher.queue(new librados::C_AioComplete(c));
这里由提一下Finisher对Context的处理,在调用librados的时候,会申请一个librados::RadosClient实例,此时会初始化一个Finisher实例,这是一个处理Context实例的线程,在RadosClient::connect的时候,会启动Finisher线程。使用者可以将Context放入Finisher队列,之后,Finisher线程就从队列中拿出Context并执行(调用complete方法)。
接着就是新的Context的结束处理函数finish方法,也就是librads/AioCompletionImpl.h中定义的Context。在这个Context中的处理逻辑就相对简单点,基本就是根据前面对AioCompletionImpl的一些设置来调用回调处理,没什么特别的,最终都是调用回调处理函数
cb_complete(c, cb_complete_arg);
cb_safe(c, cb_safe_arg);
例子
了解完librados的AIO回调机制之后,我们以一个例子来理解下这个机制。
librbd的创建rbd镜像接口调用流:
librbd::rbd_create2 -> internal::create -> CreateRequest::send() -> CreateRequest::validate_pool()
template<typename I>
void CreateRequest<I>::validate_pool() {
if (!m_cct->_conf->rbd_validate_pool) {
create_id_object();
return;
}
ldout(m_cct, 20) << this << " " << __func__ << dendl;
using klass = CreateRequest<I>;
librados::AioCompletion *comp =
create_rados_ack_callback<klass, &klass::handle_validate_pool>(this);
librados::ObjectReadOperation op;
op.stat(NULL, NULL, NULL);
int r = m_ioctx.aio_operate(RBD_DIRECTORY, comp, &op, &m_outbl);
assert(r == 0);
comp->release();
}
aio_operate先经过librados::IoCtx,再到librados::IoCtxImpl
int librados::IoCtxImpl::aio_operate(const object_t& oid,
::ObjectOperation *o, AioCompletionImpl *c,
const SnapContext& snap_context, int flags)
{
FUNCTRACE();
OID_EVENT_TRACE(oid.name.c_str(), "RADOS_WRITE_OP_BEGIN");
auto ut = ceph::real_clock::now();
/* can't write to a snapshot */
if (snap_seq != CEPH_NOSNAP)
return -EROFS;
Context *onack = new C_aio_Complete(c);
#if defined(WITH_LTTNG) && defined(WITH_EVENTTRACE)
((C_aio_Complete *) onack)->oid = oid;
#endif
c->io = this;
queue_aio_write(c);
Objecter::Op *op = objecter->prepare_mutate_op(
oid, oloc, *o, snap_context, ut, flags,
onack, &c->objver);
objecter->op_submit(op, &c->tid);
return 0;
}
onack会被丢入Objecter::Op.onfinish,在处理Op回复的时候,会调用
void Objecter::handle_osd_op_reply(MOSDOpReply *m)
....
// do callbacks
if (onfinish) {
onfinish->complete(rc);
}
这个onfinish是过渡Context,即C_aio_Complete,因此下面看C_aio_Complete的finish方法
void librados::IoCtxImpl::C_aio_Complete::finish(int r)
{
c->lock.Lock();
c->rval = r;
c->complete = true;
c->cond.Signal();
if (r == 0 && c->blp && c->blp->length() > 0) {
if (c->out_buf && !c->blp->is_provided_buffer(c->out_buf))
c->blp->copy(0, c->blp->length(), c->out_buf);
c->rval = c->blp->length();
}
if (c->callback_complete ||
c->callback_safe) {
c->io->client->finisher.queue(new C_AioComplete(c));
}
....
finisher线程取出item执行的Context是C_AioComplete,下面看下C_AioComplete的finish方法
void finish(int r) {
c->lock.Lock();
c->rval = r;
c->complete = true;
c->lock.Unlock();
rados_callback_t cb_complete = c->callback_complete;
void *cb_complete_arg = c->callback_complete_arg;
if (cb_complete)
cb_complete(c, cb_complete_arg);
rados_callback_t cb_safe = c->callback_safe;
void *cb_safe_arg = c->callback_safe_arg;
if (cb_safe)
cb_safe(c, cb_safe_arg);
c->lock.Lock();
c->callback_complete = NULL;
c->callback_safe = NULL;
c->cond.Signal();
c->put_unlock();
}
这个回调即是创建librados::AioCompletion的时候制定的handle_validate_pool参数。
注意点
每个Op对应一个回调,比如创建块设备镜像会对应很多Op,第一个Op的处理是handle_validate_pool,在这个handle里面会再发送第二Op,并挂上第二个Op的回调handle_create_id_object,以此类推下去。
本博文的AioCompletion机制只是librados的机制,ceph中很多模块都设计有这种类似的机制,librbd里面也又AioCompltion模块,读者需要注意命名空间。
原文链接:
(17条消息) ceph源代码分析之librados:1. AioCompletion回调机制分析_hawkerou的博客-优快云博客