diff --git a/so2/assign7-kvm-vmm.rst b/so2/assign7-kvm-vmm.rst index 3eb0b20..f48a85a 100644 --- a/so2/assign7-kvm-vmm.rst +++ b/so2/assign7-kvm-vmm.rst @@ -1,26 +1,24 @@ ===================================================== -Assignment 7 - SO2 Virtual Machine Manager with KVM +作业 7——使用 KVM 的 SO2 虚拟机管理器 ===================================================== -- Deadline: :command:`Tuesday, 29 May 2023, 23:00` -- This assignment can be made in teams (max 2). Only one of them must submit the assignment, and the names of the student should be listed in a README file. +- 截止日期: :command:`2023 年 5 月 29 日,23:00` +- 这个作业可以由团队完成(最多 2 人)。只需由其中一人提交作业,并在 README 文件中列出学生的姓名。 -In this assignment we will work on a simple Virtual Machine Manager (VMM). We will be using the KVM API -from the Linux kernel. +在本作业中,我们将使用 Linux 内核中的 KVM API 来开发一个简单的虚拟机管理器(VMM)。 -The assignment has two components: the VM code and the VMM code. We will be using a very simple protocol -to enable the communication between the two components. The protocol is called SIMVIRTIO. +本作业分为两个部分:虚拟机(VM)代码和 VMM 代码。我们将使用一个非常简单的协议来实现这两个组件之间的通信。该协议名为 SIMVIRTIO。 -I. Virtual Machine Manager +I. 虚拟机管理器 ========================== -In general, to build a VMM from scratch we will have to implement three main functionalities: initialize the VMM, initialize the virtual CPU and run the guest code. We will split the implementation of the VMM in these three phases. +通常情况下,要从零开始构建一个 VMM,我们需要实现三个主要功能:初始化 VMM、初始化虚拟 CPU 和运行客户机代码。我们将把 VMM 的实现分为这三个阶段。 -1. Initialize the VMM +1. 初始化 VMM ------------------------- -A VM will be represented in general by three elements, a file descriptor used to interact with the KVM API, a file descriptor per VM used to configure it (e.g. set its memory) and a pointer to the VM's memory. We provide you with the following structure to start from when working with a VM. +一个 VM 通常由三个元素表示,一个是用于与 KVM API 进行交互的文件描述符,一个是用于配置 VM 文件描述符(每个 VM 对应一个)(例如设置其内存),还有一个是指向 VM 内存的指针。我们为你提供了以下结构,用于在使用 VM 时进行参考。 .. code-block:: c @@ -31,9 +29,9 @@ A VM will be represented in general by three elements, a file descriptor used to } virtual_machine; -The first step in initializing the KVM VM is to interract with the [KVM_API](https://www.kernel.org/doc/html/latest/virt/kvm/api.html]. The KVM API is exposed via ``/dev/kvm``. We will be using ioctl calls to call the API. +初始化 KVM 虚拟机的第一步是与 [KVM_API](https://www.kernel.org/doc/html/latest/virt/kvm/api.html) 进行交互。KVM API 通过 ``/dev/kvm`` 进行公开。我们需要使用 ioctl 调用来调用该 API。 -The snippet below shows how one can call ``KVM_GET_API_VERSION`` to get the KVM API Version +下面的代码片段展示了如何调用 ``KVM_GET_API_VERSION`` 来获取 KVM API 版本。 .. code-block:: c @@ -49,29 +47,28 @@ The snippet below shows how one can call ``KVM_GET_API_VERSION`` to get the KVM exit(1); } -Let us now go briefly through how a VMM initializes a VM. This is only the bare bones, a VMM may do lots of other things during VM initialization. +现在让我们简要介绍 VMM 如何初始化 VM。以下只是最基本的步骤,实际上在 VM 初始化过程中,VMM 可能会执行许多其他操作。 -1. We first use KVM_GET_API_VERSION to check that we are running the expected version of KVM, ``KVM_API_VERSION``. -2. We now create the VM using ``KVM_CREATE_VM``. Note that calling ``KVM_CREATE_VM`` returns a file descriptor. We will be using this file descriptor for the next phases of the setup. -3. (Optional) On Intel based CPUs we will have to call ``KVM_SET_TSS_ADDR`` with address ``0xfffbd000`` -4. Next, we allocate the memory for the VM, we will be using ``mmap`` for this with ``PROT_WRITE``, ``MAP_PRIVATE``, ``MAP_ANONYMOUS`` and ``MAP_NORESERVE``. We recommend allocating 0x100000 bytes for the VM. -5. We flag the memory as ``MADV_MERGEABLE`` using ``madvise`` -6. Finally, we use ``KVM_SET_USER_MEMORY_REGION`` to assign the memory to the VM. +1. 首先,使用 KVM_GET_API_VERSION 检查我们是否运行了预期的 KVM 版本,即 ``KVM_API_VERSION``。 +2. 然后,使用 ``KVM_CREATE_VM`` 创建虚拟机。请注意,调用 ``KVM_CREATE_VM`` 会返回一个文件描述符。我们将在后续的设置阶段使用这个文件描述符。 +3. (可选)在基于 Intel 的 CPU 上,我们需要调用 ``KVM_SET_TSS_ADDR``,并将地址设置为 ``0xfffbd000``。 +4. 接下来,为虚拟机分配内存。我们需要使用 ``mmap`` 函数进行分配,调用时需要使用 ``PROT_WRITE``, ``MAP_PRIVATE``, ``MAP_ANONYMOUS`` 和 ``MAP_NORESERVE`` 参数。我们建议为虚拟机分配 0x100000 字节的内存。 +5. 使用 ``madvise`` 函数将内存标记为 ``MADV_MERGEABLE``。 +6. 最后,使用 ``KVM_SET_USER_MEMORY_REGION`` 将内存分配给虚拟机。 -**Make sure you understand what file descriptor to use and when, we use the KVM fd when calling KVM_CREATE_VM, but when interacting with the vm such as calling KVM_SET_USER_MEMORY_REGION we use the VMs -file descriptor** +**请确保你理解在何时使用哪个文件描述符,在调用 KVM_CREATE_VM 时我们使用 KVM 文件描述符,但在与虚拟机交互(例如调用 KVM_SET_USER_MEMORY_REGION)时我们使用虚拟机的文件描述符** -TLDR: API used for VM initialization: +简而言之,用于虚拟机初始化的 API 包括: * KVM_GET_API_VERSION * KVM_CREATE_VM * KVM_SET_TSS_ADDR -* KVM_SET_USER_MEMORY_REGION. +* KVM_SET_USER_MEMORY_REGION。 -2. Initialize a virtual CPU +2. 初始化虚拟 CPU ___________________________ -We need a Virtual CPU (VCPU) to store registers. +我们需要虚拟 CPU(VCPU)来存储寄存器值。 .. code-block:: c @@ -80,100 +77,97 @@ We need a Virtual CPU (VCPU) to store registers. struct kvm_run *kvm_run; } virtual_cpu; -To create a virtual CPU we will do the following: -1. Call ``KVM_CREATE_VCPU`` to create the virtual CPU. This call returns a file descriptor. -2. Use ``KVM_GET_VCPU_MMAP_SIZE`` to get the size of the shared memory -3. Allocated the necessary VCPU mem size with ``mmap``. We will be passing the VCPU file descriptor to the ``mmap`` call. We can store the result in ``kvm_run``. +要创建虚拟 CPU,我们需要执行以下操作: +1. 调用 ``KVM_CREATE_VCPU`` 创建虚拟 CPU。此调用将返回一个文件描述符。 +2. 使用 ``KVM_GET_VCPU_MMAP_SIZE`` 获取共享内存的大小。 +3. 使用 ``mmap`` 分配所需的 VCPU 内存大小。我们需要把 VCPU 文件描述符传递给 ``mmap`` 调用。我们可以将结果存储在 ``kvm_run`` 中。 -TLDR: API used for VM +简而言之,用于虚拟机的 API 包括: * KVM_CREATE_VCPU * KVM_GET_VCPU_MMAP_SIZE -**We recommend using 2MB pages to simplify the translation process** +**我们建议使用 2MB 页面以简化翻译过程。** -Running the VM +运行虚拟机 ============== -Setup real mode ---------------- +设置实模式(real mode) +---------------------------- -At first, the CPU will start in Protected mode. To do run any meaningful code, we will switch the CPU to [Real mode](https://wiki.osdev.org/Real_Mode). To do this we will -need to configure several CPU registers. +首先,CPU 会以保护模式启动。要想运行任何有意义的代码,我们需要把 CPU 切换到[实模式](https://wiki.osdev.org/Real_Mode)。为此,我们需要配置几个 CPU 寄存器。 -1. First, we will use ``KVM_GET_SREGS`` to get the registers. We use ``struct kvm_regs`` for this task. -2. We will need to set ``cs.selector`` and ``cs.base`` to 0. We will use ``KVM_SET_SREGS`` to set the registers. -3. Next we will clear all ``FLAGS`` bits via the ``rflags`` register, this means setting ``rflags`` to 2 since bit 1 must always be to 1. We alo set the ``RIP`` register to 0. +1. 首先,我们需要使用 ``KVM_GET_SREGS`` 获取寄存器的值。我们可以使用 ``struct kvm_regs`` 结构来完成这个任务。 +2. 我们需要将 ``cs.selector`` 和 ``cs.base`` 设置为 0。我们可以使用 ``KVM_SET_SREGS`` 来设置这些寄存器。 +3. 接下来,我们需要通过 ``rflags`` 寄存器清除所有 ``FLAGS`` 位。我们需要将 ``rflags`` 设置为 2,因为第一位必须始终为 1。我们还需要将 ``RIP`` 寄存器设置为 0。 -Setup long mode ---------------- +设置长模式(long mode) +------------------------- -Read mode is all right for very simple guests, such as the one found in the folder `guest_16_bits`. But, -most programs nowdays need 64 bits addresses, and such we will need to switch to long mode. The following article from OSDev presents all the necessary information about [Setting Up Long Mode](https://wiki.osdev.org/Setting_Up_Long_Mode). +实模式适用于非常简单的客户机,比如在 `guest_16_bits` 文件夹中的客户机。但是,大多数现代程序需要 64 位地址,因此我们需要切换到长模式。OSDev 上的以下文章提供了[设置长模式](https://wiki.osdev.org/Setting_Up_Long_Mode)所需的所有必要信息。 -In ``vcpu.h``, you may found helpful macros such as CR0_PE, CR0_MP, CR0_ET, etc. +在 ``vcpu.h`` 中,你可以找到有用的宏,例如 CR0_PE、CR0_MP 以及 CR0_ET 等。 -Since we will running a more complex program, we will also create a small stack for our program -``regs.rsp = 1 << 20;``. Don't forget to set the RIP and RFLAGS registers. +由于我们需要运行更复杂的程序,我们还需要为我们的程序创建一个小的堆栈 ``regs.rsp = 1 << 20;``。不要忘记设置 RIP 和 RFLAGS 寄存器。 -Running +运行 ------- -After we setup our VCPU in real or long mode we can finally start running code on the VM. +在设置了实模式或长模式的 VCPU 之后,我们终于可以在虚拟机上运行代码了。 -1. We copy to the vm memory the guest code, `memcpy(vm->mem, guest_code, guest_code_size)` The guest code will be available in two variables which will be discussed below. -2. In a infinite loop we run the following: - * We call ``KVM_RUN`` on the VCPU file descriptor to run the VPCU - * Through the shared memory of the VCPU we check the ``exit_reason`` parameter to see if the guest has made any requests: - * We will handle the following VMEXITs: `KVM_EXIT_MMIO`, `KVM_EXIT_IO` and ``KVM_EXIT_HLT``. ``KVM_EXIT_MMIO`` is triggered when the VM writes to a MMIO address. ``KVM_EXIT_IO`` is called when the VM calls ``inb`` or ``outb``. ``KVM_EXIT_HLT`` is called when the user does a ``hlt`` instruction. +1. 我们将客户机代码复制到虚拟机内存中, `memcpy(vm->mem, guest_code, guest_code_size)`。客户机代码将在下面两个将讨论的变量中提供。 +2. 在无限循环中,我们执行以下操作: + * 调用 VCPU 文件描述符上的 ``KVM_RUN`` 来运行 VCPU。 + * 通过 VCPU 的共享内存,检查 ``exit_reason`` 参数,以查看客户机是否发出了任何请求: + * 处理以下 VMEXIT: ``KVM_EXIT_MMIO``, ``KVM_EXIT_IO`` 以及 ``KVM_EXIT_HLT``。当 VM 写入 MMIO 地址时,会触发 ``KVM_EXIT_MMIO``。当 VM 调用 ``inb`` 或 ``outb`` 时,会调用 ``KVM_EXIT_IO``。当用户执行 ``hlt`` 指令时,会调用 ``KVM_EXIT_HLT``。 -Guest code +客户机代码 ---------- -The VM that is running is also called guest. We will be using the guest to test our implementation. +正在运行的虚拟机也被称为客户机。我们将使用客户机来测试我们的实现。 -1. To test the implementation before implementing SIMVIRTIO. The guest will write at address 400 and the RAX register the value 42. -2. To test a more complicated implementation,we will extend the previous program to also write "Hello, world!\n" on port `0xE9` using the `outb` instruction. -3. To test the implementation of `SIMVIRTIO`, we will +1. 在实现 SIMVIRTIO 之前进行测试。客户机会在地址 400 和 RAX 寄存器中写入值 42。 +2. 为了测试更复杂的实现,我们需要扩展上述程序,使用 `outb` 指令在端口 `0xE9` 上也写入“Hello, world!\n”。 +3. 为了测试 `SIMVIRTIO` 的实现,我们需要 -How do we get the guest code? The guest code is available at the following static pointers guest16, guest16_end-guest16. The linker script is populating them. +如何获取客户机代码?客户机代码在以下静态指针 guest16、guest16_end-guest16 中可用。链接器脚本会填充它们。 -## SIMVIRTIO: -From the communication between the guest and the VMM we will implement a very simple protocol called ``SIMVIRTIO``. It's a simplified version of the real protocol used in the real world called virtio. +## SIMVIRTIO: +从客户机和 VMM 之间的通信中,我们将实现一种非常简单的协议称为 ``SIMVIRTIO``。它是现实世界中使用的名为 virtio 的真实协议的简化版本。 -Configuration space: +配置空间: -+--------------+----------------+----------------+----------------+------------------+-------------+-------------+ -| u32 | u16 | u8 | u8 | u8 | u8 | u8 | -+==============+================+================+================+==================+=============+=============+ -| magic value | max queue len | device status | driver status | queue selector | Q0(TX) CTL | Q1(RX) CTL | -| R | R | R | R/W | R/W | R/W | R/w | -+--------------+----------------+----------------+----------------+------------------+-------------+-------------+ ++-------+---------+--------+---------+----------+-------+-------+ +| u32     | u16      | u8      | u8       | u8        | u8     | u8     | ++=======+=========+========+=========+==========+=======+=======+ +| 魔术值   | 最大队列长度  | 设备状态   | 驱动程序状态  | 队列选择器    | Q0(TX) CTL| Q1(RX) CTL| +| R      | R        | R       | R/W      | R/W        | R/W    | R/w     | ++-------+---------+--------+---------+----------+-------+-------+ -Controller queues +控制器队列 ----------------- -We provide you with the following structures and methods for the ``SIMVIRTIO`` implementation. +对于 ``SIMVIRTIO`` 实现,我们为你提供了以下结构和方法。 .. code-block:: c typedef uint8_t q_elem_t; typedef struct queue_control { - // Ptr to current available head/producer index in 'buffer'. + // 指向‘buffer’中当前可用的头部/生产者索引的指针。 unsigned head; - // Ptr to last index in 'buffer' used by consumer. + // 指向消费者使用的‘buffer’中最后一个索引的指针。 unsigned tail; } queue_control_t; typedef struct simqueue { - // MMIO queue control. + // MMIO 队列控制。 volatile queue_control_t *q_ctrl; - // Size of the queue buffer/data. + // 队列缓冲区/数据的大小。 unsigned maxlen; - // Queue data buffer. + // 队列数据缓冲区。 q_elem_t *buffer; } simqueue_t; int circ_bbuf_push(simqueue_t *q, q_elem_t data) @@ -184,7 +178,7 @@ We provide you with the following structures and methods for the ``SIMVIRTIO`` i } -Device structures +设备结构 ----------------- .. code-block:: c @@ -209,37 +203,37 @@ Device structures } device_table_t; -We will be implementing the following handles: +我们需要执行以下处理程序: * MMIO (read/write) VMEXIT * PIO (read/write) VMEXIT -Using the skeleton +使用骨架 ================== -Debugging +调试 ========= -Tasks +任务 ===== -1. 30p Implement a simple VMM that runs the code from `guest_16_bits`. We will be running the VCPU in read mode for this task -2. 20p Extend the previous implementation to run the VCPU in real mode. We will be running the `guest_32_bits` example -3. 30p Implement the `SIMVIRTIO` protocol. -4. 10p Implement pooling as opposed to VMEXIT. We will use the macro `USE_POOLING` to switch this option on and off. -5. 10p Add profiling code. Measure the number of VMEXITs triggered by the VMM. +1. 30分 实现一个简单的 VMM,运行来自 `guest_16_bits` 的代码。在此任务中,我们需要以读模式运行 VCPU。 +2. 20分 扩展前面的实现,以在实模式下运行 VCPU。我们需要运行 `guest_32_bits` 示例。 +3. 30分 实现 `SIMVIRTIO` 协议。 +4. 10分 实现池化而不是 VMEXIT。我们需要使用宏 `USE_POOLING` 来打开和关闭此选项。 +5. 10分 添加性能分析代码。测量 VMM 触发的 VMEXIT 次数。 -Submitting the assigment +提交作业 ------------------------ -The assignment archive will be submitted on **Moodle**, according to the rules on the `rules page `__. +作业存档需要根据 `规则页面 `__ 上的规定,通过 **Moodle** 提交。 -Tips +提示 ---- -To increase your chances of getting the highest grade, read and follow the Linux kernel coding style described in the `Coding Style document `__. +为了增加获得最高分的机会,请阅读并遵循 Linux 内核编码风格,该风格在 `编码风格文档 `__ 中有描述。 -Also, use the following static analysis tools to verify the code: +此外,使用以下静态分析工具来验证代码: * checkpatch.pl @@ -262,34 +256,33 @@ Also, use the following static analysis tools to verify the code: $ sudo apt-get install cppcheck $ cppcheck /path/to/your/file.c -Penalties +扣分项 --------- -Information about assigments penalties can be found on the `General Directions page `__. +关于作业扣分项的信息可以在 `常规说明页面`__ 中找到。 -In exceptional cases (the assigment passes the tests by not complying with the requirements) and if the assigment does not pass all the tests, the grade will may decrease more than mentioned above. +在特殊情况下(作业通过测试但不符合要求),以及如果作业未通过所有测试,成绩可能会比上述提到的降低更多。 -## References -We recommend you the following readings before starting to work on the homework: -* [KVM host in a few lines of code](https://zserge.com/posts/kvm/) +参考资料 +------------------------------------ - -TLDR ----- +我们建议你在开始做作业之前阅读以下内容: +* [用几行代码创建 KVM 主机](https://zserge.com/posts/kvm/) -1. The VMM creates and initializes a virtual machine and a virtual CPU -2. We switch to real mode and check run the simple guest code from `guest_16_bits` -3. We switch to long mode and run the more complex guest from `guest_32_bits` -4. We implement the SIMVIRTIO protocol. We will describe how it behaves in the following subtasks. -5. The guest writes in the TX queue (queue 0) the ascii code for `R` which will result in a `VMEXIT` -6. the VMM will handle the VMEXIT caused by the previous write in the queue. When the guests receiver the -`R` letter it will initiate the reser procedure of the device and set the device status to `DEVICE_RESET` -7. After the reset handling, the guest must set the status of the device to `DRIVER_ACK`. After this, the guest will write to the TX queue the letter `C` -8. In the VMM we will initialize the config process when letter `C` is received.It will set the device status to `DEVICE_CONFIG` and add a new entry in the device_table -9. After the configuration process is finished, the guest will set the driver status to `DRIVER_OK` -10. Nex, the VMM will set the device status to `DEVICE_READY` -11. The guest will write in the TX queue "Ana are mere" and will execute a halt -12. The VMM will print to the STDOUT the message received and execute the halt request -13. Finally, the VMM will verify that at address 0x400 and in register RAX is stored the value 42 +太长不看 +------------ +1. 虚拟机管理程序(VMM)创建并初始化虚拟机和虚拟 CPU。 +2. 切换到实模式并运行 `guest_16_bits` 中的简单客户机代码。 +3. 切换到长模式并运行 `guest_32_bits` 中更复杂的客户机代码。 +4. 实现 SIMVIRTIO 协议。我们将在以下子任务中描述其行为。 +5. 客户机在 TX 队列(队列 0)中写入 `R` 的 ASCII 码,这将导致 `VMEXIT`。 +6. VMM 需要处理由前面在队列中的写入引起的 VMEXIT。当客户端接收到 `R` 字母时,它将启动设备的复位过程,并将设备状态设置为 `DEVICE_RESET`。 +7. 在处理复位后,客户机必须将设备状态设置为 `DRIVER_ACK`。在此之后,客户机需要向 TX 队列中写入字母 `C`。 +8. 在收到字母 `C` 时,VMM 需要初始化配置过程。它会将设备状态设置为 `DEVICE_CONFIG` 并在 device_table 中添加新条目。 +9. 配置过程完成后,客户机会将驱动程序状态设置为 `DRIVER_OK`。 +10. 接下来,VMM 会将设备状态设置为 `DEVICE_READY`。 +11. 客户机需要在 TX 队列中写入“Ana are mere”并执行停机指令。 +12. VMM 需要向 STDOUT 打印接收到的消息并执行停机请求。 +13. 最后,VMM 需要验证地址 0x400 处和寄存器 RAX 中是否存储了值 42。