Librbd是Ceph提供块存储的库,它实现了RBD接口,基于LIbrados实现了对RBD的基本操作。Librbd对于元数据的相关操作是通过cls_rbd实现的。cls_rbd是Cls的一个扩展模块,Cls允许用户自定义对象的操作接口和实现方法,为用户提供了一种比较直接的接口扩展方式。Librbd的数据读写相关操作则是通过直接Librados来直接访问。

Librbd 包含了rbd的相关操作,并发送给ImageRequestWQ类处理(队列),而后该类将其中的的请求发送给ImageRequest处理,ImageRequest将Image进行分片(将一个 块 分解成 对象 进行处理,Ceph的底层本质还是对象存储) 等操作后,将各个对象调用ObjectRequest类进行处理 ,每个ObjectRequest请求分别处理。ImageRequest和ObjectRequest下面都包含很多相关操作的子类,用子类实现了具体的接口。关于RBD的读写流程用了函数调用的流程图表示,思路更加清晰,并且最新版本函数、类的名字都改变了。 
结构如图:

ClsCeph的扩展模块
Librbd 库
RBD元数据
源码目录简单介绍
RBD的创建
RBD 数据读写流程
Cls——Ceph的扩展模块
cls_rbd是Cls的一个扩展模块,Cls允许用户自定义对象的操作接口和实现方法,为用户提供了一种比较直接的接口扩展方式。 
该模块下包含几个文件:

1)cls_rbd_types 主要是注册了rbd模块,以及自定义的一些方法...

...
...
//对应的方法
int create(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{string object_prefix;uint64_t features, size;uint8_t order;int64_t data_pool_id = -1;try {bufferlist::iterator iter = in->begin();::decode(size, iter);::decode(order, iter);::decode(features, iter);::decode(object_prefix, iter);if (!iter.end()) {::decode(data_pool_id, iter);}} catch (const buffer::error &err) {return -EINVAL;}.........//注册模块以及方法cls_register("rbd", &h_class);cls_register_cxx_method(h_class, "create",CLS_METHOD_RD | CLS_METHOD_WR,create, &h_create);.........

2)cls_rbd_client 定义了客户端访问rbd函数。调用模块和客户端的读写操作流程类似,都是通过ioctx->ecex 函数,封装操作并发送给对应的OSD处理。见( 超链接留空 )

Librbd 库
RBD是ceph中提供块存储的客户端服务,之所以说是客户端服务是相对于RADOS而言,RBD是基于librados API开发的一个块存储服务。Ceph底层的实现是对象存储,通过librbd实现了块存储的接口,对外提供块存储的服务。 
所以,块存储在客户端也会被拆成n个对象来存储处理。

下面摘取 
麦子迈:解析Ceph: Librbd–块存储库 中的基础名词介绍。

Image: 对应于LVM的Logical Volume,是能被attach/detach到VM的载体。在RBD中,Image的数据有多个Object组成。(image 镜像 可以作是块存储的呈现形式)

Snapshot: Image的某一个特定时刻的状态,只能读不能写但是可以将Image回滚到某一个Snapshot状态。Snapshot必定属于某一个Image。

Clone: 为Image的某一个Snapshot的状态复制变成一个Image。如ImageA有一个Snapshot-1,clone是根据ImageA的Snapshot-1克隆得到ImageB。ImageB此时的状态与Snapshot-1完全一致,区别在于ImageB此时可写,并且拥有Image的相应能力。
关于 块存储 原理的一些东西,可以操作集群 实际看看:Ceph介绍之RBD实现原理

RBD元数据
当在一个pool里面创建一个RBD对象的时候,会对应的生成其它的对象以及相关的元数据用于管理rbd。

rbd _dircetory对象:该对象在每一个Pool都存储在,用来保存该pool下的所有的RBD的目录信息。当创建RBD设备的时候,会首先检查该对象的存在,如无则创建。该对象中包含了 Ceph中专门用于存储元数据 的结构omap。该omap属性里保存了所有RBD设备的名字和ID信息。在omap属性中 保存这key-value键值对,key为 “name”+设备名字,value为”_id“+RBD的ID 。
另外还会创建id_obj对象:对象名字为 rbd_id. ,内容为RBD的ID信息。
head_obj对象:其中的omap保存了RBD相关的元数据。
rbd_object_map. :保存其对象和父image对象的映射信息。
另外还会创建数据对象 ,用于存储数据,因为Ceph存储的本质就对象存储,所以即使提供的块存储也要拆分成对象来管理。

源码目录简单介绍
librbd 模块下的源代码现在文件比较多。根据网上别人的总结。。。。 
http://blog.csdn.net/scaleqiao/article/details/51165598

1)operation, 这个目录里实现了关于image的主要操作,包括flatten、resize、trim、snapshot create、snapshot remove、snapshot protect、snapshot unprotect、snapshot rename、snapshot rollback

2)librbd.cc,这个文件里并没有具体实现所有的接口,而其实大部分接口的具体实现是在internal.cc,除了I/O相关的接口,read/write。

3) ImageCtx.h和ImageCtx.cc,定义了image的上下文,包括image layout的初始化、卷/snap相关元数据的获取接口以及后来引入的rbd mirror的相关元数据结构(object map、journal 等)的创建。

4)AioImageRequestWQ.h/AioImageRequestWQ.cc、AioImageRequest.h/AioImageRequest.cc,实现了aio相关的I/O接口,进入rbd的read请求会转变成一个AioImageRead的实例,而write请求会变成一个AioImageWrite的对象。而这些对象最终会分别调到AioImageRead.send_request()和AbstractAioImageWrite::send_request()这两个函数,来处理IO请求(其中主路径中关于journal的部分可以先略过去)。

5) AioObjectRequest.h/AioObjectRequest.cc,I/O请求会通过Striper::file_to_extents()被影射成针对某个/某些对象的操作,这些操作都被定义/实现在这两个文件中

6) AioCompletion.h/AioCompletion.cc,这两个文件实现了Aio的回调处理逻辑。上层应用可以通过rbd_aio_create_completion()创建一个AioCompletion的对象,并传入自己的回调函数。这样当具体操作结束之后,AioCompletion对象会调用上层应用的回调函数

7) LibrbdWriteback.h/LibrbdWriteback.cc,这两个文件主要是通过现有的ObjectCacher机制,在librbd中支持Writeback这种I/O方式

8) ImageWatcher.h/ImageWatcher.cc,这是后来引入的watch/notify机制,用于增加了管理image exlusive lock的支持

9) 日志 Journal*,这些文件主要实现rbd mirror的功能

10)CopyupRequest.h/CopyupRequest.cc,这个主要用于处理clone卷的逻辑

看完思路清晰不少。但是目前最新的版本而言,改动了一些,主要是名字的AIo都没了。 但是跳转的处理逻辑依然还是差不多 集中在 ImageRequestWQ、ImageRequest、ObjectRequest之中。

RBD的创建
rbd有很多个创建函数。根据传入的参数不同来调用不同的函数。其过程比较简单,都是调用librbd::create来设置相应的参数。RBD的创建过程,其本质是调用Cls的RBD模块设置相关的元数据信息。相关的元数据的操作函数也一样。

 int RBD::create(IoCtx& io_ctx, const char *name, uint64_t size, int *order){TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));tracepoint(librbd, create_enter, io_ctx.get_pool_name().c_str(), io_ctx.get_id(), name, size, *order);int r = librbd::create(io_ctx, name, size, order);//设置参数tracepoint(librbd, create_exit, r, *order);return r;}.........int RBD::create4(IoCtx& io_ctx, const char *name, uint64_t size,ImageOptions& opts){TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));tracepoint(librbd, create4_enter, io_ctx.get_pool_name().c_str(), io_ctx.get_id(), name, size, opts.opts);int r = librbd::create(io_ctx, name, "", size, opts, "", "", false);//设置参数tracepoint(librbd, create4_exit, r);return r;}

RBD 数据读写流程
在Imagectx中处理了Image的上下文自信,每一个Image中都有一个AioImageRequestWQ ,用于处理Image的操作,他是一个工作队列,通过线程池来实现异步请求处理。

RBD中包含几个数据操作的关键的类,其中 ObjectRequest用于对某一个对象的处理的基类,而ImageRequest则是处理Image的基类。图中可以看到他们都有不少子类,其中 AbstractImageWrite下面还有 imageWrite、ImageDiscard等子类,图中为画出。而 ImageRequest和ObjectRequest的之间的联系 是在当一个Image的操作跨越了多个对象的时候(一个块会被拆成多个对象进行操作,Ceph本质是对象存储),在每个对象中产生一个ObjectRequest 请求,用于在各自的对象中各自处理。

根据该函数调用关系图,一一展开看卡各个函数,其中用写流程来看,读流程操作类似。

1) rbd_aio_write 处于Librbd里面,librbd 是对外提供rbd接口的库,所以其中包含了较多的rbd请求。rbd的请求都是从这里开始的。extern "C" int rbd_aio_write(rbd_image_t image, uint64_t off, size_t len,
                 const char *buf, rbd_completion_t c)

{librbd::ImageCtx *ictx = (librbd::ImageCtx *)image;librbd::RBD::AioCompletion *comp = (librbd::RBD::AioCompletion *)c;tracepoint(librbd, aio_write_enter, ictx, ictx->name.c_str(), ictx->snap_name.c_str(), ictx->read_only, off, len, buf, comp->pc);bufferlist bl;bl.push_back(create_write_raw(ictx, buf, len));//调用ImageRequestWQ<I>::aio_write  来生成ImageRequest对象ictx->io_work_queue->aio_write(get_aio_completion(comp), off, len,std::move(bl), 0);tracepoint(librbd, aio_write_exit, 0);return 0;
}

2) ImageRequestWQ::aio_write 处于 ImageRequestWQ中,

template <typename I>
void ImageRequestWQ<I>::aio_write(AioCompletion *c, uint64_t off, uint64_t len,bufferlist &&bl, int op_flags,bool native_async) {CephContext *cct = m_image_ctx.cct;ZTracer::Trace trace;if (m_image_ctx.blkin_trace_all) {trace.init("wq: write", &m_image_ctx.trace_endpoint);trace.event("init");}c->init_time(util::get_image_ctx(&m_image_ctx), AIO_TYPE_WRITE);ldout(cct, 20) << "ictx=" << &m_image_ctx << ", "<< "completion=" << c << ", off=" << off << ", "<< "len=" << len << ", flags=" << op_flags << dendl;if (native_async && m_image_ctx.event_socket.is_valid()) {c->set_event_notify(true);}if (!start_in_flight_io(c)) {return;}RWLock::RLocker owner_locker(m_image_ctx.owner_lock);if (m_image_ctx.non_blocking_aio || writes_blocked()) {//将写请求 的 ImageRequest 加入队列中queue(ImageRequest<I>::create_write_request(m_image_ctx, c, {{off, len}}, std::move(bl), op_flags, trace));} else {c->start_op();//直接调用ImageRequest<I>::aio_write处理写请求ImageRequest<I>::aio_write(&m_image_ctx, c, {{off, len}},std::move(bl), op_flags, trace);finish_in_flight_io();}trace.event("finish");
}

3)在2)中加入队列里面的额请求,都会用process来进行处理,最后调用ImageRequest::send 函数来处理

template <typename I>
void ImageRequestWQ<I>::process(ImageRequest<I> *req) {CephContext *cct = m_image_ctx.cct;ldout(cct, 20) << "ictx=" << &m_image_ctx << ", "<< "req=" << req << dendl;req->send();//调用处理finish_queued_io(req);if (req->is_write_op()) {finish_in_flight_write();}delete req;finish_in_flight_io();
}

4) 对于2)中直接调用该函数处理的,最后也是直接调用ImageWriteRequest::send()处理

template <typename I>
void ImageRequest<I>::aio_write(I *ictx, AioCompletion *c,Extents &&image_extents, bufferlist &&bl,int op_flags,const ZTracer::Trace &parent_trace) {ImageWriteRequest<I> req(*ictx, c, std::move(image_extents), std::move(bl),op_flags, parent_trace);req.send();//处理
}

5)类 ImageWriteRequest 继承自 AbstractImageWriteRequest 所以最后调用的是,AbstractImageWriteRequest的send_request()函数。并且,后面调用的函数都类似,都是在ImageWriteRequest类的函数中调用AbstractImageWriteRequest对应的函数。因此直接看AbstractImageWriteReques对应的函数。

ImageRequest.cc

template <typename I>
void AbstractImageWriteRequest<I>::send_request() {I &image_ctx = this->m_image_ctx;CephContext *cct = image_ctx.cct;RWLock::RLocker md_locker(image_ctx.md_lock);bool journaling = false;AioCompletion *aio_comp = this->m_aio_comp;uint64_t clip_len = 0;ObjectExtents object_extents;::SnapContext snapc;{// prevent image size from changing between computing clip and recording// pending async operationRWLock::RLocker snap_locker(image_ctx.snap_lock);if (image_ctx.snap_id != CEPH_NOSNAP || image_ctx.read_only) {aio_comp->fail(-EROFS);return;}for (auto &extent : this->m_image_extents) {if (extent.second == 0) {continue;}// map to object extents//!!!!!!!将image 分片,块->对象的映射(块存储 的image 依然要用对象存储来处理)Striper::file_to_extents(cct, image_ctx.format_string, &image_ctx.layout,extent.first, extent.second, 0, object_extents);clip_len += extent.second;}snapc = image_ctx.snapc;journaling = (image_ctx.journal != nullptr &&image_ctx.journal->is_journal_appending());}int ret = prune_object_extents(object_extents);if (ret < 0) {aio_comp->fail(ret);return;}if (!object_extents.empty()) {uint64_t journal_tid = 0;aio_comp->set_request_count(object_extents.size() + get_object_cache_request_count(journaling));//将分成的对象 分别生成 ObjectRequests 对象 调用send_object_requests 各个对象各自处理ObjectRequests requests;send_object_requests(object_extents, snapc,(journaling ? &requests : nullptr));if (journaling) {// in-flight ops are flushed prior to closing the journalassert(image_ctx.journal != NULL);journal_tid = append_journal_event(requests, m_synchronous);}if (image_ctx.object_cacher != NULL) {send_object_cache_requests(object_extents, journal_tid);}} else {// no IO to perform -- fire completionaio_comp->unblock();}update_stats(clip_len);aio_comp->put();
}

6) AbstractImageWriteRequest::send_object_requests 用于发送请求,如果 journaling 则 将请求加入队列中 否则直接 调用AbstractObjectWriteRequest::send() 发送请求。

template <typename I>
void AbstractImageWriteRequest<I>::send_object_requests(const ObjectExtents &object_extents, const ::SnapContext &snapc,ObjectRequests *object_requests) {I &image_ctx = this->m_image_ctx;CephContext *cct = image_ctx.cct;AioCompletion *aio_comp = this->m_aio_comp;for (ObjectExtents::const_iterator p = object_extents.begin();p != object_extents.end(); ++p) {ldout(cct, 20) << "oid " << p->oid << " " << p->offset << "~" << p->length<< " from " << p->buffer_extents << dendl;C_AioRequest *req_comp = new C_AioRequest(aio_comp);//ObjectRequestHandle是ObjectRequest的父类ObjectRequestHandle *request = create_object_request(*p, snapc,req_comp);// if journaling, stash the request for later; otherwise send//如果 journaling 则 将请求加入队列中 否则直接 调用AbstractObjectWriteRequest<I>::send() if (request != NULL) {if (object_requests != NULL) {object_requests->push_back(request);} else {request->send();}}}
}

7)AbstractObjectWriteRequest::send()先处理object_map相关 ,然后调用AbstractObjectWriteRequest<I>::pre_write_object_map_update(),然后最终调用AbstractObjectWriteRequest<I>::write_object()发送请求,将请求发送到librados处理。

ObjectRequest.cctemplate <typename I>
void AbstractObjectWriteRequest<I>::send() {I *image_ctx = this->m_ictx;ldout(image_ctx->cct, 20) << this->get_op_type() << " " << this->m_oid << " "<< this->m_object_off << "~" << this->m_object_len<< dendl;{RWLock::RLocker snap_lock(image_ctx->snap_lock);if (image_ctx->object_map == nullptr) {m_object_may_exist = true;} else {// should have been flushed prior to releasing lockassert(image_ctx->exclusive_lock->is_lock_owner());m_object_may_exist = image_ctx->object_map->object_may_exist(this->m_object_no);}}if (!m_object_may_exist && is_no_op_for_nonexistent_object()) {ldout(image_ctx->cct, 20) << "skipping no-op on nonexistent object"<< dendl;this->async_finish(0);return;}//最终调用 该函数pre_write_object_map_update();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
template <typename I>
void AbstractObjectWriteRequest<I>::pre_write_object_map_update() {I *image_ctx = this->m_ictx;image_ctx->snap_lock.get_read();if (image_ctx->object_map == nullptr || !is_object_map_update_enabled()) {image_ctx->snap_lock.put_read();write_object();return;}if (!m_object_may_exist && m_copyup_enabled) {// optimization: copyup requiredimage_ctx->snap_lock.put_read();copyup();return;}uint8_t new_state = this->get_pre_write_object_map_state();ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off<< "~" << this->m_object_len << dendl;image_ctx->object_map_lock.get_write();if (image_ctx->object_map->template aio_update<AbstractObjectWriteRequest<I>,&AbstractObjectWriteRequest<I>::handle_pre_write_object_map_update>(CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace, this)) {image_ctx->object_map_lock.put_write();image_ctx->snap_lock.put_read();return;}image_ctx->object_map_lock.put_write();image_ctx->snap_lock.put_read();write_object();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
template <typename I>
void AbstractObjectWriteRequest<I>::handle_pre_write_object_map_update(int r) {I *image_ctx = this->m_ictx;ldout(image_ctx->cct, 20) << "r=" << r << dendl;assert(r == 0);write_object();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
template <typename I>
void AbstractObjectWriteRequest<I>::write_object() {I *image_ctx = this->m_ictx;ldout(image_ctx->cct, 20) << dendl;librados::ObjectWriteOperation write;if (m_copyup_enabled) {ldout(image_ctx->cct, 20) << "guarding write" << dendl;write.assert_exists();}add_write_hint(&write);add_write_ops(&write);assert(write.size() != 0);librados::AioCompletion *rados_completion = util::create_rados_callback<AbstractObjectWriteRequest<I>,&AbstractObjectWriteRequest<I>::handle_write_object>(this);int r = image_ctx->data_ctx.aio_operate(this->m_oid, rados_completion, &write, m_snap_seq, m_snaps,(this->m_trace.valid() ? this->m_trace.get_info() : nullptr));assert(r == 0);rados_completion->release();
}

--------------------- 
作者:SEU_PAN 
来源:CSDN 
原文:https://blog.csdn.net/csnd_pan/article/details/78728743 
版权声明:本文为博主原创文章,转载请附上博文链接!

Ceph学习——Librbd块存储库与RBD读写流程源码分析相关推荐

  1. Ceph Cache Tier中缓存读写流程源码分析

    存储系统:ceph-14.2.22 PrimaryLogPG::do_request [ 文件路径 ] ceph/src/osd/PrimaryLogPG.cc OSD在收到客户端发送的请求时,会调用 ...

  2. Ceph RBD API librdb 读流程源码分析

    文章目录 API 介绍 API 用法 类 源码分析 Image::aio_read2 读写流程 Image::aio_read2 AioImageRequestWQ::aio_read AioImag ...

  3. 【ceph】mkdir|mksnap流程源码分析|锁状态切换实例

    目录 一.mkdir Clientd端的处理 发送请求的流程 发送请求的内容 处理请求的流程 后记 二.mkdir  MDS端的处理 MDS对于来自客户端请求的通用处理 Locker::process ...

  4. [源码学习][知了开发]WebMagic-总体流程源码分析

    写在前面 前一段时间开发[知了]用到了很多技术(可以看我前面的博文http://blog.csdn.net/wsrspirit/article/details/51751568),这段时间抽空把这些整 ...

  5. nimble源码学习——广播流程源码分析1

    广播流程源码分析1 在controller层有多种状态:广播.空闲.连接等等,这次分析的是广播这个状态或者叫 做角色.在前面controller层循环的分析中,可以明确controller层也有eve ...

  6. 分布式定时任务—xxl-job学习(三)——调度中心(xxl-job-admin)的启动和任务调度过程源码分析

    分布式定时任务-xxl-job学习(三)--调度中心(xxl-job-admin)的启动和任务调度过程源码分析 RabbitsInTheGrass 2020-06-30 10:31:08  813   ...

  7. Framework学习之路(一)—— UI绘制深入源码分析

    Framework学习之路(一)-- UI绘制深入源码分析 本篇为笔者对Android SDK 33版本的UI绘制入口进行追踪的过程,主要作笔记作用.由于笔者经验尚浅,水平也有限,所以会存在很多不足的 ...

  8. Ceph 学习——OSD读写流程与源码分析(一)

    消息从客户端发送而来,之前几节介绍了 客户端下 对象存储.块存储库的实现以及他们在客户端下API请求的发送过程(Ceph学习--Librados与Osdc实现源码解析 . Ceph学习--客户端读写操 ...

  9. RiruEdxposed学习研究(二)手把手编译Riru和Edxposed工程源码

    一.准备工程源码 从以下网址下载Riru工程,下载地址: https://github.com/RikkaApps/Riru 从以下网址下载Edxposed工程源码,下载地址: https://git ...

最新文章

  1. python php 通信,Python和php通信乱码问题解决方法
  2. 0.0 环境搭建 - PyTorch学习笔记
  3. linux 网络编程学习
  4. hadoop集群中的日志文件
  5. Codeforces Round #703 (Div. 2) D . Max Median 二分 +思维
  6. java分布式对象(RMI+部署使用RMI的程序)
  7. 发送邮件 的类 C# .net
  8. 当不同公司的产品经理在一块聊天,会聊什么?
  9. 48 CO配置-控制-获利能力分析-创建经营组织
  10. 后盾网php多少钱_亿多星全脑开发招代理多少钱@山东聊城网
  11. Android和iOS智能机去年出货超7亿 同比增长46%
  12. spring boot (整合redis)
  13. HALCON 20.11:深度学习笔记(6)---有监督训练
  14. 关于JSBuilder2的使用.
  15. Eclipse 插件使用
  16. 【CLR Via C#笔记】 值类型与拆装箱、参数传递
  17. 从EXCEL导入CAD后如何设置表格文字大小?
  18. Linux 第一次学习笔记
  19. plsql报错:Not logged on
  20. 现代软件工程 第一章 【概论】练习与讨论 第2题 邓杰

热门文章

  1. 如何顺时针或者逆时针记录多边形的每个点
  2. 把 html标签转化为 html标签代码
  3. 埃瓦里斯特·伽罗瓦Évariste Galois
  4. 易水之畔 自动刷冒险任务 刷金币 安卓模拟器 手机助手
  5. ios备忘录下载安卓版_苹果备忘录app下载-苹果备忘录 安卓版v2.0-PC6安卓网
  6. csgo调出参数_CSGO参数设定 参数大全汇总
  7. html-based script和url-based script区别,使用方式
  8. FF新闻、头条新闻等开机弹出的广告删除办法
  9. 【舆情搜索】删除百度负面新闻的方法
  10. 详解docker镜像管理命令