Ceph RBD API librdb 读流程源码分析


RBD 读写流程

librdb 中提供块设备的用户空间实现, 让用户可以直接操作 Ceph RBD, 在 RBD 上直接读写数据.
本文主要介绍 librbd 的块设备读写接口, 包括基本的使用方法和基本的读写流程(源码角度), 本文内容基于 Ceph 10.2.11 版本的源码

API 介绍

读写相关的 API 一共 8 个, 属于 Image 类, 这 8 个 API 的原型如下

ssize_t Image::read(uint64_t ofs, size_t len, bufferlist& bl)
ssize_t Image::read2(uint64_t ofs, size_t len, bufferlist& bl, int op_flags)
int Image::aio_read(uint64_t off, size_t len, bufferlist& bl, RBD::AioCompletion *c)
int Image::aio_read2(uint64_t off, size_t len, bufferlist& bl, RBD::AioCompletion *c, int op_flags)

ssize_t Image::write(uint64_t ofs, size_t len, bufferlist& bl)
ssize_t Image::write2(uint64_t ofs, size_t len, bufferlist& bl, int op_flags)
int Image::aio_write(uint64_t off, size_t len, bufferlist& bl, RBD::AioCompletion *c)
int Image::aio_write2(uint64_t off, size_t len, bufferlist& bl, RBD::AioCompletion *c, int op_flags)

其中:

  • ofs 代表读写的起始位置,
  • len 代表读写的长度,
  • bl 是要写入 image 的数据或者要存放读取到的数据的空间,
  • op_flags 是一些读写的标志未, 一般传 0 即可
  • *c 是异步 IO 的回调函数
  • 前缀为 aio_ 的 API 为异步读写, 没有 aio_ 前缀的为同步读写.
  • 后缀为 2 的 API 支持指定 op_flags 参数

实际上以上 8 个 API 分别调用了 AioImageRequestWQ 中的异步读写方法, 其函数原型如下

void AioImageRequestWQ<I>::aio_write(AioCompletion *c, uint64_t off,  int64_t len, const char *buf, int op_flags, bool native_async)

void AioImageRequestWQ<I>::aio_read(AioCompletion *c, uint64_t off, uint64_t len, char *buf, bufferlist *pbl, int op_flags, bool native_async)

因为这两个方法都是异步操作函数, 所以 8 个 API 中的同步函数 Image::read() Image::write() 会在内部创建一个条件变量, 作为回调函数, 再调用AioImageRequestWQ<I> 的异步读写, 然后阻塞等待读/写 完成.
8 个 API 中没有后缀 2 的 API 则会默认使用 0 作为 op_flags
所以这 8 个 API 实际上就是对 AioImageRequestWQ 中异步读写方法的封装, 所以只要了解 AioImageRequestWQ 中的两个异步读写方法即可.

源码片段:


ssize_t Image::read(uint64_t ofs, size_t len, bufferlist& bl){
	...
    int r = ictx->aio_work_queue->read(ofs, len, bl.c_str(), 0);
    ...
    return r;
  }
  
template <typename I>
ssize_t AioImageRequestWQ<I>::read(uint64_t off, uint64_t len, char *buf, int op_flags) {
  ...
  C_SaferCond cond;
  AioCompletion *c = AioCompletion::create(&cond);
  aio_read(c, off, len, buf, NULL, op_flags, false);
  return cond.wait();
}

API 用法

下面的代码调用了 Image::aio_read2Image::aio_write2 两个接口, 再 Image 上写入一段数据后再读出

#include <rbd/librbd.hpp>
#include <rados/librados.hpp>

#include <cstring>
#include <iostream>
#include <string>

using namespace std;
using namespace librbd;

const char *config = "/etc/ceph/ceph.conf";
const char *POOL = "rbd"; // pool name
const char *IMAGE= "image"; // RBD name

void err_msg(int ret, const std::string &msg = "") {
    std::cerr << "[error] msg:" << msg << " strerror: " << strerror(-ret) << std::endl;
}

void err_exit(int ret, const std::string &msg = "") {
    err_msg(ret, msg);
    exit(EXIT_FAILURE);
}

int rados_connect(librados::Rados &rados, const char *user = "admin") {
    int ret = 0;
    ret = rados.init(user);
    if (ret < 0)
        err_exit(ret, "failed to initialize rados");
    ret = rados.conf_read_file(config);
    if (ret < 0)
        err_exit(ret, "failed to parse %s" + string(config));
    ret = rados.connect();
    if (ret < 0)
        err_exit(ret, "failed to connect to rados cluster");
    return 0;
}

//简单的回调函数,用于librbd::RBD::AioCompletion
void simple_cb(librbd::completion_t cb, void *arg) {
    std::cout << "read completion cb called!" << std::endl;
}


int main(int argc, char *argv[]) {
    // connect rados
    librados::Rados rados;
    int ret = 0;
    ret = rados_connect(rados);
    if (ret < 0) {
        err_exit(ret, "failed to connect to rados cluster");
    }
    
    // open pool 
    librados::IoCtx io_ctx;
    ret = rados.ioctx_create(POOL, io_ctx);
    if (ret < 0) {
        rados.shutdown();
        err_exit(ret, "failed to create ioctx");
    }

    // open image(RBD)
    RBD rbd;
    Image image;
    ret = rbd.open(io_ctx, image, IMAGE);
    if (ret < 0) {
        io_ctx.close();
        rados.shutdown();
        err_exit(ret, "failed to open rbd image");
    }
    
    ceph::bufferlist bl_in;
    ceph::bufferlist bl_out;
    // 定义回调函数
    auto *cbw = new RBD::AioCompletion(nullptr, (librbd::callback_t) simple_cb);
    auto *cbr = new RBD::AioCompletion(nullptr, (librbd::callback_t) simple_cb);
    bl_in.append("12345678910");

    image.aio_write2(0, bl_in.length(), bl_in, cbw, 0);
    cbw->wait_for_complete();
    image.aio_read2(0, bl_in.length(), bl_out, cbr, 0);
    cbr->wait_for_complete();
    
    image.close();
    io_ctx.close();
    rados.shutdown();
    exit(EXIT_SUCCESS);
}

librbd 读写的过程主要的代码位于源码根目录的 src/librbd, 和 osdc 两个目录中, 主要的业务逻辑在 src/librbd 中.

源码分析

Image::aio_read2Image::aio_write2 这两个异步读写接口为例

Image::aio_read2

#### 流程图:

异步IO
同步IO
int Image::aio_read2
void AioImageRequestWQ<I>::aio_read
void AioImageRequestWQ<I>::queue
把请求加入线程池
线程池调用
AioImageRequestWQ<librbd::ImageCtx>::process
void AioImageRequest<I>::send
void AioImageRequestWQ<I>::aio_read
void AioImageRequest<I>::aio_read
void AioImageRead<I>::send_request
image_ctx.aio_read_from_cache
void AioImageRead<I>::send
int librados::IoCtx::aio_operate
librados层的操作, 读取数据
int ObjectCacher::_readx
读写流程
Image::aio_read2

Image::aio_read2librbd 提供给用户的接口, 具备了异步读写的能力, 函数中通过 ImageCtx 调用 AioImageRequestWQ<I>::aio_read, ictx->aio_work_queueAioImageRequestWQ 类型的成员变量 .

  int Image::aio_read2(uint64_t off, size_t len, bufferlist& bl, RBD::AioCompletion *c, int op_flags)
  {
    ImageCtx *ictx = (ImageCtx *)ctx; // todo init?
    ....
    ictx->aio_work_queue->aio_read(get_aio_completion(c), off, len, NULL, &bl, op_flags);
    return 0;
  }
AioImageRequestWQ::aio_read

AioImageRequestWQ<I>::aio_read 中判断 IO 的类型, 如果是非阻塞IO,或者有rbd mirror,或者有其它阻塞的IO请求,就调用函数 AioImageReques 对象,加入到AioImageRequestWQ的工作队列里.
否则如果是阻塞IO请求就直接调用 AioImageRequest<I>::aio_read 处理读请求
本函数实现了阻塞 IO 请求与非阻塞 IO 请求的分离. 通过线程池的 queue 实现了异步的 IO

template <typename I>
void AioImageRequestWQ<I>::aio_read(AioCompletion *c, uint64_t off, uint64_t len, char *buf, bufferlist *pbl, int op_flags, bool native_async) {
  ...
  RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
  if (m_image_ctx.non_blocking_aio || writes_blocked() || !writes_empty() || require_lock_on_read()) {
    queue(AioImageRequest<I>::create_read_request(m_image_ctx, c, off, len, buf, pbl, op_flags));
  } else {
    c->start_op();
    AioImageRequest<I>::aio_read(&m_image_ctx, c, off, len, buf, pbl, op_flags);
    finish_in_flight_io();
  }
}
AioImageRequest::aio_read

AioImageRequest<I>::aio_read 中创建一个 AioImageRead<I> req 请求. 然后调用 req.send() 发送请.
本函数实现了将一个请求转换为具体的针对于镜像的 IO 请求.
如果是在上一步中的非阻塞 IO, 即放入线程池的 queue 中的请求, 线程池最终也会调用 req.send, 可以看到在 AioImageRequestWQ<I>::aio_read 将请求放入 queue 时调用的 AioImageRequest<I>::create_read_request 实际上就是创建了一个 req, 然后通过线程池实现了异步的 req.send
AioImageRequest 继承自 AioImageRequest 并且并没有实现 send 函数, 所以这里的 req.send()最终会调用父类的 void AioImageRequest<I>::send .

template <typename I>
void AioImageRequest<I>::aio_read(I *ictx, AioCompletion *c, uint64_t off, size_t len, char *buf, bufferlist *pbl, int op_flags) {
  AioImageRead<I> req(*ictx, c, off, len, buf, pbl, op_flags);
  req.send();
}
template <typename I>
AioImageRequest<I>* AioImageRequest<I>::create_read_request( I &image_ctx, AioCompletion *aio_comp, uint64_t off, size_t len, char *buf, bufferlist *pbl, int op_flags) {
  return new AioImageRead<I>(image_ctx, aio_comp, off, len, buf, pbl, op_flags);
}
void AioImageRequest::send

AioImageRequest 是一个虚类, 此时的 send 是通过子类调用的, 子类实现了 send_request() 所以这里最终将会调用子类的 send_request 即 AioImageRead
本函数实际上就是通过抽象层, 实现了部分不同的业务中相同的逻辑

template <typename I>
void AioImageRequest<I>::send() { 
  I &image_ctx = this->m_image_ctx;
  ...
  CephContext *cct = image_ctx.cct;
  AioCompletion *aio_comp = this->m_aio_comp;
  ...
  aio_comp->get();
  send_request();
}
AioImageRead::send_request

AioImageRead 是整个读流程中的核心函数, 其实现了 预读, 回调函数处理,Image 到 Object 的转换等功能, 并且实现了直接在 RBD cache 中读取数据的功能
其中 Image 到 Object 的转换是通过 file_to_extents 实现的, 将 Image 读写转换为对象的读写后, 逐个对象的进行读写, 如果开启了缓存就进入 ImageCtx::aio_read_from_cache 函数, 否则就调用 void AioObjectRead<I>::send 直接通过 librados 来读取数据
部分代码以及函数解析:

template <typename I>
void AioImageRead<I>::send_request() {
  I &image_ctx = this->m_image_ctx;
  CephContext *cct = image_ctx.cct;
  // 开启了缓存, 并且预读 bytes 大于 0 , 并且需要预读, 则调用 readahead 进行预读操作
  if (image_ctx.object_cacher && image_ctx.readahead_max_bytes > 0 && !(m_op_flags & LIBRADOS_OP_FLAG_FADVISE_RANDOM)) {
    readahead(get_image_ctx(&image_ctx), m_image_extents);
  }
  
  // 回调函数处理
  AioCompletion *aio_comp = this->m_aio_comp;
  librados::snap_t snap_id;
  
  map<object_t,vector<ObjectExtent> > object_extents;
  uint64_t buffer_ofs = 0;
  {
    for (vector<pair<uint64_t,uint64_t> >::const_iterator p = m_image_extents.begin(); p != m_image_extents.end(); ++p) { 
      uint64_t len = p->second;
      //验证参数合法, 有必要则裁剪 io 
      int r = clip_io(get_image_ctx(&image_ctx), p->first, &len); 
      ...
      // image 读写 --> Object 的过程
      Striper::file_to_extents(cct, image_ctx.format_string, &image_ctx.layout, p->first, len, 0, object_extents, buffer_ofs);
      buffer_ofs += len;
    }
  }
  ....

  // issue the requests, 处理每一个 Object
  for (auto &object_extent : object_extents) {
    for (auto &extent : object_extent.second) {
      ...
      C_AioRead<I> *req_comp = new C_AioRead<I>(aio_comp);
      AioObjectRead<I> *req = AioObjectRead<I>::create( &image_ctx, extent.oid.name, extent.objectno, extent.offset, extent.length, extent.buffer_extents, snap_id, true, req_comp, m_op_flags);
      req_comp->set_req(req);

      if (image_ctx.object_cacher) {
        C_CacheRead<I> *cache_comp = new C_CacheRead<I>(image_ctx, req);
        image_ctx.aio_read_from_cache(extent.oid, extent.objectno, &req->data(), extent.length, extent.offset, cache_comp, m_op_flags);
      } else {
        req->send();
      }
    }
  }
  aio_comp->put();
  ...
}
Striper::file_to_extents

本函数的主要作用就是将对于 Image 的读写转换为相对于 Object 的读写操作, 因为 Ceph 底层是以对象的形式存储数据的, 而对于块设备来说通常是以off, len , 即: 从 off 位置, 读取长度为 len 的数据,.
这些数据可能分布在底层多个对象的不同位置上, 这就需要将这个一维的读写转换为针对底层对象的读写操作即一个 3 维的读写, 3 维指的是 objectset,stripeno,stripepos.
并且实际上上层的 librdb 是以条带的形式来读写底层的对象的, 有关于条带的概念可见 // todo

void Striper::file_to_extents(CephContext *cct, const char *object_format, const file_layout_t *layout, uint64_t offset, uint64_t len, uint64_t trunc_size, map<object_t,vector<ObjectExtent> >& object_extents, uint64_t buffer_offset)
{
  assert(len > 0);
  ...
  __u32 object_size = layout->object_size;  // 对象大小默认是 4MiB
  __u32 su = layout->stripe_unit; // 条带大小默认 1 
  __u32 stripe_count = layout->stripe_count; // 条带数量.默认 1
  assert(object_size >= su);
  if (stripe_count == 1) {
    ldout(cct, 20) << " sc is one, reset su to os" << dendl;
    su = object_size;
  }
    ...
  uint64_t stripes_per_object = object_size / su;

  uint64_t cur = offset;
  uint64_t left = len;
  // 把一维的读写信息, 循环计算出每一个 Object 的坐标信息
  while (left > 0) { 
    // layout into objects
    uint64_t blockno = cur / su; // which block , su 默认是 4MiB cur 就是要读写的 offset ??, blockno 就是 rbd data 对象的 编号
    // which horizontal stripe (Y)
    uint64_t stripeno = blockno / stripe_count;
    // which object in the object set (X)
    uint64_t stripepos = blockno % stripe_count;
    // which object set
    uint64_t objectsetno = stripeno / stripes_per_object;
    // object id
    uint64_t objectno = objectsetno * stripe_count + stripepos;

    // find oid, extent
    char buf[strlen(object_format) + 32];
    
    // 拼接出一个完整的 Object name 如 rbd_data.04b73a65f737b2.000000000000328e
    snprintf(buf, sizeof(buf), object_format, (long long unsigned)objectno); 
    object_t oid = buf;

    // map range into object
    uint64_t block_start = (stripeno % stripes_per_object) * su;
    uint64_t block_off = cur % su;
    uint64_t max = su - block_off;

    uint64_t x_offset = block_start + block_off;
    uint64_t x_len;
    if (left > max)
      x_len = max;
    else
      x_len = left;
	
	// 赋值给 object_extents 相应的 oid
    ObjectExtent *ex = 0;
    vector<ObjectExtent>& exv = object_extents[oid];
    if (exv.empty() || exv.back().offset + exv.back().length != x_offset) {
      exv.resize(exv.size() + 1);
      ex = &exv.back();
      ex->oid = oid;
      ex->objectno = objectno;
      ex->oloc = OSDMap::file_to_object_locator(*layout);
      ...
      ex->offset = x_offset;
      ex->length = x_len;
      ex->truncate_size = object_truncate_size(cct, layout, objectno, trunc_size);
      ...
    } else {
      // add to extent
      ex = &exv.back();
      ex->length += x_len;
    }
    ex->buffer_extents.push_back(make_pair(cur - offset + buffer_offset, x_len)); // 这里是计算每个 Object 中读写数据的位置和长度,    pair<offset, object>

    ...
    left -= x_len;
    cur += x_len;
  }
}

object_extents

Striper::file_to_extents最终将转换的结果保存在 object_extents 中, object_extents 结构为 map<object_t,vector<ObjectExtent> > object_extents. 其中:

  • obect_t 是一个object_t 结构体, 其最主要的参数就是 就是 name, 即对象名称如 rbd_data.519646b8b4567.000000000000000
  • ObjectExtent 就是 object_t 对象有关的读写信息
    obect_tObjectExtent 定义如下
struct object_t {
  string name;

  object_t() {}
  // cppcheck-suppress noExplicitConstructor
  object_t(const char *s) : name(s) {}
  // cppcheck-suppress noExplicitConstructor
  object_t(const string& s) : name(s) {}

  void swap(object_t& o) {
    name.swap(o.name);
  }
  void clear() {
    name.clear();
  }
  
  void encode(bufferlist &bl) const {
    ::encode(name, bl);
  }
  void decode(bufferlist::iterator &bl) {
    ::decode(name, bl);
  }
};
class ObjectExtent {
 public:
  object_t    oid;       // object id
  uint64_t    objectno;
  uint64_t    offset;    // in object
  uint64_t    length;    // in object
  uint64_t    truncate_size;	// in object

  object_locator_t oloc;   // object locator (pool etc)

  vector<pair<uint64_t,uint64_t> >  buffer_extents;  // off -> len.  extents in buffer being mapped (may be fragmented bc of striping!)
  
  ObjectExtent() : objectno(0), offset(0), length(0), truncate_size(0) {}
  ObjectExtent(object_t o, uint64_t ono, uint64_t off, uint64_t l, uint64_t ts) : oid(o), objectno(ono), offset(off), length(l), truncate_size(ts) { }
};
image_ctx.aio_read_from_cache

该函数会调用 readx 来进行缓存的读取

void ImageCtx::aio_read_from_cache(object_t o, uint64_t object_no, bufferlist *bl, size_t len, uint64_t off, Context *onfinish, int fadvise_flags) {
    snap_lock.get_read();
    ObjectCacher::OSDRead *rd = object_cacher->prepare_read(snap_id, bl, fadvise_flags);
    snap_lock.put_read();
    ObjectExtent extent(o, object_no, off, len, 0);
    extent.oloc.pool = data_ctx.get_id();
    extent.buffer_extents.push_back(make_pair(0, len));
    rd->extents.push_back(extent);
    cache_lock.Lock();
    int r = object_cacher->readx(rd, object_set, onfinish);
    cache_lock.Unlock();
    if (r != 0)
      onfinish->complete(r);
  }
readx

readx 的函数的工作就是去处理 file_to_exnts 中分片出来的每一个 objectextent 中的 Object(这里的 Object 就是 Ceph 标准意义上的, 直接存储在底层的 Object), 一个简单的方案就是将整个 Object 一次性读取出来, 但是底层的 Object 默认大小为 4MiB, 如果这样直接的读取, 如果读取的数据本身就比较小, 那么可能会读取到大量无关的数据, 所以这里每次读/写并不一定是读/写整个 Object 中的内容, 可能只是读/写 Object 中的部分数据(即根据 objectextent 中的 offset, len 确定 Object 中真正要读取的数据) , 并且由于有些数据片段已经存在在本地缓存, 有些不在, 所以还要对此进行处理, 最大限度的利用已有的缓存.

readx 函数很长, 这里只摘抄了部分代码

get_object() 这里会根据 objectno 来从本地内存中查询是否存在对于 Obejct 的缓存, 存在则直接返回相应缓存, 不存在就在内存上创建该缓存空间让后续填充map_read() 这里是将要读取的数据分为 hits, missing, rx, hits 就是内存中已经存在的数据, missing 就是不存在的, rx 应该是指接受缓冲区的数据

bh_it->second->waitfor_read 这个队列中放入的是未命中的缓存, 在队列中注册一个回调函数, 回调函数倍调用后, 会和 osd 通讯读取对应的数据, 然后 readx 会二次读取, 这时就可以读取到相应的数据了


int ObjectCacher::_readx(OSDRead *rd, ObjectSet *oset, Context *onfinish, bool external_call)
{
    ...
    uint64_t bytes_in_cache = 0;
    uint64_t bytes_not_in_cache = 0;
    uint64_t total_bytes_read = 0;
    map<uint64_t, bufferlist> stripe_map;  // final buffer offset -> substring
    ...

    for (vector<ObjectExtent>::iterator ex_it = rd->extents.begin(); ex_it != rd->extents.end(); ++ex_it) {
        total_bytes_read += ex_it->length;
	
        // get Object cache
        Object *o = get_object(soid, ex_it->objectno, oset, ex_it->oloc, ex_it->truncate_size, oset->truncate_seq);
        ......

        // map extent into bufferheads
        map<loff_t, BufferHead*> hits, missing, rx, errors;
        o->map_read(*ex_it, hits, missing, rx, errors);
        ......
    
        if (!missing.empty() || !rx.empty()) {
            // 处理 miss 和 rx 的数据
            for (map<loff_t, BufferHead *>::iterator bh_it = missing.begin(); bh_it != missing.end(); ++bh_it) {
                // 处理 miss 的数据
                ......
                if (!waitfor_read.empty() || (stat_rx > 0 && rx_bytes > max_size)) {
                    if (success) {
                        ......
                        waitfor_read.push_back(new C_RetryRead(this, rd, oset, onfinish)); // 注册一个重读回调
                    }
                    bh_remove(o, bh_it->second);
                    delete bh_it->second;
                } else {
                    bh_it->second->set_nocache(nocache);
                    bh_read(bh_it->second, rd->fadvise_flags); // 调用 LibrbdWriteback::read() 发送读请求
                    if ((success && onfinish) || last != missing.end())
                        last = bh_it;
                }
                success = false;
            }
            ......
            for (map<loff_t, BufferHead *>::iterator bh_it = rx.begin(); bh_it != rx.end(); ++bh_it) {
                // 处理 rx 的数据
                ......
                if (success && onfinish) {
                    ......
                    bh_it->second->waitfor_read[bh_it->first].push_back(new C_RetryRead(this, rd, oset, onfinish)); // 注册一个重读回调
                }
                ......
                success = false;
            }
            ......
        }else{
            // 处理 hit 的数据
            for (map<loff_t, BufferHead*>::iterator bh_it = hits.begin(); bh_it != hits.end(); ++bh_it) {
                // 提升 hit 数据的 LRU 中的热度
                ......
	            BufferHead *bh = bh_it->second;
                if (bh->get_nocache() && bh->is_clean())
                    bh_lru_rest.lru_bottouch(bh);
                else
                    touch_bh(bh);
         	    .....
            }
            ......
            while (1) {
                // 将 hit 的数据放到 stripe_map 中, 
                .....
                BufferHead *bh = bh_it->second;
                uint64_t len = MIN(f_it->second - foff, bh->length() - bhoff);
                bufferlist bit;
                ......

                if (bh->is_zero()) {
                    stripe_map[f_it->first].append_zero(len);
                } else {
                    bit.substr_of(bh->bl, opos - bh->start(), len);
                    stripe_map[f_it->first].claim_append(bit);
                }
            ......
            if (rd->bl && !error) {
                // 将 stripe_map 数据放到 rd->bl 中
                rd->bl->clear();
                for (map<uint64_t, bufferlist>::iterator i = stripe_map.begin(); i != stripe_map.end(); ++i) {
                    pos += i->second.length();
                    rd->bl->claim_append(i->second);
                    assert(rd->bl->length() == pos);
                }
            } else if (!error) {
                map<uint64_t, bufferlist>::reverse_iterator i = stripe_map.rbegin();
                pos = i->first + i->second.length();
            }
        }
    }
  return ret;
}
AioObjectRead::send

如果没有开启缓存, 或者缓存的空间为 0 , 那么就直接调用 send 去去读取相应的数据

    template<typename I>
    void AioObjectRead<I>::send() {
        ImageCtx *image_ctx = this->m_ictx;
        ......

        {
            RWLock::RLocker snap_locker(image_ctx->snap_lock);
		   // 处理镜像是快照生成的情况, 需要去父镜像读取数据. 如果该镜像是由快照生成的, 因为 Ceph 快照采用的是 COW (写时复制), 所以如果快照上的某段数据从未写过, 那么此段数据实际上会和父镜像重用, 所以需要到父快照上读取
            if (image_ctx->object_map != nullptr && !image_ctx->object_map->object_may_exist(this->m_object_no)) {
                image_ctx->op_work_queue->queue(util::create_context_callback <AioObjectRequest < I > > (this), -ENOENT);
                return;
            }
        }
		// 通过 libados 相关 api 读取 objetct 上相应的数据
        librados::ObjectReadOperation op;
        int flags = image_ctx->get_read_flags(this->m_snap_id);
        if (m_sparse) {
            op.sparse_read(this->m_object_off, this->m_object_len, &m_ext_map, &m_read_data, nullptr);
        } else {
            op.read(this->m_object_off, this->m_object_len, &m_read_data, nullptr);
        }
        op.set_op_flags2(m_op_flags);

        librados::AioCompletion *rados_completion = util::create_rados_ack_callback(this);
        int r = image_ctx->data_ctx.aio_operate(this->m_oid, rados_completion, &op, flags, nullptr);

        rados_completion->release();
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值