文章首发于我的公众号「Linux云计算网络」,欢迎关注,第一时间掌握技术干货!

1. 什么是 vhost-user

在 vhost 的方案中,由于 vhost 实现在内核中,guest 与 vhost 的通信,相较于原生的 virtio 方式性能上有了一定程度的提升,从 guest 到 kvm.ko 的交互只有一次用户态的切换以及数据拷贝。这个方案对于不同 host 之间的通信,或者 guest 到 host nic 之间的通信是比较好的,但是对于某些用户态进程间的通信,比如数据面的通信方案,openvswitch 和与之类似的 SDN 的解决方案,guest 需要和 host 用户态的 vswitch 进行数据交换,如果采用 vhost 的方案,guest 和 host 之间又存在多次的上下文切换和数据拷贝,为了避免这种情况,业界就想出将 vhost 从内核态移到用户态。这就是 vhost-user 的实现。

2 vhost-user 的实现


vhost-user 和 vhost 的实现原理是一样,都是采用 vring 完成共享内存,eventfd 机制完成事件通知。不同在于 vhost 实现在内核中,而 vhost-user 实现在用户空间中,用于用户空间中两个进程之间的通信,其采用共享内存的通信方式。

vhost-user 基于 C/S 的模式,采用 UNIX 域套接字(UNIX domain socket)来完成进程间的事件通知和数据交互,相比 vhost 中采用 ioctl 的方式,vhost-user 采用 socket 的方式大大简化了操作。

vhost-user 基于 vring 这套通用的共享内存通信方案,只要 client 和 server 按照 vring 提供的接口实现所需功能即可,常见的实现方案是 client 实现在 guest OS 中,一般是集成在 virtio 驱动上,server 端实现在 qemu 中,也可以实现在各种数据面中,如 OVS,Snabbswitch 等虚拟交换机。

如果使用 qemu 作为 vhost-user 的 server 端实现,在启动 qemu 时,我们需要指定 -mem-path 和 -netdev 参数,如:

1
2
3
$ qemu -m 1024 -mem-path /hugetlbfs,prealloc=on,share=on \
-netdev type=vhost-user,id=net0,file=/path/to/socket \
-device virtio-net-pci,netdev=net0

指定 -mem-path 意味着 qemu 会在 guest OS 的内存中创建一个文件,share=on 选项允许其他进程访问这个文件,也就意味着能访问 guest OS 内存,达到共享内存的目的。

-netdev type=vhost-user 指定通信方案,file=/path/to/socket 指定 socket 文件。

当 qemu 启动之后,首先会进行 vring 的初始化,并通过 socket 建立 C/S 的共享内存区域和事件机制,然后 client 通过 eventfd 将 virtio kick 事件通知到 server 端,server 端同样通过 eventfd 进行响应,完成整个数据交互。

3 几个例子


开源社区中实现了一个项目 Vapp,主要是用来测试 vhost-user 的 C/S 模式的,github 地址如下:
https://github.com/virtualopensystems/vapp.git

使用:

1
2
3
4
5
6
7
$ git clone https://github.com/virtualopensystems/vapp.git
$ cd vapp
$ make
// 运行 server 端
$ ./vhost -s ./vhost.sock
// 运行 client 端
$ ./vhost -q ./vhost.sock

通过以上步骤,就可以启动 vhost-user 的 C/S 模式。

另外还有例子就是集成在虚拟交换机 Snabbswitch 上的 vhost-user,通过以下方式获得 vhost-user 分支:

1
2
3
4
5
$ git clone -b vhostuser --recursive https://github.com/SnabbCo/snabbswitch.git
$ cd snabbswitch
$ make
测试:
$ sudo src/snabbswitch -t apps.vhost.vhost_user

还有例子就是 qemu 上的实现,这也是最原早的实现,同样通过以下方式来获得使用:

1
2
3
4
5
$ git clone -b vhost-user-v5 https://github.com/virtualopensystems/qemu.git
$ mkdir qemu/obj
$ cd qemu/obj/
$ ../configure --target-list=x86_64-softmmu
$ make -j

除此之外,还有很多的实现,如 OVS 和 DPDK 上都有实现,这实际上是集成了 vhost-user 的通用 API。

4 总结


virtio,vhost,vhost-user 是基于场景和性能而提出的三种 guest 和 host 之间的通信方案,三种方案,各有优劣。
vhost-user 用在很多数据面之上的进程间通信,效率高。

PS:文章未经我允许,不得转载,否则后果自负。

–END–

欢迎扫👇的二维码关注我的微信公众号,后台回复「m」,可以获取往期所有技术博文推送,更多资料回复下列关键字获取。

vhost 简介

文章首发于我的公众号「Linux云计算网络」,欢迎关注,第一时间掌握技术干货!

1. 什么是 vhost


vhost 是 virtio 的一种后端实现方案,在 virtio 简介中,我们已经提到 virtio 是一种半虚拟化的实现方案,需要虚拟机端和主机端都提供驱动才能完成通信,通常,virtio 主机端的驱动是实现在用户空间的 qemu 中,而 vhost 是实现在内核中,是内核的一个模块 vhost-net.ko。为什么要实现在内核中,有什么好处呢,请接着往下看。

2. 为什么要用 vhost


在 virtio 的机制中,guest 与 用户空间的 Hypervisor 通信,会造成多次的数据拷贝和 CPU 特权级的上下文切换。例如 guest 发包给外部网络,首先,guest 需要切换到 host kernel,然后 host kernel 会切换到 qemu 来处理 guest 的请求, Hypervisor 通过系统调用将数据包发送到外部网络后,会切换回 host kernel , 最后再切换回 guest。这样漫长的路径无疑会带来性能上的损失。

vhost 正是在这样的背景下提出的一种改善方案,它是位于 host kernel 的一个模块,用于和 guest 直接通信,数据交换直接在 guest 和 host kernel 之间通过 virtqueue 来进行,qemu 不参与通信,但也没有完全退出舞台,它还要负责一些控制层面的事情,比如和 KVM 之间的控制指令的下发等。

3. vhost 的数据流程


下图左半部分是 vhost 负责将数据发往外部网络的过程, 右半部分是 vhost 大概的数据交互流程图。其中,qemu 还是需要负责 virtio 设备的适配模拟,负责用户空间某些管理控制事件的处理,而 vhost 实现较为纯净,以一个独立的模块完成 guest 和 host kernel 的数据交换过程。

vhost 与 virtio 前端的通信主要采用一种事件驱动 eventfd 的机制来实现,guest 通知 vhost 的事件要借助 kvm.ko 模块来完成,vhost 初始化期间,会启动一个工作线程 work 来监听 eventfd,一旦 guest 发出对 vhost 的 kick event,kvm.ko 触发 ioeventfd 通知到 vhost,vhost 通过 virtqueue 的 avail ring 获取数据,并设置 used ring。同样,从 vhost 工作线程向 guest 通信时,也采用同样的机制,只不过这种情况发的是一个回调的 call envent,kvm.ko 触发 irqfd 通知 guest。

4. 总结


vhost 与 kvm 的事件通信通过 eventfd 机制来实现,主要包括两个方向的 event,一个是 guest 到 vhost 方向的 kick event,通过 ioeventfd 实现;另一个是 vhost 到 guest 方向的 call event,通过 irqfd 实现。

代码分析整个通信的流程:
http://royluo.org/2014/08/22/vhost/

PS:文章未经我允许,不得转载,否则后果自负。

–END–

欢迎扫👇的二维码关注我的微信公众号,后台回复「m」,可以获取往期所有技术博文推送,更多资料回复下列关键字获取。

KVM 初探

文章首发我的公众号「Linux云计算网络」,欢迎关注,第一时间掌握技术干货!

KVM 是业界最为流行的 Hypervisor,全称是 Kernel-based Virtual Machine。它是作为 Linux kernel 中的一个内核模块而存在,模块名为 kvm.ko,也可以看作是一个进程,被内核调度并管理,从 Linux 2.6.20 版本开始被完全正式加入到内核的主干开发和正式发布代码中。 KVM 主要用于管理 CPU 和内存的虚拟化,IO 设备的虚拟化则是由 Qemu 来完成。为什么会有这样的分工,请继续往下看。

KVM 与 Qemu 的前世今生


Qemu 是一个纯软件实现的开源「模拟」软件,它能够模拟整套虚拟机的实现,包括 CPU、内存、各种 IO 设备、鼠标、键盘、USB 、网卡、声卡等等,基本上没有它不能模拟的。有人可能会比较疑惑它跟 KVM 之间到底有何关系,我们可以把它们看成是合作关系,好基友,谁都离不开彼此。

KVM 离不开 Qemu。KVM 实现初期,为了简化开发和代码重用,在 Qemu 的基础上进行了修改,主要是将比较耗性能的 CPU 虚拟化和内存虚拟化部分移到了内核中实现,保留 IO 虚拟化模块在用户空间实现。这样的做法主要是考虑到性能的原因,CPU 和 内存虚拟化是非常复杂的虚拟化模块,而且使用非常频繁,如果实现在用户空间的话,用户态和内核态的频繁切换势必会对性能造成很大的影响。那为什么要单独保留 IO 虚拟化在用户空间呢,这个也是权衡之下的结果,首先 IO 设备太多了,其次 IO 虚拟化相对其他两个模块使用不是很频繁,开销会小一些,所以,为了尽可能保持内核的纯净性,才有了这样的分配。

Qemu 离不开 KVM。上面也说了,Qemu 是一个纯软件的实现,运行在用户空间,性能非常低下,所以,从 Qemu 的角度,可以说是 Qemu 使用了 KVM 的虚拟化功能,为自身虚拟机提供加速。

早期两者还没有区分(没有同居),KVM 修改的模块叫 qemu-kvm,到 Qemu1.3 版本之后,两者就合二为一了(同居啦),如果我们在用 Qemu 创建虚拟机时,要加载 KVM 模块,需要为其指定参数 --enable-kvm


KVM 与 Qemu 的关系(图片来源于网络,侵权必删)

KVM 架构


KVM 是基于硬件虚拟化(Intel VT 或 AMD-V)实现的一套虚拟化解决方案,通过以上一个与 Qemu 关系的分析,我们基本上知道它在虚拟化领域处在一个什么样的地位。它其实只负责 CPU 和内存的虚拟化,不负责任何设备的模拟,而是提供接口给用户空间的 Qemu 来模拟。这个接口是 /dev/kvm,
Qemu 通过 /dev/kvm 接口设置一个虚拟机的地址空间,然后向它提供模拟好的 I/O 设备,并将相关的设备回显操作映射到宿主机,完成整个 I/O 设备的虚拟化操作。


KVM 架构

/dev/kvm 接口是 Qemu 和 KVM 交互的“桥梁”,基本的原理是:/dev/kvm 本身是一个设备文件,这就意味着可以通过 ioctl 函数来对该文件进行控制和管理,从而可以完成用户空间与内核空间的数据交互。在 KVM 与 Qemu 的通信过程主要就是一系列针对该设备文件的 ioctl 调用。

我就拿创建虚拟机举个例子,虚拟机本质上是宿主机的一个进程,包括用户态数据结构和内核态数据结构,用户态部分由 Qemu 创建并初始化,内核态部分则由 KVM 来完成,完成后会返回一个文件句柄来代表所创建的虚拟机,针对该文件句柄的 ioctl 调用就可以对虚拟机进行相应的管理,比如建立虚拟机地址空间和宿主机地址空间的映射关系,创建多个线程(虚拟处理器,vCPU)来供虚拟机使用等,对于创建出的 vCPU,也会生成相应的文件句柄,同样,对 vCPU 的文件句柄的 ioctl 调用就可以对 vCPU 进行管理。

关于这块的具体细节,后面会有文章来专门讨论。

VMM 管理工具 —— libvirt


目前,虚拟化这个领域可以说是百花齐放,针对不同的场景提出了很多的虚拟化解决方案,KVM、Xen、VMware、VirtualBox、Hyper-V 等等,具体的这些方案有什么特点,可以看前文「虚拟化技术总览」。这么多方案势必有很多通用的模块,不同之处可能在于,与不同硬件厂商的适配上,为了支持更多厂商,以及应用更多的领域,有很多 IaaS 解决方案需要融合多种虚拟化技术。这个时候如果有一个平台类的管理工具就会非常方便,libvirt 就是这样一个工具。


libvirt 架构(图片来源于网络,侵权必删)

libvirt 除了能够支持多种虚拟化方案之外,还支持 OpenVZ、LXC 等容器虚拟化系统。它提供一套完善的虚拟机管理工具,支持 GUI 和命令行的形式,如 virsh、virt-install、virt-manager。由于它的通用性和易管理,很多云计算框架平台都在底层使用 libvirt 的 API 来管理虚拟机,比如 OpenStack、OpenNebula、Eucalyptus 等。这个工具我们仅仅提一下,有兴趣的可以装个玩玩。

下面给出 KVM 和 Qemu 的 git 路径,有兴趣的可以把源码下下来研究下。

1
2
3
4
kvm.git:
git clone git://git.kernel.org/pub/scm/virt/kvm/kvm.git
qemu.git(包括了 kvm):
git clone git://git.qemu-project.org/qemu.git

PS:文章未经我允许,不得转载,否则后果自负。

–END–

欢迎扫👇的二维码关注我的微信公众号,后台回复「m」,可以获取往期所有技术博文推送,更多资料回复下列关键字获取。

文章首发我的公众号「Linux云计算网络」,欢迎关注,第一时间掌握技术干货!

Qemu 架构


Qemu 是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机,虚拟机认为自己和硬件打交道,但其实是和 Qemu 模拟出来的硬件打交道,Qemu 将这些指令转译给真正的硬件。

正因为 Qemu 是纯软件实现的,所有的指令都要经 Qemu 过一手,性能非常低,所以,在生产环境中,大多数的做法都是配合 KVM 来完成虚拟化工作,因为 KVM 是硬件辅助的虚拟化技术,主要负责 比较繁琐的 CPU 和内存虚拟化,而 Qemu 则负责 I/O 虚拟化,两者合作各自发挥自身的优势,相得益彰。


Qemu 总结结构

从本质上看,虚拟出的每个虚拟机对应 host 上的一个 Qemu 进程,而虚拟机的执行线程(如 CPU 线程、I/O 线程等)对应 Qemu 进程的一个线程。下面通过一个虚拟机启动过程看看 Qemu 是如何与 KVM 交互的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第一步,获取到 KVM 句柄
kvmfd = open("/dev/kvm", O_RDWR);
// 第二步,创建虚拟机,获取到虚拟机句柄。
vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
// 第三步,为虚拟机映射内存,还有其他的 PCI,信号处理的初始化。
ioctl(kvmfd, KVM_SET_USER_MEMORY_REGION, &mem);
// 第四步,将虚拟机镜像映射到内存,相当于物理机的 boot 过程,把镜像映射到内存。
// 第五步,创建 vCPU,并为 vCPU 分配内存空间。
ioctl(kvmfd, KVM_CREATE_VCPU, vcpuid);
vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
// 第五步,创建 vCPU 个数的线程并运行虚拟机。
ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0);
// 第六步,线程进入循环,并捕获虚拟机退出原因,做相应的处理。
for (;;) {
ioctl(KVM_RUN)
switch (exit_reason) {
case KVM_EXIT_IO: /* ... */
case KVM_EXIT_HLT: /* ... */
}
}
// 这里的退出并不一定是虚拟机关机,
// 虚拟机如果遇到 I/O 操作,访问硬件设备,缺页中断等都会退出执行,
// 退出执行可以理解为将 CPU 执行上下文返回到 Qemu。

Qemu 源码结构


Qemu 软件虚拟化实现的思路是采用二进制指令翻译技术,主要是提取 guest 代码,然后将其翻译成 TCG 中间代码,最后再将中间代码翻译成 host 指定架构的代码,如 x86 体系就翻译成其支持的代码形式,ARM 架构同理。

所以,从宏观上看,源码结构主要包含以下几个部分:

  • /vl.c:最主要的模拟循环,虚拟机环境初始化,和 CPU 的执行。
  • /target-arch/translate.c:将 guest 代码翻译成不同架构的 TCG 操作码。
  • /tcg/tcg.c:主要的 TCG 代码。
  • /tcg/arch/tcg-target.c:将 TCG 代码转化生成主机代码。
  • /cpu-exec.c:主要寻找下一个二进制翻译代码块,如果没有找到就请求得到下一个代码块,并且操作生成的代码块。

其中,涉及的主要几个函数如下:

知道了这个总体的代码结构,再去具体了解每一个模块可能会相对容易一点。

Qemu 的使用


1. 源码下载


1
2
3
4
5
6
7
centos:sudo apt-get install qemu
ubuntu:sudo yum install qemu -y
安装包:
$wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2
$tar xjvf qemu-2.0.0.tar.bz2
Git:
$git clone git://git.qemu-project.org/qemu.git

2. 编译及安装


1
2
3
4
$cd qemu-2.0.0 //如果使用的是git下载的源码,执行cd qemu
$./configure --enable-kvm --enable-debug --enable-vnc --enable-werror --target-list="x86_64-softmmu"
$make -j8
$sudo make install

configure 脚本用于生成 Makefile,其选项可以用 ./configure –help 查看。

这里使用到的选项含义如下:

  • –enable-kvm:编译 KVM 模块,使 Qemu 可以利用 KVM 来访问硬件提供的虚拟化服务。
  • –enable-vnc:启用 VNC。
  • –enalbe-werror:编译时,将所有的警告当作错误处理。
  • –target-list:选择目标机器的架构。默认是将所有的架构都编译,但为了更快的完成编译,指定需要的架构即可。

安装好之后,会生成如下应用程序:

  • ivshmem-client/server:这是一个 guest 和 host 共享内存的应用程序,遵循 C/S 的架构。
  • qemu-ga:这是一个不利用网络实现 guest 和 host 之间交互的应用程序(使用 virtio-serial),运行在 guest 中。
  • qemu-io:这是一个执行 Qemu I/O 操作的命令行工具。
  • qemu-system-x86_64:Qemu 的核心应用程序,虚拟机就由它创建的。
  • qemu-img:创建虚拟机镜像文件的工具,下面有例子说明。
  • qemu-nbd:磁盘挂载工具。

下面通过创建虚拟机操作来对这些工具有个初步的认识。

3. 创建虚拟机


  • 使用qemu-img创建虚拟机镜像

虚拟机镜像用来模拟虚拟机的硬盘,在启动虚拟机之前需要创建镜像文件。

1
qemu-img create -f qcow2 test-vm-1.qcow2 10G

-f 选项用于指定镜像的格式,qcow2 格式是 Qemu 最常用的镜像格式,采用来写时复制技术来优化性能。test-vm-1.qcow2 是镜像文件的名字,10G是镜像文件大小。镜像文件创建完成后,可使用 qemu-system-x86 来启动x86 架构的虚拟机:

  • 使用 qemu-system-x86 来启动 x86 架构的虚拟机
    1
    qemu-system-x86_64 test-vm-1.qcow2

因为 test-vm-1.qcow2 中并未给虚拟机安装操作系统,所以会提示 “No bootable device”,无可启动设备。

  • 启动 VM 安装操作系统镜像
    1
    qemu-system-x86_64 -m 2048 -enable-kvm test-vm-1.qcow2 -cdrom ./Centos-Desktop-x86_64-20-1.iso

-m 指定虚拟机内存大小,默认单位是 MB, -enable-kvm 使用 KVM 进行加速,-cdrom 添加 fedora 的安装镜像。可在弹出的窗口中操作虚拟机,安装操作系统,安装完成后重起虚拟机便会从硬盘 ( test-vm-1.qcow2 ) 启动。之后再启动虚拟机只需要执行:

1
qemu-system-x86_64 -m 2048 -enable-kvm test-vm-1.qcow2

qemu-img 支持非常多种的文件格式,可以通过 qemu-img -h 查看
其中 raw 和 qcow2 是比较常用的两种,raw 是 qemu-img 命令默认的,qcow2 是 qemu 目前推荐的镜像格式,是功能最多的格式。这些知识后面会有文章来专门讲述。

这篇文章写得有点长,可能是 Qemu 唯一一篇文章,这并不是说 Qemu 不重要,而是我们平时在使用过程中主要把它当工具用,遇到不懂的查就行了,当然,如果你觉得看代码爽一点,非常鼓励,如果看了有什么心得,我们可以一起交流交流。好了,老铁们,看在我深夜一点还在写干货给你们,就给我点个赞吧。

PS:文章未经我允许,不得转载,否则后果自负。

–END–

欢迎扫👇的二维码关注我的微信公众号,后台回复「m」,可以获取往期所有技术博文推送,更多资料回复下列关键字获取。