当我们使用krbd 的时候,第一件事就是rbd map,这个命令的目的是将一个rbd image 挂载到linux 成为一个block 设备。
比如:
[root@atest-guest build]# rbd ls
test
[root@atest-guest build]# rbd info test
rbd image 'test':
size 1 GiB in 256 objects
order 22 (4 MiB objects)
snapshot_count: 0
id: 1726c754beda9
block_name_prefix: rbd_data.1726c754beda9
format: 2
features: layering, exclusive-lock, object-map, fast-diff, deep-flatten, journaling
op_features:
flags:
create_timestamp: Thu Jul 25 10:15:15 2019
access_timestamp: Thu Jul 25 10:15:15 2019
modify_timestamp: Thu Jul 25 10:15:15 2019
journal: 1726c754beda9
mirroring state: disabled
[root@atest-guest build]# rbd map test
/dev/rbd0
[root@atest-guest build]# lsblk /dev/rbd0
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
rbd0 250:0 0 1G 0 disk
[root@atest-guest build]# mkfs.xfs -f /dev/rbd0
meta-data=/dev/rbd0 isize=512 agcount=8, agsize=32768 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=0, sparse=0
data = bsize=4096 blocks=262144, imaxpct=25
= sunit=16 swidth=16 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal log bsize=4096 blocks=2560, version=2
= sectsz=512 sunit=16 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
如上,rbd map test 之后,linux 模拟出一个block 设备:/dev/rbd0 我们可以吧这个设备当做一块普通盘进行操作,比如mkfs.xfs。
下面我们来详细分析一下rbd map和unmap的过程:
(1) userspace rbd 命令:
首先看一下rbd map 命令的入口:src/tools/rbd/action/Kernel.cc
以上逻辑比较简单:
1, 用户执行rbd map test 命令,rbd 进程先注册一个udev monior,用来监听udev event。
2. 然后通过sysfs 告诉krbd driver 去添加一个rbd block device。这个过程在下面一部分详细介绍。添加结束之后,会发送一个kerne event 给udevd 服务。
3. udevd 服务执行rbd-rule,这个rule 的任务其实主要是建立一个link,/dev/rbd/rbd/test -> /dev/rbd0。另外回去读取block设备的superblock,如果有必要回去执行其他的rule。结束之后,会抛出一个udev 的event 给接受者。
4. rbd map 会接收到udev的event,解析之后发现设备添加结束,然后返回给user。退出进程。
其中有一个地方,rbd map 进程在等待udev event的时候,可能会出现一直等不到的情况,具体原因现在还不清楚。但是现在有timeout 在wait 里面,如果超时,就失败退出。
(2)do_rbd_add
在(1)里面可以看到,整个过程中,最重要的一部分就是krbd driver去添加block 设备的过程。可以看到代码如下:
540 static BUS_ATTR_WO(add);
541 static BUS_ATTR_WO(remove);
542 static BUS_ATTR_WO(add_single_major);
543 static BUS_ATTR_WO(remove_single_major);
544 static BUS_ATTR_RO(supported_features);
545
546 static struct attribute *rbd_bus_attrs[] = {
547 &bus_attr_add.attr,
548 &bus_attr_remove.attr,
549 &bus_attr_add_single_major.attr,
550 &bus_attr_remove_single_major.attr,
551 &bus_attr_supported_features.attr,
552 NULL,
553 };
所以当我们去写入/sys/bus/rbd/add_single_major 的时候,会去调用
7138 static ssize_t add_single_major_store(struct bus_type *bus, const char *buf,
/* [previous][next][first][last][top][bottom][index][help] [+7138 drivers/block/rbd.c] */
7139 size_t count)
7140 {
7141 return do_rbd_add(bus, buf, count);
7142 }
所以重点来了,主要的实现在do_rbd_add,下面来看看do_rbd_add 到底做了什么?
如上图所示:do_rbd_add 主要三个步骤:
(2.1)解析参数,创建设备。(rbd_add_parse_args -> rbd_dev_create)
(2.2)读取设备信息,初始化设备metadata。并且读取和初始化parent信息,知道没有parent为止。(#define RBD_MAX_PARENT_CHAIN_LEN 16) 目前krbd 支持最多16 层父子链接。(rbd_dev_image_probe)
(2.3)创建disk 并且加入到linux device 的hierarchy。(rbd_dev_device_setup -> blk_put_queue)
(3)do_rbd_remove
do_rbd_remove 其实就很简单了,主要是将do_rbd_add 里面的事情逆向再做一遍:
7220 if (force) {
7221 /*
7222 * Prevent new IO from being queued and wait for existing
7223 * IO to complete/fail.
7224 */
7225 blk_mq_freeze_queue(rbd_dev->disk->queue);
7226 blk_set_queue_dying(rbd_dev->disk->queue);
7227 }
7228
7229 del_gendisk(rbd_dev->disk);
7230 spin_lock(&rbd_dev_list_lock);
7231 list_del_init(&rbd_dev->node);
7232 spin_unlock(&rbd_dev_list_lock);
7233 device_del(&rbd_dev->dev);
7234
7235 rbd_dev_image_unlock(rbd_dev);
7236 rbd_dev_device_release(rbd_dev);
7237 rbd_dev_image_release(rbd_dev);
7238 rbd_dev_destroy(rbd_dev);
唯一值得注意的是force 这个option,他会忽略其他打开这个rbd device 的进程,继续去做remove。但是如果有IO hang住了,即使有force 参数,也不能成功unmap。这里我们会实现一个full_force option,这个option会abort 掉inflight的所有request,然后进行remove。
综上,本文分三个部分介绍了一个rbd 设备的创建和删除过程。从userspace 到kernelspace,详细浏览了代码执行过程。其中rbd map 是一个复杂的过程,当然也还有可以优化空间。比如V1 已经从librbd 里面mark 成deprecated,甚至已经移除了v1 的支持。所以krbd 也可以开始考虑将v1的支持去掉了。