长话短说

长话短说

背景:我正在使用 QEMU 进行虚拟化xv6-riscv在 WSL2 之上。我正在寻找创建某种干净的操作系统关闭过程,让人想起 Linux 的exit命令。目前我正在使用Ctrl-a x来终止 qemu,但我希望能够在操作系统内以编程方式执行此操作。

我知道 Linuxexit命令只是关闭当前的 shell,这意味着我需要一种内核空间方法来检测是否还有任何活动的 shell,以及一种向 QEMU 发出信号表示它正在关闭的方法。我可以完全访问内核以进行重新编译,但我无法使用外部 C 库,因为没有。我开发的任何解决方案都必须基本上从头开始。

因此,我的问题是:操作系统需要做什么才能告诉 RISC-V QEMU 实例它正在关闭?如何从操作系统中退出 QEMU 本身?第二点特别重要,因为我正在 CLI 中从 Makefile 实例化 QEMU,并且我希望在操作系统终止后能够访问终端,而不是让 QEMU 坐在那儿模拟没有任何内容的 RISC-V 。

注意:这个问题不同于另一个因为我问的是客户操作系统启动的关闭而不是主机启动的 ACPI 关闭,并且更深入地了解 QEMU 和操作系统的内部结构。

答案1

长话短说

从主管模式:

(*(volatile uint32 *) 0x100000) = 0x5555;

您还需要更改内核页表以映射地址0x100000。你可以看到这次提交以便全面实施。

长答案

不幸的是,QEMU RISC-V 模拟器的文档很差。所以我们需要深入研究代码。我们从 xv6 得知生成文件它使用 QEMU 的通用虚拟平台 (virt) 来虚拟化 xv6。

virt.cQEMU 源代码中的文件包含 QEMU 通用虚拟平台的实现。如果我们poweroff在该文件中搜索,我们最终会得到代码片段:

name = g_strdup_printf("/poweroff");
qemu_fdt_add_subnode(ms->fdt, name);
qemu_fdt_setprop_string(ms->fdt, name, "compatible", "syscon-poweroff");
qemu_fdt_setprop_cell(ms->fdt, name, "regmap", test_phandle);
qemu_fdt_setprop_cell(ms->fdt, name, "offset", 0x0);
qemu_fdt_setprop_cell(ms->fdt, name, "value", FINISHER_PASS);
g_free(name);

简而言之,这段代码片段告诉 QEMU,如果FINISHER_PASS写入test_phandle寄存器映射的第 0 个索引,则关闭 QEMU。如果我们看上面的几行,我们会意识到在写入时FINISHER_RESET我们可以重置 QEMU。在我们可以观察到这一行之前的几行指示我们应该写入的来宾中的内存映射位置FINISHER_PASS

qemu_fdt_setprop_cells(ms->fdt, name, "reg",
    0x0, memmap[VIRT_TEST].base, 0x0, memmap[VIRT_TEST].size);

如果我们VIRT_TEST在同一个文件中搜索,我们会得到线:

[VIRT_TEST] =         {   0x100000,        0x1000 },

所以我们应该写入的位置FINISHER_PASS是0x100000。另外,如果我们在整个项目中搜索FINISHER_PASS和,我们可以看到它们的值FINISHER_RESET这个文件:

enum {
    FINISHER_FAIL = 0x3333,
    FINISHER_PASS = 0x5555,
    FINISHER_RESET = 0x7777
};

长话短说,我们目前知道要关闭操作系统,我们必须将 0x5555 写入地址 0x100000。您可能认为可以通过在同一地址中写入 0x7777 来重新启动操作系统,但由于某种原因我无法做到这一点...如果您找到原因,请告诉我!

现在我们将实现一个系统调用来关闭 xv6。首先,我们必须更改内核的页表,以便将虚拟地址 0x100000 映射到物理地址 0x100000(如果实现了 ASLR,则可以映射其他虚拟地址)。我通过将以下行添加到vm.cand function来做到这一点kvmmake

kvmmap(kpgtbl, 0x100000, 0x100000, PGSIZE, PTE_R | PTE_W);

我们使用页面大小是因为根据 QEMU 的区域大小为 0x1000 字节来源

之后,我们只需向操作系统添加一个系统调用即可。我是这样做的:

// Power off QEMU
static uint64
sys_poweroff(void)
{
  printf("Powering off...\n");
  (*(volatile uint32 *) 0x100000) = 0x5555;
  panic("sys_poweroff");
}

将此系统调用添加到系统调用表中,现在我们就可以开始了!

然后,您可以编写一个简单的用户程序来使用系统调用:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
  poweroff();
  exit(1); // never reached
}

将这个用户程序添加到Makefile中,然后就可以使用这个应用程序来关闭操作系统。

正如我所说,你可以看到这次提交以便全面实施。

相关内容