纯Qemu模拟

虽然说纯软件模拟的方式在大部分的情况下已经被替代了,但是对软件模拟方式的理解能够加深对虚拟化的理解,并且对软硬件进化的过程有个概念。

继承关系

    TYPE_OBJECT
    +-----------------------------+
    |class_init                   | = object_class_init
    |                             |
    |instance_size                | = sizeof(Object)
    +-----------------------------+


    TYPE_DEVICE
    +-----------------------------+
    |class_size                   | = sizeof(DeviceClass)
    |class_init                   | = device_class_init
    |                             |
    |instance_size                | = sizeof(Object)
    |instance_init                | = device_initfn
    |instance_finalize            | = device_finalize
    |                             |
    |realize                      | = apic_common_realize
    +-----------------------------+

    APICCommonClass TYPE_APIC_COMMON "apic-common"
    +-----------------------------+
    |class_size                   | = sizeof(APICCommonClass)
    |class_init                   | = apic_common_class_init   
    |                             |
    |instance_size                | = sizeof(APICCommonState)
    |instance_init                | = apic_common_initfn       
    |                             |
    +-----------------------------+

    APICCommonClass TYPE_APIC "apic"                  
    +-----------------------------+                   
    |class_init                   | = apic_class_init
    |                             |                   
    |instance_size                | = sizeof(APICCommonState)
    |                             |                   
    |realize                      | = apic_realize
    +-----------------------------+

这个继承层次也挺长的了,不过还好,没有cpu那个长。而且总的来说也还算蛮清晰的。

初始化

APIC的初始化和CPU还是有很大关联的,因为在硬件上他们两个人就是在一起的。所以qemu中APIC的创建也是随着CPU一起创建的。

为了说明问题,我们还是以x86 cpu为例。这个创建的过程就在x86_cpu_realizefn函数里。相关内容参见x86 cpu

接着我们就打开这个函数,看看究竟这个是怎么玩的。

实现

了解了初始化的流程,接下来我们看看APIC是怎么模拟的。

先来看看APICCommonState这个数据结构

首先是这个io_memory,这个就是在初始化时注册的内存空间。有意思的是不管有多少cpu,这个空间只有一个。而其中关键的就是对应的apic_io_ops了,具体的模拟手段都隐藏在这个操作中。

其次是apicbase,这就是系统默认的APIC访问的地址了。这没啥说的,就是按照手册来。

剩下的就是APIC的一些寄存器了,每次读写都会对这些寄存器访问。

发送中断

那现在我们就来看看对发送中断的模拟。

我们以IPI为例,在SDM中10.6小节描述了如何发送IPI。简单来说就是通过写ICR(Interrupt Command Register)来达到目的。

对应的在代码中,写APIC最后要走到apic_mem_write中0x30选项。

你看,是不是和手册上说的一样。写入时设置了ICR?

实际上msi的中断也是在apic_mem_write中执行的。貌似这两段内存空间是重合的?没搞懂,不过代码注释好像是这么写的。

从这里看,所有的路径基本都会走到cpu_interrupt函数。而这个函数的工作是设置s->interrupt_request并发送一个信号给vcpu thread。

处理中断

处理中断和vcpu thread有很大关系。这里我们只看没有kvm介入时tcg的情况 qemu_tcg_rr_cpu_thread_fn。

当vcpu thread起来后,每个周期中会去检测有没有异常和中断。当检测到有的时候,则模拟中断的处理。

这个代码可是老复杂了,感觉能写这个代码的人简直是神。

不过从上面的分析中可以看出,软件模拟的情况下中断处理会有两个问题:

  • 中断处理是在vcpu thread处理的间歇执行的,而并没有强制打断vcpu thread的正常执行。所以这个中断将会有较大的延时。

  • 如果持续有中断好像会一直处理中断,所以中断太过频繁,也会导致系统无法继续。

以上是对用户态软件模拟的LAPIC的理解,希望是正确的。

Last updated

Was this helpful?