【firecracker】时钟与中断
转载 吴斌的博客 » 【firecracker】时钟与中断
firecracker虚拟机的时钟与中断系统完全是由KVM模块和硬件实现的,这里仅简要说明其原理,更深入的分析需要结合KVM代码和内核代码进行。
时钟系统原理
时钟系统包含时间源和时钟事件源两部分:
- 时间源类似生活中的手表,系统通过它可以获知时间,其本质为一个递增的计数器,计数器数值代表当前时间;firecracker中的时钟源有KVM_CLOCK和TSC两种,默认使用KVM_CLOCK;
- 时种 事件源类似生活中的闹钟,系统通过它可以获得时间到期通知事件,其本质为一个递减的计数器,且当计数器清零时会产生中断通知;firecracker中的时间事件源有PIT和LAPIC_TIMER两种,系统启动过程中使用PIT,一旦系统启动完成后会切换到LAPIC_TIMER。
中断系统原理
X86系统在单核时代是通过两片级联的8259A芯片实现可编程中断控制器,其原理如下图所示。KVM内核也实现了对8259A的模拟,SMP系统在启动初期是采用8259A作为中断控制器。
到了SMP多核时代,X86系统采用LAPIC+IOAPIC作为中断控制器系统:每个CPU核都有一个LAPIC,作为本地中断控制器;主板上有一个或多个IOAPIC,外设通过IOAPIC广播中断消息给LAPIC,并由各个LAPIC判断是否需要接收并处理该中断消息,如下图蓝色线条所示。SMP在启动过程中通过MP_Table获知了系统中断控制器信息,之后便可以由8259A切换到LAPIC+IOAPIC。
虽然中断控制器是由KVM模块实现的,但是外设是由用户态VMM程序模拟的,因此KVM模块需要给用户态程序提供触发中断的系统调用接口,即irqfd。它的大致用法如下:
// initializing irqfd for irq(0) let device_fd = EventFd::new().unwrap(); // 创建一个新的EventFd句柄 Vm.register_irqfd(device_fd.as_raw_fd(), 0); // 通知KVM模块将该句柄与irq进行关联,这里以0号irq(即时钟中断)为例 // trigger interrupt for irq(0) device_fd.wirte(1); // 通过写EventFd句柄触发一次0号中断,KVM内核会将0号中断通过IOAPIC // 路由到对应的LAPIC,并向vCPU注入中断
时钟与中断初始化流程setup_interrupt_controller
firecracker/vmm/src/lib.rs: struct Vmm { … } impl Vmm { fn setup_interrupt_controller(&mut self) -> std::result::Result<(), StartMicrovmError> { self.vm .setup_irqchip() .map_err(StartMicrovmError::ConfigureVm) } } firecracker/vmm/src/vstate.rs: struct Vm { … } impl Vm { pub fn setup_irqchip(&self) -> Result<()> { self.fd.create_irq_chip().map_err(Error::VmSetup)?; // 通知KVM内核模块创建8259A及IOPAIC+LAPIC中断控制器 let mut pit_config = kvm_pit_config::default(); pit_config.flags = KVM_PIT_SPEAKER_DUMMY; self.fd.create_pit2(pit_config).map_err(Error::VmSetup) // 通知KVM创建PIT时钟事件源 } }