From 35b70b5d4e7219562b5b2c67d095bab69077b39b Mon Sep 17 00:00:00 2001 From: KouweiLee <98637586+KouweiLee@users.noreply.github.com> Date: Sat, 6 Jul 2024 09:05:09 +0800 Subject: [PATCH] Finish virtio and cmdtool usage doc, and implementation docs for virtio devices (#9) * finish usage doc for virtio device and cmdtool, close #7 * Add implementation doc for virtio devices,close #8 --- src/SUMMARY.md | 27 +-- src/chap03/CMDTools.md | 59 +++++++ src/chap03/VirtIOUseage.md | 92 ++++++++++ src/chap04/subchap03/VirtIO/BlockDevice.md | 76 +++++++++ src/chap04/subchap03/VirtIO/ConsoleDevice.md | 46 +++++ src/chap04/subchap03/VirtIO/NetDevice.md | 47 +++++ src/chap04/subchap03/VirtIO/VirtIO-Define.md | 160 ++++++++++++++++++ .../subchap03/VirtIO/img/architecture.svg | 21 +++ .../VirtIO/img/control_plane_queue.svg | 21 +++ .../subchap03/VirtIO/img/data_plane_queue.svg | 21 +++ .../VirtIO/img/hvisor-virtio-net.svg | 21 +++ src/chap04/subchap03/VirtIO/img/ps_module.svg | 21 +++ src/chap04/subchap03/VirtIO/img/virtio.svg | 21 +++ .../subchap03/VirtIO/img/virtio_console.svg | 21 +++ .../subchap03/VirtIO/img/virtio_real.svg | 21 +++ src/chap04/subchap03/VirtIO/img/virtqueue.svg | 21 +++ 16 files changed, 677 insertions(+), 19 deletions(-) create mode 100644 src/chap04/subchap03/VirtIO/img/architecture.svg create mode 100644 src/chap04/subchap03/VirtIO/img/control_plane_queue.svg create mode 100644 src/chap04/subchap03/VirtIO/img/data_plane_queue.svg create mode 100644 src/chap04/subchap03/VirtIO/img/hvisor-virtio-net.svg create mode 100644 src/chap04/subchap03/VirtIO/img/ps_module.svg create mode 100644 src/chap04/subchap03/VirtIO/img/virtio.svg create mode 100644 src/chap04/subchap03/VirtIO/img/virtio_console.svg create mode 100644 src/chap04/subchap03/VirtIO/img/virtio_real.svg create mode 100644 src/chap04/subchap03/VirtIO/img/virtqueue.svg diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 5f84f88..7cba434 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -44,9 +44,7 @@ # hvisor架构与实现 - [hvisor 架构](./chap04/Structure.md) - - [hvisor 启动与运行](./chap04/BootAndRun.md) - - [CPU 虚拟化](./chap04/subchap01/CPUVirtualization.md) - [PerCPU 定义](./chap04/subchap01/PerCPU.md) @@ -56,9 +54,7 @@ - [RISC-V 处理器虚拟化](./chap04/subchap01/RISCVirtualization.md) - [LoongArch处理器虚拟化](./chap04/subchap01/LoongArchVirtualization.md) - - [内存虚拟化](./chap04/MemVirtualization.md) - - [中断虚拟化](./chap04/subchap02/InterruptVirtualization.md) - [ARM 中断控制 GIC](./chap04/subchap02/ARM-GIC.md) @@ -68,7 +64,6 @@ - [RISC-V 中断控制 AIA](./chap04/subchap02/RISC-AIA.md) - [LoongArch 中断控制](./chap04/subchap02/LoonArch-Controller.md) - - [I/O 虚拟化](./chap04/subchap03/IO-Virtualization.md) - [IOMMU](./chap04/subchap03/IOMMU/IOMMU-Define.md) @@ -76,21 +71,15 @@ - [ARM SMMU 的实现](./chap04/subchap03/IOMMU/ARM-SMMU.md) - [RISC-V IOMMU 标准的实现](./chap04/subchap03/IOMMU/RISC-IOMMU.md) - - - [VirtIO](./chap04/subchap03/VirtIO/VirtIO-Define.md) - - - [Block](./chap04/subchap03/VirtIO/BlockDevice.md) - - - [Console](./chap04/subchap03/VirtIO/ConsoleDevice.md) - - - [Net](./chap04/subchap03/VirtIO/NetDevice.md) - - - [GPU]() - - - [PCI 虚拟化](./chap04/subchap03/PCI-Virtualization.md) - -- [Hvisor 管理工具](./chap04/subchap04/ManageTools.md) +- [VirtIO](./chap04/subchap03/VirtIO/VirtIO-Define.md) + - [Block](./chap04/subchap03/VirtIO/BlockDevice.md) + - [Net](./chap04/subchap03/VirtIO/NetDevice.md) + - [Console](./chap04/subchap03/VirtIO/ConsoleDevice.md) + - [GPU]() +- [PCI 虚拟化](./chap04/subchap03/PCI-Virtualization.md) +- [Hvisor 管理工具](./chap04/subchap04/ManageTools.md) + - [Hypercall](./chap04/subchap04/HyperCall.md) # hvisor 的规划 diff --git a/src/chap03/CMDTools.md b/src/chap03/CMDTools.md index e69de29..4c9dc51 100644 --- a/src/chap03/CMDTools.md +++ b/src/chap03/CMDTools.md @@ -0,0 +1,59 @@ +# 命令行工具 + +命令行工具是附属于hvisor的管理工具,用于在管理虚拟机Root Linux上创建和关闭其他虚拟机,并负责启动Virtio守护进程,提供Virtio设备模拟。仓库地址位于[hvisor-tool](https://github.com/syswonder/hvisor-tool)。 + +## 如何编译 + +命令行工具目前支持两种体系结构:arm64和riscv,且需要配合一个内核模块才能使用。在x86主机上通过交叉编译可针对不同体系结构进行编译 + +* arm64编译 + +在hvisor-tool目录下执行以下命令,即可得到面向arm64体系结构的命令行工具hvisor以及内核模块hvisorl.ko。 + +``` +make all ARCH=arm64 KDIR=xxx +``` + +其中KDIR为Root Linux源码路径,用于内核模块的编译。 + +* riscv编译 + +编译面向riscv体系结构的命令行工具和内核模块: + +``` +make all ARCH=riscv KDIR=xxx +``` + +## 对虚拟机进行管理 + +### 加载内核模块 + +使用命令行工具前,需要加载内核模块,便于用户态程序与Hyperviosr进行交互: + +``` +insmod hvisor.ko +``` + +卸载内核模块的操作为: + +``` +rmmod hvisor.ko +``` + +其中hvisor.ko位于hvisor-tool/driver目录下。 + +### 启动一个虚拟机 + +在Root Linux上可通过以下命令,创建一个id为1的虚拟机。该命令会将虚拟机的操作系统镜像文件`Image`加载到真实物理地址`xxxa`处,将虚拟机的设备树文件`linux2.dtb`加载到真实物理地址`xxxb`处,并进行启动。 + +``` +./hvisor zone start --kernel Image,addr=xxxa --dtb linux2.dtb,addr=xxxb --id 1 +``` + +### 关闭一个虚拟机 + +关闭id为1的虚拟机: + +``` +./hvisor zone shutdown -id 1 +``` \ No newline at end of file diff --git a/src/chap03/VirtIOUseage.md b/src/chap03/VirtIOUseage.md index e69de29..0c46a32 100644 --- a/src/chap03/VirtIOUseage.md +++ b/src/chap03/VirtIOUseage.md @@ -0,0 +1,92 @@ +# VirtIO设备的使用 + +目前hvisor支持三种Virtio设备:Virtio block,Virtio net和Virtio Console,以MMIO方式呈现给Root Linux以外的虚拟机。Virtio设备源码仓库位于[hvisor-tool](https://github.com/syswonder/hvisor-tool),随命令行工具一起编译和使用。通过命令行工具创建Virtio设备后,Virtio设备会成为Root Linux上的一个守护进程,其日志信息会输出到nohup.out文件中。 + +## 创建和启动Virtio设备 + +通过命令行创建Virtio设备前,需执行`insmod hvisor.ko`加载内核模块。 + +### Virtio blk设备 + +在Root Linux控制台执行以下示例指令,可创建一个Virtio blk设备: + +```shell +nohup ./hvisor virtio start \ + --device blk,addr=0xa003c00,len=0x200,irq=78,zone_id=1,img=rootfs2.ext4 & +``` + +其中`--device blk`表示创建一个Virtio磁盘设备,供id为`zone_id`的虚拟机使用。该虚拟机会通过一片MMIO区域与该设备交互,这片MMIO区域的起始地址为`addr`,长度为`len`,设备中断号为`irq`,对应的磁盘镜像路径为`img`。 + +> 使用Virtio设备的虚拟机需要在设备树中增加该Virtio mmio节点的相关信息。 + +### Virtio net设备 + +#### 创建网络拓扑 + +使用Virtio net设备前,需要在root Linux中创建一个网络拓扑图,以便Virtio net设备通过Tap设备和网桥设备连通真实网卡。在root Linux中执行以下指令: + +```shell +mount -t proc proc /proc +mount -t sysfs sysfs /sys +ip link set eth0 up +dhclient eth0 +brctl addbr br0 +brctl addif br0 eth0 +ifconfig eth0 0 +dhclient br0 +ip tuntap add dev tap0 mode tap +brctl addif br0 tap0 +ip link set dev tap0 up +``` + +便可创建`tap0设备<-->网桥设备<-->真实网卡`的网络拓扑。 + +#### 启动Virtio net + +在Root Linux控制台执行以下示例指令,可创建一个Virtio net设备: + +```shell +nohup ./hvisor virtio start \ + --device net,addr=0xa003600,len=0x200,irq=75,zone_id=1,tap=tap0 & +``` + +`--device net`表示创建一个Virtio网络设备,供id为`zone_id`的虚拟机使用。该虚拟机会通过一片MMIO区域与该设备交互,这片MMIO区域的起始地址为`addr`,长度为`len`,设备中断号为`irq`,并连接到名为`tap`的Tap设备。 + +### Virtio console设备 + +在Root Linux控制台执行以下示例指令,可创建一个Virtio console设备: + +```shell +nohup ./hvisor virtio start \ + --device console,addr=0xa003800,len=0x200,irq=76,zone_id=1 & +``` + +`--device console`表示创建一个Virtio控制台,供id为`zone_id`的虚拟机使用。该虚拟机会通过一片MMIO区域与该设备交互,这片MMIO区域的起始地址为`addr`,长度为`len`,设备中断号为`irq`。 + +执行`cat nohup.out | grep "char device"`,可观察到输出`char device redirected to /dev/pts/xx`。在Root Linux上执行: + +``` +screen /dev/pts/xx +``` + +即可进入该虚拟控制台,与该虚拟机进行交互。按下快捷键`Ctrl +a d`,即可返回Root Linux终端。执行`screen -r [session_id]`,即可重新进入虚拟控制台。 + +### 创建多个Virtio设备 + +执行以下命令,可同时创建Virtio blk、net、console设备,所有设备均位于一个守护进程。 + +```shell +nohup ./hvisor virtio start \ + --device blk,addr=0xa003c00,len=0x200,irq=78,zone_id=1,img=rootfs2.ext4 \ + --device net,addr=0xa003600,len=0x200,irq=75,zone_id=1,tap=tap0 \ + --device console,addr=0xa003800,len=0x200,irq=76,zone_id=1 & +``` + +## 关闭Virtio设备 + +执行该命令即可关闭Virtio守护进程及所有创建的设备: + +``` +pkill hvisor +``` + diff --git a/src/chap04/subchap03/VirtIO/BlockDevice.md b/src/chap04/subchap03/VirtIO/BlockDevice.md index e69de29..53a0c8d 100644 --- a/src/chap04/subchap03/VirtIO/BlockDevice.md +++ b/src/chap04/subchap03/VirtIO/BlockDevice.md @@ -0,0 +1,76 @@ +# Virtio Block + +Virtio磁盘设备的实现遵循Virtio规范约定,采用MMIO的设备访问方式供其他虚拟机发现和使用。目前支持`VIRTIO_BLK_F_SEG_MAX`、`VIRTIO_BLK_F_SIZE_MAX`、`VIRTIO_F_VERSION_1`、`VIRTIO_RING_F_INDIRECT_DESC`和`VIRTIO_RING_F_EVENT_IDX`五种特性。 + +## Virtio设备的顶层描述——VirtIODevice + +一个Virtio设备由VirtIODevice结构体表示,该结构体包含设备ID、Virtqueue的个数vqs_len、所属的虚拟机ID、设备中断号irq_id、MMIO区域的起始地址base_addr、MMIO区域长度len、设备类型type、部分由设备保存的MMIO寄存器regs、Virtqueue数组vqs、指向描述特定设备信息的指针dev。通过这些信息,可以完整描述一个Virtio设备。 + +```c +// The highest representations of virtio device +struct VirtIODevice +{ + uint32_t id; + uint32_t vqs_len; + uint32_t zone_id; + uint32_t irq_id; + uint64_t base_addr; // the virtio device's base addr in non root zone's memory + uint64_t len; // mmio region's length + VirtioDeviceType type; + VirtMmioRegs regs; + VirtQueue *vqs; + // according to device type, blk is BlkDev, net is NetDev, console is ConsoleDev. + void *dev; + bool activated; +}; + +typedef struct VirtMmioRegs { + uint32_t device_id; + uint32_t dev_feature_sel; + uint32_t drv_feature_sel; + uint32_t queue_sel; + uint32_t interrupt_status; + uint32_t interrupt_ack; + uint32_t status; + uint32_t generation; + uint64_t dev_feature; + uint64_t drv_feature; +} VirtMmioRegs; +``` + +## Virtio Block设备的描述信息 + +对于Virtio磁盘设备,VirtIODevice中type字段为VirtioTBlock,vqs_len为1,表示只有一个Virtqueue,dev指针指向描述磁盘设备具体信息的virtio_blk_dev结构体。virtio_blk_dev中config用来表示设备的数据容量和一次数据传输中最大的数据量,img_fd为该设备打开的磁盘镜像的文件描述符,tid、mtx、cond用于工作线程,procq为工作队列,closing用来指示工作线程何时关闭。virtio_blk_dev和blkp_req结构体的定义见图4.6。 + +```c +typedef struct virtio_blk_dev { + BlkConfig config; + int img_fd; + // describe the worker thread that executes read, write and ioctl. + pthread_t tid; + pthread_mutex_t mtx; + pthread_cond_t cond; + TAILQ_HEAD(, blkp_req) procq; + int close; +} BlkDev; + +// A request needed to process by blk thread. +struct blkp_req { + TAILQ_ENTRY(blkp_req) link; + struct iovec *iov; + int iovcnt; + uint64_t offset; + uint32_t type; + uint16_t idx; +}; +``` + +## Virtio Block设备工作线程 + +每个Virtio磁盘设备,都拥有一个工作线程和工作队列。工作线程的线程ID保存在virtio_blk_dev中的tid字段,工作队列则是procq。工作线程负责进行数据IO操作及调用中断注入系统接口。它在Virtio磁盘设备启动后被创建,并不断查询工作队列中是否有新的任务,如果队列为空则等待条件变量cond,否则处理任务。 + +当驱动写磁盘设备MMIO区域的QueueNotify寄存器时,表示可用环中有新的IO请求。Virtio磁盘设备(位于主线程的执行流)收到该请求后,首先会读取可用环得到描述符链的第一个描述符,第一个描述符指向的内存缓冲区包含了IO请求的类型(读/写)、要读写的扇区编号,之后的描述符指向的内存缓冲区均为数据缓冲区,对于读操作会将读到的数据存入这些数据缓冲区,对于写操作则会从数据缓冲区获取要写入的数据,最后一个描述符对应的内存缓冲区(结果缓冲区)用于设备描述IO请求的完成结果,可选项有成功(OK)、失败(IOERR)、不支持的操作(UNSUPP)。 据此解析整个描述符链即可获得有关该IO请求的所有信息,并将其保存在blkp_req结构体中,该结构体中的字段iov表示所有数据缓冲区,offset表示IO操作的数据偏移量,type表示IO操作的类型(读/写),idx为描述符链的首描述符索引,用于更新已用环。随后设备会将blkp_req加入到工作队列procq中,并通过signal函数唤醒阻塞在条件变量cond上的工作线程。工作线程即可对任务进行处理。 + +工作线程获取到任务后,会根据blkp_req指示的IO操作信息通过preadv和pwritev函数读写img_fd所对应的磁盘镜像。完成读写操作后,会首先更新描述符链的最后一个描述符,该描述符用于描述IO请求的完成结果,例如成功、失败、不支持该操作等。然后更新已用环,将该描述符链的首描述符写到新的表项中。随后进行中断注入,通知其他虚拟机。 + +工作线程的设立,可以有效地将耗时操作分散到其他CPU核上,提高主线程分发请求的效率和吞吐量,提升设备性能。 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/ConsoleDevice.md b/src/chap04/subchap03/VirtIO/ConsoleDevice.md index e69de29..e631059 100644 --- a/src/chap04/subchap03/VirtIO/ConsoleDevice.md +++ b/src/chap04/subchap03/VirtIO/ConsoleDevice.md @@ -0,0 +1,46 @@ +# Virtio Console + +Virtio Console设备,本质上是一个虚拟控制台设备,用于数据的输入和输出,可作为虚拟终端供其他虚拟机使用。目前hvisor支持`VIRTIO_CONSOLE_F_SIZE`和`VIRTIO_F_VERSION_1`特性。 + +## Virtio Console设备的描述信息 + +对于Virtio控制台设备,VirtIODevice结构体中type字段为VirtioTConsole,vqs_len为2,表示共有两个Virtqueue,分别是receive virtqueue接收队列和transmit virtqueue发送队列,用于端口0的接收数据和发送数据。dev指针指向描述控制台设备具体信息的virtio_console_dev结构体,该结构体中config用来表示该控制台的行数和列数,master_fd为该设备连接的伪终端主设备的文件描述符,rx_ready表示接收队列是否可用,event则用于event monitor线程通过epoll监视伪终端主设备的可读事件。 + +```c +typedef struct virtio_console_dev { + ConsoleConfig config; + int master_fd; + int rx_ready; + struct hvisor_event *event; +} ConsoleDev; +``` + +## 伪终端 + +终端,本质上是一个输入输出设备。终端在计算机刚刚发展时,名叫电传打印机Teleprinter(TTY)。现在终端在计算机上成为了一种虚拟设备,由终端模拟程序连接显卡驱动和键盘驱动,实现数据的输入和输出。终端模拟程序有两种不同的实现形式,第一种是作为Linux的内核模块,并以`/dev/tty[n]`设备暴露给用户程序;第二种是作为一个应用程序,运行在Linux用户态,被称为伪终端(**pseudo terminal, PTY**)。 + +伪终端本身不是本文的重点,但伪终端使用的两种可互相传递数据设备——伪终端主设备`PTY master`和从设备`PTY slave`,被本文用来实现Virtio Console设备。 + +应用程序通过执行`posix_openpt`,可获取一个可用的`PTY master`,通过`ptsname`函数,可获取该`PTY master`对应的`PTY slave`。一个TTY驱动程序连接`PTY master` 和 `PTY slave`,会在 master 和 slave 之间复制数据。这样当程序向master(或slave)写入数据时,程序从slave(或master)可读到同样的数据。 + +## Virtio Console总体设计 + +Virtio Console设备作为Root Linux上的一个守护进程,会在设备初始化过程中打开一个`PTY master`,并向日志文件中输出master对应的`PTY slave`的路径`/dev/pts/x`,供screen会话连接。同时Virtio守护进程中的event monitor线程会监视`PTY slave`的可读事件,以便`PTY master`及时获取到用户的输入数据。 + +当用户在Root Linux上执行`screen /dev/pts/x`时,会在当前终端上创建一个screen会话,该会话会连接`PTY slave`对应的设备 `/dev/pts/x`,并接管当前终端的输入和输出。Virtio Console设备的实现结构图如下图所示。 + +![virtio_console](./img/virtio_console.svg) + +### 输入命令 + +当用户在键盘上输入命令时,输入的字符会通过终端设备传递给Screen会话,Screen会话会将字符写入`PTY slave`。event monitor线程通过epoll发现`PTY slave`可读时,会调用`virtio_console_event_handler`函数。该函数会读取`PTY slave`,并将数据写入Virtio Console设备的`Receive Virtqueue`中,并向对应的虚拟机发送中断。 + +对应的虚拟机收到中断后,会将收到的字符数据通过TTY子系统传递给Shell,交由Shell解释执行。 + +### 显示信息 + +当使用Virtio Console驱动的虚拟机要通过Virtio Console设备输出信息时,Virtio Console驱动会将要输出的数据写入`Transmit Virtqueue`中,并写MMIO区域的`QueueNotify`寄存器通知Virtio Console设备处理IO操作。 + +Virtio Console设备会读取`Transmit Virtqueue`,获取要输出的数据,并写入`PTY master`。Screen会话就会从`PTY slave`获取要输出的数据,并通过终端设备在显示器上显示输出信息。 + +> 由于`PTY master`和`PTY slave`之间由TTY driver相连接,TTY driver包含一个line discipline,用于将`PTY master`写给`PTY slave`的数据回传给`PTY master`。由于我们不需要该功能,因此需通过函数`cfmakeraw`将line discipline功能关闭。 diff --git a/src/chap04/subchap03/VirtIO/NetDevice.md b/src/chap04/subchap03/VirtIO/NetDevice.md index e69de29..45ecd3f 100644 --- a/src/chap04/subchap03/VirtIO/NetDevice.md +++ b/src/chap04/subchap03/VirtIO/NetDevice.md @@ -0,0 +1,47 @@ +# Virtio Network设备 + +Virtio网络设备,本质上是一块虚拟网卡。目前支持的特性包括`VIRTIO_NET_F_MAC`、`VIRTIO_NET_F_STATUS`、`VIRTIO_F_VERSION_1`、`VIRTIO-RING_F_INDIRECT_DESC`、`VIRTIO_RING_F_EVENT_IDX`。 + +## Virtio Network设备的描述信息 + +对于Virtio网络设备,VirtIODevice中type字段为VirtioTNet,vqs_len为2,表示有2个Virtqueue,分别是Receive Queue接收队列和Transmit Queue发送队列,dev指针指向描述网络设备具体信息的virtio_net_dev结构体。Virtio_net_dev中config用来表示该网卡的MAC地址和连接状态,tapfd为该设备对应的Tap设备的文件描述符,rx_ready表示接收队列是否可用,event则用于接收报文线程通过epoll监视Tap设备的可读事件。 + +```c +typedef struct virtio_net_dev { + NetConfig config; + int tapfd; + int rx_ready; + struct hvisor_event *event; +} NetDev; + +struct hvisor_event { + void (*handler)(int, int, void *); + void *param; + int fd; + int epoll_type; +}; +``` + +## Tap设备和网桥设备 + +Virtio网络设备的实现基于两种Linux内核提供的虚拟设备:Tap设备和网桥设备。 + +Tap设备是一个由Linux内核用软件实现的以太网设备,通过在用户态读写Tap设备就可以模拟以太网帧的接收和发送。具体而言,当进程或内核执行一次对Tap设备的写操作时,就相当于将一个报文发送给Tap设备。对Tap设备执行一次读操作时,就相当于从Tap设备接收一个报文。这样,分别对Tap设备进行读和写操作,即可实现内核与进程之间报文的传递。 + +创建tap设备的命令为:`ip tuntap add dev tap0 mode tap`。该命令会创建一个名为tap0的tap设备。如果一个进程要使用该设备,需要首先打开/dev/net/tun设备,获得一个文件描述符tun_fd,并对其调用ioctl(TUNSETIFF),将进程链接到tap0设备上。之后tun_fd实际上就成为了tap0设备的文件描述符,对其进行读写和epoll即可。 + +网桥设备是一个Linux内核提供的功能类似于交换机的虚拟设备。当其他网络设备连接到网桥设备时,其他设备会退化成网桥设备的端口,由网桥设备接管所有设备的收发包过程。当其他设备收到报文时,会直接发向网桥设备,由网桥设备根据MAC地址转发到其他端口。因此,连接在网桥上的所有设备可以互通报文。 + +创建网桥设备的命令为:`brctl addbr br0`。将物理网卡eth0连接到br0上的命令为:brctl addif br0 eth0。将tap0设备连接到br0上的命令为:brctl addif br0 tap0。 + +在Virtio网络设备启动前,Root Linux需要提前在命令行中创建和启动tap设备和网桥设备,并将tap设备和Root Linux上的物理网卡分别与网桥设备进行连接。每个Virtio网络设备都需要连接一个tap设备,最终形成一张如下图的网络拓扑图。这样,Virtio网络设备通过读写tap设备,就可以与外网进行报文的传输了。 + +![hvisor-virtio-net](./img/hvisor-virtio-net.svg) + +## 发送报文 + +Virtio网络设备的Transmit Virtqueue用于存放发送缓冲区。当设备收到驱动写QueueNotify寄存器的请求时,如果此时QueueSel寄存器指向Transmit Queue,表示驱动告知设备有新的报文要发送。Virtio-net设备会从可用环中取出描述符链,一个描述符链对应一个报文,其指向的内存缓冲区均为要发送的报文数据。报文数据包含2部分,第一部分为Virtio协议规定的报文头virtio_net_hdr_v1结构体,该结构体包含该报文的一些描述信息,第二部分为以太网帧。发送报文时只需将以太网帧的部分通过writev函数写入Tap设备,Tap设备收到该帧后会转发给网桥设备,网桥设备根据MAC地址会通过物理网卡转发到外网。 + +## 接收报文 + +Virtio网络设备在初始化时,会将Tap设备的文件描述符加到event monitor线程epoll实例的interest list中。event monito线程会循环调用epoll_wait函数,监视tap设备的可读事件,一旦发生可读事件,说明tap设备收到了内核发来的报文,epoll_wait函数返回,执行接收报文处理函数。处理函数会从Receive Virtqueue的可用环中取出一个描述符链,并读取tap设备,将数据写入描述符链指向的内存缓冲区中,并更新已用环。处理函数将重复该步骤,直到读取tap设备返回值为负并且errno为EWOULDBLOCK,表明tap设备已经没有新的报文,之后中断通知其他虚拟机收报文。 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/VirtIO-Define.md b/src/chap04/subchap03/VirtIO/VirtIO-Define.md index e69de29..4cb3a63 100644 --- a/src/chap04/subchap03/VirtIO/VirtIO-Define.md +++ b/src/chap04/subchap03/VirtIO/VirtIO-Define.md @@ -0,0 +1,160 @@ +# Virtio + +## Virtio简介 + +Virtio由Rusty Russell于2008年提出,是一个旨在提高设备性能, 统一各种半虚拟设备方案的设备虚拟化标准。目前,Virtio已囊括了十几种外设如磁盘、网卡、控制台、GPU等,同时许多操作系统包括Linux均已实现多种Virtio设备的前端驱动程序。因此虚拟机监控器只需实现Virtio后端设备,便可直接允许Linux等已实现Virtio驱动的虚拟机使用Virtio设备。 + +Virtio协议定义了一组半虚拟IO设备的驱动接口,规定虚拟机的操作系统需要实现前端驱动,Hypervisor需要实现后端设备,虚拟机和Hypervisor之间通过数据面接口、控制面接口进行通信和交互。 + +![virtio](./img/virtio.svg#pic_center) + +### 数据面接口 + +数据面接口(Data plane)是指驱动和设备之间进行IO数据传输的方式。对于Virtio,数据面接口是指驱动和设备之间的一片共享内存Virtqueue。Virtqueue是Virtio协议中一个重要的数据结构,是Virtio设备进行批量数据传输的机制和抽象表示,用于驱动和设备之间执行各种数据传输操作。Virtqueue包含三大组成部分:描述符表、可用环和已用环,其作用分别是: + +1. 描述符表(Descriptor Table):是以描述符为元素的数组。每个描述符包含4个字段:addr、len、flag、next。描述符可以用来表示一段内存缓冲区的地址(addr)、大小(len)和属性(flag),内存缓冲区中可以包含IO请求的命令或数据(由Virtio驱动填写),也可以包含IO请求完成后的返回结果(由Virtio设备填写)。描述符可以根据需要由next字段链接成一个描述符链,一个描述符链表示一个完整的IO请求或结果。 + +2. 可用环(Available Ring):是一个环形队列,队列中的每个元素表示Virtio驱动发出的IO请求在描述符表中的索引,即每个元素指向一条描述符链的起始描述符。 +3. 已用环(Used Ring):是一个环形队列,队列中的每个元素表示Virtio设备完成IO请求后,写入的IO结果在描述符表中的索引。 + +![virtqueue](./img/virtqueue.svg) + +因此利用这三个数据结构就可以完整地描述驱动和设备之间进行IO数据传输请求的命令、数据和结果。Virtio驱动程序负责分配Virtqueue所在的内存区域,并将其地址分别写入对应的MMIO控制寄存器中告知Virtio设备,这样设备获取到三者的内存地址后,便可与驱动通过Virtqueue进行IO传输。 + +### 控制面接口 + +控制面接口(Control Plane)是指驱动发现、配置和管理设备的方式,在hvisor中,Virtio的控制面接口主要是指基于内存映射的MMIO寄存器。操作系统首先通过设备树探测基于MMIO的Virtio设备,并通过读写这些内存映射的控制寄存器,便可以与设备进行协商、配置和通知。其中较为重要的几个寄存器为: + +* QueueSel:用于选择当前操作的Virtqueue。一个设备可能包含多个Virtqueue,驱动通过写该寄存器指示它在操作哪个队列。 + +* QueueDescLow、QueueDescHigh:用于指示描述符表的中间物理地址IPA。驱动写这两个32位寄存器告知设备描述符表的64位物理地址,用于建立共享内存。 + +* QueueDriverLow、QueueDriverHigh:用于指示可用环的中间物理地址IPA。 + +* QueueDeviceLow、QueueDeviceHigh:用于指示已用环的中间物理地址IPA。 + +* QueueNotify:驱动写该寄存器时,表示Virtqueue中有新的IO请求需要处理。 + +除了控制寄存器外,每个设备所在的MMIO内存区域还包含一个设备配置空间。对于磁盘设备,配置空间会指示磁盘的容量和块大小;对于网络设备,配置空间会指示设备的MAC地址和连接状态。对于控制台设备,配置空间提供控制台大小信息。 + +对于Virtio设备所在的MMIO内存区域,Hypervisor不会为虚拟机进行第二阶段地址翻译的映射。当驱动读写这片区域时,会因缺页异常发生VM Exit,陷入Hypervisor,Hypervisor根据导致缺页异常的访问地址即可确定驱动访问的寄存器,并做出相应处理,例如通知设备进行IO操作。处理完成后,Hypervisor通过VM Entry返回虚拟机。 + +### Virtio设备的IO流程 + +一个运行在虚拟机上的用户进程,从发出IO操作,到获得IO结果,大致可以分为以下4步: + +1. 用户进程发起IO操作,操作系统内核中的Virtio驱动程序收到IO操作命令后,将其写入Virtqueue,并写QueueNotify寄存器通知Virtio设备。 +2. 设备收到通知后,通过解析可用环和描述符表,得到具体的IO请求及缓冲区地址,并进行真实的IO操作。 +3. 设备完成IO操作后,将结果写入已用环。如果驱动程序采用轮询已用环的方式等待IO结果,那么驱动可以立即收到结果信息;否则,则需要通过中断通知驱动程序。 +4. 驱动程序从已用环中得到IO结果,并返回到用户进程。 + +## Virtio后端机制的设计与实现 + +hvisor中的Virtio设备遵循[Virtio v1.2](https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.pdf)协议进行设计和实现。为了在保证hvisor轻量的情况下维持设备较好的性能,Virtio后端的两个设计要点为: + +1. 采用微内核的设计思想,将Virtio设备的实现从Hypervisor层移到管理虚拟机用户态。管理虚拟机运行Linux操作系统,称为Root Linux。物理磁盘和网卡等设备会直通给Root Linux,而Virtio设备会作为Root Linux上的守护进程,为其他虚拟机(Non Root Linux)提供设备模拟。这样可以保证Hypervisor层的轻量性,便于形式化验证。 + +2. 位于其他虚拟机上的Virtio驱动程序和位于Root Linux上的Virtio设备之间,直接通过共享内存进行交互,共享内存区域存放交互信息,称为通信跳板,并采用生产者消费者模式,由Virtio设备后端和Hypervisor进行共享。这样可以减小驱动和设备之间交互的开销,提升设备的性能。 + +根据以上两个设计要点,Virtio后端设备的实现将分为通信跳板、Virtio守护进程、内核服务模块三个部分: + +![architecture](./img/architecture.svg) + +### 通信跳板 + +为了实现分布在不同虚拟机上的驱动和设备之间的高效交互,本文设计了一个通信跳板作为驱动和设备传递控制面交互信息的桥梁,它本质上是一片共享内存区域,包含2个环形队列:请求提交队列和请求结果队列, 分别存放由驱动发出的交互请求和设备返回的结果。两个队列位于Hypervisor与Virtio守护进程共享的内存区域中,并采用生产者消费者模型,Hypervisor作为请求提交队列的生产者和请求结果队列的消费者, Virtio守护进程作为请求提交队列的消费者和请求结果队列的生产者。这样就便于Root Linux和其他虚拟机之间传递Virtio控制面交互的信息。需要注意的是,请求提交队列和请求结果队列与Virtqueue并不相同。Virtqueue是驱动和设备之间的数据面接口,用于数据传输,本质上包含了数据缓冲区的地址、结构等信息。而通信跳板则是用于驱动和设备之间的控制面进行交互和通信。 + +
ps_module
+ + + +* 通信跳板结构体 + +通信跳板由结构体virtio_bridge表示,其中req_list为请求提交队列,res_list和cfg_values共同组成请求结果队列。device_req结构体表示驱动发往设备的交互请求,device_res结构体表示设备要注入的中断信息,用于通知虚拟机驱动程序IO操作已完成。 + +```c +// 通信跳板结构体: +struct virtio_bridge { + __u32 req_front; + __u32 req_rear; + __u32 res_front; + __u32 res_rear; + // 请求提交队列 + struct device_req req_list[MAX_REQ]; + // res_list、cfg_flags、cfg_values共同组成请求结果队列 + struct device_res res_list[MAX_REQ]; + __u64 cfg_flags[MAX_CPUS]; + __u64 cfg_values[MAX_CPUS]; + __u64 mmio_addrs[MAX_DEVS]; + __u8 mmio_avail; + __u8 need_wakeup; +}; +// 驱动发往设备的交互请求 +struct device_req { + __u64 src_cpu; + __u64 address; // zone's ipa + __u64 size; + __u64 value; + __u32 src_zone; + __u8 is_write; + __u8 need_interrupt; + __u16 padding; +}; +// 设备要注入的中断信息 +struct device_res { + __u32 target_zone; + __u32 irq_id; +}; +``` + +#### 请求提交队列 + +请求提交队列,用于驱动向设备传递控制面的交互请求。当驱动读写Virtio设备的MMIO内存区域时,由于预先Hypervisor不为这段内存区域进行第二阶段地址映射,因此执行驱动程序的CPU会收到缺页异常,陷入Hypervisor。Hypervisor会将当前CPU编号、缺页异常的地址、地址宽度、要写入的值(如果是读则忽略)、虚拟机ID、是否为写操作等信息组合成名为device_req的结构体,并将其加入到请求提交队列req_list,这时监视请求提交队列的Virtio守护进程就会取出该请求进行处理。 + +为了方便Virtio守护进程和Hypervisor之间基于共享内存的通信,请求提交队列req_list采用环形队列的方式实现,队头索引req_front仅由Virtio进程取出请求后更新,队尾索引req_rear仅由Hypervisor放入请求后更新。如果队头和队尾索引相等,则表示队列为空;如果队尾索引加1并取模后与队头索引相等,则表示队列已满,再加入请求时驱动需要原地阻塞,等待队列可用。为了保证Hypervisor和Virtio进程对共享内存的实时观察以及互斥访问,Hypervisor每次向队列增加请求后,需要执行写内存屏障,再更新队尾索引,保证Virtio进程观察到队尾索引更新时,可以正确获取队列中的请求;Virtio守护进程从队列中取出请求后,需要执行写内存屏障,保证Hypervisor可立刻观察到队头索引的更新。通过这种生产者消费者模型及环形队列的方式,加上必要的内存屏障,就解决了在不同特权级下共享内存的互斥问题。由于多虚拟机情况下可能有多个CPU同时向请求提交队列加入请求,因此CPU需要首先获取互斥锁,才能操作请求提交队列;而Virtio守护进程只有主线程操作请求提交队列,因此无需加锁。这样就解决了同一特权级下共享内存的互斥问题。 + +#### 请求结果队列 + +当Virtio守护进程完成请求的处理后,会将与结果相关的信息放入请求结果队列,并通知驱动程序。为了提升通信效率,根据Virtio交互信息的分类,请求结果队列分为了两个子队列: + +* 数据面结果子队列 + +数据面结果队列,由res_list结构体表示,用于存放注入中断的信息。当驱动程序写设备内存区域的Queue Notify寄存器时,表示可用环有新的数据,需要设备进行IO操作。由于IO操作耗时过长,Linux为了避免不必要的阻塞,提高CPU利用率,要求Hypervisor将IO请求提交给设备后,CPU需要立刻从Hypervisor返回到虚拟机,执行其他任务。这要求设备在完成IO操作后通过中断通知虚拟机。因此Virtio进程完成IO操作并更新已用环后,会将设备的中断号irq_id和设备所属的虚拟机ID组合成device_res结构体,加入到数据面结果子队列res_list中,并通过ioctl和hvc陷入到Hypervisor。数据面结果队列res_list类似于请求提交队列,是一个环形队列,通过队头索引res_front和队尾索引res_rear可确定队列长度。Hypervisor会从res_list中取出所有元素,并将其加入到中断注入表VIRTIO_IRQS。中断注入表是一个基于B树的键值对集合,键为CPU编号,值为一个数组,数组的第0个元素表示该数组的有效长度,后续的各个元素表示要注入到本CPU的中断。为了防止多个CPU同时操作中断注入表,CPU需要首先获取全局互斥锁才能访问中断注入表。通过中断注入表,CPU可以根据自身CPU编号得知需要为自己注入哪些中断。之后Hypervisor会向这些需要注入中断的CPU发送IPI核间中断,收到核间中断的CPU就会遍历中断注入表,向自身注入中断。下图描述了整个过程,图中黑色实心三角箭头表示运行其他虚拟机CPU执行的操作,黑色普通箭头表示运行Root Linux的CPU执行的操作。 + +data_plane_queue + +* 控制面结果子队列 + +控制面结果队列,由cfg_values和cfg_flags两个数组共同表示,数组索引为CPU编号,即每个CPU都唯一对应两个数组的同一个位置。cfg_values用于存放控制面接口交互的结果,cfg_flags用于指示设备是否完成控制面交互请求。当驱动程序读写设备内存区域的寄存器时(除Queue Notify寄存器),发出配置和协商相关的控制面交互请求,当该交互请求加入到请求提交队列后,由驱动陷入到Hypervisor的CPU需要等待结果返回后才能回到虚拟机。由于Virtio守护进程无需对这种请求进行IO操作,只需查询相关信息,因此可以迅速完成请求的处理,且无需更新已用环。完成请求后,守护进程会根据驱动的CPU编号id将结果值写入cfg_values[id](对于读请求),并执行写内存屏障,随后递增cfg_flags[id],再执行第二次写内存屏障,保证驱动侧CPU观察到cfg_flags[id]变化时,cfg_values[id]已保存正确的结果值。驱动侧的CPU观察到cfg_flags[id]改变时,便可确定设备已返回结果,直接从cfg_values[id]取出值并返回到虚拟机即可。这样Virtio设备就可以避免执行ioctl和hvc,造成不必要的CPU上下文切换,从而提升设备的性能。下图描述了整个过程,图中黑色实心三角箭头表示运行其他虚拟机的CPU执行的操作,黑色普通箭头表示运行Root Linux的CPU执行的操作。 + +![control_plane_queue](./img/control_plane_queue.svg) + +### 内核服务模块 + +由于位于Root Linux用户态的Virtio守护进程需要与hvisor进行通信,因此本文将Root Linux中的内核模块hvisor.ko作为通信的桥梁。该模块除了被命令行工具使用外,还承担了如下工作: + +1. 在Virtio设备初始化时,为Virtio守护进程和Hypervisor之间建立通信跳板所在的共享内存区域。 + +在Virtio守护进程初始化时,会通过ioctl请求内核模块分配通信跳板所在的共享内存,此时内核模块会通过内存分配函数`__get_free_pages`分配一页连续的物理内存作为共享内存,并通过`SetPageReserved`函数设置页面属性为保留状态,避免因Linux的页面回收机制导致该页面被交换到磁盘。之后,内核模块需要让Virtio守护进程和Hypervisor均能获取到这片内存。对于Hypervisor,内核模块会执行hvc通知Hypervisor,并将共享内存的物理地址作为参数传递。对于Virtio守护进程,进程会对/dev/hvisor调用mmap,内核模块会在hvisor_map函数中将共享内存映射到Virtio进程的一片空闲的虚拟内存区域,该区域的起始地址会作为mmap的返回值返回。 + +2. 当Virtio后端设备需要为其他虚拟机注入设备中断时,会通过ioctl通知内核模块,内核模块会通过hvc命令向下调用Hypervisor提供的系统接口,通知Hypervisor进行相应的操作。 + +3. 唤醒Virtio守护进程。 + +当驱动访问设备的MMIO区域时,会陷入EL2,进入mmio_virtio_handler函数。该函数会根据通信跳板中的need_wakeup标志位判断是否需要唤醒Virtio守护进程。如果标志位为1, 则向Root Linux的第0号CPU发送event id为IPI_EVENT_WAKEUP _VIRTIO_DEVICE的SGI中断,0号CPU收到SGI中断后,会陷入EL2,并向自身注入Root Linux设备树中hvisor_device节点的中断号。当0号CPU返回虚拟机时,会收到注入到自身的中断,进入内核服务模块提前注册的中断处理函数。该函数会通过send_sig_info函数向Virtio守护进程发送SIGHVI信号。Virtio守护进程事先阻塞在sig_wait函数,收到SIGHVI信号后,便会轮询请求提交队列,并设置need_wakeup标志位为0。 + +### Virtio守护进程 + +为了保证Hypervisor的轻量,本文没有采用传统的Virtio设备实现方式,将其实现在Hypervisor层,而是将其移动到了Root Linux的用户态,作为守护进程提供设备模拟服务。守护进程包含两个部分,分发器和各种Virtio设备。其中分发器负责轮询请求提交队列,当队列不为空时取出交互请求并根据类型分发给对应的后端设备进行处理。Virtio设备的实现均遵循Virtio规范,控制面采用MMIO方式呈现, 数据面通过VirtQueue与其他客户机驱动进行数据传输。当守护进程完成一个交互请求的处理后,会将结果信息加入请求结果队列,并通过ioctl通知内核服务模块。 + +![virtio_real](./img/virtio_real.svg) + +hvisor在启动后,会将物理设备如磁盘、网卡、串口以直通的形式提供给Root Linux。Virtio守护进程最终会操作这些真实设备进行IO操作。具体而言,守护进程中的每个Virtio磁盘设备会绑定一个物理磁盘上的磁盘镜像文件,该镜像作为Virtio磁盘的存储介质,所有对Virtio磁盘的IO操作最终都会对应到这个磁盘镜像。每个Virtio网络设备都会绑定一个Tap设备,由网桥设备连接所有Tap设备与真实网卡,从而与外网进行通信。每个Virtio控制台设备则会绑定一个伪终端,由Root Linux的控制台与用户交互。 + +* 休眠与唤醒机制 + +为了避免分发器持续轮询造成的CPU占用率过高的问题,本文还对守护进程实现了休眠机制。当分发器轮询请求提交队列时,发现队列一段时间内一直为空,表明后续可能很长一段时间都没有新的请求了,此时进入休眠模式。当Virtio驱动向请求提交队列提交新的请求时,如果发现分发器处于休眠状态,则通过中断和信号唤醒分发器线程,使其进入轮询状态。 + +* event monitor线程 + +为了便于实现Virtio net和console设备,需要一个独立的线程监视`tap`设备和`PTY slave`设备的可读事件,因此Virtio守护进程启动时,会启动一个名为event monitor的线程,通过epoll监控这些设备,当发现设备可读时,会调用提前注册的处理函数,进行处理。 diff --git a/src/chap04/subchap03/VirtIO/img/architecture.svg b/src/chap04/subchap03/VirtIO/img/architecture.svg new file mode 100644 index 0000000..86bf928 --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/architecture.svg @@ -0,0 +1,21 @@ + + + + + + + + Root Linux用户态内核态Virtio守护进程分发器Virtio-blk设备Virtio-net设备Hvisor其他虚拟机用户进程Virtio Driver用户态内核态Hypervisor服务模块请求提交队列请求结果队列通信跳板hvcioctl共享内存共享内存VirtqueueVM ExitVM Entry \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/control_plane_queue.svg b/src/chap04/subchap03/VirtIO/img/control_plane_queue.svg new file mode 100644 index 0000000..232b868 --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/control_plane_queue.svg @@ -0,0 +1,21 @@ + + + + + + + + Root Linux用户态内核态Virtio守护进程分发器Virtio-blk设备Virtio-net设备Hvisor其他虚拟机用户进程Virtio Driver用户态内核态请求提交队列控制面结果队列通信跳板1. 陷入HypervisorMMIO陷入信息2. 加入请求3. 轮询队列3. 取出请求4. 配置协商5. 返回结果6. 返回虚拟机 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/data_plane_queue.svg b/src/chap04/subchap03/VirtIO/img/data_plane_queue.svg new file mode 100644 index 0000000..535b335 --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/data_plane_queue.svg @@ -0,0 +1,21 @@ + + + + + + + + 共享内存VirtqueueRoot Linux用户态内核态Virtio守护进程分发器Virtio-blk设备Virtio-net设备Hvisor其他虚拟机用户进程Virtio Driver用户态内核态1. 陷入Hypervisor请求提交队列数据面结果队列MMIO陷入信息2. 加入请求3. 取出请求6. 加入注入中断的信息3. 返回虚拟机4. 解析请求物理磁盘/网卡5. 进行IO操作内核模块7.ioctl8. hvcHVC处理模块9. SGI核间中断10. 陷入Hypervisor11. 中断注入并返回虚拟机 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/hvisor-virtio-net.svg b/src/chap04/subchap03/VirtIO/img/hvisor-virtio-net.svg new file mode 100644 index 0000000..ef3a7b9 --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/hvisor-virtio-net.svg @@ -0,0 +1,21 @@ + + + + + + + + Virtio-net Device0Virtio-net Device1tap0tap1真实网卡网关网桥设备Root Linux用户态内核态 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/ps_module.svg b/src/chap04/subchap03/VirtIO/img/ps_module.svg new file mode 100644 index 0000000..39e0f4c --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/ps_module.svg @@ -0,0 +1,21 @@ + + + + + + + + Virtio守护进程hvisor请求结果队列Virtio驱动程序发起交互请求,陷入hypervisorCPU从hypervisor返回VM其他虚拟机Root Linux请求提交队列 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/virtio.svg b/src/chap04/subchap03/VirtIO/img/virtio.svg new file mode 100644 index 0000000..f00cb27 --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/virtio.svg @@ -0,0 +1,21 @@ + + + + + + + + 进程进程进程virito-blk驱动文件系统网络子系virito-net驱动virito-gpu驱动图形子系客户机操作系统virtqueue设备配置空间Hypervisorvirtio-blk设备virtio-net设备virtio-gpu设备数据面接口控制面接口设备状态域 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/virtio_console.svg b/src/chap04/subchap03/VirtIO/img/virtio_console.svg new file mode 100644 index 0000000..848c03d --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/virtio_console.svg @@ -0,0 +1,21 @@ + + + + + + + + Hypervisor其他虚拟机Root Linux共享内存Virtqueue用户态内核态shellVirtio-console驱动TTY子系统Virtio Console设备Screen会话PTY masterPTY slaveTTY driver用户 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/virtio_real.svg b/src/chap04/subchap03/VirtIO/img/virtio_real.svg new file mode 100644 index 0000000..2582bfc --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/virtio_real.svg @@ -0,0 +1,21 @@ + + + + + + + + Hypervisor其他虚拟机Root Linux共享内存Virtqueue用户态内核态Virtio-blk驱动Virtio-net驱动分发器文件系统磁盘文件tap设备网桥真实网卡Virtio-console驱动Virtio-console设备Virtio-net设备Virtio-blk设备伪终端 \ No newline at end of file diff --git a/src/chap04/subchap03/VirtIO/img/virtqueue.svg b/src/chap04/subchap03/VirtIO/img/virtqueue.svg new file mode 100644 index 0000000..2a39746 --- /dev/null +++ b/src/chap04/subchap03/VirtIO/img/virtqueue.svg @@ -0,0 +1,21 @@ + + + + + + + + 可用环addrlenflagnext0x30000x200N10x34000x200W | N20x40000x200W·······已用环描述符表Virtio驱动Virtio设备AddFetchAddFetch \ No newline at end of file