关注微信公众号:Linux内核拾遗
一、为什么选择kvmtool
在虚拟化技术领域,QEMU(Quick Emulator)无疑是应用最广泛的全能型选手。这个开源机器仿真器能够:
- 支持x86、ARM、RISC-V等20+处理器架构。
- 提供完整的设备模拟(网卡、显卡、声卡等)。
- 兼容多种虚拟化方案(KVM、Xen、TCG等)。
- 实现复杂的功能特性(实时迁移、快照管理)。
但正是这种全能性使得QEMU的代码规模超过150万行,学习其源码如同大海捞针。当我们的目标是专注学习KVM虚拟化技术的核心原理时,QEMU的复杂性反而成为了认知负担:

KVMtool(全称Linux Kernel Virtual Machine Tool)则采取了截然不同的设计思路:
c
+---------------------+
| KVMtool (用户态) |
+---------------------+
| /dev/kvm |
+---------------------+
| KVM内核模块 |
+---------------------+
| 硬件虚拟化支持 (VT-x) |
+---------------------+
其核心特点包括:
- 代码精简:约1万行C代码(仅为QEMU的0.6%)。
- 功能聚焦:仅使用KVM进行硬件辅助虚拟化。
- 零抽象层:直接操作虚拟化扩展指令集。
- 开发友好:代码逻辑线性可追踪。
如果只是为了学习 kvm 虚拟化技术,那么就可以尽量简化用户态管理程序。这里以开源的工具 kvmtool 为例讲讲 kvm 是如何运行和调试我们的Linux系统的。
二、环境准备
由于kvmtool底层使用的是kvm,因此我们在实践之前,需要准备一台运行Linux内核、并且带有kvm子系统的主机。目前主流的Linux发行版都能满足要求,可以通过lsmod | grep kvm
命令来检查:

如果你是在Windows或者macOS环境中安装Linux虚拟机,请确保处理器硬件支持嵌套虚拟化,并且在VMWare、Parallel Desktop等虚拟机软件中为虚拟机开启嵌套虚拟化能力。这里不再深入展开,如果恰好你是这种情况,麻烦各位自行采取各种手段解决吧。
三、源码获取与编译
1. kvmtool编译和安装
可以直接从Github上获取kvmtool源代码:
bash
git clone https://github.com/kvmtool/kvmtool.git
cd kvmtool
kvmtool的编译很简单,直接make
即可,然后使用make install
进行安装:
bash
make -j$(nproc)
make install
安装完成后,可以通过lkvm
来使用kvmtool了:
bash
[root@iZbp1a2ioxwnzygize00vnZ kvmtool]# lkvm version
kvm tool 3.18.0
2. Linux系统准备
为了运行Linux系统,我们需要准备"Linux内核映像"和"根文件系统映像"这两个东西。
具体过程这里不再详细阐述,大家可以参考:mp.weixin.qq.com/s/y27zD6DPY...
最后只需要两个文件:
- Linux内核映像:
linux/arch/x86/boot/bzImage
。 - 根文件系统映像:
initramfs.img
。
四、使用kvmtool启动虚拟机
kvmtool启动虚拟机的方式和QEMU很类似,基本就是指定内核文件、根文件系统,然后配置核心数、内存大小以及内核启动参数等:
bash
lkvm run \
-k ../linux/arch/x86/boot/bzImage \
--initrd ../initramfs.img \
-m 1G \
-c 2 \
--console serial \
-p "console=ttyS0,115200n8 earlycon=uart8250,io,0x3f8,115200n8 root=/dev/ram0 rw init=/init"
可以看到,Linux内核正常跑起来了:

同样也支持基本的BusyBox命令:

1. kvmtool常用命令
kvmtool允许用户对虚拟机执行一些基本操作,例如查看当前虚拟机状态、暂停或者恢复虚拟机执行等:

2. GDB调试虚拟机
kvmtool支持通过gdb来调试我们的虚拟机,其底层利用的是Linux内核的kgdb调试机制。
这里先贴一下kvmtool官方提供的调试步骤:

但是我在实践过程中没有办法直接复现,下面就介绍一下我自己摸索过后切实可行的步骤。
首先我们需要重新编译我们的Linux内核映像,编辑.config
文件如下:
bash
# 模板1:
# CONFIG_STRICT_KERNEL_RWX is not set
CONFIG_FRAME_POINTER=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_KGDB_KDB=n
CONFIG_KDB_KEYBOARD=n
# 或者使用模板2:
CONFIG_STRICT_KERNEL_RWX=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_KGDB_KDB=n
CONFIG_KDB_KEYBOARD=n
上面的配置只启用了KGDB,没有使用KDB,原因后面说明。
重新编译内核,得到arch/x86/boot/bzImage
。
然后kvmtool启动虚拟机,并且添加kgdb相关启动参数:
bash
lkvm run \
-m 512M \
-c 2 \
-k ./linux/arch/x86/boot/bzImage \
--initrd ./initramfs.img \
-p "kgdboc=ttyS1,115200 kgdbwait nokaslr" \
--tty 1
终端输出内核日志如下:


日志中Info: Assigned terminal 1 to pty /dev/pts/1
意思是说虚拟机的串口1ttyS1
绑定到了宿主机上的伪终端/dev/pts/1
。
我们可以另起一个宿主机终端,通过gdb来连接到调试串口/dev/pts/1
:
bash
gdb --tui ./linux/vmlinux
target remote /dev/pts/1
可以看到虚拟机执行暂停在了kgdb断点处:

接下来我们就可以跟调试普通程序一样,使用常用的gdb命令来调试Linux内核了。对于需要修改内核代码,或者开发驱动模块,使用kgdb调试是非常有用的。
3. 使用KDB?
上一节提到过,我们重新编译Linux内核前将KDB模式给关闭了,原因是我当时在开启KDB的情况下,gdb无论如何也连接不上去,从kdb也没法切换到kgdb模式,遂放弃了Orz。。。
开启KDB模式的情况下,虚拟机启动后自动进入了KDB shell:

Linux内核官方文档中指出,有两种方式从KDB模式切换到KGDB模式:

一种方式是通过gdb连接到调试端口后kdb shell会自动识别到并切换到kgdb模式,另一种是直接在kdb shell中直接执行kgdb
命令来手动切换。
但是我两种方式尝试下来都不行。
下面是gdb连接报错:


然后尝试直接进入kdb shell,手动切换kgdb:
bash
screen /dev/pts/1
[1]kdb> kgdb

有哪位大佬知道是什么原因吗?欢迎私信或者留言探讨交流,非非非常感谢!
关注微信公众号:Linux内核拾遗