清单 1. 测试 KVM 系统管理程序的应用程序片断
int main() { void *vm_mem; kvm = kvm_init(&test_callbacks, 0); if (!kvm) { fprintf(stderr, "kvm_init failed\n"); return 1; } if (kvm_create(kvm, 128 * 1024 * 1024, &vm_mem) < 0) { kvm_finalize(kvm); fprintf(stderr, "kvm_create failed\n"); return 1; } if (ac > 1) if (strcmp(av[1], "-32") != 0) load_file(vm_mem + 0xf0000, av[1]); else enter_32(kvm); if (ac > 2) load_file(vm_mem + 0x100000, av[2]); kvm_show_regs(kvm, 0); kvm_run(kvm, 0); return 0; }
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("qemu:///system");
if(conn==NULL)
{
fprintf(stderr,"failed to connect tohypervisor/n");
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,"Domain %screated from %s\n",virDomainGetName(dom),from);
virDomainFree(dom);
}
else{
fprintf(stdout,"Failed to createdomain from %s",from);
}
}
int main(int argc,char *argv[])
{
if(argc<2){
fprintf(stdout,"there are too fewparameters,should has two more parameters!");
}
from=*++argv;
cmdCreate();
return 0;
}
4. 在命令窗口中先执行gcc -lvirt -o 701 701.c ,然后执行./701 701.xml,就可以看到这个虚拟机被创建并启动起来了。
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]的值为-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中描述的函数赋值给用户态虚拟机上下文变量中的虚拟机描述符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以及停机等事件。