Skip to the content.

中断是如何产生的

具体分析 kbd_update_irq_lines 的内容:

/* XXX: not generating the irqs if KBD_MODE_DISABLE_KBD is set may be
   incorrect, but it avoids having to simulate exact delays */
static void kbd_update_irq_lines(KBDState *s)
{
    int irq_kbd_level, irq_mouse_level;

    irq_kbd_level = 0;
    irq_mouse_level = 0;

    if (s->status & KBD_STAT_OBF) {
        if (s->status & KBD_STAT_MOUSE_OBF) {
            if (s->mode & KBD_MODE_MOUSE_INT) {
                irq_mouse_level = 1;
            }
        } else {
            if ((s->mode & KBD_MODE_KBD_INT) &&
                !(s->mode & KBD_MODE_DISABLE_KBD)) {
                irq_kbd_level = 1;
            }
        }
    }
    qemu_set_irq(s->irq_kbd, irq_kbd_level);
    qemu_set_irq(s->irq_mouse, irq_mouse_level);
}

总而言之,是通过 interrupt 模块提供给设备模拟模块的标准函数,例如 qemu_irq_raise 和 qemu_set_irq

中断控制器的具体模拟

相关文件

QEMU 可以同时支持 kvm 模拟和用户态模拟这些 intc, 所以自然可以拆分为不同的模块,这体现在相关的源文件上。

TypeInfo 是 QEMU Object Model 中抽象出来的概念,简单来说一个 non abstract Class 了。

static const TypeInfo apic_info = {
    .name          = TYPE_APIC,
    .instance_size = sizeof(APICCommonState),
    .parent        = TYPE_APIC_COMMON,
    .class_init    = apic_class_init,
};

static const TypeInfo kvm_apic_info = {
    .name = "kvm-apic",
    .parent = TYPE_APIC_COMMON,
    .instance_size = sizeof(APICCommonState),
    .class_init = kvm_apic_class_init,
};
static const TypeInfo ioapic_info = {
    .name          = TYPE_IOAPIC,
    .parent        = TYPE_IOAPIC_COMMON,
    .instance_size = sizeof(IOAPICCommonState),
    .class_init    = ioapic_class_init,
};

static const TypeInfo kvm_ioapic_info = {
    .name  = TYPE_KVM_IOAPIC,
    .parent = TYPE_IOAPIC_COMMON,
    .instance_size = sizeof(KVMIOAPICState),
    .class_init = kvm_ioapic_class_init,
};
static const TypeInfo i8259_info = {
    .name       = TYPE_I8259,
    .instance_size = sizeof(PICCommonState),
    .parent     = TYPE_PIC_COMMON,
    .class_init = i8259_class_init,
    .class_size = sizeof(PICClass),
};

static const TypeInfo kvm_i8259_info = {
    .name = TYPE_KVM_I8259,
    .parent = TYPE_PIC_COMMON,
    .instance_size = sizeof(PICCommonState),
    .class_init = kvm_i8259_class_init,
    .class_size = sizeof(KVMPICClass),
};

从上面的看到,对于一个 intc QEMU 总是定义两种解决方案,可以让 QEMU 在用户态模拟或者是让 kvm 在内核模拟。

使用 qemu 还是内核的中断控制器

man qemu

              kernel-irqchip=on|off|split
                     Controls KVM in-kernel irqchip support. The default is
                     full  acceleration  of  the  interrupt controllers. On
                     x86, split irqchip reduces the kernel attack  surface,
                     at  a  performance  cost  for non-MSI interrupts. Dis‐
                     abling the in-kernel irqchip completely is not  recom‐
                     mended except for debugging purposes.

QEMU 一共支持三种模式,分别 off / split / on

hw/intc/ioapic.c 会出现 kvm_irqchip_is_split 是因为 ioapic 在用户态模拟,但是需要将消息注入到 kernel 中。

观察上面的 TypeInfo 还有一个比较有意思的地方在于,其 instance_size 总是等于 sizeof(CommonState),这说明 QEMU 模拟和内核模拟使用结构体是相同的,实际上,对于 kvm 而言,因为 irqchip 是在内核模拟的,其作用在于临时保存一下内核中的数据(使用 kvm_get_apic_state / kvm_put_apic_state) 从而实现虚拟机的迁移

第一步: 初始化 KVMState 的成员

第二个,使用 KVMState 的成员初始化全局变量 kvm_kernel_irqchip_split 和 kvm_split_irqchip

第三部: 利用全局变量 kvm_kernel_irqchip_split 和 kvm_split_irqchip 构建的 macro 来决定具体初始化哪一个 TypeInfo

全局变量 kvm_kernel_irqchip_split 和 kvm_split_irqchip 构建的 macro 构建的 macro 就是长成这个样子了

#define kvm_irqchip_in_kernel() (kvm_kernel_irqchip)
#define kvm_irqchip_is_split() (kvm_split_irqchip)
#define kvm_apic_in_kernel() (kvm_irqchip_in_kernel())

#define kvm_pit_in_kernel() (kvm_irqchip_in_kernel() && !kvm_irqchip_is_split())
#define kvm_pic_in_kernel() (kvm_irqchip_in_kernel() && !kvm_irqchip_is_split())
#define kvm_ioapic_in_kernel() (kvm_irqchip_in_kernel() && !kvm_irqchip_is_split())

中断控制器在内核中

  1. pc_gsi_create : 创建了 qemu_irq,分配了 GSIState , 但是 GSIState 没有被初始化
  1. apic_common_realize
    • x86_cpu_new
    • qdev_realize
      • x86_cpu_realizefn
        • x86_cpu_apic_realize
          • qdev_realize
            • apic_common_realize
              • kvm_apic_realize
                • memory_region_init_io(&s->io_memory, OBJECT(s), &kvm_apic_io_ops, s, “kvm-apic-msi”, APIC_SPACE_SIZE);
              • apic_realize
                • memory_region_init_io(&s->io_memory, OBJECT(s), &apic_io_ops, s, “apic-msi”, APIC_SPACE_SIZE);
                • timer_new_ns(QEMU_CLOCK_VIRTUAL, apic_timer, s);
  2. pc_i8259_create(isa_bus, gsi_state->i8259_irq);
  1. ioapic_init_gsi(gsi_state, "i440fx");

总结一下,在 tcg 模式下,gsi_handler 对于中断号小于 16 的会分别调用 pic 和 iopaic 的 hook,

lapic 因为是每一个 CPU 需要的,所以其初始化在 x86_cpu_realizefn 中进行的。下面分析 pic 和 ioapic 的中断如何送到 lapic 的。

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。