【块存储block源码分析】 ceph nbd server源码分析

DESC

    map成块设备,主要有两种方式:

   

     Network Block Device(nbd) 已经经过了很长时间的实践,稳定性有所保证。对块设备的请求,由 nbd 间接调用 librbd 完成,可以支持最新的特性,是比较理想的方式


     ceph 社区为 nbd 实现了一个 server,这个 server 会接收来自内核中 nbd 的请求,然后转调 librdb 完成请求

1. rbd_nbd 函数

    路径 ceph/src/tools/rbd_nbd/rbd-nbd.cc     

    1.1 Connect

      相当于命令,rbd-nbd [options] map <image-or-snap-spec>,核心函数调用 do_map,将一个 rbd 镜像 map 到块设备

static int do_map(int argc, const char *argv[], Config *cfg)
{
  int r;

  librados::Rados rados;
  librbd::RBD rbd;
  librados::IoCtx io_ctx;
  librbd::Image image;

      1.1.1 创建子进程

       fork 出一个server进程,对系统的 fork 调用封装了一个类 Preforker

  if (global_init_prefork(g_ceph_context) >= 0) {
    std::string err;
    r = forker.prefork(err);
    if (r < 0) {
      cerr << err << std::endl;
      return r;
    }
    if (forker.is_parent()) {
      if (forker.parent_wait(err) != 0) {
        return -ENXIO;
      }
      return 0;
    }
    global_init_postfork_start(g_ceph_context);
  }

      1.1.2 socketpair 产生的文件描述符是一对socket

       这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写。为该server 进程和内核 client 指定通信所用的 socket fd

  if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
    r = -errno;
    goto close_ret;
  }

      1.1.3 与 rbd 进行连接

  r = rados.init_with_context(g_ceph_context);
  if (r < 0)
    goto close_fd;

  r = rados.connect();
  if (r < 0)
    goto close_fd;

  r = rados.ioctx_create(cfg->poolname.c_str(), io_ctx);
  if (r < 0)
    goto close_fd;

  io_ctx.set_namespace(cfg->nsname);

  r = rbd.open(io_ctx, image, cfg->imgname.c_str());
  if (r < 0)
    goto close_fd;

      1.1.4 start_server 函数

      实例化 NBDServer,调用起 start 方法,start 函数创建了两个 rbd_reader rbd_writer 线程,

static NBDServer *start_server(int fd, librbd::Image& image)
{
  NBDServer *server;

  server = new NBDServer(fd, image);
  server->start();

  init_async_signal_handler();
  register_async_signal_handler(SIGHUP, sighup_handler);
  register_async_signal_handler_oneshot(SIGINT, handle_signal);
  register_async_signal_handler_oneshot(SIGTERM, handle_signal);

  return server;
}

      1.1.5 try_ioctl_setup 函数

         设置 timeout、block size、device size 等等一些参数,以及一些 flags

         ioctl(nbd, NBD_SET_SOCK, fd) 将 socket 信息传入内核模块

      1.1.6 check_device_size 主要路径为 /sys/block/nbd

      1.1.7 run_server 调用 ioctl NBD_DO_IT 

      进入 nbd kernel module 代码,进入阻塞状态,直到unmap过程中调用ioctl(nbd, NBD_DISCONNECT)。创建一个 kernel thread,在内核监听 socket,收取 nbd server 请求

static void run_server(Preforker& forker, NBDServer *server, bool netlink_used)
{
  if (g_conf()->daemonize) {
    global_init_postfork_finish(g_ceph_context);
    forker.daemonize();
  }

  if (netlink_used)
    server->wait_for_disconnect();
  else
    ioctl(nbd, NBD_DO_IT);

  unregister_async_signal_handler(SIGHUP, sighup_handler);
  unregister_async_signal_handler(SIGINT, handle_signal);
  unregister_async_signal_handler(SIGTERM, handle_signal);
  shutdown_async_signal_handler();
}

2. reader_entry 函数

    提取请求参数,一个为读线程,接收来自client的请求,解析

  void reader_entry()
  {
    while (!terminated) {
      std::unique_ptr<IOContext> ctx(new IOContext());
      ctx->server = this;

      dout(20) << __func__ << ": waiting for nbd request" << dendl;

      int r = safe_read_exact(fd, &ctx->request, sizeof(struct nbd_request));
      if (r < 0) {
        derr << "failed to read nbd request header: " << cpp_strerror(r)
             << dendl;
        goto signal;
      }

      if (ctx->request.magic != htonl(NBD_REQUEST_MAGIC)) {
        derr << "invalid nbd request header" << dendl;
        goto signal;
      }

      ctx->request.from = ntohll(ctx->request.from);
      ctx->request.type = ntohl(ctx->request.type);
      ctx->request.len = ntohl(ctx->request.len);

      ctx->reply.magic = htonl(NBD_REPLY_MAGIC);
      memcpy(ctx->reply.handle, ctx->request.handle, sizeof(ctx->reply.handle));

      ctx->command = ctx->request.type & 0x0000ffff;

    2.1 根据 command 调用 image 方法

      io_start 函数放入一个pending 队列,然后调用 image.aio_write``image.aio_read``image.aio_flush 等命令异步执行操作

     完成后会调用提前注册的回调函数 aio_callback,将请求从 pending 队列转移到 finish 队列

      IOContext *pctx = ctx.release();
      io_start(pctx);
      librbd::RBD::AioCompletion *c = new librbd::RBD::AioCompletion(pctx, aio_callback);
      switch (pctx->command)
      {
        case NBD_CMD_WRITE:
          image.aio_write(pctx->request.from, pctx->request.len, pctx->data, c);
          break;
        case NBD_CMD_READ:
          image.aio_read(pctx->request.from, pctx->request.len, pctx->data, c);
          break;
        case NBD_CMD_FLUSH:
          image.aio_flush(c);
          break;
        case NBD_CMD_TRIM:
          image.aio_discard(pctx->request.from, pctx->request.len, c);
          break;
        default:
          derr << *pctx << ": invalid request command" << dendl;
          c->release();
          goto signal;
      }

3. writer_entry 函数写线程

   wait_io_finish,finish队列为空时,写线程阻塞,当finish队列不为空时,写线程负责从finish队列中取出完成的请求

   safe_write(fd, &ctx->reply, sizeof(struct nbd_reply)),将结果返回给 nbd client 端



 

发布了236 篇原创文章 · 获赞 301 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/zhonglinzhang/article/details/103821991