Skip to content

Commit

Permalink
Finish virtio and cmdtool usage doc, and implementation docs for virt…
Browse files Browse the repository at this point in the history
…io devices (#9)

* finish usage doc for virtio device and cmdtool, close #7

* Add implementation doc for virtio devices,close #8
  • Loading branch information
KouweiLee authored Jul 6, 2024
1 parent 641a4a1 commit 35b70b5
Show file tree
Hide file tree
Showing 16 changed files with 677 additions and 19 deletions.
27 changes: 8 additions & 19 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@
# hvisor架构与实现

- [hvisor 架构](./chap04/Structure.md)

- [hvisor 启动与运行](./chap04/BootAndRun.md)

- [CPU 虚拟化](./chap04/subchap01/CPUVirtualization.md)

- [PerCPU 定义](./chap04/subchap01/PerCPU.md)
Expand All @@ -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)
Expand All @@ -68,29 +64,22 @@
- [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)

- [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 的规划
Expand Down
59 changes: 59 additions & 0 deletions src/chap03/CMDTools.md
Original file line number Diff line number Diff line change
@@ -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
```
92 changes: 92 additions & 0 deletions src/chap03/VirtIOUseage.md
Original file line number Diff line number Diff line change
@@ -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
```

76 changes: 76 additions & 0 deletions src/chap04/subchap03/VirtIO/BlockDevice.md
Original file line number Diff line number Diff line change
@@ -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核上,提高主线程分发请求的效率和吞吐量,提升设备性能。
46 changes: 46 additions & 0 deletions src/chap04/subchap03/VirtIO/ConsoleDevice.md
Original file line number Diff line number Diff line change
@@ -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功能关闭。
Loading

0 comments on commit 35b70b5

Please sign in to comment.