vhost-user-scsi resize 的支持

在去年的一篇 文章 里,我们实现了 vhost-user-blk 关于 resize volume 的支持。同时,也简单介绍了 virtio-scsi 如何感知到 resize 事件的。本文继续那篇文章遗留的问题,给 SPDK 加上 vhost-user-scsi 的 resize 支持。

跟 vhost-user-blk 不同,因为 SCSI 有丰富的命令支持,获取大小可以通过 SCSI 命令来实现。
在 Linux kernel 的 virtio-scsi 实现中,通过 SPDK_SBC_SAI_READ_CAPACITY_16 命令来查询后端设备的大小。
我们先来看一下流程图:

vhost-user-scsi resize

对于 vhost-user-blk,由于不具有这个能力,所以不得不实现新的 vhost-user 协议命令(GET_CONFIG),且大小信息需要放到 PCI 配置空间里,才能传递到 guest os。
我们从 virtio-scsi 协议说起。

virtio-scsi

virtio 协议 详情见这里,可以找到 virtio-scsi 的章节。
virtio-scsi 分为前后端实现。前端 guest 驱动在 linux kernel 里面,即 drivers/scsi/virtio_scsi.c

SCSI Host Device

virtio-scsi 定义了一个 SCSI host device。virtio SCSI host device 把一个或者多个 lun 聚合在一起,允许通过 SCSI transport protocol 来通信。
一个设备实例代表一个 SCSI host ,上面有多个 target 和 lun attach 在上面。

virtio SCSI 设备服务两种 requests:

  • 对 lun 的命令请求;
  • Task Management Functions(TMF)

Device ID

分配给 virtio-scsi 的 device id 是 8。
通过 lspci 可以查看。

00:0a.0 SCSI storage controller: Red Hat, Inc. Virtio SCSI
        Subsystem: Red Hat, Inc. Device 0008
        Physical Slot: 10
        Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx+
        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
        Latency: 0
        Interrupt: pin A routed to IRQ 10
        Region 0: I/O ports at d080 [size=64]
        Region 1: Memory at fea5a000 (32-bit, non-prefetchable) [size=4K]
        Region 4: Memory at fe210000 (64-bit, prefetchable) [size=16K]
        Capabilities: [98] MSI-X: Enable+ Count=4 Masked-
                Vector table: BAR=1 offset=00000000
                PBA: BAR=1 offset=00000800
        Capabilities: [84] Vendor Specific Information: VirtIO: <unknown>
                BAR=0 offset=00000000 size=00000000
        Capabilities: [70] Vendor Specific Information: VirtIO: Notify
                BAR=4 offset=00003000 size=00001000 multiplier=00000004
        Capabilities: [60] Vendor Specific Information: VirtIO: DeviceCfg
                BAR=4 offset=00002000 size=00001000
        Capabilities: [50] Vendor Specific Information: VirtIO: ISR
                BAR=4 offset=00001000 size=00001000
        Capabilities: [40] Vendor Specific Information: VirtIO: CommonCfg
                BAR=4 offset=00000000 size=00001000
        Kernel driver in use: virtio-pci
        Kernel modules: virtio_pci

virtqueues

virtqueue 用来进行消息传递的。
virtio-scsi 定义了 3 种 virtqueue。

0: controlq
1: eventq
2…n: request queues(SCSI multiqueue)

特性位

feature bits 是 virtio 的基本能力,用于功能协商和前后向兼容。
virtio-scsi 支持如下:

VIRTIO_SCSI_F_INOUT 
VIRTIO_SCSI_F_HOTPLUG 
VIRTIO_SCSI_F_CHANGE 
VIRTIO_SCSI_F_T10_PI 

设备空间布局

这里是 SCSI 的控制器基本属性。

struct virtio_scsi_config { 
        le32 num_queues; 
        le32 seg_max; 
        le32 max_sectors; 
        le32 cmd_per_lun; 
        le32 event_info_size; 
        le32 sense_size; 
        le32 cdb_size; 
        le16 max_channel; 
        le16 max_target; 
        le32 max_lun; 
};
里面的字段都很熟悉,不一一介绍了。

设备操作: request

下面是 scsi req 的命令结构,挑最重要的分析。
其他都是 SCSI 的 SAM 定义的。

struct virtio_scsi_req_cmd { 
        // Device-readable part 
        u8 lun[8]; 
        le64 id; 
        u8 task_attr; 
        u8 prio; 
        u8 crn; 
        u8 cdb[cdb_size]; 
        // The next two fields are only present if VIRTIO_SCSI_F_T10_PI 
        // is negotiated. 
        le32 pi_bytesout; 
        le32 pi_bytesin; 
        u8 pi_out[pi_bytesout]; 
        u8 dataout[]; 

        // Device-writable part 
        le32 sense_len; 
        le32 residual; 
        le16 status_qualifier; 
        u8 status; 
        u8 response; 
        u8 sense[sense_size]; 
        // The next two fields are only present if VIRTIO_SCSI_F_T10_PI 
        // is negotiated 
        u8 pi_in[pi_bytesin]; 
        u8 datain[]; 
}; 

lun:
8 字节的 lun 字段用于定位 一个 target 和 lun,格式如下:

  | Byte 0| Byte 1| Byte 2| Byte 3| Byte 4| Byte 5| Byte 6 | Byte 7|
  |<- 1 ->|<- t ->|<-  lun id   ->|<-         0                  ->|

根据协议规定,可以支持 256 个 target(2^8) 和 16384(2^16)个 lun。
下面是 lun 的解析。

vhost_scsi_task_init_target(struct spdk_vhost_scsi_task *task, const __u8 *lun)
{
    struct spdk_vhost_scsi_session *svsession = task->svsession;
    struct spdk_scsi_dev_session_state *state;
    uint16_t lun_id = (((uint16_t)lun[2] << 8) | lun[3]) & 0x3FFF;

    SPDK_LOGDUMP(SPDK_LOG_VHOST_SCSI_QUEUE, "LUN", lun, 8);

    /* First byte must be 1 and second is target */
    if (lun[0] != 1 || lun[1] >= SPDK_VHOST_SCSI_CTRLR_MAX_DEVS) {
        return -1;
    }

SPDK 限制一个 controller 的 target 数量为 8.
lun[1] 表示 target 的 id,如果 target id >= 8,那么就超过了 一个 controller 的上限。

设备操作: controlq

VIRTIO_SCSI_T_TMF(最重要)     
  - Task management function                 
VIRTIO_SCSI_T_AN_QUERY
  - Asynchronous notification query
VIRTIO_SCSI_T_AN_SUBSCRIBE
  - Asynchronous notification subscription

设备操作: eventq

#define VIRTIO_SCSI_T_EVENTS_MISSED   0x80000000 

struct virtio_scsi_event { 
        // Device-writable part 
        le32 event; 
        u8  lun[8]; 
        le32 reason; 
}

VIRTIO_SCSI_T_NO_EVENT
VIRTIO_SCSI_T_TRANSPORT_RESET
VIRTIO_SCSI_T_ASYNC_NOTIFY
VIRTIO_SCSI_T_PARAM_CHANGE  

其中 VIRTIO_SCSI_T_TRANSPORT_RESET 可以有如下 3 个 reason :

#define VIRTIO_SCSI_T_TRANSPORT_RESET  1 

#define VIRTIO_SCSI_EVT_RESET_HARD         0 
#define VIRTIO_SCSI_EVT_RESET_RESCAN       1 
#define VIRTIO_SCSI_EVT_RESET_REMOVED      2

如果是添加 target,对应的事件 + reason 为:
VIRTIO_SCSI_T_TRANSPORT_RESET + VIRTIO_SCSI_EVT_RESET_RESCAN
如果是删除 target,对应的事件 + reason 为:
VIRTIO_SCSI_T_TRANSPORT_RESET + VIRTIO_SCSI_EVT_RESET_REMOVED

下面是 SPDK 对应的代码:

vhost_scsi_session_add_tgt:
    if (vhost_dev_has_feature(vsession, VIRTIO_SCSI_F_HOTPLUG)) {
        eventq_enqueue(svsession, scsi_tgt_num,
              VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_RESCAN);

vhost_scsi_session_remove_tgt:
    /* Send a hotremove Virtio event */
    if (vhost_dev_has_feature(vsession, VIRTIO_SCSI_F_HOTPLUG)) {
        eventq_enqueue(svsession, scsi_tgt_num,
                   VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_REMOVED);
    }

继续看 eventq_enqueue 的实现。我们知道 virtio-scsi 至少有 3 个 virtqueue:

#define VIRTIO_SCSI_CONTROLQ   0
#define VIRTIO_SCSI_EVENTQ   1
#define VIRTIO_SCSI_REQUESTQ   2

事件通知是放在 virtqueue 1 上。
注意:eventq virtqueue 是由 guest driver 放置一个空 的 buffer 在 queue 里,然后有新的事件后,device 取出这个描述符,填充事件,然后 guest driver 在完成回调里进行处理。

    /* 拿到 desc 对应的 va */
    vhost_vq_avail_ring_get
    vhost_vq_get_desc
    vhost_gpa_to_vva
        /* 设置 event */
    desc_ev->event = event;
    desc_ev->lun[0] = 1;
    desc_ev->lun[1] = scsi_dev_num;
    /* virtio LUN id 0 can refer either to the entire device
     * or actual LUN 0 (the only supported by vhost for now)
     */
    desc_ev->lun[2] = 0 >> 8;
    desc_ev->lun[3] = 0 & 0xFF;
    /* virtio doesn't specify any strict format for LUN id (bytes 2 and 3)
     * current implementation relies on linux kernel sources
     */
    memset(&desc_ev->lun[4], 0, 4);
    desc_ev->reason = reason;
        /* 放到 used ring,guest driver 可以处理事件了*/
        vhost_vq_used_ring_enqueue(vsession, vq, req, req_size);

virtio-scsi 事件处理:(virtscsi_handle_event)

我们主要关心 VIRTIO_SCSI_T_PARAM_CHANGE
如果 virtio-scsi 的容量或者 cache type 变化,就会触发此事件。
如果要支持此事件,Feature bits 需要支持:VIRTIO_SCSI_F_CHANGE

调用 scsi_rescan_device,然后通过发送 SCSI 命令 SPDK_SBC_SAI_READ_CAPACITY_16 来获取更新后的大小。

总结

给 SPDK 添加 resize vhost-scsi 的支持非常直接,按照 virtio-scsi spec 的要求实现对应的功能即可。
想看具体的实现,可以点击这里

参考

  1. https://zhuanlan.zhihu.com/p/98744432
  2. http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html
  3. https://review.spdk.io/gerrit/c/spdk/spdk/+/4124
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352