= 测试

== 功能测试

对VirtIO驱动程序功能的测试分别使用 #link("https://github.com/rcore-os/virtio-drivers/tree/master/examples/riscv")[rCore所提供的riscv架构测试程序] 的 #link("https://github.com/huyufeifei/svdrivers/tree/master/qemu")[改进版本] 和Alien中进行测试。测试代码已在链接中给出。对于Uart16550设备驱动的测试仅在Alien中进行测试。

对于不同设备的功能测试方式分别如下：
- *块设备：*对于设备的每一个块，使用一个函数计算出一个对应的均匀分布的数字，对该块的所有字节写入该数字。随后使用一个初始化为0的内存空间缓冲区，向其中读取目标块内的数据。如果所读取到的数据与写入的数据完全相同，则说明该块设备驱动的读写操作功能是正确的。
- *网卡：*对于一个被初始化的网卡，它将不断侦听是否收到了包。如果有新收到的包，就会像系统发送中断标志。因此，在tcp网络协议栈未启用的情况下，使用`ping`命令向qemu模拟器所映射到虚拟机内网卡的端口发送查询命令，开启日志输出并编译后，在Alien虚拟机系统中可以观察到收到网络包带来的中断。输出收到的包内容，为所发出的ping请求包。在tcp网络协议栈启用的情况下，使用`netcat` / `nc`命令对其进行tcp连接的测试，能够看到nc输出tcp连接成功建立的消息。使用`netcat`发送测试数据`"hello"`，并在系统中将收到的数据逆序输出，如果观测到虚拟机输出`"olleh"`，则可以证明驱动程序对网络包的接受和发送功能正确。
- *输入设备：*由于硬件条件限制，对于输入设备只测试了鼠标和键盘的设置。通过在qemu的运行指令里启用鼠标和键盘的VirtIO设备，可以把宿主机的鼠标和键盘输出传入到虚拟机的设备中。使用同一个驱动程序对此两设备进行测试。在初始化之后，将系统焦点聚集于虚拟机的窗口中，随后进行键盘按键、长按或者鼠标点击、移动、滚轮操作，并启用日志输出。对于所有的输入设备操作均能看到在测试程序中输出了接收到的输入事件，说明输入设备驱动的功能正确。
- *Uart16550 串口设备：*串口设备被用于在Alien中与终端进行通信。在没有启用或初始化串口设备时，虚拟机与用户的交互直接经过qemu完成。在使用串口之后，系统向串口输出的内容和会在终端呈现，串口收到的内容会被系统检测到。在初始化串口驱动后使用其输出函数，向终端输出`"uart test"`，结果正确的在终端显示，说明串口驱动正确。

测试的结果，所开发的安全驱动，可以正常操作设备，完成所需的功能。

== 性能测试

通常认为使用底层的C语言的一个优势是其效率更高。而Rust的零成本抽象为其带来了与C/C++类似的性能。使用Rust仅比C++程序慢4-8%@y4，而不安全的C风格Rust速度则与C/C++程序相差无几@redleaf。通过对有较高性能需求的块设备驱动的速度测试，可以展现出使用安全代码编写的设备驱动与不安全代码相比性能变化如何。

以下的测试所使用的环境是：
/ 中央处理器: 13th Gen Intel(R) Core(TM) i9-13900HX
/ 操作系统: Windows Subsystem for Linux (WSL) 2 Ubuntu 20.04
/ 虚拟机软件:qemu 7.0.0

=== 块设备

对块设备的测试采用顺序读写的方式进行。使用的设备大小为20MB。该设备是一个由`dd`命令所创建的文件，每次写入1MB的源于`/dev/zero`的数据，总共执行数量为20次。随后可以在qemu的运行命令中将该20MB的文件作为一个存储设备传递给虚拟机中。测试中分别使用本项目的安全Rust实现的驱动和用于对比的使用了不安全代码的设备驱动对其进行总共200次全盘读写，并记录每次的时间和速度。最后以此计算出两种不同驱动的平均速度和方差。写入时所使用的数据全部为0xFF，而对于读取时的数据未进行任何初始化。200次全盘读写会分为20次进行，每次会读写十倍于整个设备的数据量。这些操作按照块编号由低到高的顺序被执行，并在超出最大空间之后回到0号块继续进行循环。每次读写在开始前后都会读取riscv架构中用于计时的寄存器，将之相减后能够获知该读写操作总共所花费的时钟周期。由于qemu中riscv机器的cpu频率已知，因此通过计算可以获知该计时时长对应多少现实时间。块的大小通过驱动的查询容量功能获取，而非硬编码在测试程序中，可以应对不同大小的设备的测试需要。计时的区域不包括查询块设备大小所耗费的时间。但是会计入循环语句的时间与对驱动程序提供的功能进行函数调用的时间。最后通过计算每次总读写的数据量与耗时之商，可以获知该次操作的实际吞吐量。驱动所提供的查询容量所返回的单位是块。而一块在VirtIO块设备的规定中大小为512字节，因此该返回值与一块的存储大小相乘就是整个设备的容量。为了保证测试结果的精确性，在进行测试时，不对测试机器进行任何其他操作以避免有程序与qemu抢占CPU从而导致测试结果不准确。为提高计算精度，所有数值都被转化为64位浮点数进行运算。每次单次测试出来的结果将会被存入一个数组中，此操作不会被计入所耗费时间。在所有次测试都结束后，对于所获得的每次速度数组，可以进行求平均与求方差的操作，以此减小测例结果中的误差，增大可信度。方差被用于说明设备驱动的运行速度稳定性。

测试所得的原始数据可以在#link("https://github.com/huyufeifei/grad/tree/master/info/test_data")[此链接处]查看，测试所用代码分别在github代码仓库下的`svdrivers/qemu`和`virtio-drivers/example/riscv`文件夹中。测试的结果如下表：

#figure(
  table(columns: (5em, 1fr, 1fr, 1fr, 1fr), 
    stroke: 1pt + black,
    table.cell(rowspan:2, "操作类型"), table.cell(colspan:2, "安全代码"), table.cell(colspan:2, "不安全代码"),
    "平均速度", "方差", "平均速度", "方差",
    "读", "19.86MB/s", "0.027", "19.87MB/s", "0.086",
    "写", "18.04MB/s", "0.129", "17.40MB/s", "0.038",
  ),
  caption: "块设备读写性能",
)

从如上数据中可以看出，使用安全代码实现的VirtIO块设备驱动相比于不安全的设备驱动，在顺序读取操作上速度相差无几，而在顺序写入时，其速度甚至比不安全的实现快了3.6%。

写操作普遍比读操作缓慢，但是差距并不大，只有约2MB/s的差距。读和写操作都接近一个上限，即20MB/s。这个速度很可能是在虚拟环境中使用单处理器对块设备进行读写的速度上限。安全驱动和不安全驱动均接近这个上限，表明在现有应用场景中驱动的性能并没有成为操作的瓶颈。

这些数据说明了在块设备驱动这方面，使用不安全代码并没有显著的速度优势。因此可以放心的使用安全的设备驱动，而不需要担心其可能对性能造成的影响。
