基于qemu9.0.0
简介
QEMU是一个开源的虚拟化软件,它能够模拟各种硬件设备,支持多种虚拟化技术,如TCG、Xen、KVM等
TCG 是 QEMU 中的一个组件,它可以将高级语言编写的代码(例如 C 代码)转换为可在虚拟机中执行的低级代码(例如 x86 机器指令)。TCG 生成的代码通常比直接使用 CPU 指令更简单、更小,但执行速度可能稍慢。同时,TCG不仅可以将高级语言代码转换为低级代码,还可以执行其他优化,例如常量折叠和死代码消除。
Xen 是一种开源虚拟化技术,它直接嵌入到 Linux 内核中。这意味着 Xen 可以直接访问硬件资源,从而提供高性能的虚拟化。然而,Xen 的配置和管理可能比较复杂。Xen 支持准虚拟化,这允许客户机操作系统直接访问某些硬件资源,从而提高性能。
KVM 是 QEMU 中最常使用的一种虚拟化技术,它利用 Linux 内核提供的虚拟化功能。KVM 的优势在于其因为它提供了良好的性能和广泛的操作系统支持。但是要注意一点的是:KVM 依赖于 Linux 内核提供的虚拟化功能,因此它仅适用于 Linux 主机操作系统在 QEMU 中,KVM 的初始化过程主要包括以下步骤:
**加载虚拟机监控器模块:**首先,需要加载 KVM 模块,以便在内核中启用虚拟化功能。这一步通常在系统启动时完成。
**创建虚拟机:**接下来,使用 QEMU 命令或 API 创建一个新的虚拟机实例。在创建过程中,需要指定虚拟机的配置参数,例如内存大小、CPU 数量等。
**分配资源:**在虚拟机创建后,需要为其分配所需的资源,包括 CPU、内存和设备。这些资源由物理硬件提供,并通过虚拟化技术映射到虚拟机上。
**启动虚拟机:**一旦资源分配完成,就可以启动虚拟机了。这时,KVM 将接管虚拟机的执行,并将其与物理硬件隔离。
**执行客户机操作系统:**客户机操作系统现在可以在虚拟机中执行,就好像它直接运行在物理硬件上一样。
相关功能的源码在
target/$(arch)/kvm.c(tcg/)
QEMU 可以模拟几百个设备:
QEMU 所有支持的机器类型QEMU 可以模拟的设备QEMU 在设备模拟上采取了前端和后端分离的设计模式:
前端:
QEMU 虚拟机管理器:负责管理虚拟机实例和提供用户界面。
ARM 虚拟化扩展 (VE):在 ARM 处理器上提供虚拟化支持。
后端:
ARM CPU 模型:模拟 ARM 处理器,包括指令集、寄存器和内存管理单元 (MMU)。
ARM 虚拟 I/O 设备模型:模拟 ARM 架构中的通用虚拟 I/O 设备,例如:virtio-blk:模 拟虚拟块设备
virtio-net:模拟虚拟网络接口
virtio-serial:模拟虚拟串行端口
检查可以支持的后端的方法(字符和网络):
QEMU 初始化过程分析
select_machine函数(选择机器类型)
/system/vl.c
此函数用于选择要运行的机器类型。它从命令行选项或默认值中获取机器类型,然后返回所选机器的 MachineClass 结构。
static MachineClass *select_machine(QDict *qdict, Error **errp)
{
const char *machine_type = qdict_get_try_str(qdict, "type");
GSList *machines = object_class_get_list(TYPE_MACHINE, false);
MachineClass *machine_class;
Error *local_err = NULL;
if (machine_type) {
machine_class = find_machine(machine_type, machines);
qdict_del(qdict, "type");
if (!machine_class) {
error_setg(&local_err, "unsupported machine type");
}
} else {
machine_class = find_default_machine(machines);
if (!machine_class) {
error_setg(&local_err, "No machine specified, and there is no default");
}
}
g_slist_free(machines);
if (local_err) {
error_append_hint(&local_err, "Use -machine help to list supported machines\n");
error_propagate(errp, local_err);
}
return machine_class;
}
cpu_exec_init_all(初始化所有 CPU 的执行引擎)
io_mem_init函数
此函数初始化 I/O 内存区域。,可见其调用了memory_region_init_io()函数
static void io_mem_init(void)
{
memory_region_init_io(&io_mem_unassigned, NULL, &unassigned_mem_ops, NULL,
NULL, UINT64_MAX);
}
memory_region_init_io函数
- 调用
memory_region_init
函数初始化内存区域的公共部分。 - 设置内存区域的操作集。如果未指定操作集,则使用
unassigned_mem_ops
默认操作集。 - 设置内存区域的不透明数据指针。
- 将内存区域标记为终止区域。这意味着当内存区域被销毁时,它将自动从其父区域中删除。
/system/memory.c
void memory_region_init_io(MemoryRegion *mr,
Object *owner,
const MemoryRegionOps *ops,
void *opaque,
const char *name,
uint64_t size)
{
memory_region_init(mr, owner, name, size);
mr->ops = ops ? ops : &unassigned_mem_ops;
mr->opaque = opaque;
mr->terminates = true;
}
memory_region_init 函数
用于初始化 MemoryRegion
结构,调用了object_initialize
函数和memory_region_do_init函数
object_initialize
函数用于初始化一个对象。它执行以下操作:
- 分配对象的内存。
- 设置对象的类型。
- 设置对象的父对象(如果存在)。
- 调用对象的
init
函数(如果存在)。
/system/memory.c
void memory_region_init(MemoryRegion *mr,
Object *owner,
const char *name,
uint64_t size)
{
object_initialize(mr, sizeof(*mr), TYPE_MEMORY_REGION);
memory_region_do_init(mr, owner, name, size);
}
memory_region_do_init函数
它执行以下操作:
- 设置内存区域的大小。如果大小为
UINT64_MAX
,则将其设置为INT128_MAX
。 - 设置内存区域的名称。
- 设置内存区域的所有者对象。
- 设置内存区域的设备状态对象(如果所有者对象是设备)。
- 设置内存区域的 RAM 块(如果存在)。
- 如果内存区域有名称,则将其添加到其所有者的子对象列表中。
static void memory_region_do_init(MemoryRegion *mr,
Object *owner,
const char *name,
uint64_t size)
{
mr->size = int128_make64(size);
if (size == UINT64_MAX) {
mr->size = int128_2_64();
}
mr->name = g_strdup(name);
mr->owner = owner;
mr->dev = (DeviceState *) object_dynamic_cast(mr->owner, TYPE_DEVICE);
mr->ram_block = NULL;
if (name) {
char *escaped_name = memory_region_escape_name(name);
char *name_array = g_strdup_printf("%s[*]", escaped_name);
if (!owner) {
owner = container_get(qdev_get_machine(), "/unattached");
}
object_property_add_child(owner, name_array, OBJECT(mr));
object_unref(OBJECT(mr));
g_free(name_array);
g_free(escaped_name);
}
}
补充:
MemoryRegion 是 QEMU 中表示内存区域的抽象数据结构。它提供了一个统一的接口来访问和操作不同的类型的内存,例如物理内存、I/O 内存和设备内存。可以将 MemoryRegion 想象成一个计算机中的内存块。它有一个名称、大小和地址。你可以通过 MemoryRegion 的接口来读取和写入内存块中的数据,也可以设置回调函数来处理对内存块的访问。
/include/exec/memory.h
memory_map_init函数
-
分配内存:
分配内存用于系统内存和 I/O 空间。 -
初始化内存区域:
使用memory_region_init
函数初始化系统内存区域。使用memory_region_init_io
函数初始化 I/O 空间区域。 -
初始化地址空间:
使用address_space_init
函数初始化用于访问系统内存和 I/O 空间的地址空间。
/system/physmem.c
static void memory_map_init(void)
{
system_memory = g_malloc(sizeof(*system_memory));
memory_region_init(system_memory, NULL, "system", UINT64_MAX);
address_space_init(&address_space_memory, system_memory, "memory");
system_io = g_malloc(sizeof(*system_io));
memory_region_init_io(system_io, NULL, &unassigned_io_ops, NULL, "io",
65536);
address_space_init(&address_space_io, system_io, "I/O");
}
通俗的讲:QEMU 是一个城市,而内存映射是城市的地图。memory_map_init
函数负责创建这个地图,它定义了城市中不同区域(内存和 I/O 空间)的位置和大小。system_memory
和 io_memory
是两个容器,分别代表城市中的住宅区(内存)和商业区(I/O 空间)。address_space_io
和 address_space_memory
是两张地图,分别显示如何到达住宅区和商业区。
page_size_init(初始化页大小)
configure_accelerator(配置加速器)
accel_init_machine函数
- 将加速器与虚拟机关联起来
- 调用加速器的
init_machine
函数来进行特定于加速器的初始化 - 设置加速器的兼容性属性
/accl/accl-system.c
int accel_init_machine(AccelState *accel, MachineState *ms)
{
AccelClass *acc = ACCEL_GET_CLASS(accel);
int ret;
ms->accelerator = accel;
*(acc->allowed) = true;
ret = acc->