目录
一、需求
从云的管理面来看,对每个设备的管理都要有一个id来作为唯一标识,这个id会通过管理面下发给每个终端,后续使用该id对终端的设备进行管理。比如net设备的端口号,block设备的by-id。
当前的需求,云管控面向DPU卡的管理面发起创建block设备的请求,同时传入block设备的管理ID,要求主机侧实际看到的block设备在/dev/disk/by-id下绑定下发的管理ID。
云管理面下发ID:
主机侧显示ID:
二、实现
这个ID如何提供给主机的?在控制面的block设备config信息里找不到相关的字段。
查阅virtio-block设备的内核驱动/drivers/block/virtio-blk.c,看到一个serial相关信息,这是在block设备下面提供的一个attribute属性文件。找到一个virtio block设备的主机环境,到/sys/block/vda/下查看确实有一个serial文件,cat文件显示
1、VIRTIO_BLK驱动的serial属性
对于attribute文件来说,驱动里的接口就是show和store,查看serial_show做了什么?
static ssize_t serial_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gendisk *disk = dev_to_disk(dev);
int err;
/* sysfs gives us a PAGE_SIZE buffer */
BUILD_BUG_ON(PAGE_SIZE < VIRTIO_BLK_ID_BYTES);
buf[VIRTIO_BLK_ID_BYTES] = '\0';
err = virtblk_get_id(disk, buf);
if (!err)
return strlen(buf);
if (err == -EIO) /* Unsupported? Make it empty. */
return 0;
return err;
}
serial_show接口调用了virtblk_get_id接口来获取真正显示出来的id。
static int virtblk_get_id(struct gendisk *disk, char *id_str)
{
struct virtio_blk *vblk = disk->private_data;
struct request_queue *q = vblk->disk->queue;
struct request *req;
int err;
req = blk_get_request(q, REQ_OP_DRV_IN, 0);
if (IS_ERR(req))
return PTR_ERR(req);
err = blk_rq_map_kern(q, req, id_str, VIRTIO_BLK_ID_BYTES, GFP_KERNEL);
if (err)
goto out;
blk_execute_rq(vblk->disk, req, false);
err = blk_status_to_errno(virtblk_result(blk_mq_rq_to_pdu(req)));
out:
blk_put_request(req);
return err;
}
virtblk_get_id这个接口里,调用了blk设备驱动收发数据典型的4个接口,分别是blk_get_request、blk_rq_map_kern以及blk_execute_rq和blk_put_request。这三个接口是block设备层的接口,并不是virtio特有的。一次block设备的访问:
1)首先blk_get_request是从request queue中分配了一条类型为REQ_OP_DRV_IN的请求,记录在req变量;
2)然后blk_rq_map_kern是分配一条bio请求,这个bio绑定到前面的request请求,而且bio内部记录读取数据的存储空间首地址和数据长度;
3)blk_execute_rq是真正执行了这条request请求和bio操作,完成后退出,数据存储在id_str中;
4)blk_put_request,释放这条请求;
由此可以看出,这个serial字段最终是在block的数据通道实现的,而非控制通道。负责实现的可能是SDPK或者qemu。
2、REQ_OP_DRV_IN请求
上一节描述了serial字段是通过block设备的通用接口,执行了一次完整的block设备访问操作来获得的,是block层的抽象操作;
具体的执行还是在每个底层驱动里,对于virtio设备来说,blk_execute_rq最终会执行virtio-blk.c里的virtio_queue_rq接口,进而驱动传输层的数据通信。
static blk_status_t virtio_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *req = bd->rq;
switch (req_op(req)) {
case REQ_OP_READ:
case REQ_OP_WRITE:
type = 0;
break;
/*....*/
case REQ_OP_DRV_IN: //serial请求,REQ_OP_DRV_IN类型
type = VIRTIO_BLK_T_GET_ID; //传输层的关键字,VIRTIO_BLK_T_GET_ID
break;
default:
WARN_ON_ONCE(1);
return BLK_STS_IOERR;
}
vbr->out_hdr.type = cpu_to_virtio32(vblk->vdev, type);
blk_mq_start_request(req);
num = blk_rq_map_sg(hctx->queue, req, vbr->sg);
err = virtblk_add_req(vblk->vqs[qid].vq, vbr, vbr->sg, num);
}
通过上述代码看到,最终REQ_OP_DRV_IN转化成了virtio传输层的type字段,type= VIRTIO_BLK_T_GET_ID;然后SPDK或者qemu中接收到这个type的消息,就会返回相应的ID。
最终确实在SPDK和QEMU代码中找到了同样的字段,对于DPU,只需要在SPDK里返回相应字段即可。
3、UDEV命名规则
还有一个关键的环节,前面理清了serial字段的前因后果,那么serial到底是不是/dev/disk/by-id下显示的ID呢,这个还不知道呢。
BLOCK设备是遵循UDEV命名规则的,UDEV是linux系统的设备管理器,它主要是负责管理 /dev 目录中的设备节点(device nodes)和 /dev/disk 子目录中的与设备 ID 相关的的符号连接文件,当新的硬件设备(hardware devices)加入系统或者从系统删除时,Linux 内核通过 netlink socket 通知 udev,然后 udev 根据存在的 udev rules 来做相应的处理,默认地,它会在 /dev 目录中创建或者删除设备节点。
udev rules文件都存储在/lib/udev/rules.d/,其中block设备的rules文件是/lib/udev/rules.d/60-persistent-storage.rules。
打开这个文件,搜索VIRTIO字段,可以看到virtio-blk设备的命名规则,其中SYMLINK就是/dev/disk/by-id/下的设备名称,命名格式为virtio-$env{ID_SERIAL},而ID_SERIAL是从vd*设备的serial ATTR中获得的。也就是说/dev/disk/by-id下的设备名称确实是通过serial属性文件,然后从SDPK中获取的。