设为首页 收藏本站
查看: 976|回复: 0

[经验分享] KVM虚拟机创建功能详细讲解

[复制链接]

尚未签到

发表于 2015-10-10 10:09:00 | 显示全部楼层 |阅读模式
。  ⑶ 在cmdCreate主程序中有一个特别重要的函数:virDomainCreateXML(),这个函数的最初原型是: virDomainPtr virDomainCreateXML (virConnectPtr conn,const char*xmlDesc,unsigned int flags),这个函数是基于一个指定的XML文件来创建一个虚拟机,其中conn表示一个指向hypervisor的连接,xmlDesc表示一个 XML文件,flags表示命令选项的标志。
  2.2 通过libvirt创建虚拟机的关键API
  通过分析2.1中的virsh源码我们可以看出,使用libvirt进行虚拟机创建要调用两个关键的API-- virFileReadAll和virDomainCreateXML,下面分别进行说明。
  2.2.1 virFileReadAll
  该函数原型为intvirFileReadAll(const char *path, int maxlen, char **buf),功能是将参数“path”指定路径的文件内容读到一个缓冲区中,并将缓冲区地址记录在参数“*buf”中,而参数“maxlen”指定文件的最大长度。利用该API,我们可以将xml配置文件都到一个缓冲区中,以方便接下来的使用。
  2.2.2virDomainCreateXML
  该函数原型为virDomainPtr    virDomainCreateXML      (virConnectPtrconn,  const char* xmlDesc,  unsigned int flags),功能是根据参数“xmlDesc”定义的配置方式创建一个域并返回该域的指针。参数“conn”是指向虚拟机管理器的指针,而通过设置不同的“flags”标志,可以使创建的域具有不同的属性。
  
  三. 利用libvirt库编写自己的虚拟机创建程序
  Virsh命令用来创建虚拟机的命令是:virsh create,这个命令主要是从给定的XML文件生成客户端并启动客户端。
  下面用一个测试例子来说明如何通过virsh命令来创建虚拟机的。
  具体的操作实践步骤是:

  • 首先需要创建虚拟硬盘,为了放置操作系统的地方,命令是:kvm-img create
  701.img10G,也就是创建一个大小为10G的虚拟硬盘。
  2.  编写一个xml文件,这个文件里面包含启动操作系统的一些特征,比如:内存容量,操作系统位置,虚拟硬盘位置等等,其实有很多的字段,可以简写一个xml 文件,如果有些字段没有定义,那么系统就会默认,下面给出一个xml文件,命名为701.xml,程序为:
  <domain type='qemu'>
  <name>linux10.0421</name>
  <uuid></uuid>
  <memory>512000</memory>
  <currentMemory>512000</currentMemory>
  <vcpu>1</vcpu>
  <os>
  <type arch='i686' machine='pc'>hvm</type>
  <boot dev='cdrom'/>  
  <boot dev='hd'/>
  </os>
  <devices>
  <emulator>/usr/bin/qemu-system-x86_64</emulator>
  
  <disk type='file' device='cdrom'>
  <source file='/usr/src/ubuntu-10.04-desktop-i386.iso'/>
  <target dev='hdc'/>
  <readonly/>
  </disk>
  <disk type='file' device='disk'>
  <sourcefile='/var/lib/libvirt/images/701.img'/>
  <target dev='hda'/>
  </disk>
  <graphics type='vnc' port='5901'listen='127.0.0.1'/>
  </devices>
  </domain>
  3.  接着编写一个c文件,名称为701.c这个文件主要实现的功能就是调用这个xml文件来创建并启动虚拟机。这个c程序代码为:
  #include<stdio.h>
  #include<stdlib.h>
  #include<memory.h>
  #include<libvirt/libvirt.h>
  const char *from=NULL;
  static virConnectPtr conn=NULL;
  #define VIRSH_MAX_XML_FILE 10*1024*1024
  void closeConn()
  {
  if(conn!=NULL)
  virConnectClose(conn);
  }
  int cmdCreate()
  {
  virDomainPtr dom;
  char *buffer;
  unsigned int flags=VIR_DOMAIN_NONE;
  conn=virConnectOpen(&quot;qemu:///system&quot;);
  if(conn==NULL)
  {
  fprintf(stderr,&quot;failed to connect tohypervisor/n&quot;);
  closeConn();
  return 0;
  }
  if(virFileReadAll(from,VIRSH_MAX_XML_FILE,&buffer)<0)
  return 0;
  dom=virDomainCreateXML(conn,buffer,flags);
  memset(buffer,0,sizeof(buffer));
  if(dom!=NULL){
  
  fprintf(stdout,&quot;Domain %screated from %s\n&quot;,virDomainGetName(dom),from);
  virDomainFree(dom);
  }
  else{
  fprintf(stdout,&quot;Failed to createdomain from %s&quot;,from);
  }
  }
  int main(int argc,char *argv[])
  {
  if(argc<2){
  fprintf(stdout,&quot;there are too fewparameters,should has two more parameters!&quot;);
  }
  from=*&#43;&#43;argv;
  cmdCreate();
  return 0;
  }
  4. 在命令窗口中先执行gcc -lvirt -o 701 701.c  ,然后执行./701 701.xml,就可以看到这个虚拟机被创建并启动起来了。
  
  四.KVM内核如何实现底层虚拟机创建功能
  4.1 KVM虚拟机创建和运行虚拟机的流程
  开源的Lbvirt库实现了很多的虚拟化API,这些API的实现还是要靠底层的KVM内核的实现,下面重点讲讲KVM内核中是如何实现虚拟机创建和运行功能的操作系统层的实现。
  KVM虚拟机创建和运行虚拟机分为用户态和核心态两个部分,用户态主要提供应用程序接口,为虚拟机创建虚拟机上下文环境,在libkvm中提供访问内核字符设备/dev/kvm的接口;内核态为添加到内核中的字符设备/dev/kvm,模块加载进内核后,即可进行接口用户空间调用创建虚拟机。在创建虚拟机过程中,kvm字符设备主要为客户机创建kvm数据结构,创建该虚拟机的虚拟机文件描述符及其相应的数据结构以及创建虚拟机处理器及其相应的数据结构。 kvm创建虚拟机的流程如下图:
  根据上图就可以大致知道虚拟机创建和运行的流程了。首先申明一个kvm_context_t变量用以描述用户态虚拟机上下文信息,然后调用 kvm_init()函数初始化虚拟机上下文信息;函数kvm_create()创建虚拟机实例,该函数通过ioctl系统调用创建虚拟机相关的内核数据结构并且返回文件描述符给用户态kvm_context_t数据结构;创建完内核虚拟机数据结构后,再创建内核pit以及mmio等外设模拟设备,然后调用kvm_create_vcpu()函数来创建虚拟处理器,kvm_create_vcpu()函数通过系统调用向由vm_fd文件描述符指向的虚拟文件调用创建虚拟处理器,并将虚拟处理器的文件描述符返回给用户态程序,供以后的调度使用;创建完虚拟处理器后,由用户态的QEMU程序申请客户机用户空间,用以加载和运行客户机代码;为了使得客户虚拟机正确执行,必须要在内核中为客户机建立正确的内存映射关系,即影子页表信息。因此,申请客户机内存地址空间之后,调用函数kvm_create_phys_mem()创建客户机内存映射关系,该函数主要通过ioctl系统调用向vm_fd指向队的虚拟文件调用设置内核数据结构中客户机内存映射关系,主要建立影子页表信息;当创建好虚拟处理器和影子页表后,即可读取客户机到指定分配的空间中,然后调度虚拟处理器运行。调度虚拟机的函数为kvm_run(),该函数通过ioctl系统调用调用由虚拟处理器文件描述符指向的虚拟文件调度处理函数kvm_run()调度虚拟处理器的执行,该系统调用将虚拟处理器vcpu信息加载到物理处理器中,通过vm_entry执行进入客户机执行。在客户机正常运行期间kvm_run()函数不返回,只有发生以下两种情况时,函数返回:1,发生了I/O事件,如客户机发出读写I/O的指令;2,产生了客户机和内核KVM都无法处理的异常。I/O事件处理完毕后,通过重新调用KVM_RUN()函数继续调度客户机的执行。
  4.2 KVM虚拟机创建和运行虚拟机的主要函数分析以及流程
  1.函数kvm_init():该函数在用户态创建一个虚拟机上下文,用以在用户态保存基本的虚拟机信息,这个函数是创建虚拟机的第一个需要调用的函数,函数返回一个kvm_context_t结构体。该函数原型的实现在libkvm.c中,该函数原型是:
  kvm_context_t kvm_init(struct kvm_callbacks*callbacks,void *opaque);
  参数:callbacks为结构体kvm_callbacks变量,该结构体包含指向函数的一组指针,用于在客户机执行过程中因为I/O事件退出到用户态的时候处理的回调函数。参数opaque一般未使用。
  函数执行基本过程:打开字符设备dev/kvm,申请虚拟机上下文变量kvm_context_t空间,初始化上下文的基本信息:设置fd文件描述符指向 /dev/kvm,禁止虚拟机文件描述符vm_fd(-1),设置I/O事件回调函数结构体,设置IRQ和PIT的标志位以及内存页面记录的标志位。
  用户态数据结构kvm_context_t用以描述虚拟机实例的用户态上下文信息。在kvm_common.h文件里面有kvm_context的结构体定义。
  structkvm_context {
  /// Filedescriptor to /dev/kvm
  int fd;
  int vm_fd;
  int vcpu_fd[MAX_VCPUS];
  struct kvm_run *run[MAX_VCPUS];
  /// Callbacks that KVM uses to emulatevarious unvirtualizable functionality
  struct kvm_callbacks *callbacks;
  void *opaque;
  /// A pointer to the memory used as thephysical memory for the guest
  void *physical_memory;
  /// is dirty pages logging enabled for allregions or not
  int dirty_pages_log_all;
  /// memory regions parameters
  struct kvm_memory_regionmem_regions[KVM_MAX_NUM_MEM_REGIONS];
  /// do not create in-kernel irqchip if set
  int no_irqchip_creation;
  /// in-kernel irqchip status
  int irqchip_in_kernel;
  };
  各个数据域的解释为:
  int fd :指向内核标准字符设备/dev/kvm的文件描述符。
  int vm_fd:指向所创建的内核虚拟机数据结构相关文件的文件描述符。
  intvcpu_fd[MAX_VCPUS]:指向虚拟机所有的虚拟处理器的文件描述符数组。
  struct kvm_run*run[MAX_VCPUS]:指向虚拟机运行环境上下文的指针数组。
  struct kvm_callbacks*call_backs: 回调函数结构体指针,该结构体用于处理用户态I/O事件。
  void *opaque:指针(还未弄清楚)
  int dirty_page_log_all:设置是否记录脏页面的标志。
  int no_ira_creation: 用于设置是否再kernel里设置irq芯片。
  int_irqchip_in_kernel:内核中irqchip的状态
  structkvm_callbacks:该结构体用于在用户态中处理I/O事件,在KVM中调用KVM_QEMU实现,主要包含的数据域为:
  int (*inb)(void *opaque, uint16_t addr,uint8_t *data):用于模拟客户机执行8位的inb指令。
  int (*inw)(void *opaque, uint16_t addr,uint16_t *data):用于模拟客户机执行16位的inw指令。
  int (*inl)(void *opaque, uint16_t addr,uint32_t *data):用于模拟客户机执行32位的inl指令。
  int (*outb)(void *opaque, uint16_t addr,uint8_t data):用于模拟客户机执行8位的outb指令。
  int (*outw)(void *opaque, uint16_t addr,uint16_t data):用于模拟客户机执行16位的outw指令。
  int (*outl)(void *opaque, uint16_t addr,uint32_t data):用于模拟客户机执行32位的outl指令。
  int (*mmio_read)(void *opaque, uint64_taddr, uint8_t *data,int len):用于模拟客户机执行mmio读指令。
  int (*mmio_write)(void *opaque, uint64_taddr, uint8_t *data,int len):用于模拟客户机执行mmio写指令。
  int (*debug)(void *opaque, void *env,struct kvm_debug_exit_arch *arch_info):用户客户机调试的回调函数。
  int (*halt)(void *opaque, int vcpu):用于客户机执行halt指令的响应。
  int (*shutdown)(void *opaque, void *env):用于客户机执行shutdown指令的响应。
  int (*io_window)(void *opaque):用于获得客户机io_windows。
  int (*try_push_interrupts)(void *opaque):用于注入中断的回调函数。
  void (*push_nmi)(void *opaque):用于注入nmi中断的函数。
  void (*post_kvm_run)(void *opaque, void*env);用户得到kvm运行状态函数。
  int (*pre_kvm_run)(void *opaque, void*env);用于获得kvm之前运行状态的函数
  int (*tpr_access)(void *opaque, int vcpu,uint64_t rip, int is_write);获得tpr访问处理函数
  int (*powerpc_dcr_read)(int vcpu, uint32_tdcrn, uint32_t *data);用于powerpc的dcr读操作
  nt (*powerpc_dcr_write)(int vcpu, uint32_tdcrn, uint32_t data);用于powerpc的dcr写操作
  int (*s390_handle_intercept)(kvm_context_tcontext, int vcpu,struct kvm_run *run);用于s390的中断处理。
  int (*s390_handle_reset)(kvm_context_tcontext, int vcpu,struct kvm_run *run);用于s390的重设处理。
  }
  
  当客户机执行I/O事件或者停机操作等事件时,KVM会交给用户态的QEMU模拟外部I/O事件,调用这个结构体指向的相关的函数进行处理。
  Struct kvm_run: 用于KVM运行时一些的一些状态信息。主要包含的数据域为:
  __u8 request_interrupt_window;
  __u8 padding1[7];
  __u32 exit_reason;
  __u8 ready_for_interrupt_injection;
  __u8 if_flag;
  __u8 padding2[2];
  /* in (pre_kvm_run), out (post_kvm_run) */
  __u64 cr8;
  __u64 apic_base;
  union {
  /* KVM_EXIT_UNKNOWN */
  struct {
  __u64 hardware_exit_reason; 记录退出原因
  } hw;
  /* KVM_EXIT_FAIL_ENTRY */  客户机执行过程中执行VM_ENTRY失败。
  struct {
  __u64hardware_entry_failure_reason;
  } fail_entry;
  /* KVM_EXIT_EXCEPTION */  客户机因为异常退出
  struct {
  __u32exception;
  __u32error_code;
  } ex;
  /* KVM_EXIT_IO */   客户机因为IO事件退出。
  struct kvm_io {
  #define KVM_EXIT_IO_IN  0
  #define KVM_EXIT_IO_OUT 1
  __u8 direction;
  __u8 size; /* bytes */
  __u16 port;
  __u32 count;
  __u64 data_offset; /* relative to kvm_runstart */
  } io;
  struct {
  struct kvm_debug_exit_arch arch;
  } debug;
  /* KVM_EXIT_MMIO */ 客户机因为MMIO退出
  struct {
  __u64 phys_addr;
  __u8 data[8];
  __u32 len;
  __u8 is_write;
  } mmio;
  /* KVM_EXIT_HYPERCALL */ 客户机退出的超调用参数。
  struct {
  __u64 nr;
  __u64 args[6];
  __u64 ret;
  __u32 longmode;
  __u32 pad;
  } hypercall;
  /*KVM_EXIT_TPR_ACCESS */ 客户机退出访问TPR参数
  struct {
  __u64rip;
  __u32is_write;
  __u32pad;
  } tpr_access;
  /* KVM_EXIT_S390_SIEIC */  和S390相关数据
  struct {
  __u8 icptcode;
  __u64 mask; /* psw upper half */
  __u64 addr; /* psw lower half */
  __u16 ipa;
  __u32 ipb;
  } s390_sieic;
  /* KVM_EXIT_S390_RESET */
  #define KVM_S390_RESET_POR       1
  #define KVM_S390_RESET_CLEAR     2
  #define KVM_S390_RESET_SUBSYSTEM 4
  #define KVM_S390_RESET_CPU_INIT  8
  #define KVM_S390_RESET_IPL       16
  __u64 s390_reset_flags;
  /* KVM_EXIT_DCR */
  struct {
  __u32dcrn;
  __u32data;
  __u8  is_write;
  } dcr;
  /* Fix the size of the union. */
  char padding[256];
  2. 函数kvm_create():该函数主要用于创建一个虚拟机内核环境。该函数原型为:
  int kvm_create(kvm_context_t kvm,unsignedlong phys_mem_bytes, void **phys_mem);
  参数:kvm_context_t 表示传递的用户态虚拟机上下文环境,phys_mem_bytes表示需要创建的物理内存的大小,phys_mem表示创建虚拟机的首地址。这个函数首先调用kvm_create_vm()分配IRQ并且初始化为0,设置vcpu[0]的&#20540;为-1,即不允许调度虚拟机执行。然后调用ioctl系统调用 ioctl(fd,KVM_CREATE_VM,0)来创建虚拟机内核数据结构struct kvm。
  3. 系统调用函数ioctl(fd,KVM_CREATE_VM,0),用于在内核中创建和虚拟机相关的数据结构。该函数原型为:
  Static long kvm_dev_ioctl(struct file *filp,unsigned intioctl, unsignedlong arg);其中ioctl表示命令。这个函数调用kvm_dev_ioctl_create_vm()创建虚拟机实例内核相关数据结构。该函数首先通过内核中kvm_create_vm()函数创建内核中kvm上下文struct kvm,然后通过函数
  Anno_inode_getfd(“kvm_vm”,&kvm_vm_fops,kvm,0)返回该虚拟机的文件描述符,返回给用户调用函数,由2中描述的函数赋&#20540;给用户态虚拟机上下文变量中的虚拟机描述符kvm_vm_fd。
  4. 内核创建虚拟机kvm对象后,接着调用kvm_arch_create函数用于创建一些体系结构相关的信息,主要包括kvm_init_tss、 kvm_create_pit以及kvm_init_coalsced_mmio等信息。然后调用kvm_create_phys_mem创建物理内存,函数kvm_create_irqchip用于创建内核irq信息,通过系统调用 ioctl(kvm->vm_fd,KVM_CREATE_IRQCHIP)。
  5,函数kvm_create_vcpu():用于创建虚拟处理器。该函数原型为:
  int kvm_create_vcpu(kvm_context_t kvm, intslot);
  参数:kvm表示对应用户态虚拟机上下文,slot表示需要创建的虚拟处理器的个数。
  该函数通过ioctl系统调用ioctl(kvm->vm_fd,KVM_CREATE_VCPU,slot)创建属于该虚拟机的虚拟处理器。该系统调用函数:
  Static init kvm_vm_ioctl_create_vcpu(struct*kvm, n) 参数kvm为内核虚拟机实例数据结构,n为创建的虚拟CPU的数目。
  6,函数kvm_create_phys_mem()用于创建虚拟机内存空间,该函数原型:
  Void * kvm_create_phys_mem(kvm_context_tkvm,unsigned long phys_start,unsigned len,int log,int writable);
  参数:kvm 表示用户态虚拟机上下文信息,phys_start为分配给该虚拟机的物理起始地址,len表示内存大小,log表示是否记录脏页面,writable表示该段内存对应的页表是否可写。
  该函数首先申请一个结构体kvm_userspace_memory_region 然后通过系统调用KVM_SET_USER_MEMORY_REGION来设置内核中对应的内存的属性。该系统调用函数原型:
  Ioctl(int kvm->vm_fd,KVM_SET_USER_MEMORY_REGION,&memory);
  参数:第一个参数vm_fd为指向内核虚拟机实例对象的文件描述符,第二个参数KVM_SET_USER_MEMORY_REGION为系统调用命令参数,表示该系统调用为创建内核客户机映射,即影子页表。第三个参数memory表示指向该虚拟机的内存空间地址。系统调用首先通过参数memory通过函数copy_from_user从用户空间复制struct_user_momory_region 变量,然后通过kvm_vm_ioctl_set_memory_region函数设置内核中对应的内存域。该函数原型:
  Int kvm_vm_ioctl_set_memory_region(struct*kvm,struct kvm_usersapce_memory_region *mem,int user_alloc);该函数再调用函数kvm_set_memory_resgion()设置影子页表。当这一切都准备完毕后,调用 kvm_run()函数即可调度执行虚拟处理器。
  7,函数kvm_run():用于调度运行虚拟处理器。该函数原型为:
  Int kvm_run(kvm_context_t kvm,int vcpu,void *env) 该函数首先得到vcpu的描述符,然后调用系统调用ioctl(fd,kvm_run,0)调度运行虚拟处理器。Kvm_run函数在正常运行情况下并不返回,除非发生以下事件之一:一是发生了I/O事件,I/O事件由用户态的QEMU处理;一个是发生了客户机和KVM都无法处理的异常事件。 KVM_RUN()中返回截获的事件,主要是I/O以及停机等事件。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-124979-1-1.html 上篇帖子: qemu-kvm 准备知识 下篇帖子: KVM虚拟机的xml配置文件
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表