
StratoVirt 的 virtio-blk 设备是如何实现的?
StratoVirt 是开源在 openEuler 社区的面向云数据中心的企业级虚拟化平台,具备轻量低噪、软硬协同、Rust 语言级安全等关键技术竞争优势。
virtio-blk 是虚拟化 KVM 平台下虚拟磁盘的一种实现方式,本质上为一种半模拟技术。virtio-blk 设备中采用 io_event_fd 进行前端到后端通知,采用中断注入方式实现后端到前端的通知,并通过 IO 环(vring) 进行数据的共享。
基本原理
IO 总体流程可以分为以下几个步骤,如下图所示:
1.Guest 内部下发 IO 请求,通过 Eventfd 唤醒 StratoVirt 的 IO 线程
2.IO 主线程从共享环中取出 IO 请求,下发异步 IO 系统调用
3.Host 执行具体 IO 操作
4.IO 处理完成后唤醒 IO 线程
5.IO 线程 IO 结果生成响应放入共享环,向 Guest 注入中断
6.Guest 内部处理 IO 中断
具体实现
virtio-blk 的具体代码实现位于 StratoVirt 项目的 /virtio/src/block.rs 文件中,相关细节可参考代码理解。代码架构如下:
StratoVirt 的 virtio crate 中的 lib.rs 中定义了为所有 virtio 设备定义的 VirtioDevice Trait。virtio-blk 设备实现了该 Trait。
当前 StratoVirt 中 virtio-blk 设备支持一个队列:request_queue。该队列负责 block 设备的初始化以及 IO 命令传输。StratoVirt 为该队列配置了对应的 event_fd 和 handler 函数。
定义 BlockIoHandler 作为 virtio-blk 设备事件处理的主体。
其中包含了上述的一个 virtio 队列即 queue 变量,以及对应的触发事件描述符 (EventFd) queue_evt。队列使用了 Mutex 锁,保证在同一时刻只有一个使用者会对该队列进行操作,确保了多线程环境下的数据安全。
当该队列的事件描述符被触发只有,对应的处理函数 process_queue 会被调用。接下来结合代码讲解一下具体处理逻辑。
当该 handler 函数被触发时,首先从 virtio 队列中取出对应的元素,随后按照特定格式将取出的队列元素组合为 block 设备的 IO 请求。随后循环遍历执行 IO 请求,根据执行结果决定是否需要向 Guest 注入中断通知。
virtio-blk 设备的 IO 请求通过调用 Host 上的系统调用来完成处理。IO 请求结构体代码如下:
如果 virtio-blk 设备的命令行配置中指定 direct 为 on,则会调用异步 IO 系统调用:io_submit 和 io_getevents 进行 IO 请求的下发以及 IO 请求处理结果的获取;如果 virtio-blk 设备的命令行配置中指定 direct 为 off,则会调用同步 IO 系统调用:pread 和 pwrite。
性能优化
StratoVirt 还对 virtio-blk 设备进行了对应的优化处理。优化处理主要有两点。
第一点优化是对地址空间连续的 IO 请求进行了合并操作,减少了对 Host 上 IO 系统调用的调用次数,从而获得了一定程度的性能提升。具体代码处理逻辑如下:
第二点优化操作是对于异步 IO 系统调用的处理优化。下发 io_submit 系统调用之后,Host 会通过 io_getevents 系统调用通知 StratoVirt 上一次 IO 请求处理的结果。
如果采用 Epoll 的事件唤醒机制就会导致 IO 请求处理结果获取的速度变慢。因此 StratoVirt 的处理方式是在 IO 线程内每次先预先轮询一段时间,查看上一次 IO 请求处理是否完成,如果完成了则直接进行下一步;如果没有轮询成功则继续进行 Epoll 等待。具体代码如下:
通过以上的优化措施之后,virtio-blk 设备性能就可以达到理论上限。经过测试,StratoVirt 的 virtio-blk 设备的磁盘 IO 性能与 Qemu 的 virtio-blk 设备的磁盘 IO 性能基本持平。
(来源公众号: openEuler )
