Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1493219
  • 博文数量: 487
  • 博客积分: 161
  • 博客等级: 入伍新兵
  • 技术积分: 5064
  • 用 户 组: 普通用户
  • 注册时间: 2011-07-01 07:37
个人简介

只有偏执狂才能生存

文章分类

全部博文(487)

文章存档

2016年(10)

2015年(111)

2014年(66)

2013年(272)

2012年(28)

分类: LINUX

2014-06-28 14:17:17

  在虚拟机中,一个相当重要的环节就是host和guest之间的切换,我称之为World Switch,在World Switch完成的前后,完全是两个世界(从host切换到guest或者从guest切回host)。对于硬件辅助虚拟化技术来说,最关键的点即为vmlauch(vmx)和vmrun(svm)指令,该指令执行之后,就开始执行guest的代码了。这里看起来很简单,很清晰,但是实际上在这之前是需要做很多很多的工作的,在这之后也一样,我们这次先来看一下从qemu进入kmod之后,是如何完成host->guest->host切换这一流程的(以vmx为例)。

        关于preemptnotifier:在vmx环境下,我们知道需要在vmcs中保存各种信息,比如host和guest的state,以及某些控制字段。目前绝大多数的cpu都是多核的,这里就存在一个问题:考虑如下情况:

        


         这时,关注如下几点:

                1、  在这个点,有可能cpu 1根本就没开启vmx模式,这里就直接oops了。

                2、  如果侥幸赶上cpu 1开启了vmx,这里也可能会失败,因为cpu1的vmcs pointer指向的不一定是 vmcs 0。

                3、  如果1和2都成功,这里很大的几率会挂掉,因为这时从vmcs中恢复的host state,极有可能是错误的,这时很大几率host就会挂掉。

        在实际运行中,由于world switch频率极大,如果出现了上述情况,虚拟机一启动host会瞬间挂掉。

        下面我们来看一下如何解决这个问题:

                1、  在vcpu n 的线程进入kmod的时候,禁用抢占。

                2、  在vcpu n的线程被sched out和sched in的时候,采用某种机制来得知这个事件,并且对vmcs等作出处理。

         方案1中,无疑是最简单的,但是禁用抢占之后,对host的影响比较大,因为world switch整个过程中的指令流是比较多的,这里会对其他进程造成影响。

         方案2看起来是很不错的解决方案,但是这里存在一个问题,就是host os需要有一种通知机制来通知vmm,使得vmm在这几个关键的点上,及时作出处理,避免出现问题。在windows内核中,是没有这种机制的,但是linux内核可以直接利用preempt notifier。

         在preempt notifier机制中,如果发生了内核抢占,如果该进程注册了preempt notifier,有两个回调函数:


  1. void (*sched_in)(struct preempt_notifier *notifier, int cpu);  
        


        该回调在线程被调度执行之前调用。


  1. void (*sched_out)(struct preempt_notifier *notifier, struct task_struct *next);  
        



        该回调在线程刚刚被抢占之后执行。


        现在linux内核有这种机制,我们就可以高效的解决这个问题了,下面我们看一下kvm是怎么做的:

在vcpu创建的时候,会初始化该vcpu的notifier:

        
  1. preempt_notifier_init(&vcpu->preempt_notifier, &kvm_preempt_ops);  

kvm_main.c

  1. int vcpu_load(struct kvm_vcpu *vcpu)  
  2. {  
  3.         ……  
  4.         cpu = get_cpu();   //先关掉抢占,同时获取当前物理cpu的id   
  5.         preempt_notifier_register(&vcpu->preempt_notifier);   //注册一下preempt notifier   
  6.         kvm_arch_vcpu_load(vcpu, cpu);   //load vcpu的state,可以理解为把vcpu和物理cpu帮顶起来   
  7.         put_cpu();   //打开抢占   
  8.         return 0;  
  9. }  
  10. void vcpu_put(struct kvm_vcpu *vcpu)  
  11. {  
  12.     preempt_disable();    
  13.     kvm_arch_vcpu_put(vcpu);  //把当前vcpu从物理cpu解下来,主要是做一些关闭vmx以及清理的操作   
  14.     kvm_fire_urn();    //fire掉user return notifier,这个我们以后会讲   
  15.     preempt_notifier_unregister(&vcpu->preempt_notifier);  //反注册一下   
  16.     preempt_enable();  
  17.     mutex_unlock(&vcpu->mutex);  
  18. }  


由此可见,在worldswitch开始和结束的时候,kmod会对preempt notifier进行操作,以在被抢占时,能够做出处理。下面我们来看在两个回调中是如何处理的:

kvm_main.c:

  1. static inline  
  2. struct kvm_vcpu *preempt_notifier_to_vcpu(struct preempt_notifier *pn)  
  3. {  
  4.     return container_of(pn, struct kvm_vcpu, preempt_notifier);  //封装一下preempt notifier到vcpu的转换   
  5. }  
  6.   
  7. static void kvm_sched_in(struct preempt_notifier *pn, int cpu)  
  8. {  
  9.     struct kvm_vcpu *vcpu = preempt_notifier_to_vcpu(pn);  
  10.     if (vcpu->preempted)  
  11.         vcpu->preempted = false;  //置个标志   
  12.   
  13.     kvm_arch_vcpu_load(vcpu, cpu);    //绑定   
  14. }  
  15.   
  16. static void kvm_sched_out(struct preempt_notifier *pn,  
  17.               struct task_struct *next)  
  18. {  
  19.     struct kvm_vcpu *vcpu = preempt_notifier_to_vcpu(pn);  
  20.   
  21.     if (current->state == TASK_RUNNING)  
  22.         vcpu->preempted = true;  //置个标志   
  23.     kvm_arch_vcpu_put(vcpu);    //被强占了,解绑,释放当前的物理cpu   
  24.     kvm_fire_urn();             //fire掉user return notifier   
  25. }  

总结:

从上文可以看到,kvm对linux内核利用的极其巧妙,而linux内核中灵活的各种notifier,也是kvm能够在宿主虚拟机架构下仍然能够保持较高性能的必要条件。fvm在windows上运行的时候,由于windows内核的封闭,我们很难做出完美的处理,只能选择禁用抢占这一方法。

在后续的文章中,我们会逐一分析kvm在对vcpu调度的过程中的种种细节。在vcpu调度的最后一篇文章中,我们将把整个流程串起来。

阅读(2113) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~