本节以libvirt 1.2.8为基础,来分析libvirt如何控制qemu虚拟机.
9.2.1 libvirt qemu的架构
int qemuRegister(void) (qemu/qemu_driver.c)
{
if(virRegisterDriver(&qemuDriver) < 0)
return -1;
if(virRegisterStateDriver(&qemuStateDriver) < 0)
return -1;
return 0;
}
static virDriver qemuDriver = {
.no = VIR_DRV_QEMU,
.name =QEMU_DRIVER_NAME,
.connectOpen =qemuConnectOpen, /* 0.2.0 */
.connectClose =qemuConnectClose, /* 0.2.0 */
.connectSupportsFeature = qemuConnectSupportsFeature, /* 0.5.0 */
.connectGetType =qemuConnectGetType, /* 0.2.0 */
.connectGetVersion =qemuConnectGetVersion, /* 0.2.0 */
.connectGetHostname = qemuConnectGetHostname,/* 0.3.3 */
.connectGetSysinfo =qemuConnectGetSysinfo, /* 0.8.8 */
.connectGetMaxVcpus =qemuConnectGetMaxVcpus, /* 0.2.1 */
.............
} //该结构为qemu的底层操作接口
virRegisterDriver 会将上述结构存于:
virDriverTab[virDriverTabCount] = driver;
virDriverTabCount++;
下面看看libvirt如何从上层调用到底层qemu_driver.
上一节的例子中,代码首先会调用virConnectOpenReadOnly
virConnectOpenReadOnly ==> do_open
a .因为name 为null所以调用virConnectGetDefaultURI 从环境变量中获取name.
b. 遍历驱动,调用virDriverTab[i]->connectOpen ==> qemuConnectOpen
9.2.2 qemu调用情景分析
本小节通过如下几个情景来分析,libvirt对qemu的控制方式
a.虚拟机的配置与创建
b. vcpu信息和最大内存信息查询
c. 虚拟机supspend
(1) 虚拟机的配置与创建
virDomainCreateXML (libvirt.c) ==>
conn->driver->domainCreateXML(conn,xmlDesc, flags);
qemuDomainCreateXML
a) virQEMUDriverGetCapabilities取得虚拟机支持的能力; 存于driver->caps= caps;
b) XML 解析virDomainDefParseString
c) 创建domain, 并加入列表
qemuDomainAssignAddresses
virDomainObjListAdd
d) 准备启动,建立job
qemuDomainObjBeginJob(driver,vm, QEMU_JOB_MODIFY);
e) 根据配置启动qemu进程
qemuProcessStart
f) 建立虚拟机创建与引导的event
virDomainEventLifecycleNewFromObj
g) 取得domain结构指针
virGetDomain(conn,vm->def->name, vm->def->uuid);
下面看看qemuProcessStart的流程:
a) 得到配置结构cfg =virQEMUDriverGetConfig(driver);
b) 初始化虚拟机id, 和domain 状态
vm->def->id= qemuDriverAllocateID(driver);
qemuDomainSetFakeReboot(driver,vm, false);
virDomainObjSetState(vm,VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN);
c) 设备的准备
qemuNetworkPrepareDevices
qemuPrepareHostDevices
virDomainChrDefForeach
d) Cgroup的准备
e) log qemuDomainCreateLog
f) vcpu数量检察
g) 直接io 设备准备qemuAssignDeviceAliases
h) 根据配置机建立qemu的启动命令行参数
qemuBuildCommandLine
启动qemu
ret= virCommandRun(cmd, NULL);
virCommandHandshakeWait(cmd);
i) 后处理,如设置cgroupqemuSetupCgroup;cpu亲和性设置qemuProcessInitCpuAffinity
j) 连接qemu 的monitor : qemuProcessWaitForMonitor
向qemu发送monitor命令,例如:qemuProcessVerifyGuestCPU==》qemuMonitorGetGuestCPU 取guest的vcpu.
退出 qemu monitor. qemuDomainObjExitMonitor
qemu启动的核心在于commandline, 我们下面分析qemuBuildCommandLine(qemu_command.c)
a) 建立启动command结构cmd = virCommandNew(emulator);
b) cpu相关命令行建立qemuBuildCpuArgStr
c) 内存
virCommandAddArg(cmd,"-m");
def->mem.max_balloon= VIR_DIV_UP(def->mem.max_balloon, 1024) * 1024;
virCommandAddArgFormat(cmd,"%llu", def->mem.max_balloon / 1024);
d) smp, smbios,graphics chardev, rtc等设置
e) VIR_DOMAIN_CONTROLLER_TYPE_PCI guest os 的pci controller设置
f) usb,与usbdevice相关设置
。。。。。
该函数较长,但功能比较单一。
(2) vcpu信息和最大内存信息查询
qemuConnectGetMaxVcpus ==》 kvmGetMaxVCPUs ==》
ioctl(fd, KVM_CHECK_EXTENSION, KVM_CAP_NR_VCPUS);
通过iotrl方式获得vcpu数目。
qemuDomainGetMaxMemory ==》
return vm->def->mem.max_balloon;
直接通过配置信息返回
(3) 虚拟机supspend
qemuDomainSuspend
a) 根据虚拟机domain的到虚拟机vm = qemuDomObjFromDomain(dom);
b) qemuDomainObjBeginJob ==》 qemuDomainObjBeginJobInternal(qem_domain.c)启动任务
c) 生成事件VIR_DOMAIN_EVENT_SUSPENDED
d) qemuProcessStopCPUs停止vcpu的运行
virDomainSaveStatus保存虚拟机状态
e) qemuDomainObjEndJob(driver,vm);
qemuDomainEventQueue(driver,event);
其中重点是qemuProcessStopCPUs:
{
.......
if(qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
goto cleanup;
ret = qemuMonitorStopCPUs(priv->mon);
qemuDomainObjExitMonitor(driver, vm);
}
如果支持json, 将使用json向qemu发送命令
qemuMonitorStopCPUs ==> qemuMonitorJSONStopCPUs(mon);
qemuMonitorJSONStopCPUs(qemuMonitorPtr mon) {
..........
ret =qemuMonitorJSONCommand(mon, cmd, &reply);
if (ret == 0)
ret =qemuMonitorJSONCheckError(cmd, reply);
..........
return ret;
}
qemuMonitorJSONCommand ==> qemuMonitorJSONCommandWithFd
小结: libvrit 可以直接通过ioctl 获取内核态kvm信息,或通过qemumonitor控制qemu.