学vm虚拟机源码,别想着一口气读完,正确路线是:先跑通一个虚拟机,再从内存虚拟化、CPU虚拟化、设备模拟三条主线各挑一条路径追下去,最后回到共同的核心循环。
很多人下载了QEMU或者KVM的源码,打开目录就懵了,上千个文件,几十万行代码,完全不知道从哪下手,这很正常,虚拟机源码的复杂度不在于单个文件有多难,而在于它横跨了CPU架构、操作系统、硬件模拟三个领域,我当初也是绕了不少弯路,所以想把我自己的学习路径整理出来,给你当个参考。
vm虚拟机源码从哪开始看?先搞懂三条主线
先说结论:不要按目录顺序从头读到尾,虚拟机源码的逻辑不是线性的,你从main()开始读,读一个礼拜可能还在初始化流程里打转,真正核心的东西一点没碰。
我的做法是,把整个源码拆成三条主线,每条主线选一个代表文件作为入口。
内存虚拟化
这是虚拟机最核心的部分,也是初学者最容易绕晕的地方,你不需要懂所有细节,但必须搞清楚一个关键问题:客户机的物理地址,是怎么映射到宿主机的物理地址的?
在QEMU里,这个问题的答案藏在exec.c和softmmu.c里,你不需要读完整文件,只需要找到几个带address_space、phys_map字样的函数,比如address_space_write和address_space_read,这两个函数就是客户机访问物理内存的入口,顺着这两个函数往下追,你会看到TLB的处理、页表的遍历,慢慢就明白“影子页表”或者“EPT”是怎么回事了。
在KVM里,入口则是kvm_mmu.c,这个文件很长,但开头部分的注释很有价值,你重点看kvm_mmu_setup和kvm_mmu_load这两个函数,理解它们如何把客户机的页表映射到宿主机。
CPU虚拟化
CPU虚拟化的核心是KVM的kvm_vcpu_run,以及QEMU里的cpu_exec,这两个函数是虚拟机“心脏跳动”的地方,每一次跳转都代表一条客户机指令被执行。
建议你先看QEMU的cpu-exec.c,找找cpu_exec这个函数,里面有个大循环,不断调用cpu_tb_exec执行翻译好的指令块,这就引出了QEMU最经典的“TCG(Tiny Code Generator)”动态翻译机制,你可能听说过“动态二进制翻译”这个名字,其实指的就是这个。
看懂了QEMU的CPU执行循环,再去看KVM的virt/kvm/kvm_main.c里的kvm_vcpu_run,你会发现一个有意思的对比:QEMU是软件翻译指令,而KVM是利用硬件的VMX/SVM功能直接让CPU进入客户机模式,两种思路,同样都叫虚拟机,但底层差别很大。
设备模拟
设备模拟最直观,也最适合建立信心,你不需要看PCI、USB这些复杂设备,从最简单的串口(uart)开始,在QEMU源码中,找hw/char/目录下的serial.c。
这个文件就几百行,逻辑很清晰,你只需要注意两点:第一,串口设备的寄存器地址映射(MMIO)是怎么注册的;第二,客户机往串口发送一个字节的时候,数据经过了哪些函数,最终跑到宿主机终端上的。
串口看完了,再试着看网卡virtio-net,这里你会接触到virtio这个重要的接口标准,不需要全懂,先知道“前端驱动”和“后端设备”是怎么通过共享内存通信的。
vm虚拟机源码怎么学?我的阅读顺序建议
搞清楚三条主线之后,你还需要一个更具体的时间表,我个人推荐分三个阶段走,每个阶段都有明确产出,避免漫无目的地翻源码。
先跑起来,再读代码
很多人的习惯是“看懂了再运行”,对于虚拟机源码,恰恰相反,你应该先把QEMU编译出来,跑一个最小化的Linux系统。
具体操作:
- 下载QEMU源码,安装编译依赖(Ubuntu下是
apt-get build-dep qemu)。 - 配置编译选项,只保留
x86_64-softmmu目标,减少编译时间。 - 下载一个精简内核镜像(比如用
tinycorelinux),用qemu-system-x86_64 -kernel vmlinux -initrd initrd.img命令启动。 - 确认虚拟机正常运行后,再用GDB调试运行中的QEMU进程。
这一步的意义在于,当你看到代码里的某个函数时,知道它对应的是正在运行的那个虚拟机的哪个行为。代码和现象之间有了对应关系,阅读效率会翻倍。
追踪一条指令的执行路径
这是最核心的训练,我强烈建议你完整记录一条“加法指令”从客户机到宿主机再到执行的整个过程,画一张时序图。
具体做法:
- 在客户机里写一个简单的C程序,里面只有一个
a = b + c; - 在QEMU源码的
cpu_exec处打断点(GDB命令:break cpu_exec)。 - 观察每次进入
cpu_exec时,当前翻译缓冲区里的指令是什么。 - 使用QEMU的
-d in_asm,int日志选项,打印翻译前后的汇编指令。
当你亲手画完这张图,你会发现原来虚拟机执行一条指令也不是简单的事情:要先取指、翻译、优化、生成host代码、再执行。这条路径上的关键函数,比你看十篇博客都记得牢。
动手改一个最小功能
源码学到一定程度,必须用“改”来验证自己是不是真懂了,这里给你一个安全又有趣的实践:
修改QEMU的串口设备,让它每收到一个字节时就往宿主机日志输出一条记录。
具体步骤:
- 在
serial.c的serial_ioport_write函数里,加入一条printf或fprintf(stderr, ...)日志。 - 重新编译QEMU,启动虚拟机。
- 在客户机里用
echo hello > /dev/ttyS0发送串口数据。 - 观察宿主机的终端日志,确认你的改动生效。
这个实验虽然小,但能证明你已经清楚“客户机IO请求如何路由到具体设备模型”的路径。这比任何书面测试都有效。
虚拟机源码学习路径:从KVM到QEMU的推荐顺序
我知道很多同学纠结一个问题:我先学QEMU还是先学KVM?行业共识认为,先学QEMU稍后学KVM,理由有三个:
- QEMU是纯软件实现,不依赖特殊硬件,你手里的普通PC就能跑。
- QEMU的代码结构更“教材化”,注释也比较丰富。
- 学完QEMU再学KVM,能自然理解“为什么需要硬件加速”以及“KVM加速在哪一步插进来的”。
下面是我建议的具体顺序。
第一步:整体认知层
先读QEMU的docs/about目录下的设计文档,再通读vl.c的前面200行(程序入口和参数解析),你不需要理解每一个选项,只需要知道系统虚拟机部分的启动流程大致是:解析参数 → 初始化内存 → 创建CPU → 加载固件 → 开始执行。
第二步:单核裸机执行层
配置QEMU为单核,使用-machine pc默认机器,不加载任何外部设备,只启动一个Linux内核,在这个配置下,追踪cpu_exec和tb_gen_code这些函数,你会发现QEMU把连续的基本块翻译缓存起来,形成“翻译缓存”。
第三步:硬件加速层
阅读KVM相关代码,QEMU侧看target/i386/kvm/,KVM侧看arch/x86/kvm/,重点了解:
- ioctl接口如何从QEMU用户态切入KVM内核态。
- 创建vm、创建vcpu、设置内存区域的三个主要ioctl(
KVM_CREATE_VM、KVM_CREATE_VCPU、KVM_SET_USER_MEMORY_REGION)。 KVM_RUNioctl如何触发kvm_vcpu_run。
如果你觉得底层太难,可以先跳过vmx.c这种专属文件,只看kvm_main.c里通用的部分。
第四步:整机系统层
回到QEMU,这次加上网卡、磁盘、显示器,你不需要逐行读设备代码,只需要搞懂总线模型,QEMU用pci总线把所有设备串起来,所以hw/pci/pci.c值得花时间看,理解一对多的设备注册机制后,再看任何新设备,都能快速归类。
源码阅读的实用工具和技巧
读源码不只是用眼睛看,合适的工具能让你提前几个月入门。
-
用GDB加断点,不要只靠printf。 QEMU支持在调试模式下启动虚拟机,你可以在宿主机上用GDB attach到QEMU进程,然后设置断点,常见做法是编译时加
--enable-debug选项。 -
充分利用QEMU自带的trace事件,QEMU有
-trace help列出所有可追踪事件,你可以开启-trace virtio来观察设备操作的调用链,这在追踪未知路径时非常有用。 -
把源码版本和文档对应起来,不同版本的代码结构差异较大,网上很多文章讲的是老版本,你下手前先
git log确认代码版本,再看对应版本的官方文档,据统计,相当一部分初学者卡住是因为看的教程和当前源码版本对不上。 -
画图比截图重要,我不推荐你建一个庞大的笔记文件夹,每研究透一条路径,就用一张流程图把它画出来,我习惯画“函数调用链图”,把关键函数、文件、行数标上去,回头复习时效率极高。
-
面对C语言宏和多层指针,别硬看,虚拟机源码里到处都是
container_of、CPUState cpu这类间接层,遇到看不懂的宏,先在build目录下运行make生成预处理后的.i文件,展开宏之后会清晰很多。
关于vm虚拟机源码学习的常见疑问
看源码之前,需要先把操作系统原理学完吗?
不需要,你只需要具备三个基础:会C语言指针和链表,理解虚拟内存的页表概念,能用Linux命令行操作,遇到不懂的知识(比如EPP),现查现学效率更高,操作系统是“精读”过程中自然补课的,不用先从头啃完一本教材。
看不懂x86汇编,还能学会虚拟机源码吗?
能,但你至少得认识常见的几十条指令,你不需要懂汇编优化技巧,只要能看懂mov、add、push、pop和指令前缀就够了,QEMU里的TCG生成的是x86汇编,你追踪时经常要对照翻译前后的指令,建议花三天时间看一本人手册的汇编入门章节,只要六七成理解,就能支撑你读CPU虚拟化部分的代码。
需要读完整份QEMU或KVM源码吗?
从来没有不完整读完源码就找不到工作这一回事,即便是内核维护者也只深钻某个子模块,你的目标是建立“从客户机到宿主机”的完整映射感,而不是背下每个函数,当你把内存、CPU、设备三条主线各走一遍后,已经达到了学习目标,剩下的按需查阅即可。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/727528.html





