简单介绍 FUSE
FUSE 技术用于在用户态实现文件系统。效果就是在读写 fuse 文件系统中的文件时,读写请求会被转发到一个用户态的 fuse daemon 中,这个 fuse daemon 决定读到什么内容、写内容会被写到哪里等。为实现 FUSE,Linux 主要提供了两个功能模块:fuse 内核模块和 libfuse 用户态库。他们和 VFS 协作运行的架构图如下:

用户态的 fuse daemon 调用 libfuse 注册一个 fuse 文件系统,daemon 通过回调函数提供读写请求的处理逻辑。libfuse 会帮助 daemon 通过 mount fuse 操作告知 VFS 一个 fuse 文件系统挂载到了目录树中,后续对这个文件系统的访问要传递给 fuse 内核模块处理。fuse 内核模块提供 /dev/fuse 作为和 libfuse 通信的 api,读写等请求传递到 fuse 内核模块后,fuse 内核模块会将其转换为 fuse 协议的请求,libfuse 通过在用户态阻塞的 polling /dev/fuse 获取到 fuse 协议请求,之后解析请求、调用用户自定义的处理函数,得到结果后再封装成 fuse 协议响应写回 /dev/fuse 中,fuse 内核模块解析响应,把结果返回给访问 fuse 文件系统的用户。
上述描述可能不够严谨,但已经把 fuse 的工作流程和实现思路介绍清楚了。
virtiofs 在 Qemu-kvm 上的使用
virtiofs 大意为使用 virtio 的数据传输方式传输 FUSE 协议来实现的文件系统。Qemu-kvm 为实现 virtiofs 主要用到 Qemu 提供的 virtio-pci virtio-fs 设备、Qemu 提供的 vhost-user、kvm 提供的 irqfd 和 ioeventfd 机制、单独的 virtiofsd 后端这几部分。
virtio-pci virtio-fs 设备
virtio-pci 是一种"传输层",是一种 virtio transport,与其并列的还有 virtio-mmio。而 virtio-fs 是设备类型。Qemu 使用 virtio-pci 作为传输层创建 virtio-fs 设备。这就意味着 qemu 会为 Guest 模拟一条 pci bus,Guest 会在这条 pci bus 上枚举到 virtio-fs 设备。之后 Guest 会根据 pci 设备配置空间中的配置为 virtqueue 分配 guest 物理内存,并把基地址写入到 pci 设备的配置空间中。这就为 Guest 和 Qemu 之间的通信打下了基础。
Qemu 提供的 vhost-user
vhost 技术用于将 virtio device 后端的处理从 qemu 中抽离并放置到 kernel 中,vhost-user 技术是将 virtio device 后端的处理从 qemu 中抽离并放置到另一个独立的用户态进程中。
Qemu 提供 vhost user 把 guest memory/virtqueue/eventfd 等信息告知 virtiofsd,使其能够基于此做 virtio fs device 的后端模拟工作。
kvm 提供的 irqfd 和 ioeventfd 机制
irqfd 和 ioeventfd 本质上都是 eventfd,eventfd 是 linux 提供的一种特殊的 fd,用于支持时间通知功能。eventfd 背后是一个 count,可以通过读写 eventfd 来改变 count 值,还可以在 eventfd 上执行 epoll,异步等待 eventfd 的状态变化。
irqfd 用于 qemu 通知 kvm 发起特定中断,ioeventfd 用于 kvm 通知 qemu 发生了 mmio or pio 事件,如 guest 写 virtio notify 寄存器。
单独的 virtiofsd 后端
这是实现 virtiofs 的最后一块拼图,他是一个用户进程,通过 vhost-user,他已经得到了 virtqueue 的地址以及相关的 eventfd,他可以从 virtqueue 中从 guest 传来的 fuse 协议请求,他需要解析这些请求并代理 guest 在 host 上完成这些请求,对于一些请求他还需要将数据封装成 fuse 响应再放置到 virtqueue 中,并通过 irqfd 或其他机制通过 kvm 向 guest 发起中断,告知 guest 响应已就位。
通过以上四个模块,qemu kvm 成功搭建起 virtiofs,帮助 guest 直接访问 host 的文件系统。