Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1320547
  • 博文数量: 179
  • 博客积分: 4141
  • 博客等级: 中将
  • 技术积分: 2083
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-21 20:04
文章存档

2024年(1)

2019年(13)

2016年(1)

2014年(16)

2011年(8)

2010年(25)

2009年(115)

分类: 虚拟化

2009-08-09 17:12:32

引言(Lguest 使用方法)

1.  在一个正常的Linux内核中插入一个lg.ko模块。我们称这个Linux内核为主机操作系统。运行在这个Linux上的操作系统成为客户操作系统。Lg.ko负责建立和配置客户操作系统。

2.  客户操作系统的内核需要经过修改(开源社区已经制定了半虚拟化接口,并且还在完善当中)。这个在下面我们会稍作介绍。

Lguest 总架构图

下图是Lguest 总架构图

l  Hardware:指CPU,内存,硬盘等等

l  Hardware API:指硬件提供给软件访问硬件的编程接口

l  Host OS Kernel:指直接运行在硬件之上的操作系统。注意:host OS kernel 和guest OS kernel必须是同一版本

l  Lg hypervisor module:指加载到Host OS 的lguest 模块,用于处理必须由hypervisor完成的工作

l  launcher:Lguest hypervisor 和guest OS的中间层

 

Lguest 之guest篇

Lguest属于半虚拟化虚拟机。因此guest OS kernel需要修改才能够运行在host OS 之上。VMware 起先提出了VMI 半虚拟化接口。后来经过经过开源社区的努力,设计出了paravirt_ops接口。

Paravirt_ops linux 内核中的初始化:

摘自:linux 2.6.23.13\arch\i386\kernel
struct paravirt_ops paravirt_ops = {
    .name = "bare hardware",
    .paravirt_enabled = 0,
    .kernel_rpl = 0,
    .shared_kernel_pmd = 1,    /* Only used when CONFIG_X86_PAE is set */

    .patch = native_patch,
    .banner = default_banner,
    .arch_setup = paravirt_nop,
    .memory_setup = machine_specific_memory_setup,
    .get_wallclock = native_get_wallclock,
    .set_wallclock = native_set_wallclock,
    .time_init = hpet_time_init,
    .init_IRQ = native_init_IRQ,

    .cpuid = native_cpuid,
    .get_debugreg = native_get_debugreg,
    .set_debugreg = native_set_debugreg,
    .clts = native_clts,
    .read_cr0 = native_read_cr0,
    .write_cr0 = native_write_cr0,
    .read_cr2 = native_read_cr2,
    .write_cr2 = native_write_cr2,
    .read_cr3 = native_read_cr3,
    .write_cr3 = native_write_cr3,
    .read_cr4 = native_read_cr4,
    .read_cr4_safe = native_read_cr4_safe,
    .write_cr4 = native_write_cr4,
    .save_fl = native_save_fl,
    .restore_fl = native_restore_fl,
    .irq_disable = native_irq_disable,
    .irq_enable = native_irq_enable,
    .safe_halt = native_safe_halt,
    .halt = native_halt,
    .wbinvd = native_wbinvd,
    .read_msr = native_read_msr_safe,
    .write_msr = native_write_msr_safe,
    .read_tsc = native_read_tsc,
    .read_pmc = native_read_pmc,
    .sched_clock = native_sched_clock,
    .get_cpu_khz = native_calculate_cpu_khz,
    .load_tr_desc = native_load_tr_desc,
    .set_ldt = native_set_ldt,
    .load_gdt = native_load_gdt,
    .load_idt = native_load_idt,
    .store_gdt = native_store_gdt,
    .store_idt = native_store_idt,
    .store_tr = native_store_tr,
    .load_tls = native_load_tls,
    .write_ldt_entry = write_dt_entry,
    .write_gdt_entry = write_dt_entry,
    .write_idt_entry = write_dt_entry,
    .load_esp0 = native_load_esp0,

    .set_iopl_mask = native_set_iopl_mask,
    .io_delay = native_io_delay,

#ifdef CONFIG_X86_LOCAL_APIC
    .apic_write = native_apic_write,
    .apic_write_atomic = native_apic_write_atomic,
    .apic_read = native_apic_read,
    .setup_boot_clock = setup_boot_APIC_clock,
    .setup_secondary_clock = setup_secondary_APIC_clock,
    .startup_ipi_hook = paravirt_nop,
#endif
    .set_lazy_mode = paravirt_nop,

    .pagetable_setup_start = native_pagetable_setup_start,
    .pagetable_setup_done = native_pagetable_setup_done,

    .flush_tlb_user = native_flush_tlb,
    .flush_tlb_kernel = native_flush_tlb_global,
    .flush_tlb_single = native_flush_tlb_single,
    .flush_tlb_others = native_flush_tlb_others,

    .alloc_pt = paravirt_nop,
    .alloc_pd = paravirt_nop,
    .alloc_pd_clone = paravirt_nop,
    .release_pt = paravirt_nop,
    .release_pd = paravirt_nop,

    .set_pte = native_set_pte,
    .set_pte_at = native_set_pte_at,
    .set_pmd = native_set_pmd,
    .pte_update = paravirt_nop,
    .pte_update_defer = paravirt_nop,

#ifdef CONFIG_HIGHPTE
    .kmap_atomic_pte = kmap_atomic,
#endif

#ifdef CONFIG_X86_PAE
    .set_pte_atomic = native_set_pte_atomic,
    .set_pte_present = native_set_pte_present,
    .set_pud = native_set_pud,
    .pte_clear = native_pte_clear,
    .pmd_clear = native_pmd_clear,

    .pmd_val = native_pmd_val,
    .make_pmd = native_make_pmd,
#endif

    .pte_val = native_pte_val,
    .pgd_val = native_pgd_val,

    .make_pte = native_make_pte,
    .make_pgd = native_make_pgd,

    .irq_enable_sysexit = native_irq_enable_sysexit,
    .iret = native_iret,

    .dup_mmap = paravirt_nop,
    .exit_mmap = paravirt_nop,
    .activate_mm = paravirt_nop,
};

以上是Paravirt_ops在内核中的初始化。当OS初始虚拟机的客户端的时候,该结构体会被赋以另外一个值。这个,OS在执行的时候,只需要调用该接口,而不需要知道他是处于物理机器上的,还是作为虚拟机的guest而存在的,极大的简化了内核设计。

 

下面给出lguest 在guest os 中初始化的流程图:


Lguest之Launcher篇

Launcher只要负责分配客户操作系统的物理地址(实际上是主机操作系统的虚拟地址),然后运行客户操作系统。

下图给出了launcher 初始化流程图:


下面给出了launcher初始化时的内存分布图:


Lguest之host篇

Host主要负责处理客户操作系统的服务请求。

下面给出Lguest的host端的初始化流程图:

下面给出运行guest的流程图:


 

影子页表原理及其在Lguest的应用

在系统级虚拟中,有一个概念非常的重要。就是影子页表。这里介绍一下影子页表的基础知识。

1.启用影子页表的原因

Xen利用影子页表机制来实现硬件虚拟机的内存虚拟。宿主机就是真实的物理机器,Xen的监控程序就运行在宿主机上。客户机是指在宿主机上执行的硬件虚拟机,也被称为虚拟域。客户机认为它所拥有的内存地址空间总是从0开始,但它在宿主机上执行时不可能总是拥有从0开始的地址所在的物理内存。也就是说客户机的物理地址并不等于宿主机上的机器物理地址。图描述了客户机的物理地址与宿主机上机器物理地址的关系。

这样监控程序必须把客户机线性地址到客户机物理地址的转换修正为客户机线性地址到宿主机物理地址的转换。这样的转换显然不是客户机的页表所能支持的,客户机的页表只知道客户机的物理地址,而监控程序为了实现对各个客户机的隔离与保护,也不会让客户机了解宿主机的物理地址。对于完全虚拟化的客户机,监控程序甚至不能够修改客户机的页表。但是,客户机的线性地址到宿主机物理地址的转换是保证客户机在宿主机上访问内存运行正确的核心环节,这样,为了支持和保存这种转换或映射,并能根据客户机修改页表的需要及时更新,Xen就启用了另外一张页表,这就是影子页表。

2.影子页表

影子页表是监控程序真正使用和维护的页表。客户机执行时,监控程序在宿主机的页表基地址寄存器(CR3)中放人的是影子页表中指向最高级的影子页表的指针(物理地址)。当物理机上没有监控程序运行时,在物理机的页表基地址寄存器中放入的是物理机上运行的惟一操作系统所指向最高级页表的指针。

Xen中,客户机维护着客户机自己的页表,而监控程序维护着影子页表。客户机实际上是通过影子页表在访问真实的机器物理地址,影子页表以客户机页表为蓝本建立起来,并且会随着客户机页表的更新而更新,就像客户机页表的影子。这就是称它为影子页表的原因。

在对应的客户机页表和影子页表中,相应位置上的客户机物理地址与机器物理地址之间,有两种对应关系,一种是通过哈希表来得到,一种则通过P2M table 来转换。图描述了这两种页表的对应关系。

 

小结

硬件虚拟机支持在一台计算机上存在多个运行时环境,每个运行时环境可以支持一个操作系统。硬件虚拟机能够使为某种操作系统编写的程序在另外一种操作系统上运行,或者提供比单一操作系统上多进程之间更严格的运行时沙箱。硬件虚拟机现在也被称作虚拟化或者虚拟服务器。Lguest作为硬件虚拟机的一种,虽然还没成功应用在工业方面。但是它短小精炼,是学习虚拟化技术的最佳入门材料。

参考文献

[1] 虚拟机的设计与实现:C/C++,(美)Bill Blunden,2003

[2] 虚拟机—系统与进程的通用平台,James E.Smith/Ravi Nair ,2006

[3] An introduction to lguest,corbet ,2007

 

阅读(6997) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~