# 纯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](../00-vcpu/02-x86_cpu)

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

```
x86_cpu_realizefn
    x86_cpu_apic_create
        apic_common_initfn
        set apic->id with cpu->apic_id
    x86_cpu_apic_realize
        object_property_set_bool(cpu->apic_state, realized)
            apic_common_realize
                apic_realize
          // only one memory region installed
        memory_region_add_subregion_overlap(&apic->io_memory)
```

## 实现

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

先来看看APICCommonState这个数据结构

```
    APICCommonState
    +-----------------------------+       
    |io_memory                    |
    |    ops                      | = apic_io_ops
    |                             |                                                                            
    |apicbase                     | = APIC_DEFAULT_ADDRESS
    |                             |
    |isr/irr/tmr                  |
    |lvt                          |
    |sipi_vector                  |
    |                             |
    |cpu                          |
    |    (X86CPU *)               |
    |    +------------------------+
    |    |interrupt_request       |
    |    |                        |
    +----+------------------------+
```

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

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

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

## 发送中断

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

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

对应的在代码中，写APIC最后要走到apic\_mem\_write中0x30选项。

```
case 0x30:
    s->icr[0] = val;
    apic_deliver(dev, (s->icr[1] >> 24) & 0xff, (s->icr[0] >> 11) & 1,
                 (s->icr[0] >> 8) & 7, (s->icr[0] & 0xff),
                 (s->icr[0] >> 15) & 1);
```

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

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

```
apic_mem_write
    apic_send_msi()
        apic_deliver_irq
            apic_bus_deliver
    apic_deliver
        apic_startup
            cpu_interrupt = generic_handle_interrupt
                qemu_cpu_kick_thread
                    pthread_kill(cpu->thread->thread, SIG_IPI)
        apic_bus_deliver
            cpu_interrupt
              apic_set_irq
                apic_update_irq
                    cpu_interrupt
```

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

## 处理中断

处理中断和vcpu thread有很大关系。这里我们只看没有kvm介入时tcg的情况 qemu\_tcg\_rr\_cpu\_thread\_fn。

```
tcg_cpu_exec
    cpu_exec
        cpu_handle_interrupt
            cpu_handle_exception()
                cc->do_interrupt = x86_cpu_do_interrupt
                    do_interrupt_all
                        do_interrupt_protected
            cpu_handle_interrupt()
                cc->cpu_exec_interrupt = x86_cpu_exec_interrupt
```

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

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

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

* 中断处理是在vcpu thread处理的间歇执行的，而并没有强制打断vcpu thread的正常执行。所以这个中断将会有较大的延时。
* 如果持续有中断好像会一直处理中断，所以中断太过频繁，也会导致系统无法继续。

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