5. 纯模拟

硬件中断发生(qemu模拟设备) 1.硬件产生中断的接口 void qemu_set_irq(qemu_irq irq, int level);

2.中断过程 void qemu_set_irq(qemu_irq irq, int level) { if (!irq) return;

irq->handler(irq->opaque, irq->n, level);

} 设置中断控制器hander,大致分为三种情况 1.cpu_irq的hander===> pic_irq_request 2.内核模拟中断控制器的hander===>kvm_i8259_set_irq 3.用户模拟中断控制器的hander===>i8259_set_irq

/* PC hardware initialisation */ static void pc_init1() { cpu_irq = qemu_allocate_irqs(pic_irq_request, NULL, 1); #ifdef KVM_CAP_IRQCHIP if (kvm_enabled() && kvm_irqchip_in_kernel()) { isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state)); isa_irq = i8259 = kvm_i8259_init(cpu_irq[0]); } else #endif { i8259 = i8259_init(cpu_irq[0]); isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state)); isa_irq_state->i8259 = i8259; isa_irq = qemu_allocate_irqs(isa_irq_handler, isa_irq_state, 24); } 先研究用户空间中断控制器的中断发生过程 static void i8259_set_irq(void *opaque, int irq, int level) { PicState2 s = opaque; pic_set_irq1(&s->pics[irq >> 3], irq & 7, level); pic_update_irq(s); } 中断触发方式分为电平触发和边沿触发,isa设备大多数采用边沿触发,pci设备采用电平触发。 假如采用边沿触发,如果leveld等于1,并且没有等待的中断请求(没有pending中断请求),设置中断请求寄存器为1,另外设置pending中断请求为1. 如果有pending中断请求,并不设置中断请求寄存器,可见允许中断丢失。 / set irq level. If an edge is detected, then the IRR is set to 1 */ static inline void pic_set_irq1(PicState s, int irq, int level) { int mask; mask = 1 << irq; if (s->elcr & mask) { / level triggered / if (level) { s->irr |= mask; s->last_irr |= mask; } else { s->irr &= ~mask; s->last_irr &= ~mask; } } else { / edge triggered / if (level) { if ((s->last_irr & mask) == 0) s->irr |= mask; s->last_irr |= mask; } else { s->last_irr &= ~mask; } } } 每次有中断请求,必须调用该函数。该函数调用造成中断嵌套。另外必须话必须注入中断。什么情况下是必须呢?具体可参照pic_get_irq()函数。 这个函数对产生中断优先级和正在处理中断优先级进行比较,如果大于话,注入请求中断。注入中断时机由qemu_irq_raise触发的,下面列出该函数。 / raise irq to CPU if necessary. must be called every time the active irq may change */ void pic_update_irq(PicState2 s) { / look at requested irq */ irq = pic_get_irq(&s->pics[0]); if (irq >= 0) { qemu_irq_raise(s->parent_irq); } } 不要认为,好像又循环到中断入口了,实际没有,关键在于参数s->parent_irq,该参数实际调用cpu_irq的hander===> pic_irq_request static inline void qemu_irq_raise(qemu_irq irq) {
qemu_set_irq(irq, 1); }
目前只研究用户态模拟中断控制器i8259(剔除KVM模拟和apic中断控制器), cpu_interrupt函数实际中断目前虚拟处理器运行,为硬件中断注入做好准备,目前就是中断注入时机。如何中断(暂停)虚拟处理器运行呢,通过该函数pthread_kill(env->kvm_cpu_state.thread, SIG_IPI)中断处理器运行;
static void pic_irq_request(void *opaque, int irq, int level) { CPUState *env = first_cpu; if (level) cpu_interrupt(env, CPU_INTERRUPT_HARD); else cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); } }

中断注入 中断注入负责将虚拟中断控制器采集的中断请求注入到虚拟处理器。需要处理两个问题,什么时候注入,如何注入? static int kvm_main_loop_cpu(CPUState *env) { while (1) { int run_cpu = !is_cpu_stopped(env); if (run_cpu && !kvm_irqchip_in_kernel()) { process_irqchip_events(env); run_cpu = !env->halted; } if (run_cpu) { kvm_cpu_exec(env); kvm_main_loop_wait(env, 0); } else { kvm_main_loop_wait(env, 1000); } } pthread_mutex_unlock(&qemu_mutex); return 0; } 如果中断控制器不是内核空间模拟(用户空间模拟),进行中断注入。

kvm_main_loop_cpu-->kvm_cpu_exec-->kvm_run
int kvm_run(CPUState *env)
{

#if !defined(__s390__)
    if (!kvm->irqchip_in_kernel)
        run->request_interrupt_window = kvm_arch_try_push_interrupts(env);
#endif
}

1.首先满足三个条件 1)内核kvm准备好了接受中断注入 2)有中断请求并且为硬件中断请求 3)虚拟处理器运行中断(开中断) 2.获取中断请求号 3.kvm注入中断请求

int kvm_arch_try_push_interrupts(void *opaque)
{
    CPUState *env = cpu_single_env;
    int r, irq;

    if (kvm_is_ready_for_interrupt_injection(env) && 
        (env->interrupt_request & CPU_INTERRUPT_HARD) &&
        (env->eflags & IF_MASK)) { 
            env->interrupt_request &= ~CPU_INTERRUPT_HARD;
            irq = cpu_get_pic_interrupt(env);
            if (irq >= 0) { 
                r = kvm_inject_irq(env, irq);
                if (r < 0)
                    printf("cpu %d fail inject %x\n", env->cpu_index, irq);
            }
    }
        
    return (env->interrupt_request & CPU_INTERRUPT_HARD) != 0;
}

你可能感兴趣的:(5. 纯模拟)