Contents
概述第 1 步:xm 创建 domain第 2 步:xend 处理创建 domain 请求第 3 步:xend 处理启动 domain 请求第 4 步:hypervisor 中创建 domain第 5 步:hypervisor 中初始化 domain mini-os本文总结在 Xen 中创建一个半虚拟化 domain 的过程。
概述过程概括介绍:
用户输入 xmcreate$config_file ,创建一个 domain。
xm 通过多次调用 xend,依据配置文件,创建 domain 和其他设备。
这里 xend 只是在自身内部创建一个包含 domain 信息的实例,并没有调用 hypervisor。
xm 再次调用 xend,启动之前创建好了的 domain。这个步骤由下面两步完成。
xend 首先调用 hypervisor,创建一个 domain。这只是一个只有共性而没有特性的 domain。
然后 xend 多次调用 hypervisor,初始化 domain,比如内核文件,内存大小等。最后启动 domain。
说明:
第 2 步和第 4 步都是创建 domain。但前者是在 xend 内部创建,后者是在 hypervisor 内部创建。
其中第 2,3 步 xm 对 xend 的调用是通过 XML-RPC 实现的。第 3 步 Xend 对 hypervisor 的 调用是通过 Dom0 的 privcmd 设备调用 hypercall 陷入 hypervisor 实现的。关于实现细节不是 本文内容,在下面的详细介绍里不做讨论。
下面对代码的罗列中:以缩进代表调用关系;代码前面的号码对应下面的 代码分析 中的号码;代码后面的号码对应下面的 路径 中的号码,表示当前行代码所在文件。在正文中出现的中括号也代表对应的路径。下面是详细介绍。
第 1 步:xm 创建 domain上面第 1 步,xm 接收到用户输入后,主文件[1]解析参数,会调用文件 [2] 中的 main()。
下面从 main() 开始分析:
main [2](1) xenapi_create_inst.create [3] (2) create_vdis [3] server.xenapi.VDI.create [4] create_networks [3](3) create_vms [3] server.xenapi.VM.create [4] create_vbds [3] create_vifs [3] create_vtpms [3] create_consoles [3] create_pcis [3](4) server.xenapi.VM.start [4]代码分析 :
xenapi_create_inst 是 xenapi_create 的实例。create.py[2] 是创建 domain 的程序的主文件, 但只负责解析用户命令行输入参数。真正调用 xenapi 创建 domain 的工作由 xenapi_create [3] 完成。
解析配置文件中 VDI 的信息,并创建 VDI (virtual disk image)。
所有型如 create_XXXs 的函数(除了下面要讲到的 create_vms)的功能都是创建相应设备,实现方式是:
首先循环调用 create_XXX;后者调用 server.xenapi.XXX.create() 方法,来调用 xend,参数是 xx_record 格式命 名的字典变量,包含了设备或 VM 的参数信息;xend 接收到请求后,创建相应的对象,并返回这个对象的一个引用。第 2 步:xend 处理创建 domain 请求 会介绍 xend对这些创建请求的处理过程。
根据上面创建的 VID 和 NETWORK 以及从配置文件里解析的其他信息,创建 VM。这步会创建很多 设备,所以比较麻烦,没有详细跟踪。只知道最终将根据配置文件创建一个完整的 domain。
之前所有的操作都是在 xend 内部创建并初始化 domain。这步是向 xend 发出 VM.start 请求, 启动之前创建完成的 domain。 第 3 步:xend 处理启动 domain 请求 会介绍 xend 对请求的处理过程。
路径 :
[1] tools/python/xen/xm/main.py[2] tools/python/xen/xm/create.py[3] tools/python/xen/xm/xenapi_create.py[4] tools/python/xen/xm/XenApi.py 第 2 步:xend 处理创建 domain 请求上一节的第 2 步是创建 VM 及相关的设备。这节介绍 xend 对这些请求的处理过程。
xend 的 XML-RPC 服务器[1]接收到 XXX.yyy 的请求,会调用定义在文件[2]中的 XXX_yyy 函数。 处理过程大体一致,所以这里只以创建 VM 为例介绍。
如上所述,xm 发送 server.xenapi.VM.create 请求,参数是包含 VM 信息的 vm_record。xend 接收到请求后,调用 VM_create 函数。所以从这里开始:
VM_create [2] (1) xendom.create_domain [3](2) XendConfig.XendConfig [4] (3) XendDomainInfo.createDormant [5] vm = XendDomainInfo [5]代码分析 :
xendom 是 XendDomain 的实例。这个类实现了 xenapi 中对 VM 的操作,比如 create,pause 等。 这里调用的是 create_domain 完成创建 domain 的操作。XendConfig 是分析 VM 操作请求的参数的类。根据上一节第 2 步,这个参数的结构是 vm_record, 在 tools/python/xen/xm/xenapi_create.py 文件中定义。生成的对象作为参数传递给第 3 步。根据上面解析的 XendConfig 实例,创建一个 XendDomainInfo 对象。XendDomainInfo 是标识 一个 domain 的类,包含了一个 domain 的完整信息。路径:
[1] tools/python/xen/xend/server/XMLRPCServer.py[2] tools/python/xen/xend/XendAPI.py[3] tools/python/xen/xend/XendDomain.py[4] tools/python/xen/xend/XendConfig.py[5] tools/python/xen/xend/XendDomainInfo.py 第 3 步:xend 处理启动 domain 请求下面跟踪对 xend 对启动 VM 的处理。
VM_start [1] XendDomain.domain_start [2][1] dominfo.start [3] [3] _constructDomain [3] [4] xc.domain_create [4][5] _initDomain [3] _storeVmDetails [3] _storeDomDetails [3] _registerWatches [3] refreshShutdown [3] [6] XendDomain.domain_unpause [2] xc.domain_unpause [4]代码分析 :
xend 接收到 VM.start 请求,调用 VM_start() 函数,后者调用 XendDomain 类的 domain_start()。
dominfo 是上一节讲到的 XendDomainInfo 的实例。包含了 domain 的完整信息。
_constructDomsin() 是 XendDomainInfo 的私有方法,功能是在 hypervisor 中创建 domain。
xc 在文件 [4] 中定义,xend 所有对 hypervisor 的调用都通过这里,间接调用 domctl_op 这个hypercall 实现的。不同的函数调用这个 hypercall 不同的选项。具体调用过程这里不详细介绍。
domain_create() 最终调用 domctl_op 的 XEN_DOMCTL_createdomain 选项。第 4 步:hypervisor 中创建 domain 详细介绍 hypervisor 对创建 domain 请求的处理。
创建 domain 时向 hypervisor 传递的参数结构如下面代码所列 [5], 其中ssidref 和安全策略相关;handle 是标识 domain 的全局唯一标识符;flags 只是标识全虚拟 化还是半虚拟化,以及是否使用硬件支持的分页管理。所以这个操作并没有向 hypervisor 传 递 domain 相关的特性信息,而是仅仅创建了个共性的 domain,需要后续的对 domain 初始化:
struct xen_domctl_createdomain { uint32_t ssidref; xen_domain_handle_t handle; uint32_t flags;};初始化 domain,例如创建内核镜像文件,设置内存大小等。初始化完成之后的 domain 才是个完整的 domain。 这部分比较复杂,所以在 第 5 步:hypervisor 中初始化 domain 中单独介绍。
刚创建的 domain 是暂停状态的,这里真正启动 domain。也是通过 libxc 库,最终调用hypercall domctl_op 的 XEN_DOMCTL_uppausedomain 选项实现的。
路径 :
[1] tools/python/xen/xend/XendAPI.py[2] tools/python/xen/xend/XendDomain.py[3] tools/python/xen/xend/XendDomainInfo.py[4] tools/python/lowlevel/xc/xc.c[5] xen/include/public/domctl.h 第 4 步:hypervisor 中创建 domain外部通过 hypercall domctl_op 来操作 domain。其实现函数是 do_domctl()。从这里开始分析。
(1) do_domctl:XEN_DOMCTL_create_domain [1] (2) domain_create [2](3) arch_domain_create() [3]代码分析 :
简单的参数解析。分配控制 struct domain [4]数据结构,并做些简单的和平台无关的操作。这个数据结构是 hypervisor内部标识 domain 的数据结构,包含了一个 domain 的所有信息。在第 3 步返回,即创建完成后, 将 domain 加入到调度队列中。但这时 domain 的状态的 paused,所以不被调度。做创建 domain 的实际工作。由于这是个只有共性的 domain 框架,而不是一个实际的 domain,所以 实际工作也不多,比如分配 share-info-page 等。路径 :
[1] xen/common/domctl.c[2] xen/common/domain.c[3] xen/arch/x86/domain.c[4] xen/include/xen/sched.h 第 5 步:hypervisor 中初始化 domain第 3 步:xend 处理启动 domain 请求 一节中的第 5 步是初始化 domain。由于 domain 的特性非常多,所以 初始化 domain 非常复杂。我现在还没有从整体上分析。下面就简单的就顺着初始化 hypercall_page 这 一个线索,分析初始化的过程。对其他特性的初始化的过程类似。
hypercall-page 是映射到 domain 地址空间的一页。在 guest kernel 的最开始就需要用到 hypercall, 所以 hypercall-page 必须在 domain 启动前完成初始化。
首先先返回到 xend 中,看 xend 是如何请求初始化 hypercall_page 的。
_initDomain [1] (1) image.create [1] findImageHandleClass [2] X86_Linux_ImageHandle [2](2) image.createImage [1] createDomain [2] buildDomain [2] xc.linux_build [3]代码分析 :
根据 XendDomainInfo 中保存的 domain 关于 kernel 镜像的信息,创建一个 image 的对象。image 对象是和架构,操作系统类型,虚拟化方式(半虚拟化还是全虚拟化)相关的,所以由此派生出 很多子类。这里以 x86 架构下 Linux 操作系统为例,对应子类即为 X86_Linux_ImageHandle。创建内核镜像。最终还是调用 xc 来实现。由于这部分比较复杂,所以在下面单独列出来。然后进入 libxc:
(3) xc.linux_build -> pyxc_linux_build [3] (4) xc_dom_allocate [4](5) xc_dom_linux_build [5] (6) xc_dom_kernel_file xc_dom_ramdisk_file (7) xc_linux_build_internal [5] (8) xc_dom_parse_image [4] (9) kl = xc_dom_find_loader [4] (10) kl->parser <=> xc_dom_parse_elf_loader [6] (11) elf_xen_parse [7] (12) elf_xen_parse_guest_info : HYPERCALL_PAGE [7] (13) xc_dom_boot_image [8] (14) setup_hypercall_page [8] do_domctl [9]代码分析 :
文件 [3] 中,将 linux_build 映射成 pyxc_linux_build。
申请一个 struct xc_dom_image[13] 结构。这个结构包含了所有 hypervisor 初始化 domain 所需要的信息。
下面的操作就是根据 xend 传递过来的 domain 的特性,来设置这个结构,然后传递给 hypervisor,初始化 domain。
创建以 Linux 为 kernel 的 domain。所有初始化 domain 的操作都是在这个函数内完成的。
创建 kernel 和 ramdisk 文件。和 hypercall-page 的设置无关,所以我没有深入跟踪。
实际工作。其实这个函数内部调用了 6 个很重要的函数。但我只跟踪了下面两个和 hypercall-page 相关的两个函数。
解析 kernel 镜像。我们这里关心的功能是查找 kernel 里 hypercall-page 的地址,以便下面的函数初始化之。
探测 kernel 镜像的格式。
支持的格式由 xc_dom_register_loader[4] 来注册。注册的参数是这个格式的探测函数,解析函数以及加载函数, 如下面所列数据结构所示 [14]。
当前版本支持的格式有 fw,bin,bzimage,elf 四种格式。由于 linux 和 mini-os 都使用的是 bzimage格式,所以这里只分析这种格式。:
struct xc_dom_loader { char *name; int (*probe) (struct xc_dom_image * dom); int (*parser) (struct xc_dom_image * dom); int (*loader) (struct xc_dom_image * dom); struct xc_dom_loader *next;};调用相应格式的解析函数。bzimage 格式的解析函数就是直接调用 elf 格式的解析函数。
解析 elf 文件的和 Xen 相关的内容。首先查看 notes 段;如果没有,就查看 SHT_NOTE 段; 如果还没有就查看 __xen_guest 段。
mini-os 使用的是 __xen_guest 段的方式,所以这里只分析这种格式。在 __xen_guest 段 中查找 HYPERCALL_PAGE 项对应的值,即 hypercall-page 的页号。参照mini-os 。
根据上面解析得到的信息初始化内核镜像,包括 hypercall-page。
初始化 hypercall-page。还是通过调用 domctl_op 这个 hypercall 的 XEN_DOMCTL_hypercall_init选项实现。
下面看在 hypervisor 内部接收到初始化 hypercall-page 后的处理过程:
(15) do_domctl: XEN_DOMCTL_hypercall_init [10] arch_do_domctl [11] hypercall_page_initialise [12]代码分析 :
do_domctl 是 hypercall domctl_op 的实现函数,但对 XEN_DOMCTL_hypercall_init 选项 的处理是调用了下面这个函数实现的。具体实现这里不详细介绍。路径 :
[1] tools/python/xen/xend/XendDomainInfo.py[2] tools/python/xen/xend/image.py[3] tools/python/xen/lowlevel/xc/xc.c[4] tools/libxc/xc_dom_core.c[5] tools/libxc/xc_dom_compat_linux.c[6] tools/libxc/xc_dom_elfloader.c[7] xen/common/libelf/libelf-dominfo.c[8] tools/libxc/xc_dom_boot.c[9] tools/libxc/xc_private.h[10] xen/common/domctl.c[11] xen/arch/x86/domctl.c[12] xen/arch/x86/x86_32/traps.c[13] tools/libxc/xc_dom.h mini-os下面以 mini-os 为例子,介绍 domain 的 kernel 在最开始启动时的情况。
下面代码摘自 extras/mini-os/arch/x86/x86_32.S:
(1) .section __xen_guest .ascii "GUEST_OS=Mini-OS" .ascii ",XEN_VER=xen-3.0" .ascii ",VIRT_BASE=0x0" /* &_text from minios_x86_32.lds */ .ascii ",ELF_PADDR_OFFSET=0x0" .ascii ",HYPERCALL_PAGE=0x2" .ascii ",PAE=yes[extended-cr3]" .ascii ",LOADER=generic" .byte 0 .org 0x1000(2) shared_info: .org 0x2000 hypercall_page: .org 0x3000(3) _start: push %esi(4) call start_kernel代码分析 :
__xen_guest 段。定义了很多 kernel 的信息,参照 第 5 步:hypervisor 中初始化 domain 中第 12 步。定义 shared_info 和 hypercall_page。__xen_guest 段中 "HYPERCALL_PAGE=0x2" 指示的就是 hypercall_paeg 的页号。_start 是整个 kernel 的入口。调用 start_kernel,这是 mini-os 的 C 入口函数。%esi 是 start_info 的地址。关于 start_info,这里不详细介绍。在 start_kernel 中需要做很多初始化工作,这里只简单介绍映射 shared_info 的过程。
(1) start_kernel(start_info_t *si) [1] arch_init(si) [2] memcpy(&start_info, si, sizeof(*si)) (2) HYPERVISOR_shared_info = map_shared_info(start_info.shared_info) HYPERVISOR_update_va_mapping((unsigned long)shared_info, __pte(pa | 7), UVMF_INVLPG))代码分析 :
start_kernel() 调用很多初始化函数。这里只列出了 arch_init()。调用 HYPERVISOR_update_va_mapping 将 start_info.shared_info 映射到上面定义的 shared_info 空间上。路径 :
[1] extras/mini-os/kernel.c[2] extras/mini-os/arch/x86/setup.c