-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Finish virtio and cmdtool usage doc, and implementation docs for virt…
…io devices (#9) * finish usage doc for virtio device and cmdtool, close #7 * Add implementation doc for virtio devices,close #8
- Loading branch information
Showing
16 changed files
with
677 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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核上,提高主线程分发请求的效率和吞吐量,提升设备性能。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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功能关闭。 |
Oops, something went wrong.