代码分析文章《KVM虚拟机代码揭秘——QEMU代码结构分析》、《KVM虚拟机代码揭秘——中断虚拟化》、《KVM虚拟机代码揭秘——设备IO虚拟化》、《KVM虚拟机代码揭秘——QEMU的PCI总线与设备(上)》、《KVM虚拟机代码揭秘——QEMU的PCI总线与设备(下)》。先从大的方面分析代码结构,然后分中断、IO、PCI总线与设备详细介绍。

KVM虚拟机代码揭秘——QEMU代码结构分析

关于TCG的解释:TCG(Tiny Code Generator),QEMU的官方解释在http://wiki.qemu-project.org/Documentation/TCG

TCG的作用就是将Target的指令通过TCG前端转换成TCG ops,进而通过TCG后端转换成Host上运行的指令。

需要将QEMU移植到一个新CPU上运行,需要关注TCG后端。需要基于QEMU模拟一个新CPU,需要关注TCG前端。

qemu-img

在根目录生成,参照Makefile可知有如下文件组成:

qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)

qemu-system-x86_64

由于target比较多,编译也费时。可以指定便以特定的target:

./configure --target-list=x86_64-softmmu

qemu-system-x86_64的入口定义在vl.c的main中:

main
    ->main_loop
        ->main_loop_wait
            ->os_host_main_loop_wait

1.代码结构

QEMU的main函数定义在vl.c中,是执行程序的起点,主要功能是建立一个虚拟的硬件环境。

.
├── audio
├── backends
├── block
├── bsd-user
├── chardev
├── configure
├── contrib
├── crypto  加密解密算法等。
├── docs
├── dtc
├── fpu
├── fsdev
├── hw  所有硬件设备,包括总线、串口、网卡、鼠标等等。通过设备模块串在一起。
├── include
├── io
├── linux-headers
├── linux-user
├── Makefile
├── migration
├── nbd
├── net
├── pc-bios
├── pixman
├── po
├── qapi
├── qga
├── qobject
├── qom
├── README
├── replay
├── roms
├── scripts
├── stubs
├── target  不同架构的对应目录,将客户CPU架构的TBs转化成TCG中间代码,这个就是TCG前半部分。
├── tcg  这部分是使用TCG代码生成主机的代码,不同架构对应不同子目录。整个生成主机代码的过程即TCG后半部分。
├── tests
├── trace
├── trace-events
├── trace-events-all
├── ui
├── util
├── VERSION
├── vl.c  main函数,程序执行起点。最主要的模拟循环,虚拟机环境初始化和CPU的执行。
├── x86_64-softmmu  ./configure配置生成的目录

2.TCG

QEMU是一个模拟器,它能够动态模拟特定架构CPU指令,QEMU模拟的架构叫目标架构;运行QEMU的系统架构叫主机架构。

QEMU中有一个模块叫微型代码生成器,将目标代码翻译成主机代码。

运行在虚拟CPU上的代码叫做客户机代码,QEMU主要功能就是不断提取客户机代码并且转化成主机代码。

整个翻译分成两部分:将目标代码(TB)转化成TCG中间代码,然后再将中间代码转化成主机代码。

当新的代码从TB(Translation Block)中生成以后,将会保存到一个cache中,因为很多相同的TB会被反复的进行操作,所以这样类似于内存的cache,能够提高使用效率。而cache的刷新使用LRU算法。

tb_gen_code
    ->gen_intermediate_code
    ->tcg_gen_code

由tb_gen_code调用,将客户机代码转换成主机代码。gen_intermediate_code之前是客户及代码,tcg_gen_code之后是主机代码,两者之间是TCG中间代码。

3.QEMU中的TCG代码分析

x86_cpu_realizefn  架构相关初始化函数
    ->qemu_init_vcpu 
        ->qemu_tcg_init_vcpu
            ->qemu_tcg_cpu_thread_fn  vcpu线程函数
                ->tcg_cpu_exec
                    ->cpu_exec  这个函数是主要的执行循环,这里第一次翻译TB,然后不停的执行异常处理。
                        ->tb_find  首先在Hash表中查找,如果找不到则调用tb_gen_code创建一个TB。
                            ->tb_gen_code  分配一个新的TB。
                                ->gen_intermediate_code
                                ->tcg_gen_code  将TCG代码转换成主机代码。
                        ->cpu_loop_exec_tb
                            ->cpu_tb_exec  执行TB主机代码
                                ->tcg_qemu_tb_exec

KVM虚拟机代码揭秘——中断虚拟化

KVM中断虚拟化主要依赖于VT-x技术,VT-x主要提供了两种中断事件机制,分别是中断退出和中断注入。

中断退出:指虚拟机发生中断时,主动式的客户机发生VM-Exit,这样能够在主机中实现对客户机中断的注入。

中断注入:是指将中断写入VMCS对应的中断信息位,来实现中断的注入,当中断完成后通过读取中断的返回信息来分析中断是否正确。

中断注入的标志性函数kvm_set_irq,是中断注入的最开始。

第一个参数s,传递设置IRQ需要的vmfd句柄,以及IRQ的ioctl类型。

第二、三参数,是IRQ中断号,以及触发类型。

int kvm_set_irq(KVMState *s, int irq, int level)
{
    struct kvm_irq_level event;
    int ret;

assert(kvm_async_interrupts_enabled());

event.level = level;
    event.irq = irq;
    ret = kvm_vm_ioctl(s, s->irq_set_ioctl, &event);  将irq_set_ioctl和具体IRQ信息写入vmfd。
    if (ret < 0) {
        perror("kvm_set_irq");
        abort();
    }

return (s->irq_set_ioctl == KVM_IRQ_LINE) ? 1 : event.status;
}

上面的ioctl对应内核中的kvm_vm_ioctl,内核首先case到KVM_IRQ_LINE。然后解析

static long kvm_vm_ioctl(struct file *filp,
               unsigned int ioctl, unsigned long arg)
{

#ifdef __KVM_HAVE_IRQ_LINE
    case KVM_IRQ_LINE_STATUS:
    case KVM_IRQ_LINE: {
        struct kvm_irq_level irq_event;

r = -EFAULT;
        if (copy_from_user(&irq_event, argp, sizeof(irq_event)))
            goto out;

r = kvm_vm_ioctl_irq_line(kvm, &irq_event,
                    ioctl == KVM_IRQ_LINE_STATUS);
        if (r)
            goto out;

r = -EFAULT;
        if (ioctl == KVM_IRQ_LINE_STATUS) {
            if (copy_to_user(argp, &irq_event, sizeof(irq_event)))
                goto out;
        }

r = 0;
        break;
    }
#endif


}

KVM中断路由(何为?)

kvm_arch_vm_ioctl
    ->KVM_CREATE_IRQCHIP(kvm_setup_default_irq_routing)
        ->kvm_set_irq_routing
            ->setup_routing_entry
                ->kvm_set_routing_entry
                    ->KVM_IRQCHIP_PIC_MASTER(kvm_set_pic_irq)
                    ->KVM_IRQCHIP_PIC_SLAVE(kvm_set_pic_irq)
                    ->KVM_IRQCHIP_IOAPIC(kvm_set_ioapic_irq)
                    ->KVM_IRQ_ROUTING_MSI(kvm_set_msi)
                    ->KVM_IRQ_ROUTING_HV_SINT(kvm_hv_set_sint)

从上可以看出针对不同类型的ROUTING方式和IRQCHIP,跳转到对应的中断注入函数。IRQCHIP类型的中断路由有PIC和IOAPIC;还有MSI和SINT类型。

PIC全称 Programmable Interrupt Controller,通常是指Intel 8259A双片级联构成的最多支持15个interrupts的中断控制系统。

APIC全称Advanced Programmable Interrupt Controller,APIC是为了多核平台而设计的。它由两个部分组成IOAPIC和LAPIC,其中IOAPIC通常位于南桥中用于处理桥上的设备所产生的各种中断,LAPIC则是每个CPU都会有一个。IOAPIC通过APICBUS(现在都是通过FSB/QPI)将中断信息分 派给每颗CPU的LAPIC,CPU上的LAPIC能够智能的决定是否接受系统总线上传递过来的中断信息,而且它还可以处理Local端中断的 pending、nesting、masking,以及IOAPIC于Local CPU的交互处理。

设置好虚拟中断控制器之后,在KVM_RUN退出以后,就开始遍历虚拟中断控制器,如果发现中断,就将中断写入中断信息位.

vcpu_run
    ->vcpu_enter_guest
        ->inject_pending_event

inject_pending_event在进入Guest之前被调用。

KVM虚拟机代码揭秘——设备IO虚拟化

虚拟设备的IO地址注册

KVM虚拟机设备模拟实在QEMU中实现的,而KVM实现的实质上只是IO的拦截。真正的虚拟设备IO地址注册实在QEMU代码里面实现的。

QEMU中,初始化硬件设备的时候需要注册IO空间,有两种方法:

  1. PIO(Port IO) 端口IO
  2. MIO(Memory IO) 内存映射IO

PS:发觉这里面介绍的代码和最新的4.x已经很大差异,所以略过。

KVM虚拟机代码揭秘——QEMU的PCI总线与设备(上)

QEMU的PCI总线

QEMU在初始化硬件的时候,最开始的函数就是pc_init1。在这个函数里面会相继的初始化CPU、中断控制器、ISA总线,然后就要判断是否需要支持PCI。如果支持则调用i440fx_init初始化PCI总线。

static void pc_init1(MachineState *machine,
                     const char *host_type, const char *pci_type)
{

if (pcmc->pci_enabled) {
    pci_bus = i440fx_init(host_type,
                          pci_type,
                          &i440fx_state, &piix3_devfn, &isa_bus, pcms->gsi,
                          system_memory, system_io, machine->ram_size,
                          pcms->below_4g_mem_size,
                          pcms->above_4g_mem_size,
                          pci_memory, ram_memory);
    pcms->bus = pci_bus;
} else {
    pci_bus = NULL;
    i440fx_state = NULL;
    isa_bus = isa_bus_new(NULL, get_system_memory(), system_io,
                          &error_abort);
    no_hpet = 1;
}

}

i440fx_init函数主要参数就是之前初始化好的ISA总线以及中断控制器,返回值就是PCI总线,之后我们就可以将设备统统挂载在这个上面。

QEMU的PCI-PCI桥

在QEMU中,所有的设备包括总线,桥,一般设备都对应一个设备结构,通过register函数将所有的设备链接起来,就像Linux的模块一样,在QEMU启动的时候会初始化所有的QEMU设备,而对于PCI设备来说,QEMU在初始化以后还会进行一次RESET,将所有的PCI bar上的地址清空,然后进行统一分配。

QEMU(x86)里面的PCI的默认PCI设都是挂载主总线上的,貌似没有看到PCI-PCI桥,而桥的作用一般也就是连接两个总线,然后进行终端和IO的映射。

QEMU的PCI设备

一般的PCI设备其实和桥很像,甚至更简单,关键区分桥和一般设备的地方就是class属性和bar地址。

struct PCIDevice表示了PCI设备的信息。

pci_register_bat主要给bar分配IO地址。

void pci_register_bar(PCIDevice *pci_dev, int region_num,
                      uint8_t type, MemoryRegion *memory)
{
    PCIIORegion *r;
    uint32_t addr; /* offset in pci config space */
    uint64_t wmask;
    pcibus_t size = memory_region_size(memory);

assert(region_num >= 0);
    assert(region_num < PCI_NUM_REGIONS);
    if (size & (size-1)) {
        fprintf(stderr, "ERROR: PCI region size must be pow2 "
                    "type=0x%x, size=0x%"FMT_PCIBUS"\n", type, size);
        exit(1);
    }

r = &pci_dev->io_regions[region_num];
    r->addr = PCI_BAR_UNMAPPED;
    r->size = size;
    r->type = type;
    r->memory = memory;
    r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
                        ? pci_dev->bus->address_space_io
                        : pci_dev->bus->address_space_mem;

wmask = ~(size - 1);
    if (region_num == PCI_ROM_SLOT) {
        /* ROM enable bit is writable */
        wmask |= PCI_ROM_ADDRESS_ENABLE;
    }

addr = pci_bar(pci_dev, region_num);
    pci_set_long(pci_dev->config + addr, type);

if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) &&
        r->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
        pci_set_quad(pci_dev->wmask + addr, wmask);
        pci_set_quad(pci_dev->cmask + addr, ~0ULL);
    } else {
        pci_set_long(pci_dev->wmask + addr, wmask & 0xffffffff);
        pci_set_long(pci_dev->cmask + addr, 0xffffffff);
    }
}

KVM虚拟机代码揭秘——QEMU的PCI总线与设备(下)

代码对不上,略过。

关于Linux虚拟化技术KVM的科普 科普二(KVM虚拟机代码揭秘)的更多相关文章

  1. 关于Linux虚拟化技术KVM的科普

    虚拟化技术应用越来越广泛,虚拟化技术需求越来越强劲.KVM.XEN.Docker等比较热门,尤其是KVM技术越来越受欢迎. 基于此背景,了解一下KVM+QEMU就有点必要了. 从网上收集了一些资料进行 ...

  2. 关于Linux虚拟化技术KVM的科普 科普一(先用起来!)

    是骡子是马是拉出来溜溜,通过<KVM虚拟化技术之使用Qemu-kvm创建和管理虚拟机>跑一遍,就会对KVM.QEMU-KVM有个大概的认识了. qemu-kvm已经不单独存在,qemu加上 ...

  3. Linux虚拟化技术KVM、QEMU与libvirt的关系(转)

    说明:个人理解,KVM是内核虚拟化技术,而内核是不能使用在界面上使用的,那么此时QEMU提供了用户级别的使用界面,相互辅助.当然,单独使用QEMU也是可以实现一整套虚拟机,不过QEMU+KVM基本是标 ...

  4. 关于Linux虚拟化技术KVM的科普 科普三(From OenHan)

    http://oenhan.com/archives,包括<KVM源代码分析1:基本工作原理>.<KVM源代码分析2:虚拟机的创建与运行>.<KVM源代码分析3:CPU虚 ...

  5. 关于Linux虚拟化技术KVM的科普 科普四(From humjb_1983)

    另一组关于KVM的分析文档,虚拟化相关概念.KVM基本原理和架构一-概念和术语.KVM基本原理和架构二-基本原理.KVM基本原理及架构三-CPU虚拟化.KVM基本原理及架构四-内存虚拟化.KVM基本原 ...

  6. 【原创】Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

  7. 虚拟化技术学习(一)在VMware虚拟机中安装KVM

    近期一直研究虚拟化技术,曾经对VMware虚拟机有一定的了解,近期突发奇想,能不能在VMware虚拟机中再装一个虚拟机呢? 那么问题就来了,首先,你须要一台电脑,vmware软件,(本人的电脑配置渣渣 ...

  8. 主流服务器虚拟化技术简单使用——Hyper-V(二)

    当在多台Windows Server上部署了hyper-v的时候,需要采用合适的方法管理这些hyper-v节点. 远程桌面 最简单的方法就是逐台远程桌面登陆Windows Server,再使用每台本地 ...

  9. 关于Linux虚拟化技术KVM的科普 科普五(From 世民谈云计算)

    另一位大神写到KVM文章,KVM 介绍(1):简介及安装.KVM 介绍(2):CPU 和内存虚拟化.KVM 介绍(3):I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtual ...

随机推荐

  1. 《UNIX网络编程 卷1》之"学习环境搭建"(CentOS 7)

    <UNIX网络编程 卷1>的源码可以从www.unpbook.com下载得到.解压之后的目录为unpv13e. 详细步骤 编译 进入unpv13e目录,按如下步骤编译: ./configu ...

  2. Android实现RecyclerView侧滑删除和长按拖拽-ItemTouchHelper

    RecyclerView这个被誉为ListView和GirdView的替代品,它的用法在之前的一篇博文中就已经讲过了,今天我们就来实现RecyclerView的侧滑删除和长按拖拽功能,实现这两个功能我 ...

  3. LDA主题模型

    (一)LDA作用 传统判断两个文档相似性的方法是通过查看两个文档共同出现的单词的多少,如TF-IDF等,这种方法没有考虑到文字背后的语义关联,可能在两个文档共同出现的单词很少甚至没有,但两个文档是相似 ...

  4. iOS 10正式发布:十大新功能,更注重人性化

    6月14日凌晨消息,苹果公司举行2016年WWDC全球开发者大会,介绍了watch OS.tv OS.OS X以及iOS 10系统的新特性. 据苹果介绍,iOS 10在锁屏.Siri.地图等十个各方面 ...

  5. LeetCode之“动态规划”:Maximal Square && Largest Rectangle in Histogram && Maximal Rectangle

    1. Maximal Square 题目链接 题目要求: Given a 2D binary matrix filled with 0's and 1's, find the largest squa ...

  6. PS 图像调整算法——反相

    这个顾名思义,对图像做减法. Image_new=1-Image_old; 原图: 反相:

  7. objective-c中类似ruby枚举类的实例方法

    虽然obj-c的语法格式略显繁琐,但它和ruby都从某些方面继承了smalltalk的某些动态的东西.这些东西是 C和C++之类的静态语言所不曾有的. 比如ruby中可以将一个类或对象的所有方法枚举出 ...

  8. 创建Sencha touch第一个应用

    最近学习Sencha touch ,是一个菜鸟级别.废话不多说,让我们来创建Sencha touch的第一应用. 首先,我们下载Sencha touch2.0 sdk 和SDK工具.  SDK工具直接 ...

  9. 修改win7系统sid

    百度百科定义: Windows使用SID来表示所有的安全对象(security principals).安全对象包括主机,域计算机账户,用户和安全组.名字Name是用来代表SID的一个方法,可以允许用 ...

  10. 排序算法入门之堆排序(Java实现)

    堆排序 在学习了二叉堆(优先队列)以后,我们来看看堆排序.堆排序总的运行时间为O(NlonN). 堆的概念 堆是以数组作为存储结构. 可以看出,它们满足以下规律: 设当前元素在数组中以R[i]表示,那 ...