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 端