转自:http://zhangjun2915.blog.163.com/blog/static/38086236201052791356642/
响应延时的实时测试需要读TSC,但是这两天做实验发现一个很奇怪的问题,用VxWorks作为guest时,对于类似“tsc1=rdtsc; nanosleep(100);tsc2=rdtsc;” 的程序,tsc2却总是比tsc1要小了。而用Linux作为guest时,对于类似的程序,是没有问题的。那么,是否是kvm中对于TSC的处理有问题呢?带着这个问题,我把kvm-kmod-2.6.33.1中的TSC处理机制研究了一下。 首先要提到影响guest rdtsc指令结果的因素。在vmcs中有两个很重要的字段:RDTSC_EXITING和TSC_OFFSETTING两个控制位决定了guest RDTSC将发生什么事:
1. 当EXITING为1时,guest rdtsc将导致VM-Exit;
2. 当EXITING为0时,OFFSETTING为0时,直接guest rdtsc直接将物理TSC导入EDX:EAX;
3. 当EXITING为0时,OFFSETTING为1时,guest rdtsc读出物理TSC再加上tsc_offset再导入EDX:EAX。
在"VMCS 研究总结"一文中提到过,kvm采用第3种模式,我的疑问是为什么要tsc_offset特性,为什么不采用第二种最直接的方式。还有,读取TSC的方式其实不止RDTSC一种,还有RDMSR也可以,参数指定MSR_IA32_TSC就可以了,RDMSR就会造成VM-Exit,随后在在 handle_rdmsr中处理,进而调用guest_read_tsc,其中就可以发送它也是返回host_tsc+tsc_offset。
我个人认为,这个tsc_offset可能是用于支持live migration:VM在A机器中跑,TSC读取的是A机器CPU的,如果live migrate到机器B,机器B的TSC肯定与A不同,这时用tsc_offset就可以实现guest TSC同步,主要是避免了guest TSC在live migration后发生变小的情况,保证TSC总是单调递增的。
先抛开上述的问题,现在假定有这个tsc_offset,下一个研究问题就是,kvm在哪里及如何设置这个tsc_offset呢?
kvm 中只有三个地方会写vmcs中的TSC_OFFSET字段:vmx_vcpu_load、vxm_vcpu_setup及vxm_set_msr。其中后面两个都是通过调用guest_write_tsc来完成的。vmx_vcpu_load和vmx_vcpu_setup都是在 vmx_create_vcpu中调用的,其中vmx_vcpu_load在前。注意:vmx_vcpu_setup只会在vmx_create_vcpu调用,有几个vcpu被创建就调用几次;但是vmx_vcpu_load好像是在每次进入guest前都要调用的,是通过kvm_x86_ops->vcpu_load调用。
那么首先就来看vmx_vcpu_load中关于tsc的以下代码:首先读当前tsc,如果tsc_this小于arch.host_tsc,那么 TSC_OFFSET将加上两者之间的差。这个arch.host_tsc在第一次vmx_create_vcpu里面应该是为0的,所以第一次vcpu 刚创建时if代码应该不会执行。我想它应该是用于后面第二次调用vmx_vcpu_load时同步host_tsc和guest_tsc的。写入 arch.host_tsc是在__vcpu_clear中,现在还不太清楚这个函数的作用是什么,不过我猜想是每次退出guest模式时调用 __vcpu_clear,下次重新进入guest模式前调用vmx_vcpu_load,一般情况下肯定是tsc_this大于 arch.host_tsc,但是当有live migration情况下,就不一定了,这个地方应该就是为了live migration而加的吧!
rdtscll(tsc_this);
if (tsc_this < vcpu->arch.host_tsc) {
delta = vcpu->arch.host_tsc - tsc_this;
new_offset = vmcs_read64(TSC_OFFSET) + delta;
vmcs_write64(TSC_OFFSET, new_offset);
}
再来vmx_vcpu_setup,它紧跟着vmx_vcpu_load被调用,其中关于tsc的代码如下:首先要搞清楚arch.vm_init_tsc 是指什么?它是在kvm_arch_create_vm(x86.c)中调用rdtscll读入的,也就是说它指的是vm创建时的tsc。vcpu创建必然是在vm创建之后,所以一般情况下这里的tsc_this都要比arch.vm_init_tsc大,我想只有当live migration时才会出现比其小的情况。所以一般情况下,以下if语句不会执行。再看看guest_write_tsc调用,它将第一个参数减去第二个参数的结果写入TSC_OFFSET。因此,这里的调用会将-tsc_base写入,意味着什么呢?由于guest_tsc=host_tsc + tsc_offset = host_tsc - tsc_base = host_tsc - arch.vm_init_tsc,也就是说kvm配置是让virtual tsc从vm创建的时刻,即写入arch.vm_init_tsc时开始从0计数。
tsc_base = vmx->vcpu.kvm->arch.vm_init_tsc;
rdtscll(tsc_this);
if (tsc_this < vmx->vcpu.kvm->arch.vm_init_tsc)
tsc_base = tsc_this;
guest_write_tsc(0, tsc_base);
最后来看vmx_set_msr,关于tsc代码如下:vmx_set_msr用于处理guest对msr(Machine-Specific Register)写操作,这里guest意思是将guest TSC设置成data值。那么相当于现在开始guest TSC从data开始计数,所以设置TSC_OFFSET为(data - host_tsc),也就是说实际的guest_tsc = host_tsc + TSC_OFFSET = data。
case MSR_IA32_TSC:
rdtscll(host_tsc);
guest_write_tsc(data,host_tsc)
转自:http://zhangjun2915.blog.163.com/blog/static/38086236201052791356642/
响应延时的实时测试需要读TSC,但是这两天做实验发现一个很奇怪的问题,用VxWorks作为guest时,对于类似“tsc1=rdtsc; nanosleep(100);tsc2=rdtsc;” 的程序,tsc2却总是比tsc1要小了。而用Linux作为guest时,对于类似的程序,是没有问题的。那么,是否是kvm中对于TSC的处理有问题呢?带着这个问题,我把kvm-kmod-2.6.33.1中的TSC处理机制研究了一下。 首先要提到影响guest rdtsc指令结果的因素。在vmcs中有两个很重要的字段:RDTSC_EXITING和TSC_OFFSETTING两个控制位决定了guest RDTSC将发生什么事:
1. 当EXITING为1时,guest rdtsc将导致VM-Exit;
2. 当EXITING为0时,OFFSETTING为0时,直接guest rdtsc直接将物理TSC导入EDX:EAX;
3. 当EXITING为0时,OFFSETTING为1时,guest rdtsc读出物理TSC再加上tsc_offset再导入EDX:EAX。
在"VMCS 研究总结"一文中提到过,kvm采用第3种模式,我的疑问是为什么要tsc_offset特性,为什么不采用第二种最直接的方式。还有,读取TSC的方式其实不止RDTSC一种,还有RDMSR也可以,参数指定MSR_IA32_TSC就可以了,RDMSR就会造成VM-Exit,随后在在 handle_rdmsr中处理,进而调用guest_read_tsc,其中就可以发送它也是返回host_tsc+tsc_offset。
我个人认为,这个tsc_offset可能是用于支持live migration:VM在A机器中跑,TSC读取的是A机器CPU的,如果live migrate到机器B,机器B的TSC肯定与A不同,这时用tsc_offset就可以实现guest TSC同步,主要是避免了guest TSC在live migration后发生变小的情况,保证TSC总是单调递增的。
先抛开上述的问题,现在假定有这个tsc_offset,下一个研究问题就是,kvm在哪里及如何设置这个tsc_offset呢?
kvm 中只有三个地方会写vmcs中的TSC_OFFSET字段:vmx_vcpu_load、vxm_vcpu_setup及vxm_set_msr。其中后面两个都是通过调用guest_write_tsc来完成的。vmx_vcpu_load和vmx_vcpu_setup都是在 vmx_create_vcpu中调用的,其中vmx_vcpu_load在前。注意:vmx_vcpu_setup只会在vmx_create_vcpu调用,有几个vcpu被创建就调用几次;但是vmx_vcpu_load好像是在每次进入guest前都要调用的,是通过kvm_x86_ops->vcpu_load调用。
那么首先就来看vmx_vcpu_load中关于tsc的以下代码:首先读当前tsc,如果tsc_this小于arch.host_tsc,那么 TSC_OFFSET将加上两者之间的差。这个arch.host_tsc在第一次vmx_create_vcpu里面应该是为0的,所以第一次vcpu 刚创建时if代码应该不会执行。我想它应该是用于后面第二次调用vmx_vcpu_load时同步host_tsc和guest_tsc的。写入 arch.host_tsc是在__vcpu_clear中,现在还不太清楚这个函数的作用是什么,不过我猜想是每次退出guest模式时调用 __vcpu_clear,下次重新进入guest模式前调用vmx_vcpu_load,一般情况下肯定是tsc_this大于 arch.host_tsc,但是当有live migration情况下,就不一定了,这个地方应该就是为了live migration而加的吧!
rdtscll(tsc_this);
if (tsc_this < vcpu->arch.host_tsc) {
delta = vcpu->arch.host_tsc - tsc_this;
new_offset = vmcs_read64(TSC_OFFSET) + delta;
vmcs_write64(TSC_OFFSET, new_offset);
}
再来vmx_vcpu_setup,它紧跟着vmx_vcpu_load被调用,其中关于tsc的代码如下:首先要搞清楚arch.vm_init_tsc 是指什么?它是在kvm_arch_create_vm(x86.c)中调用rdtscll读入的,也就是说它指的是vm创建时的tsc。vcpu创建必然是在vm创建之后,所以一般情况下这里的tsc_this都要比arch.vm_init_tsc大,我想只有当live migration时才会出现比其小的情况。所以一般情况下,以下if语句不会执行。再看看guest_write_tsc调用,它将第一个参数减去第二个参数的结果写入TSC_OFFSET。因此,这里的调用会将-tsc_base写入,意味着什么呢?由于guest_tsc=host_tsc + tsc_offset = host_tsc - tsc_base = host_tsc - arch.vm_init_tsc,也就是说kvm配置是让virtual tsc从vm创建的时刻,即写入arch.vm_init_tsc时开始从0计数。
tsc_base = vmx->vcpu.kvm->arch.vm_init_tsc;
rdtscll(tsc_this);
if (tsc_this < vmx->vcpu.kvm->arch.vm_init_tsc)
tsc_base = tsc_this;
guest_write_tsc(0, tsc_base);
最后来看vmx_set_msr,关于tsc代码如下:vmx_set_msr用于处理guest对msr(Machine-Specific Register)写操作,这里guest意思是将guest TSC设置成data值。那么相当于现在开始guest TSC从data开始计数,所以设置TSC_OFFSET为(data - host_tsc),也就是说实际的guest_tsc = host_tsc + TSC_OFFSET = data。
case MSR_IA32_TSC:
rdtscll(host_tsc);
guest_write_tsc(data,host_tsc)