-
创建并启动虚拟机
- 在一个
shell
终端调试qemu
进程对应的虚拟机操作系统内核,执行以下命令:
/vms/zhangjin/qemu/local-build/x86_64-softmmu/qemu-system-x86_64 \
-enable-kvm \
-m 4G,slots=4,maxmem=8G \
-cpu host -smp 4 \
-object memory-backend-ram,id=mem1,size=1G \
-device pc-dimm,id=dimm1,memdev=mem1 \
-kernel /vms/zhangjin/qemu-vm-bzImage/bzImage \
-initrd /vms/zhangjin/qemu-vm-bzImage/initramfs-4.19.90-2102.2.0.0064.ctl2.x86_64.img \
-append "root=/dev/vda1 console=ttyS0,115200 rw" \
-drive file=/vms/zhangjin/qemu-vm-bzImage/ctyunos-x86_64-20220318144244.qcow2,format=qcow2,if=virtio,index=0 \
-device virtio-net,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::60060-:22 \
-S \
-gdb tcp::1234 \
-nographic \
-serial mon:stdio
# 添加了使用 telnet 连接 qemu 的 monitor 来完成一系列热插拔命令,实现与 qemu 进行交互
/vms/zhangjin/qemu/local-build/x86_64-softmmu/qemu-system-x86_64 \
-enable-kvm \
-m 4G,slots=4,maxmem=8G \
-cpu host -smp 4 \
-object memory-backend-ram,id=mem1,size=1G \
-object memory-backend-ram,id=mem2,size=2G \
-device pc-dimm,id=dimm1,memdev=mem1 \
-kernel /vms/zhangjin/qemu-vm-bzImage/bzImage \
-initrd /vms/zhangjin/qemu-vm-bzImage/initramfs-4.19.90-2102.2.0.0064.ctl2.x86_64.img \
-append "root=/dev/vda1 console=ttyS0,115200 rw" \
-drive file=/vms/zhangjin/qemu-vm-bzImage/ctyunos-x86_64-20220318144244.qcow2,format=qcow2,if=virtio,index=0 \
-device virtio-net,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::60060-:22 \
-S \
-gdb tcp::1234 \
-nographic \
-serial stdio \
-monitor tcp:127.0.0.1:12346,server,nowait
其中:
-s 表示启用 GDB 调试服务器,监听 1234 端口。
-S 表示启动时暂停虚拟机,等待 GDB 连接。
root passwd:12345
-
qemu
Monitor 连接 - 使用
telnet
连接qemu
的monitor
:
telnet 127.0.0.1 12346
-
调试虚拟机
Linux
内核功能 -
确认虚拟机是否暂停
连接 GDB 并检查状态:
- 通过
tcp socket
套接字调试虚拟机Linux
内核功能,在另外shell
终端执行:
- 通过
gdb
(gdb) target remote :1234
(gdb) info threads
- 如果虚拟机的 CPU 暂停,可以通过
c
命令继续运行。
(gdb) c # 继续运行
这样,虚拟机的 CPU 会开始运行,相关的输出(例如启动日志)才会显示在 mon:stdio
中。虚拟机启动进入登录界面,进行登录后可在gdb
界面按 Ctrl + C
进行后续虚拟机linux
的内核调试。
-
调试 QEMU 热插拔实现的流程
-
启动 gdb
- 再新创建一个
shell
调试,启动 GDB:
- 再新创建一个
gdb ./local-build/x86_64-softmmu/qemu-system-x86_64
- 或者使用附加到 QEMU 进程(如果已经运行):
attach <QEMU进程ID>
- 或直接从调试模式启动 QEMU:
gdb --args ./qemu-system-x86_64 -m 4G,slots=4,maxmem=8G ...
-
设置断点
设置断点在与内存热插拔相关的关键函数:
- 内存设备的添加函数:
b pc_dimm_plug
QEMU 使用 pc_dimm_plug
函数处理内存插入操作。
- ACPI 热插拔事件函数实现:
b acpi_memory_device_plug
ACPI 的内存设备热插拔支持通过此函数实现。
- 内存管理函数:
b memory_region_add_subregion
b memory_region_del_subregion
-
QEMU 控制台运行热插拔
执行以下步骤观察内存热插拔的实现:
- 在
qemu
的Monitor
执行热插拔命令:
# 查询内存设备情况
(qemu) info memory-devices
(qemu) device_add pc-dimm,id=dimm2,memdev=mem2
- GDB 应捕获到
pc_dimm_plug
或相关函数的调用,触发断点,GDB 会在pc_dimm_plug
函数处暂停。可查看相应堆栈信息:
[root@gzinf-computer-55e235e17e24 ~]# gdb
GNU gdb (GDB) EulerOS 9.2-1.ctl2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-ctyunos-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) attach 516146
Attaching to process 516146
[New LWP 516147]
[New LWP 516152]
[New LWP 516153]
[New LWP 516154]
[New LWP 516155]
warning: File "/usr/lib64/libthread_db-1.0.so" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load:path/to/vmlinux-gdb.py".
# 添加内存热插拔实现的关键函数断点
(gdb) b pc_dimm_plug
Breakpoint 1 at 0x55a6c40de46e: file /vms/zhangjin/qemu/hw/mem/pc-dimm.c, line 69.
(gdb) b acpi_memory_device_plug
Function "acpi_memory_device_plug" not defined.
Make breakpoint pending on future shared library load? (y or [n]) i b
Please answer y or [n].
Make breakpoint pending on future shared library load? (y or [n]) n
# 待 qemu 的控制台执行`device_add pc-dimm,id=dimm2,memdev=mem2`热插拔命令后继续运行`qemu`,触发断点
(gdb) c
Continuing.
Thread 1 "qemu-system-x86" hit Breakpoint 1, pc_dimm_plug (dimm=0x55a6c64db770, machine=0x55a6c66e5c00,
errp=0x7fff33fb2c90) at /vms/zhangjin/qemu/hw/mem/pc-dimm.c:69
69 PCDIMMDeviceClass *ddc = PC_DIMM_GET_CLASS(dimm);
# 查看内存热插拔实现的堆栈信息
(gdb) bt
#0 pc_dimm_plug (dimm=0x55a6c64db770, machine=0x55a6c66e5c00, errp=0x7fff33fb2c90)
at /vms/zhangjin/qemu/hw/mem/pc-dimm.c:69
#1 0x000055a6c3efefb4 in pc_memory_plug (hotplug_dev=0x55a6c66e5c00, dev=0x55a6c64db770, errp=0x7fff33fb2d68)
at /vms/zhangjin/qemu/hw/i386/pc.c:1585
#2 0x000055a6c3f00742 in pc_machine_device_plug_cb
(hotplug_dev=0x55a6c66e5c00, dev=0x55a6c64db770, errp=0x7fff33fb2d68) at /vms/zhangjin/qemu/hw/i386/pc.c:1987
#3 0x000055a6c406069c in hotplug_handler_plug
(plug_handler=0x55a6c66e5c00, plugged_dev=0x55a6c64db770, errp=0x7fff33fb2d68)
at /vms/zhangjin/qemu/hw/core/hotplug.c:34
#4 0x000055a6c4058f7c in device_set_realized (obj=0x55a6c64db770, value=true, errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/hw/core/qdev.c:943
#5 0x000055a6c42b9c50 in property_set_bool (obj=0x55a6c64db770, v=
0x55a6c671fc40, name=0x55a6c457ec12 "realized", opaque=0x55a6c6554320, errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/qom/object.c:2238
#6 0x000055a6c42b7a89 in object_property_set (obj=0x55a6c64db770, v=
0x55a6c671fc40, name=0x55a6c457ec12 "realized", errp=0x7fff33fb2f40) at /vms/zhangjin/qemu/qom/object.c:1324
#7 0x000055a6c42bb427 in object_property_set_qobject
(obj=0x55a6c64db770, value=0x55a6c65c3480, name=0x55a6c457ec12 "realized", errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/qom/qom-qobject.c:26
#8 0x000055a6c42b7d6e in object_property_set_bool
(obj=0x55a6c64db770, value=true, name=0x55a6c457ec12 "realized", errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/qom/object.c:1390
#9 0x000055a6c3fca47d in qdev_device_add (opts=0x55a6c69409e0, errp=0x7fff33fb2fb0)
at /vms/zhangjin/qemu/qdev-monitor.c:680
#10 0x000055a6c3fcaa85 in qmp_device_add (qdict=0x55a6c6da2400, ret_data=0x0, errp=0x7fff33fb2ff0)
at /vms/zhangjin/qemu/qdev-monitor.c:805
#11 0x000055a6c3fcaf39 in hmp_device_add (mon=0x55a6c6790ca0, qdict=0x55a6c6da2400)
at /vms/zhangjin/qemu/qdev-monitor.c:905
#12 0x000055a6c422d379 in handle_hmp_command
(mon=0x55a6c6790ca0, cmdline=0x55a6c6851bcb "pc-dimm,id=dimm2,memdev=mem2")
at /vms/zhangjin/qemu/monitor/hmp.c:1082
#13 0x000055a6c422ab9a in monitor_command_cb
(opaque=0x55a6c6790ca0, cmdline=0x55a6c6851bc0 "device_add pc-dimm,id=dimm2,memdev=mem2", readline_opaque=0x0)
at /vms/zhangjin/qemu/monitor/hmp.c:47
#14 0x000055a6c443c9fb in readline_handle_byte (rs=0x55a6c6851bc0, ch=13) at /vms/zhangjin/qemu/util/readline.c:408
#15 0x000055a6c422dd7e in monitor_read
(opaque=0x55a6c6790ca0, buf=0x7fff33fb31c0 "\rrefreef:p1.-1#0f,p1.4#88-64bit.xml:17f7,ffb#a7elocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a\001", size=1)
at /vms/zhangjin/qemu/monitor/hmp.c:1312
#16 0x000055a6c438b968 in qemu_chr_be_write_impl
(s=0x55a6c657dc00, buf=0x7fff33fb31c0 "\rrefreef:p1.-1#0f,p1.4#88-64bit.xml:17f7,ffb#a7elocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a\001", len=1)
at /vms/zhangjin/qemu/chardev/char.c:197
#17 0x000055a6c438b9cc in qemu_chr_be_write
(s=0x55a6c657dc00, buf=0x7fff33fb31c0 "\rrefreef:p1.-1#0f,p1.4#88-64bit.xml:17f7,ffb#a7elocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a\001", len=1)
at /vms/zhangjin/qemu/chardev/char.c:209
#18 0x000055a6c4394d36 in tcp_chr_read (chan=0x55a6c6930e90, cond=G_IO_IN, opaque=0x55a6c657dc00)
at /vms/zhangjin/qemu/chardev/char-socket.c:555
#19 0x000055a6c43b0042 in qio_channel_fd_source_dispatch
(source=0x55a6c6734160, callback=0x55a6c4394b87 <tcp_chr_read>, user_data=0x55a6c657dc00)
at /vms/zhangjin/qemu/io/channel-watch.c:84
#20 0x00007f30296af184 in g_main_context_dispatch () at /usr/lib64/libglib-2.0.so.0
#21 0x000055a6c441c51d in glib_pollfds_poll () at /vms/zhangjin/qemu/util/main-loop.c:219
#22 0x000055a6c441c597 in os_host_main_loop_wait (timeout=318680) at /vms/zhangjin/qemu/util/main-loop.c:242
#23 0x000055a6c441c69c in main_loop_wait (nonblocking=0) at /vms/zhangjin/qemu/util/main-loop.c:518
#24 0x000055a6c3f344f4 in qemu_main_loop () at /vms/zhangjin/qemu/softmmu/vl.c:1713
#25 0x000055a6c43b61a2 in main (argc=34, argv=0x7fff33fb4468, envp=0x7fff33fb4580)
at /vms/zhangjin/qemu/softmmu/main.c:49
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000055a6c40de46e in pc_dimm_plug at /vms/zhangjin/qemu/hw/mem/pc-dimm.c:69
breakpoint already hit 1 time
(gdb)
- 继续使用
n
或s
单步执行,观察内存区域分配、设备树更新、ACPI 事件等关键流程。 -
虚拟机中验证内存热插拔是否生效
进入虚拟机(通过 ssh
或 console
)并检查内存是否成功加载:
dmesg | grep ACPI
free -h
分析内核事件
如果虚拟机的内核包含调试符号,可以附加到虚拟机的 GDB 接口:
target remote :1234
设置断点调试虚拟机内核的 ACPI 热插拔处理逻辑。
-
qemu
热插拔逻辑实现堆栈分析 -
关键堆栈信息
# 查看内存热插拔实现的堆栈信息
(gdb) bt
#0 pc_dimm_plug (dimm=0x55a6c64db770, machine=0x55a6c66e5c00, errp=0x7fff33fb2c90)
at /vms/zhangjin/qemu/hw/mem/pc-dimm.c:69
#1 0x000055a6c3efefb4 in pc_memory_plug (hotplug_dev=0x55a6c66e5c00, dev=0x55a6c64db770, errp=0x7fff33fb2d68)
at /vms/zhangjin/qemu/hw/i386/pc.c:1585
#2 0x000055a6c3f00742 in pc_machine_device_plug_cb
(hotplug_dev=0x55a6c66e5c00, dev=0x55a6c64db770, errp=0x7fff33fb2d68) at /vms/zhangjin/qemu/hw/i386/pc.c:1987
#3 0x000055a6c406069c in hotplug_handler_plug
(plug_handler=0x55a6c66e5c00, plugged_dev=0x55a6c64db770, errp=0x7fff33fb2d68)
at /vms/zhangjin/qemu/hw/core/hotplug.c:34
#4 0x000055a6c4058f7c in device_set_realized (obj=0x55a6c64db770, value=true, errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/hw/core/qdev.c:943
#5 0x000055a6c42b9c50 in property_set_bool (obj=0x55a6c64db770, v=
0x55a6c671fc40, name=0x55a6c457ec12 "realized", opaque=0x55a6c6554320, errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/qom/object.c:2238
#6 0x000055a6c42b7a89 in object_property_set (obj=0x55a6c64db770, v=
0x55a6c671fc40, name=0x55a6c457ec12 "realized", errp=0x7fff33fb2f40) at /vms/zhangjin/qemu/qom/object.c:1324
#7 0x000055a6c42bb427 in object_property_set_qobject
(obj=0x55a6c64db770, value=0x55a6c65c3480, name=0x55a6c457ec12 "realized", errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/qom/qom-qobject.c:26
#8 0x000055a6c42b7d6e in object_property_set_bool
(obj=0x55a6c64db770, value=true, name=0x55a6c457ec12 "realized", errp=0x7fff33fb2f40)
at /vms/zhangjin/qemu/qom/object.c:1390
#9 0x000055a6c3fca47d in qdev_device_add (opts=0x55a6c69409e0, errp=0x7fff33fb2fb0)
at /vms/zhangjin/qemu/qdev-monitor.c:680
#10 0x000055a6c3fcaa85 in qmp_device_add (qdict=0x55a6c6da2400, ret_data=0x0, errp=0x7fff33fb2ff0)
at /vms/zhangjin/qemu/qdev-monitor.c:805
#11 0x000055a6c3fcaf39 in hmp_device_add (mon=0x55a6c6790ca0, qdict=0x55a6c6da2400)
at /vms/zhangjin/qemu/qdev-monitor.c:905
#12 0x000055a6c422d379 in handle_hmp_command
(mon=0x55a6c6790ca0, cmdline=0x55a6c6851bcb "pc-dimm,id=dimm2,memdev=mem2")
at /vms/zhangjin/qemu/monitor/hmp.c:1082
#13 0x000055a6c422ab9a in monitor_command_cb
(opaque=0x55a6c6790ca0, cmdline=0x55a6c6851bc0 "device_add pc-dimm,id=dimm2,memdev=mem2", readline_opaque=0x0)
at /vms/zhangjin/qemu/monitor/hmp.c:47
#14 0x000055a6c443c9fb in readline_handle_byte (rs=0x55a6c6851bc0, ch=13) at /vms/zhangjin/qemu/util/readline.c:408
#15 0x000055a6c422dd7e in monitor_read
(opaque=0x55a6c6790ca0, buf=0x7fff33fb31c0 "\rrefreef:p1.-1#0f,p1.4#88-64bit.xml:17f7,ffb#a7elocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a\001", size=1)
at /vms/zhangjin/qemu/monitor/hmp.c:1312
#16 0x000055a6c438b968 in qemu_chr_be_write_impl
(s=0x55a6c657dc00, buf=0x7fff33fb31c0 "\rrefreef:p1.-1#0f,p1.4#88-64bit.xml:17f7,ffb#a7elocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a\001", len=1)
at /vms/zhangjin/qemu/chardev/char.c:197
#17 0x000055a6c438b9cc in qemu_chr_be_write
(s=0x55a6c657dc00, buf=0x7fff33fb31c0 "\rrefreef:p1.-1#0f,p1.4#88-64bit.xml:17f7,ffb#a7elocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a\001", len=1)
at /vms/zhangjin/qemu/chardev/char.c:209
#18 0x000055a6c4394d36 in tcp_chr_read (chan=0x55a6c6930e90, cond=G_IO_IN, opaque=0x55a6c657dc00)
at /vms/zhangjin/qemu/chardev/char-socket.c:555
#19 0x000055a6c43b0042 in qio_channel_fd_source_dispatch
(source=0x55a6c6734160, callback=0x55a6c4394b87 <tcp_chr_read>, user_data=0x55a6c657dc00)
at /vms/zhangjin/qemu/io/channel-watch.c:84
#20 0x00007f30296af184 in g_main_context_dispatch () at /usr/lib64/libglib-2.0.so.0
#21 0x000055a6c441c51d in glib_pollfds_poll () at /vms/zhangjin/qemu/util/main-loop.c:219
#22 0x000055a6c441c597 in os_host_main_loop_wait (timeout=318680) at /vms/zhangjin/qemu/util/main-loop.c:242
#23 0x000055a6c441c69c in main_loop_wait (nonblocking=0) at /vms/zhangjin/qemu/util/main-loop.c:518
#24 0x000055a6c3f344f4 in qemu_main_loop () at /vms/zhangjin/qemu/softmmu/vl.c:1713
#25 0x000055a6c43b61a2 in main (argc=34, argv=0x7fff33fb4468, envp=0x7fff33fb4580)
at /vms/zhangjin/qemu/softmmu/main.c:49
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000055a6c40de46e in pc_dimm_plug at /vms/zhangjin/qemu/hw/mem/pc-dimm.c:69
breakpoint already hit 1 time
(gdb)
-
handle_hmp_command
函数调用到hmp_device_add
的过程
// 内存热插拔时调用`hmp_device_add`函数,即 cmd->cmd函数指针指向到该函数。
cmd->cmd(&mon->common, qdict);
-
dev
设备状态信息分析
你在 GDB 调试中查看到的 dev
变量确实是 qdev_device_add
中定义的 dev
,表示的是一个新创建的 DeviceState
对象。下面是对你的调试输出的分析,以及 dev
在 qdev_device_add
中的含义。
dev
变量的含义
- 变量位置:
dev
变量是在qdev_device_add
函数中定义的,类型是DeviceState *
,用于表示新创建的设备。- 根据 GDB 输出的内容,
dev
的内存地址是0x563ba3834360
,并且它是一个指向DeviceState
对象的指针。
DeviceState
结构体的内容: GDB 输出中显示的dev
内容(即DeviceState
对象的内存布局)如下:
{
parent_obj = {class = 0x6d6d69642d6370, free = 0x0, ...},
id = 0x563ba36f5e90 "child<qemu:memory-region>",
canonical_path = 0x0,
realized = 106,
pending_deleted_event = 40,
opts = 0x0,
hotplugged = -1592055576,
allow_unplug_during_migration = 59,
parent_bus = 0x563ba11b28fe,
gpios = {lh_first = 0x0},
child_bus = {lh_first = 0x563ba3721e90},
num_child_bus = 0,
instance_id_alias = 0,
alias_required_for_version = 0,
reset = {count = 0, hold_phase_pending = 33, exit_phase_in_progress = false}
}
qdev_device_add
中 dev
的创建和意义
在 qdev_device_add
中,dev
是通过调用 DEVICE(object_new(driver))
来创建的。object_new(driver)
创建了一个 DeviceState
类型的对象,并初始化它的一些属性:
- 设备类型由
driver
参数确定,这通常是设备的驱动名称,如pc-dimm
、virtio-net
等。
QEMU 中的内存设备被称为
pc-dimm
,是源于传统计算机架构中内存模块的命名约定以及 QEMU 对设备模型的抽象。在这里,pc-dimm
是 QEMU 中表示内存模块(DIMM,即双列直插内存模块,Dual Inline Memory Module)的设备名称,属于计算机硬件架构模型的一部分。
- 创建的设备对象是基于
driver
对应的设备类(DeviceClass
)进行初始化的。
在函数中,dev
的主要作用是:
- 表示设备:
dev
是新创建的设备的表示,它包括设备的基本信息(如id
、driver
等)以及与设备相关的属性(如是否已经实现、设备是否允许热插拔等)。 - 与总线关联:如果设备需要连接到某个总线,它会在
qdev_device_add
中根据bus
选项查找目标总线,并将设备与总线关联。 - 设备初始化:
dev
还会根据设备选项进行属性设置,并最终标记为已“realized”状态,这意味着设备已经完全初始化并可用于操作。
通过 GDB 调试输出的 dev
状态
从 GDB 输出的信息可以看到:
dev->id = "child<qemu:memory-region>"
,这表示该设备是一个内存区域设备,通常与pc-dimm
或类似内存设备有关。dev->realized = 106
,表示设备已经被初始化并生效。realized
是一个布尔值(但实际值可能是106
,这可能是某个特定的状态值,表示设备已完全初始化)。dev->parent_bus
存储了设备的父总线对象的指针,如果设备连接到某个总线,这里会有一个有效的指针。
总结
- 是的,
dev
是qdev_device_add
中定义的DeviceState *dev
,它表示新创建的设备对象。 dev
包含了关于设备的各种信息,包括设备 ID、状态(如realized
)、与总线的关联等。- 设备创建过程中,
dev
会被赋予一些重要的属性,最终完成设备的初始化,并准备好加入到 QEMU 中。
-
device_set_realized
关键函数分析
在设备成功插入并准备好后,这个函数将设备标记为“已实现”(realized)。
realized
属性表示设备是否已经完成了初始化并且可以正常工作。最后通过 **
device_set_realized
标记设备为已初始化并能够开始工作。**
- 堆栈状态对比:
其中热插拔准备阶段的更具体堆栈信息如下:
-
如何向
KVM
申请内存区域 - 关键堆栈信息:
accel\kvm\kvm-all.c
中:
static int kvm_set_user_memory_region(KVMMemoryListener *kml, KVMSlot *slot, bool new)
{
KVMState *s = kvm_state;
struct kvm_userspace_memory_region mem;
int ret;
mem.slot = slot->slot | (kml->as_id << 16);
mem.guest_phys_addr = slot->start_addr;
mem.userspace_addr = (unsigned long)slot->ram;
mem.flags = slot->flags;
if (slot->memory_size && !new && (mem.flags ^ slot->old_flags) & KVM_MEM_READONLY) {
/* Set the slot size to 0 before setting the slot to the desired
* value. This is needed based on KVM commit 75d61fbc. */
mem.memory_size = 0;
ret = kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);
if (ret < 0) {
goto err;
}
}
mem.memory_size = slot->memory_size;
ret = kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);
slot->old_flags = mem.flags;
err:
trace_kvm_set_user_memory(mem.slot, mem.flags, mem.guest_phys_addr,
mem.memory_size, mem.userspace_addr, ret);
if (ret < 0) {
error_report("%s: KVM_SET_USER_MEMORY_REGION failed, slot=%d,"
" start=0x%" PRIx64 ", size=0x%" PRIx64 ": %s",
__func__, mem.slot, slot->start_addr,
(uint64_t)mem.memory_size, strerror(errno));
}
return ret;
}
该函数通过设置
kvm_userspace_memory_region
结构体的各个字段,将用户空间的内存区域映射到虚拟机的物理地址空间。
通过堆栈信息,我们可以详细了解 QEMU 如何处理内存热插拔的核心逻辑。以下是对关键调用路径的逐步分析,以及每一步的作用与实现逻辑。
2. #### QEMU 内存热插拔的调用路径
- Monitor 接收命令
堆栈信息表明,你通过 Monitor 向 QEMU 发送了以下命令:
device_add pc-dimm,id=dimm2,memdev=mem2
该命令由 monitor/hmp.c
的 handle_hmp_command()
函数处理,它将命令字符串解析为具体的设备操作。
- 对应堆栈:
#12 handle_hmp_command
#11 hmp_device_add
- 作用:
- 将命令参数解析为 QDict 数据结构。
- 调用
qmp_device_add()
进行具体设备添加操作。
- 添加设备(**
device_add
)**
hmp_device_add
调用 qmp_device_add
,这是 QMP(QEMU Machine Protocol)处理设备添加的核心入口。
- 对应堆栈:
#10 qmp_device_add
#9 qdev_device_add
- 作用:
- 使用传入的参数(如
id
和memdev
)创建新的 QEMU 对象(如pc-dimm
)。 - 设置设备属性,例如
realized
标志(表示设备是否已初始化)。
- 使用传入的参数(如
- 设置设备属性
堆栈表明,QEMU 使用 QOM(QEMU Object Model)设置设备属性。关键调用链如下:
object_property_set_bool
设置realized=true
。- 触发
device_set_realized
,进入热插拔处理逻辑。 - 对应堆栈:
#8 object_property_set_bool
#7 object_property_set
#5 device_set_realized
- 作用:
- 确保设备已经“实现”(realized),并调用设备的热插拔处理函数。
- 调用热插拔回调
当设备属性 realized=true
时,会触发热插拔处理流程,核心函数为 hotplug_handler_plug()
。
- 对应堆栈:
#4 hotplug_handler_plug
#3 pc_machine_device_plug_cb
- 作用:
- 调用机器类型(PC)的热插拔处理函数
pc_memory_plug()
。 pc_memory_plug()
进一步调用pc_dimm_plug()
实现内存设备的插入。
- 调用机器类型(PC)的热插拔处理函数
- 插入内存设备
最终,QEMU 的 pc_dimm_plug()
函数完成具体内存设备的插入逻辑。
- 对应堆栈:
#1 pc_memory_plug
#0 pc_dimm_plug
- 作用:
- 分配 ACPI 相关资源(如
_EJ0
方法和热插拔通知)。 - 更新系统内存布局。
- 如果需要,触发 KVM 热插拔回调。
- 分配 ACPI 相关资源(如
-
堆栈各层次的逻辑与实现
堆栈层次 | 文件与函数 | 作用 |
---|---|---|
Monitor 层 | monitor/hmp.c::handle_hmp_command | 解析用户输入,调用 HMP 或 QMP 处理函数。 |
QMP 层 | qdev-monitor.c::qmp_device_add | 创建新设备,调用 QOM API 设置设备属性。 |
QOM 层 | qom/object.c::object_property_set | 设置设备属性,如 realized 标志。 |
热插拔框架层 | core/hotplug.c::hotplug_handler_plug | 调用机器类型的热插拔回调函数。 |
PC 机器层 | hw/i386/pc.c::pc_memory_plug | 检查并执行内存热插拔的硬件级初始化。 |
设备层 | hw/mem/pc-dimm.c::pc_dimm_plug | 分配资源,更新内存布局,完成内存热插拔操作。 |
-
QEMU 的热插拔逻辑总结
- 命令解析:用户通过 Monitor 或 QMP 发送命令,QEMU 将其解析为具体设备添加操作。
- 设备初始化:通过 QOM 设置设备属性,触发热插拔处理流程。
- 热插拔框架调用:根据设备类型调用对应机器(PC)和设备的插入函数。
- 设备操作:最终完成 ACPI 表更新、内存布局调整、热插拔通知等操作。
-
如何继续分析?
-
调试 **
pc_dimm_plug
函数**:- 设置断点逐步跟踪其实现:
(gdb) b hw/mem/pc-dimm.c:69 (gdb) c
- 检查参数:
(gdb) p dimm (gdb) p machine
-
观察 ACPI 表的变化:
- 查看
_SRAT
或_EJ0
表是否被正确更新。
- 查看
-
测试 KVM 回调:
- 如果启用了 KVM,检查是否有对应的 VM 内核通知被触发。