前言: 基于KVM的设备虚拟化,就从这里开始吧。 分析: 1,PIO Port IO,所谓端口IO,x86上使用in、out指令进行访问。和内存的地址空间完全隔离。(ARM上没有PIO)。 Guest以Linux为例:cat /proc/ioports查看当前OS的所有的ioports:
常见的port 40---timer,60---keyboard等等。这个是否可以变呢?可以的,只是基于业界使用习惯,都会相对固定使用常用的ioport。 2,Host PIO Guest在使用in、out指令的时候,Host会感知。Host中会在linux-4.0.4/arch/x86/kvm/emulate.c中处理:
继续调用到linux-4.0.4/arch/x86/kvm/x86.c中:
好吧,要发生了vmexit了。注意exit reason是KVM_EXIT_IO,并且记录下了对应的port、count等参数。 结合一下正常的访问过程:物理机使用in、out访问port的时候,通过外部电路访问具体的设备,读取或者写入数据。 而虚拟化的场景下呢,PIO的核心就是Guest访问port的时候,并不是真正的去访问Host的物理外部电路。而是Host主动拦截对应的指令,用软件处理这个过程。 3,Guest PIO 以键盘为例,在qemu-2.8.0-rc4/hw/input/pckbd.c中:
代码反着看,首先声明了i8024 info,name就是TYPE_I8042(宏定义的"i8042"),并且注册了class的initfn是i8042_realizefn;这里会继续注册了0x60(键盘)和0x64(鼠标)的ioport。(如果想修改kbd的ioport,这里修改,注意不要和其他的设备的ioport冲突)。 如前文《[kvm][qemu]CPU虚拟化 》中所说,VCPU进入vm mode之后,遇到异常退出vm mode,返回qemu继续处理,在qemu-2.8.0-rc4/kvm-all.c中kvm_cpu_exec函数:
其中KVM_EXIT_IO就是处理PIO的分支(题外话,打开这里的log,就会发现随便动动鼠标,就有很多log喷出来)。kvm_handle_io函数第一个参数就是port,后面的代码就容易理解了:使用port参数找到之前注册的设备,对应的设备来继续处理数据。 qemu-2.8.0-rc4/hw/input/pckbd.c中:
在initfn中,已经注册过对应的read/write函数。注意read/write/cmd等函数,就是用软件实现硬件的逻辑,使用既定的协议就行数据交互。 4,full flow 总结上面的过程:qemu虚拟化出来device → Guest使用in、out访问device → Host截获in、out指令 → qemu根据port找到对应的device → 虚拟device模拟协议完成数据读写 后记: 作者感觉设备虚拟化要比CPU虚拟化和memory虚拟化更加晦涩一些。 作者建议,分析这里的代码的时候,使用systemtap统计kernel function的调用频率,抓取对应的kernel function backtrace,会容易一些。 Good Luck~