Linux virtualization

本文着重探讨下kvm的实现, 11年在snia的实习的时候, 接触过一点.
已经过去将近4年了, 当初看到qemu的代码, 被吓到了, 我靠, 这代码量赶上内核了!
现在kvm核心就1069行当前kernel 4.1 rc7, qemu虽然赶不上kernel但是也不少1144756.
看过指环王的, 都知道神奇中土大陆是托尔金虚构的魔幻世界,那里活着阿拉贡和
他的朋友们, 以及他们精彩的历程.人的想象力, 如此神奇, 他能使人超越时间与
空间去体验到不同生命历程.
今天, 我们来探索一下虚拟化
如wikipedia所说, 虚拟化始于上个世纪60年代, 这是指的计算机领域内的.
显然, 我们要把眼光放得更远, 因为我们想洞见一些深层次的内容.
法国哲学家福柯一生都在从事”知识考古学”, 而我也喜欢在研究问题时, 从语言学特别是
词源角度开始. virtual这个词, 最早来自于中世纪拉丁文virtualis, 涵义是
influencing by physical virtues or capabilities,
effective with respect to inherent natural qualities
受某种实在的长处能力影响, 源自本质的效用.
这里从两种角度理解virtual, 受影响者, 影响者本身.
所以这里virtual的真实涵义应该是延续某种内在的本质并施加于外物.
这个英文单词的汉语翻译是很有趣的, 摘自金山词霸:

virtual 音节划分:vir▪tual
英 [ˈvɜ:tʃuəl] 美 [ˈvɜ:rtʃuəl]
adj.实质上的,事实上的;(计算机)虚拟的;<物>有效的,虚像的;(粒子)实际存在的
虚;虚拟艺术;虚拟的;模拟帐户

这个词汇简直是英语翻译界的笑话, 一会实一会虚, 非常让人困惑!
话说回来, 这个词的翻译确实有难度.我翻译的话会叫质延.
很多事情, 在我们出生前就决定了, 我是89年9月出生的.
我们搞懂了所谓”虚拟化”的实质, 之后就可以继续探索了.
如wikipedia, 所说计算机领域的虚拟化更多的是指hardware virtualization.
没错就是延伸硬件的特质, 准确说是在软件层继续延伸硬件的特质.
好, 首先, 我们要搞懂硬件都有那些特质? 硬件有很多, 在计算机领域指什么呢?
没错, CPU, 硬盘, 电路板, 内存, 鼠标键盘等等. 那么他们有什么特质呢?
所谓的特质就是特别的性质, 一种属性, 哲学上, 称之为事物对外物的作用的一种表现.
那么CPU的最大特质就是指令处理, 我想这也是虚拟化的核心吧!
我们硬件虚拟化延伸的终点是让另一个操作系统运行于当前操作系统提供的硬件特质延伸之上.
终于进入正文了. 现在我们自己来设计一套虚拟化基础架构, 让一个简单的kernel运行在当前的fedora
之上.

FKVM

没错我的虚拟化技术就叫这个FucK VM, 好吧, 其实简写于Firo’s KVM!
首先, 我们必须明确一点, 即便我 or 我们不能最终实现一个虚拟化架构让另一个kernel
运行起来, 今天在这里做得事情的意义, 依然深远且几乎无可替代!
因为, 至少你是在想象力,或者更为正式表述是在概念层次上让他运行起来了, 而且跑得飞快!
你在虚拟化一个虚拟化, 这就是智力劳动的乐趣.
怎么才能让一个kernel 运行起来呢? 能想到的就是从用户态起一个进程, 之后这个进程fork下去执行
start_kernel的代码.有点眉目了. 问题也来了, 内核启动的时候会各种初始化, 这会扰乱了现在正在运行的
cpu, 我能想到的这个新的内核的内存管理不能和之前运行的fedora的冲突, 两个都玩完, 首先要隔离内存.
也就是要虚拟化一块内存. 怎么解决, 最简单的直接malloc一块空间给他吧. 怎么给? 为了简单.
在内核初始化的时候, 会用调用bios的中断来获取内存信息.
也就是说我们要先弄一个虚拟bios出来啊. 先假设bios, 我们弄出来了.
开机先执行这段bios代码, 把之前申请的内存告诉bios, 之后我们在内核内核初始化的时候, 就用这块.
现在问题用来了. 内核要访问这块内存就必须为他建页表, 申请的这块内存可能是不连续的而且还可能是
高端内存, 内核初始化的代码可没考虑这么复杂. 要么该内核, 要么改FKVM, 显然不能改内核啊.
只能去尝试, 虚拟一个CPU, cpu指令那么复杂, 怎么能搞定. 是否有必要, 搞那么复杂呢?
我们的一个cpu就是一个进程.这个cpu执行内初始化的代码, 需要想办法, 让内核感知不到底层的变化.
就好比, 他访问了一块高端内存, 但这个内核, 里面的页表却标明他是低端, 随意访问.
也就是说, 我们要截获cpu访存的指令, 并且要把他再次重定向到真正的物理内存.
如何截获?我们不能有太多假设.而且现在的工作, 实际已经被usermode linux 完成了.
这里需要很多底层的知识. 先看点资料再回来.
看了下基本实现, 这里我们的思路没有问题, 只不过指令和优先级的问题没有考虑.
访问特权指令确实是被截获的. intel引入了vt-x来解决这个问题.
探索这种复杂的系统, 就行探索一座深山一样, 如果不加思索就突入进去, 最终会迷路.
无论别人说的多么好, 我们必须要有自己的思路.
总结下所得:
控制虚拟机对全局资源的访问, 通过截获虚拟的某些指令.
虚拟化一块内存.
我们知道想中断啊, IO这些都得良好的处理掉, 否这会影响到我们早已运行的fedora.
由于缺少太多硬件知识, 在这样思考下去, 就会变成满篇假设了, 这和我们的目的背道而驰了.
同时乐趣也会减少.

KVM QEMU

现在我们来了解KVM, 在经过前面不成熟的思考好, 结果有点令人沮丧. 似乎我们什么都没完成…
探索的方式, 依然是以自我思考为主, 通过提问的形式完成思考过程.
千万不可成了, 靠一点一滴积累别人的思想感悟来成长. 万万不可.对于问题的洞见主力依然
是我们自己, 别人的知识只是辅助. 那好我们开始.
首先, 我们是为了了解KVM是如何运作的, 晚上的paper 帖子, 都说比较概括, 而分析的又不太通俗.
往往初学者, 很容易被绕进去.
从宏观上看KVM虚拟化, 由KVM和QEMU两个组件完成的, 一个内核模块, 一个用户态程序.
为什么这么设计? 还有别的虚拟化架构吗? 维基给出了, 硬件虚拟化的几种实现策略.
Full virtualization, VMware Workstation 和 QEMU就是这种, 全虚拟化定义上是说, 为虚拟机提供
全部的硬件特性的延伸支持. 这个技术上个世界70年代就由IBM推行. 在人们看来这门高深的学问在计算机的
洪荒时代, 便已出现. 那个时代一切都是新的, 人们强烈的渴望伸展四肢, 天不怕地不怕的.
Partial virtualization, 维基上给出半虚拟化的介绍, 他是全虚拟化的先驱, 现在很少本人提起.
至少, 现在都是全虚拟化和类虚拟化的天下.文中提及, 虚拟地址空间便是半虚拟化.
从虚拟化的定义角度来看, 操作系统本身, 便是一种原生且原始的虚拟化, 这无可否认.
这是一种高度抽象的虚拟化, 就好比进程是cpu的虚拟化, 虚拟地址空间是对内存的一种虚拟化.
Paravirtualization, para是在什么旁边的意思, 中文翻译成类虚拟化, 也算贴切.
类虚拟化修改guest 系统, 而不是通过虚拟化硬件.Xen(也支持全虚拟化)和UML是这一类.
Operating-system-level virtualization
这种虚拟化,概念上不同于上面三种, 是对操作系统的虚拟化, 虽然追根到底, 还是对硬件的虚拟化.
但是, 这里强调的是操作系统的资源的虚拟化. 属于操作系统的很多高级抽象, 底层硬件是不具备的.
比如协议栈这个概念, 他本身是对信息传递的抽象而非硬件, 硬件只是其中一环而已.
这里若是简单的翻译成系统级虚拟化, 就就比如LXC这个技术而言, 这里面我们并没有虚拟化任何东西.
比如我们使用socket 发包, 还是这样; 使用malloc分配内存还是这样, 并没有也不需要所谓虚拟化,
他们都是原生的original. 所以在这里我们就看到了, 不合适的翻译造成的理解差异.
这里还是直接说容器技术,比较合适, 因为他本来就强调对资源的管理.
在了解了虚拟化的概念后, 问题又回到了KVM QEMU本身, 此时, 我们依然对他们的实现没有头绪,
网上也没有找到, 说理清晰的入门文档. 我们还是要搞懂 kvm和qemu的原理.
我们从小的概念开始, 力求集合小的概念最终, 完成的对kvm 和 qemu的认知.
首先是qemu是什么? 为什么要有他, 单独一个kvm不行吗?
官网的解释:

QEMU is a generic and open source machine emulator and virtualizer.

qemu可以做emulator, 就像android的开发环境中的那个模拟器差不多.
qemu可以作为虚拟化的工具和kvm,xen联动, 重点看这个.
我们在FKVM中已经知道, cpu的虚拟化是同vtx, 截获特殊指令完成的.
那么内存虚拟化呢, 显然不应是malloc一块空间能解决的.
毫无疑问, 肯定要有一个机制把host的内存映射到一个guest的物理地址空间, 同时guest的
虚拟地址解析出来的物理地址在转到宿主机的地址, 最后转成真正的物理地址. 有三次转换.
据我所指, x86的页表转换是自动的.如何让他这么转来转去的.这个应该就是kvm orvtx干的事情
补货到访存指令, 之后进行地址转换. 看了下真正的实现, 和我们思路差不多.
不同点, guest的物理地址是用pfn加数量标志的. 不用想每次访存kvm都要参与, 而且地址转换确实复杂
肯定有优化空间. 我现在想说, vtx这个截获访存指令确实是个BUG级的存在.
内存虚拟化就到这里了, 优化去看IBM的讲解帖子吧.
下面继续看cpu的虚拟化. 这个主要就是, 如何捕获, 那些vm运行比较敏感的指令.
我们的FKVM支持一个进程跑, 能想到的就是建几个线程来模拟SMP.

下面是设备的虚拟化, 这个还是看书去吧.

代码实现

  • KVM architecture
  • Kernel running flow
    svm.ko -> svm_init(svm.c) -> kvm_init(kvm_main.c) ->
  • File
    svm.c vmx.c kvm_main.c kvm_svm.h
  • Function
  • Struct
    kvm_x86_ops x86.h
    vcpu_svm kvm_svm.h

qemu-system-ppc

-mem-path: