Qemu-KVM Guest OS Time Tick Source (1)

一个操作系统要跑起来,必须有time tick,它就像是身体的脉搏。普通情况下,OS time tick由PIT(i8254)或APIC Timer设备提供—PIT定期(1ms in Linux)产生一个timer interrupt,作为global tick,APIC Timer产生一个local tick。在虚拟化情况下,必须为guest OS模拟一个PIT和APIC Timer。

模拟的PIT和APIC Timer不能像真正硬件那样物理计时,所以一般用host的某种系统服务或软件计时器来为这个模拟PIT提供模拟"时钟源"。

在QEMU中,用SIGALARM信号来实现—QEMU利用某种机制,使timer interrupt handler会向QEMU process发送一个SIGALARM信号,处理该信号过程中再模拟PIT中产生一次时钟。QEMU再通过某种机制,将此模拟PIT发出的模拟中断交付给kvm,再由kvm注入到虚拟机中去。

目前的kvm版本支持内核PIT、APIC和内核PIC,因为这两个设备是频繁使用的,在内核模式中模拟比在用户模式模拟性能更高。这里重点是讲内核PIT的模拟实现,弄清楚它是如何为guest OS提供时钟的。

首先,创建内核PIT的路径如下所示,由qemu-kvm在创建虚拟机时向内核驱动请求创建内核模拟PIT,内核驱动根据请求创建PIT。通过hrtimer_init调用可以看出,这个内核模拟PIT的时钟实际上是由host Linux的hrtimer来提供的,hrtimer_init还只是初始化一个hrtimer timer,还没有开始计时。kvm_iodevice_init & kvm_io_bus_register_dev调用的作用是注册此内核PIT,到时候在VMExit时模拟处理guest io instructions,如果是in/out PIT的端口,那么就可以直接在内核中完成了,不需要返回到用户空间(kernel_pio函数会遍历所有注册的内核模拟设备,如果有端口匹配,则说明由内核模拟此设备)。

qemu-kvm:

main -> kvm_create_context -> kvm_create(qemu-kvm.c) -> kvm_arch_create(qemu-kvm-x86.c) -> kvm_create_pit(q-k-x86.c) -> kvm_vm_ioctl(KVM_CREATE_PIT)

kvm-mod:

kvm_arch_vm_ioctl(x86.c) -> case KVM_CREATE_PIT: kvm_create_pit(i8254.c) -> hrtimer_init_p & kvm_iodevice_init & kvm_io_bus_register_dev

其次,开始设置内核PIT使其开始计时的路径如下所示,其中kvm_pit_post_load实际上不是kvm_pit_init调用的,只是有一句"vmstate_pit.post_load = kvm_pit_post_load",具体它是什么时候调用的还不太清楚。

qemu-kvm:

pc_init1(hw/pc.c) -> kvm_pit_init(hw/i8254-kvm.c) ---> kvm_pit_post_load -> kvm_set_pit -> ioctl(KVM_SET_PIT)

kvm-mod:

kvm_arch_vm_ioctl(x86.c) -> case KVM_SET_PIT -> kvm_vm_ioctl_set_pit(x86.c) -> kvm_pit_load_count(i8254.c) -> pit_load_count(i8254.c) -> create_pit_timer(i8254.c) -> hrtimer_start_p

接着,来看内核PIT是如何利用这个hrtimer实际时钟源来提供虚拟时钟的。hrtimer具体如何实现不知道,但是它应该是用物理的timer interrupt来实现的(见Hrtimer机制一文)。当hrtimer计数器到时,内核会回调在hrtimer_start时注册的回调函数——在create_pit_timer函数中有一句"pt->timer.function = kvm_timer_fn"。因此,这个 kvm_timer_fn函数就是PIT hrtimer的回调函数。kvm_timer_fn又调用__kvm_timer_fn。__kvm_timer_fn中有一句"set_bit(KVM_REQ_PENDING_TIMER,&vcpu_request)"。那么,kvm只要检测这个bit就可以知道是否有时钟中断了。不过,kvm好像并没有检测这个bit,而是去检测pit_timer.pending。因为在__kvm_timer_fn中也有一句"atomic_inc(&ktimer->pending)",而这个ktimer地址记录在pit->pit_state结构中,pit结构地址由记录在kvm结构中。另一方面,在 __vcpu_run中,在 vcpu_enter_guest返回后,有一句" kvm_cpu_has_pending_timer(vcpu)"实际上就是检测vcpu->kvm->arch.vpit->pit_state.pit_timer.pending。
     综合以上机制介绍,来想像一个场景,就可以理解物理pit timer interrupt是如何经为虚拟pit提供时钟,进而产生虚拟timer interrupt提交给guest OS。假如:guest vcpu正在执行,来了一个时钟物理中断,而这个物理时钟中断也要传递给guest OS,过程如下:

1. 物理中断导致VMExit,guestvcpu线程返回kvm_x86_ops->run,接着vcpu_enter_guest开中断,host timer interrupt handler处理这个物理中断。

2.timer interrupt handler发现内核PIT使用的hrtimer到时,调用kvm_pit_fun回调函数,将pit_state.pit_timer.pending置位

3.timer interrupt handler返回,vcpu_enter_guest调用kvm_x86_ops->handle_exit处理VMExit,发现exit_reason是外部中断,返回1,vcpu_enter_guest传递返回值回到__vcpu_run。

4.__vcpu_run接着调用kvm_cpu_has_pending_timer检测内核PIT的timer是否pending。现在将检测到是,那么调用kvm_inject_pending_timer_irqs将虚拟时钟中断注入到内核模拟的LAPIC中,产生一个中断触发。

5.中断注入机制之前一篇笔记介绍过,将vmcs相应位按规范设置好即可。由于vcpu_enter_guest返回1,所以不需要切换到用户模式,而是再次循环调用vcpu_enter_guest。

6.在kvm_x86_ops->run之前,调用inject_pending_event,它会将LAPIC中触发的中断真正地注入到vcpu中(通过调用kvm_x86_ops->set_irq)。再次VMEntry时,硬件检测到虚拟时钟中断,然后调用guest OS timer interrupt handler进行处理。

至于内核APIC Timer的模拟实现,与内核PIT的原理是差不多的。同样用hrtimer_init和hrtimer_start初始化和启动一个hrtimer,然后将其对应的kvm_timer结构的pending位置1,在kvm_cpu_has_pending_timer中同样会检测内核APIC Timer的pending。kvm_inject_pending_timer_irqs也同样可以注入对应的irq。

最后一个问题是,在调用hrtimer_start之前,要设定hrtimer的过期时间,每次的过期时间减去当前时间now就是PIT和APIC Timer的时钟频率。按道理,这个时钟频率应该是由guest OS来设定的。那么来看两个时钟周期的设定是怎样的:

对内核PIT来讲,应该是kvm_vm_ioctl_set_pit的val参数来确定它的周期的,这个val好像是由用户态的qemu-kvm传递进来的,所以具体在哪设置还没搞清楚。

对内核APIC Timer来讲,是在start_apic_timer(lapic.c)中设定。注意有一个语句"apic->lapic_timer.period = apic_get_reg(apic,APIC_TMICT)",周期应该就是从模拟APIC的某个寄存器中读取。那么,首先是guest OS设定周期到模拟APIC的TMICT寄存器中,kvm再读出来以此来设定hrtimer的到期时间,就可以精确地模拟这个APIC Timer的时钟行为了。

A timer interrupt would send a SIGALRM to qemu, which would > eventually cause KVM to emulate a timer interrupt and execute the > guest handler in this case

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值