当前位置: 首页 > news >正文

kvmclock

Ref:

https://zhuanlan.zhihu.com/p/665543594

【kvmclock的由来】

在x86平台,系统可以通过rdtsc来获取TSC值,然后再根据TSC的频率就可以计算出系统从上电开机到现在经历的时间(ns值)。

在虚拟化平台,虚拟机的tsc初始值是由VMM管理器设置的。因为涉及到不同平台、不同cpu型号的模拟以及虚拟机的热迁移等特性,虚拟机的tsc值与Host的tsc值并不相同。比如热迁移场景,两台Host的TSC不一样,如果Dst Host的TSC比Src Host的TSC小,那么可能会让Windows蓝屏或者linux panic。 如果Dst Host的TSC比Src Host的TSC大,那么在Guest中看到tsc瞬间跳变。

kvmclock即设计来解决这些问题。虚拟机想要通过TSC值来得到一个系统时间,最好还要告诉其一个可比较的值,即某个TSC值对应一个特定的时间。假设这个TSC常量值是tsc0 , 对应的时间值是t0 纳秒,TSC时钟频率为Freq,这样虚拟机只要把指令rdtsc得到TSC值tsc1与它进行对比,就能得到具体的时间,公式为

t0+(tsc1-tsc0) * Freq * 109 。是的,kvmclock就是基于TSC来设计的。

【kvmclock的实现原理】

从上面的由来,可以看出,虚拟机需要从主机端得到CPU的TSC时钟频率,以及一个可比较的相对时间。目前kvm的实现是:虚拟机内核会在内存中分配一个结构体来存放这些信息值;为了访问过程减少锁的使用,每个vCPU都有一个对应的结构体,这个结构体定义如下:

struct pvclock_vcpu_time_info {
        u32   version; //版本号,每更新一次,就加2
        u32   pad0;
        u64   tsc_timestamp;//参考时间对应的tsc值
        u64   system_time;//参考时间对应的ns值
        u32   tsc_to_system_mul;//由TSC时钟频率计算而来,用于和tsc_shift一起将tsc相对值转为了时间ns
        s8    tsc_shift;
        u8    flags;
        u8    pad[2];
}

由于内核中,tsc值除以TSC时钟频率得出时间的这个操作太过频繁,为了提升效率,内核把相对复杂的浮点除法转化为较为高效的整数位移和整数乘法。因此,可以看到这个结构体并没有直接存放TSC时钟频率,而是存放一个位移值tsc_shift一个缩放乘数值tsc_to_system_mul,具体的运算细节后面再讲。

有了这个结构体,虚拟机内核需要把这个结构体地址告诉Host,建立一个共享映射,由host来填写guest来读取。KVM设计了一个MSR寄存器MSR_KVM_SYSTEM_TIME_NEW,地址是0x4b564d01,来负责把pvclock_vcpu_time_info结构体的物理地址通知给host

【kvmclock在guest中的初始化】

虚拟机内核启动时,内核会尝试检测虚拟化运行环境是KVM,还是HyperV,调用栈如下:

start_kernel [init\main.c]

——》setup_arch [arch\x86\kernel\setup.c]

——》——》init_hypervisor_platform [arch\x86\kernel\cpu\hypervisor.c]

——》——》——》detect_hypervisor_vendor {通过遍历各个虚拟化环境检测函数}

——》——》——》——》(*p)->detect() {对于KVM来说,调用是函数kvm_detect[arch\x86\kernel\kvm.c]

当detect_hypervisor_vendor返回虚拟化环境是KVM后,接下来就会拷贝kvm的一些初始化函数地址,并执行其中的init_platform,进而调用kvmclock的初始化函数,代码如下:

void __init init_hypervisor_platform(void)
{
    h = detect_hypervisor_vendor();
//检测
    copy_array(&h->init, &x86_init.hyper, sizeof(h->init));
    x86_init.hyper.init_platform();
//实际调用的是kvm_init_platform[arch\x86\kernel\kvm.c]
}

//file:arch\x86\kernel\kvm.c
static void __init kvm_init_platform(void)
{
        kvmclock_init();
        x86_platform.apic_post_init = kvm_apic_init;
}

接下来重点看下kvmclock_init函数,这个函数位于文件arch\x86\kernel\kvmclock.c。以下列出重要的几行代码。可以看出,它先获取本CPU的时间信息页内存的物理地址,然后通过写MSR告诉Host,让Host在这个内存地址填写详细的时间偏移量信息,最后注册guest内核启动后的第一个时钟源kvmclock:

void __init kvmclock_init(void)
{
        ......
        if (kvm_para_has_feature(KVM_FEATURE_CLOCKSOURCE2)) {
                msr_kvm_system_time = MSR_KVM_SYSTEM_TIME_NEW;
//设置kvmclock对应的MSR寄存器地址
                msr_kvm_wall_clock = MSR_KVM_WALL_CLOCK_NEW;
        }
        ......
        this_cpu_write(hv_clock_per_cpu, &hv_clock_boot[
0]);//将第1个时间信息页项分配给CPU0
        kvm_register_clock("primary cpu clock");//CPU0从主机端获取时间信息页
        ......
        
#ifdef CONFIG_X86_LOCAL_APIC
        x86_cpuinit.early_percpu_clock_init = kvm_setup_secondary_clock;//指定其他CPU online时调用kvm_register_clock从Host主机端获得时间信息
        #endif
        ......
        clocksource_register_hz(&kvm_clock, NSEC_PER_SEC);
//注册时钟源
        pv_info.name = "KVM";
}

【时间乘数的计算和应用方法】

【kvmclock在host中的实现】

host在截获guest对msr寄存器MSR_KVM_SYSTEM_TIME_NEW的写动作后,会执行模拟操作:

kvm_set_msr_common [arch/x86/kvm/x86.c]

    case MSR_KVM_SYSTEM_TIME_NEW:

    case MSR_KVM_SYSTEM_TIME: {

        struct kvm_arch *ka = &vcpu->kvm->arch;

        if (vcpu->vcpu_id == 0 && !msr_info->host_initiated) {

            bool tmp = (msr == MSR_KVM_SYSTEM_TIME);

            if (ka->boot_vcpu_runs_old_kvmclock != tmp)

                kvm_make_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu);

            ka->boot_vcpu_runs_old_kvmclock = tmp;

        }

        vcpu->arch.time = data;

        kvm_make_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu);

        /* we verify if the enable bit is set... */

        vcpu->arch.pv_time_enabled = false;

        if (!(data & 1))

            break;

        if (!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;

        break;

    }

通过kvm_gfn_to_hva_cache_init函数,会把传过来的地址参数data记录到pv_time中。这样子就可以通过pv_time来直接修改Guest中的pvclock_vcpu_time_info数据结构。

kvmclock的更新时机:在每次vcpu enter时刻更新。

vcpu_enter_guest [检查vcpu的KVM_REQ_CLOCK_UPDATE flag置位]

kvm_guest_time_update //更新vcpu->hv_clock.tsc_timestamp和vcpu->hv_clock.system_time等

kvm_setup_pvclock_page

【前后端的同步】

有了前后端共享的数据结构(pvclock_vcpu_time_info),后端host负责写,前端guest负责读。那如果前端正在读,但后端也正在修改怎么办?读取的数据是不是可能不准确?

答案就在这version字段上。version表示该结构体信息的版本号。当这个版本号为奇数时,表示主机Host正在修改这个结构体,需要循环读取直至版本号为偶数;当版本号为偶数时,表示当前这个结构体信息已经更新完成,可以用于计算时间。

http://www.dtcms.com/a/427975.html

相关文章:

  • 使用 Python 打造一个轻量级系统信息查看器
  • 旅游网站首页制作番禺核酸检测点有新调整
  • 最常用的js加解密之RSA-SHA256 加密算法简介与 jsjiami 的结合使用指南
  • 建站之星至尊版域名中的wordpress删除
  • 苹果软件混淆方式对比与场景化选择,源码混淆、成品包混淆与混合方案
  • 生产环境下oracle19c rac恢复节点2
  • 【VMware】VMware-workstation中,Ubuntu系统安装说明
  • 基于LMK04828的跨板级联时钟同步
  • 黄骅港客运站电话号码企业网站制作策划书
  • 图神经网络分享系列-transe(Translating Embeddings for Modeling Multi-relational Data) (二)
  • 安全的合肥网站建设中国建设银行移动门户
  • LVGL-UI工具
  • 长春网站建设推广网站建设佰金手指科杰二
  • 精益制造——解读麦肯锡集团精益生产与价值流图管理【附全文阅读】
  • 建站吗官方网站农产品网络营销模式
  • 苏宁易购网站设计怎么制作潍坊住房公积金个人查询入口
  • SeaTunnel 同步 KingBase 数据到 Easysearch
  • SSM基于Web的在线音乐网站935wk(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 自己做网站 需要哪些网站绑定别名好吗
  • 【设计模式】六大基本原则
  • dw做的手机端网站雄安网站建设单位
  • SpringBoot 统⼀功能处理
  • 建网站要多少费用南宁个人网站建设
  • JTCatch 缓存配置与使用
  • Android Jetpack 系列(六)WorkManager 任务调度实战详解
  • 1、docker入门简介
  • 个人小说网站怎么做娄底企业网站建设制作
  • 三层交换(h3c)
  • 网站有备案 去掉备案网页制作对联
  • 静态网站制作流程怎么查看网站收录