virtio-blkdev核心数据结构

简介

  • virtio-blk:KVM-Qemu虚拟化生态中虚拟磁盘的一种实现方式。
    • 利用了virtio共享内存的机制
    • Guest OS内核通过加载virtio-blk驱动,实现块存储的读写,无需额外的厂家专用驱动
    • 可以当作一个pci设备来看待
    • 实现虚拟机内外的事件通知和数据传递
      • 前端驱动(virtio-blk):准备好待处理的IO请求和数据存放空间并通知后端
      • 虚拟机外部的后端程序(Hypervisor):获取待处理的请求并交给真正的IO子系统处理,完成后将处理结果通知前端

块设备整体布局

image.png

扇区(sector)、块(block)、段(segment)、页(page)的区别

  • 扇区:硬盘的读写以扇区为基本单位,大小一般为512B
    • 扇区是磁盘物理层面的概念,无法在系统中进行大小的更改
    • 操作系统是不直接与扇区交互的,而是与多个连续扇区组成的磁盘块交互
  • 块:文件系统读写数据的最小单位
    • 操作系统将相邻的扇区组合在一起,形成一个块
    • 每个磁盘块可以包括 2、4、8、16、32 或 64 个扇区
    • 操作系统规定一个磁盘块中只能放置一个文件,文件所占用的空间,只能是磁盘块的整数倍。
  • 段:块驱动的传送单位
    • 是一个内存页或内存页的一部分,它包含磁盘上物理相邻的几个数据块的内容
    • 大小不定,取决于通用块层,因为它把传送的数据下发给块驱动,而通用块还可以把几个段合并成物理段、硬件段(专门总线电路);同时也取决于用户所访问的大小。
  • 页:内存的最小存储单位,大小一般4KB
    image.png

数据结构

virtio_blk_device

与unikraft的块设备进行交互,提供接口。
virtio_blkdev结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct virtio_blk_device {
    /* Pointer to Unikraft Block Device */
    struct uk_blkdev blkdev;
    /* The blkdevice identifier */
    __u16 uid;
    /* Virtio Device */
    struct virtio_dev *vdev;
    /* List of all the virtqueue in the pci device */
    struct virtqueue *vq;
    /* Nb of max_queues supported by device */
    __u16 max_vqueue_pairs;
    /* This is used when the user has decided the nb_queues to use */
    __u16    nb_queues;
    /* List of queues */
    struct   uk_blkdev_queue *qs;
    /* Maximum number of segments for a request */
    __u32 max_segments;
    /* Maximum size of a segment */
    __u32 max_size_segment;
    /* If it is set then flush request is allowed */
    __u8 writeback;
};
  • blkdev:交互的上层unikraft块设备
  • uid:virtio的块设备号,与unikraft块设备号对应
  • *vdev:关联的virtio设备,通过virtqueue与virtio_dev进行通信和数据传输
  • *vq:链接到virtio_blk所使用的virtqueue,是个链表
    • 在块设备中只使用一个队列
  • max_vqueue_pairs:设备所支持的队列数量的最大值
  • nb_queues:所包含的队列数量
  • *qs:unikraft块设备的队列,与virtqueue队列对应,同时只使用一个
  • writeback:对virtio块设备进行操作flush操作

    uk_blkdev_queue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    struct uk_blkdev_queue {
        /* The virtqueue reference */
        struct virtqueue *vq;
        /* The libukblkdev queue identifier */
        /* It is also the virtqueue identifier */
        uint16_t lqueue_id;
        /* Allocator */
        struct uk_alloc *a;
        /* The nr. of descriptor limit */
        uint16_t max_nb_desc;
        /* The nr. of descriptor user configured */
        uint16_t nb_desc;
        /* The flag to interrupt on the queue */
        uint8_t intr_enabled;
        /* Reference to virtio_blk_device  */
        struct virtio_blk_device *vbd;
        /* The scatter list and its associated fragments */
        struct uk_sglist sg;
        struct uk_sglist_seg *sgsegs;
        /* List of virtio_blkdev_requests to be freed outside of interrupt
         * handler
         */
        /* TODO: Replace with Linux llist-style list for SMP support */
        struct uk_list_head free_list;
    };
  • *vq:与uk_blkdev_queue关联的virtqueue,在块设备中只有一个uk_blkdev_queue,也只有一个virtqueue
  • lqueue_id:队列号,和virtqueue的队列号一致
  • *a:内存分配器
  • max_nb_desc:队列所能承载的描述符数量的最大值,描述符主要记录请求事件要访问数据的地址信息
  • nb_desc:队列中现有描述符的数量
  • intr_enabled:标志能否对队列进行中断
  • *vbd:uk_blkdev_queue作为vbd的成员存在
  • sg:扇区链表,会将相邻的扇区融合成段。用于将请求要访问的磁盘扇区融合成段状的逻辑地址
  • free_list:链接要释放的请求(已经完成的请求,或无法访问的请求)
    free_list

    uk_sglist

    用于存储一个一个段
    1
    2
    3
    4
    5
    6
    struct uk_sglist {
        struct uk_sglist_seg *sg_segs; /* Segment management 数组 */
        __atomic    sg_refs; /* Reference count for the sg list */
        uint16_t    sg_nseg; /* Number of segment in the sg list */
        uint16_t    sg_maxseg; /* Maximum number of segment in the sg list */
    };

    uk_sglist_seg

    段的结构体
    1
    2
    3
    4
    struct uk_sglist_seg {
        __paddr_t  ss_paddr; /* Physical address */
        size_t      ss_len;   /* Length of the buffer */
    };
  • ss_paddr:相对于unikraft(guest)的物理地址

virtio_blkdev_request

1
2
3
4
5
6
7
// 块设备请求的数据结构存储在sglist_seg中
struct virtio_blkdev_request {
    struct uk_blkreq *req;
    struct uk_list_head free_list_head;
    struct virtio_blk_outhdr virtio_blk_outhdr;
    uint8_t status;
};
  • free_list_head:请求项可以释放时,链接到uk_blkdev_queue块队列,用于请求释放。
  • virtio_blk_outhdr:host只读的descriptor
  • status:host可写,用于host反馈请求的处理结果是成功(0)、失败(1)或不支持(2)
    uk_blkreq
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    struct uk_blkreq {
        /* Input members */
        /* Operation type */
        enum uk_blkreq_op           operation;
        /* Start Sector from where the op begin */
        __sector                start_sector;
        /* Size in number of sectors */
        __sector                nb_sectors;
        /* Pointer to data */
        void                    *aio_buf;
        /* Request callback and its parameters */
        uk_blkreq_event_t           cb;
        void                    *cb_cookie;
        /* Output members */
        /* State of request: finished/unfinished*/
        __atomic                state;
        /* Result status of operation (< 0 on errors)*/
        int                 result;
    };
  • operation:对磁盘相应扇区的请求操作类型(读、写、flush)
  • start_sector:请求访问的起始磁盘扇区号
  • nb_sectors:要访问的扇区数量
  • *aio_buf:如果是写模式,则指向要写入的数据
    virtio_blk_outhdr
    1
    2
    3
    4
    5
    6
    7
    8
    struct virtio_blk_outhdr {
        /* VIRTIO_BLK_T* */
        __virtio_le32 type;
        /* io priority. */
        __virtio_le32 ioprio;
        /* Sector (ie. 512 byte offset) */
        __virtio_le64 sector;
    };
  • type:表示请求的类型:读、写、或者其他磁盘操作命令。
  • ioprio:表示请求的优先级,数值越大优先级越高,后端可以根据该字段决定请求处理。
  • sector:表示读写请求的偏移位置。这里的sector表示偏移位置以扇区(512字节)为单位。

virtio_driver

virtio驱动,可用于创建virtio块设备

1
2
3
4
5
6
7
8
9
10
struct virtio_driver {
    /** Next entry of the driver list */
    UK_TAILQ_ENTRY(struct virtio_driver) next;
    /** The id map for the virtio device */
    const struct virtio_dev_id *dev_ids;
    /** The init function for the driver */
    virtio_driver_init_func_t init;
    /** Adding the virtio device */
    virtio_driver_add_func_t add_dev;
};
  • *dev_ids:在块设备通信中指的是virtio块设备的vblk_dev_id
    1
    2
    3
    4
    static const struct virtio_dev_id vblk_dev_id[] = {
        {VIRTIO_ID_BLOCK}, // virtio块,值为2
        {VIRTIO_ID_INVALID} // 不存在块,值为0
    };
  • init:这里主要由virtio_blk_drv_init实现,负责驱动初始化
  • add_dev:这里主要由virtio_blk_add_dev实现,添加一个virtio块设备