代码版本:linux-git v4.10.0-rc3

文章目录

1.kvm clock时钟

struct pvclock_vcpu_time_info {

u32   version;

u32   pad0;

//guest的TSC时间戳,在kvm_guest_time_update中会被更新

u64   tsc_timestamp;

//guest的墙上时间(1970年距今的绝对日期),和上者在一起更新

//system_time = kernel_ns + v->kvm->arch.kvmclock_offset

//系统启动后的时间减去VM init的时间,即guest init后到现在的时间

u64   system_time;

//KVMCLOCK时钟频率固定在1GHZ

u32   tsc_to_system_mul;

s8    tsc_shift;

u8    flags;

u8    pad[2];

} __attribute__((__packed__)); /* 32 bytes */
struct pvclock_wall_clock {

u32   version;

u32   sec;

u32   nsec;

} __attribute__((__packed__));

1)KVM clock 在guest中:

kvmclock_init负责在guest启动过程中初始化kvm clock,首先更新了两个MSR值:

#define MSR_KVM_WALL_CLOCK_NEW 0x4b564d00

#define MSR_KVM_SYSTEM_TIME_NEW 0x4b564d01

然后为每个CPU分配struct pvclock_vsyscall_time_info内存,

获取了首要CPU的pvti对应的物理地址,将其写入到msr_kvm_system_time MSR中

src = &hv_clock[cpu].pvti;

low = (int)slow_virt_to_phys(src) | 1;

high = ((u64)slow_virt_to_phys(src) >> 32);

ret = native_write_msr_safe(msr_kvm_system_time, low, high);

//启用STEAL time机制

if (kvm_para_has_feature(KVM_FEATURE_CLOCKSOURCE_STABLE_BIT))

pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT);

在kvm_sched_clock_init中,

kvm_sched_clock_offset = kvm_clock_read();

获取当前的时钟偏移

在guest 虚拟的硬件平台上指定函数

x86_platform.calibrate_tsc = kvm_get_tsc_khz;

x86_platform.calibrate_cpu = kvm_get_tsc_khz;

//get_wallclock本质是获取1970年到现在的时间差,本质是绝对日期

x86_platform.get_wallclock = kvm_get_wallclock;

x86_platform.set_wallclock = kvm_set_wallclock;
重点就看上面4个函数,

kvm_get_tsc_khz

src = &hv_clock[cpu].pvti;

tsc_khz = pvclock_tsc_khz(src);

获取pv clock信息转换成hz

kvm_get_wallclock

//获取wall clock变量的物理地址,写入到msr中,

low = (int)__pa_symbol(&wall_clock);

high = ((u64)__pa_symbol(&wall_clock) >> 32);

native_write_msr(msr_kvm_wall_clock, low, high);

写完msr后wall_clock就是更新后的墙上时间,即guest启动的日期。

然后再加上pvclock_clocksource_read(vcpu_time)即guest启动时间,则就是当前guest中的real time

2)KVM clock 在host中:

(1) MSR_KVM_SYSTEM_TIME_NEW

vcpu->arch.time = data;

//time就是kvm clock对应的guest pvclock_vsyscall_time_info地址,

kvm_gfn_to_hva_cache_init(vcpu->kvm,

&vcpu->arch.pv_time, data & ~1ULL,

sizeof(struct pvclock_vcpu_time_info))

缓存地址转换的信息。

vcpu->arch.pv_time_enabled = true;

kvm_make_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu);

(2) MSR_KVM_WALL_CLOCK_NEW

//wall_clock是guest中对应的wall_clock地址

vcpu->kvm->arch.wall_clock = data;

kvm_write_wall_clock(vcpu->kvm, data);

//此处获取的是host的启动的绝对时间,即从1970到现在的时间差

getboottime64(&boot);

if (kvm->arch.kvmclock_offset) {

//kvmclock_offset是host启动后到guest启动的相对值,即几分几秒

struct timespec64 ts = ns_to_timespec64(kvm->arch.kvmclock_offset);

boot = timespec64_sub(boot, ts);

}

wc.sec = (u32)boot.tv_sec; /* overflow in 2106 guest time */
wc.nsec = boot.tv_nsec;

在kvm_arch_init_vm中有

kvm->arch.kvmclock_offset = -ktime_get_boot_ns();

因为kvmclock_offset为负值,相减即相加,host启动日期加上guest启动的距离host启动时间的差值等于guest启动的日期,所以write msr的结果就是这样。

2.kvm_guest_time_update分析

static int kvm_guest_time_update(struct kvm_vcpu *v)

{

use_master_clock = ka->use_master_clock;

if (use_master_clock) {

host_tsc = ka->master_cycle_now;

kernel_ns = ka->master_kernel_ns;

}

if (!use_master_clock) {

host_tsc = rdtsc();

kernel_ns = ktime_get_boot_ns();

}

//获取host中tsc与TSC时钟1KHZ的比例

tgt_tsc_khz = __this_cpu_read(cpu_tsc_khz);

//读取guest中当前tsc

tsc_timestamp = kvm_read_l1_tsc(v, host_tsc);

if (vcpu->tsc_catchup) {

u64 tsc = compute_guest_tsc(v, kernel_ns);

if (tsc > tsc_timestamp) {

adjust_tsc_offset_guest(v, tsc - tsc_timestamp);

tsc_timestamp = tsc;

}

}

if (kvm_has_tsc_control)

//将1KHZ TSC转换成guest TSC

tgt_tsc_khz = kvm_scale_tsc(v, tgt_tsc_khz);

//如果当前guest时钟(kvmclock)的频率不同,则更新转换比例

if (unlikely(vcpu->hw_tsc_khz != tgt_tsc_khz)) {

kvm_get_time_scale(NSEC_PER_SEC, tgt_tsc_khz * 1000LL,

&vcpu->hv_clock.tsc_shift,

&vcpu->hv_clock.tsc_to_system_mul);

//即是guest tsc转换成kvmclock的比例

vcpu->hw_tsc_khz = tgt_tsc_khz;

}

//当前kvmclock下的TSC值

vcpu->hv_clock.tsc_timestamp = tsc_timestamp;

//当前kvmclock下的guest了多少ns

vcpu->hv_clock.system_time = kernel_ns + v->kvm->arch.kvmclock_offset;

vcpu->last_guest_tsc = tsc_timestamp;

pvclock_flags = 0;

if (use_master_clock)

pvclock_flags |= PVCLOCK_TSC_STABLE_BIT;

vcpu->hv_clock.flags = pvclock_flags;

if (vcpu->pv_time_enabled)

kvm_setup_pvclock_page(v);

if (v == kvm_get_vcpu(v->kvm, 0))

kvm_hv_setup_tsc_page(v->kvm, &vcpu->hv_clock);

}

3.关于use_master_clock

在kvm_write_tsc中,本次tsc写和上次的tsc写比较,得到elapsed和usdiff

ns = ktime_get_boot_ns();

elapsed = ns - kvm->arch.last_tsc_nsec;

usdiff = data - kvm->arch.last_tsc_write;

用usdiff与elapsed进行对冲,如果二者差值小于usdiff < USEC_PER_SEC则证明tsc是稳定

因为last_tsc_write和last_tsc_nsec都是在KVM下而非vcpu下,就是证明所有tsc是稳定的意义

if (!matched) {

kvm->arch.nr_vcpus_matched_tsc = 0;

} else if (!already_matched) {

kvm->arch.nr_vcpus_matched_tsc++;

}

4.KVMCLOCK的优点

kvm_get_wallclock替代mach_get_cmos_time获取rtc时间,mach_get_cmos_time函数在guest中执行需要多个pio vmexit才能完成,而kvm_get_wallclock只需要一个msr write即可,简便了操作,也不要在QEMU RTC的支持。

通过0x70,0x71端口操作。
参考LINUX内核:mach_get_cmos_time(),启动的时候获取日期时间。虽然内核也可以在每次需要的得到当前时间的时候读取 RTC,但这是一个 IO 调用,性能低下。实际上,在得到了当前时间后,Linux 系统会立即启动 tick 中断。此后,在每次的时钟中断处理函数内,Linux 更新当前的时间值,并保存在全局变量 xtime 内。比如时钟中断的周期为 10ms,那么每次中断产生,就将 xtime 加上 10ms。

unsigned char rtc_cmos_read(unsigned char addr)

{

unsigned char val;

lock_cmos_prefix(addr);

outb(addr, RTC_PORT(0));

val = inb(RTC_PORT(1));

lock_cmos_suffix(addr);

return val;

}

对于calibrate_tsc和calibrate_cpu同理。因为kvmclock效率只在启动的时候有体现,整体看替代效率并不明显。

关键在于时钟源的读取不再依赖于xtime的中断

static struct clocksource kvm_clock = {

.name = "kvm-clock",

.read = kvm_clock_get_cycles,

.rating = 400,

.mask = CLOCKSOURCE_MASK(64),

.flags = CLOCK_SOURCE_IS_CONTINUOUS,

};

static u64 kvm_clock_read(void)

{

struct pvclock_vcpu_time_info *src;

u64 ret;

int cpu;

preempt_disable_notrace();

cpu = smp_processor_id();

src = &hv_clock[cpu].pvti;

ret = pvclock_clocksource_read(src);

preempt_enable_notrace();

return ret;

}

直接获取虚拟的clock时间。

而tsc的时钟源是

static struct clocksource clocksource_tsc = {

.name                   = "tsc",

.rating                 = 300,

.read                   = read_tsc,

.mask                   = CLOCKSOURCE_MASK(64),

.flags                  = CLOCK_SOURCE_IS_CONTINUOUS |

CLOCK_SOURCE_MUST_VERIFY,

.archdata               = { .vclock_mode = VCLOCK_TSC },

.resume= tsc_resume,

};

和TSC相比,kvmclock优势并不明显,除非TSC进行了迁移


kvmclock时钟虚拟化源代码分析来自于OenHan

链接为:http://oenhan.com/kvm-pv-kvmclock-tsc

1 thought on “kvmclock时钟虚拟化源代码分析”

发表回复