简介
- virtio-blk:KVM-Qemu虚拟化生态中虚拟磁盘的一种实现方式。
- 利用了virtio共享内存的机制
- Guest OS内核通过加载virtio-blk驱动,实现块存储的读写,无需额外的厂家专用驱动
- 可以当作一个pci设备来看待
- 实现虚拟机内外的事件通知和数据传递
- 前端驱动(virtio-blk):准备好待处理的IO请求和数据存放空间并通知后端
- 虚拟机外部的后端程序(Hypervisor):获取待处理的请求并交给真正的IO子系统处理,完成后将处理结果通知前端
块设备整体布局
扇区(sector)、块(block)、段(segment)、页(page)的区别
- 扇区:硬盘的读写以扇区为基本单位,大小一般为512B
- 扇区是磁盘物理层面的概念,无法在系统中进行大小的更改
- 操作系统是不直接与扇区交互的,而是与多个连续扇区组成的磁盘块交互
- 块:文件系统读写数据的最小单位
- 操作系统将相邻的扇区组合在一起,形成一个块
- 每个磁盘块可以包括 2、4、8、16、32 或 64 个扇区
- 操作系统规定一个磁盘块中只能放置一个文件,文件所占用的空间,只能是磁盘块的整数倍。
- 段:块驱动的传送单位
- 是一个内存页或内存页的一部分,它包含磁盘上物理相邻的几个数据块的内容
- 大小不定,取决于通用块层,因为它把传送的数据下发给块驱动,而通用块还可以把几个段合并成物理段、硬件段(专门总线电路);同时也取决于用户所访问的大小。
- 页:内存的最小存储单位,大小一般4KB
数据结构
virtio_blk_device
与unikraft的块设备进行交互,提供接口。
1 | struct virtio_blk_device { |
- 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
25struct 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:链接要释放的请求(已经完成的请求,或无法访问的请求)
uk_sglist
用于存储一个一个段1
2
3
4
5
6struct 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
4struct 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 | // 块设备请求的数据结构存储在sglist_seg中 |
- 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
19struct 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
8struct 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 | struct virtio_driver { |
- *dev_ids:在块设备通信中指的是virtio块设备的
vblk_dev_id
1
2
3
4static 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块设备