libguestfs源码浅析
一般libguestfs的调用模式是这样的(以执行类似于virt-df命令的一段代码为例):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <guestfs.h> #define STRNEQ(a, b) (strcmp((a),(b)) != 0) int main (int argc, char * argv[]) { guestfs_h *g; int i, j; char *dom; char **devices = NULL; char **fses = NULL; if ((dom = argv[1]) == NULL) { perror("domain name is NULL"); exit (EXIT_FAILURE); } g = guestfs_create (); if (g == NULL) { perror ("failed to create libguestfs handle"); exit (EXIT_FAILURE); } guestfs_add_domain(g,(const char *)dom,GUESTFS_ADD_DOMAIN_READONLY,1,-1); guestfs_set_identifier (g,dom); guestfs_set_trace (g, 0); guestfs_set_verbose (g,0); if (guestfs_launch (g) == -1) { perror ("guestfs_launch\n"); return -1; } devices = guestfs_list_devices (g); if (devices == NULL){ perror ("guesfs_list_devices(g) == NULL"); return -1; } fses = guestfs_list_filesystems (g); if (fses == NULL){ perror ("guestfs_list_filesystem(g)"); return -1; } for (i = 0; fses[i] != NULL; i += 2) { if (STRNEQ (fses[i+1], "") && STRNEQ (fses[i+1], "swap") && STRNEQ (fses[i+1], "unknown")) { char *dev = fses[i]; struct guestfs_statvfs *stat = NULL; guestfs_push_error_handler (g, NULL, NULL); if (guestfs_mount_ro (g, dev, "/") == 0) { stat = guestfs_statvfs (g, "/"); guestfs_umount_all (g); } guestfs_pop_error_handler (g); if (!stat){ continue; } fprintf(stderr,"%s %d %d %.2lf%\n",dev,stat->blocks,stat->bfree,100.0*(stat->blocks-stat->bfree)/stat->blocks); } } free(devices); free(fses); guestfs_close (g); return 0; }
可以抽象出libguestfs调用的关键也是不可或缺的几步:
guestfs_h g = guestfs_create (); guestfs_add_domain(g,(const char *)dom,GUESTFS_ADD_DOMAIN_READONLY,1,-1); guestfs_set_identifier (g,dom); guestfs_set_trace (g, 0); guestfs_set_verbose (g,0); if (guestfs_launch (g) == -1) { perror ("guestfs_launch\n"); return -1; }
下面以这几个函数为切入点,分析libguestfs的源码和原理。
1 )Libguestfs组成
Libguestfs主要有三大部分:guestfsd、guestfs-lib、guestfish。其中:guestfsd是一个daemon,libguestfs是一个lib,guestfish是一个命令行工具。
Guestfsd是一个daemon,但是它不是运行在host的daemon,而是运行在guest上,libguestfs首先用febootstrap和febootstrap-supermin-helper两个工具将host中的kernel,一些modules,配置文件和一些工具package重新组合到一起,然后在后台启动一个qemu进程读取这个有febootstrap工具链生成的image。在qemu启动的guest里运行guestfsd,guestfsd通过socket和host进行通信,之间建立了一个通信协议,它可以通过socket接受来自host端guestfs-lib写到socket的数据,guestfsd通过分析接收到的数据,进而执行相应的do*操作,do操作实际上是对guest端普通命令的一些封装,如果想实现一个NEW API,只要在guestfsd里用相应的do_对普通命令进行封装即可,然后将这个普通命令程序通过febootstrap打包到qemu启动时读取的image中。
Guestfs-lib是一个库,它实现了一些libguestfs的库函数——guestfs_*。这些库函数向socket发送相应的数据,数据就会被guest端的guestfsd接收到,进而分析索要执行的操作。
Guestfish是对guestfs-lib接口函数的一些应用,guestfish的命令都是通过调用guestfs-lib的库函数来实现的。
因此,在使用libguestfs的时候,可以使用guestfish这样的命令行工具,也可以直接在程序(包括c,java,python等)中调用guestfs-lib实现的库函数。
2) Guestfish原理
Guestfish –a vm.img 启动的进程,交互命令行是main program,当运行run时,会创建一个child process,在child process中,qemu运行一个成为appliance的小虚拟机。创建子进程是由guest_launch函数完成的。在appliance中,运行了linux kernel和一系列用户空间工具(LVM,ext2等),以及guestfsd。main process中的libguestfs和这个guestfsd通过RPC进行交互。由child process的kernel来操作vm.img。

以上是libguestfs结构图:libguestfs主程序会fork出一个子进程,在子进程中创建一个特殊的虚拟机(通过使用supermin,这个虚拟机非常小,刚好能够启动,几乎删除了所有的可执行文件,只保留了目录结构,配置文件和必要的数据文件)。这个特殊的虚拟机运行着Linux内核、必要的用户空间程序以及guestfsd守护进程。主程序通过RPC协议和guestfsd交互。
guestfs_h
libguestfs句柄,定义在lib/guestfs-internal.h中,是一个非常复杂的结构体,封装了必要的配置信息、运行时信息、drivers、mount-local必需的API等所有与这个特殊的虚拟机相关的信息。
guestfs_create
guestfs_h句柄状态的初始化:主要包括初始的状态,内存大小信息,配置信息,调用guestfs_int_set_backend指定运行这个小型的特殊虚拟机方式:a.直接使用qume启动 b.使用libvirt启动和管理。
guestfs_add_domain/guestfs_add_libvirt_dom
若调用guestfs_add_domain,则最终也会转化为对guestfs_add_domain的调用,只是在这之间多了根据dom名称(或者uuid)查询到virDomainPtr对象的操作,参见以下简化的代码:
conn = guestfs_int_open_libvirt_connection (g, libvirturi, 0); /* Try UUID first. */ if (allowuuid) dom = virDomainLookupByUUIDString (conn, domain_name); /* Try ordinary domain name. */ if (!dom) dom = virDomainLookupByName (conn, domain_name); /* Try UUID first. */ if (allowuuid) dom = virDomainLookupByUUIDString (conn, domain_name); /* Try ordinary domain name. */ if (!dom) dom = virDomainLookupByName (conn, domain_name); r = guestfs_add_libvirt_dom_argv (g, dom, &optargs2);
因此主要还是看guestfs_add_libvirt_dom_argv做了什么。guestfs_add_libvirt_dom_argv进行一些参数的解析、设置和验证
| guestfs_impl_add_libvirt_dom| a.参数设置和检验(主要还是GUESTFS_ADD_LIBVIRT_DOMAIN_XXXX这些参数) | b.virtDomainGetInfo | c.get_domain_xml,获取domain的xml文档,下面获取disk信息需要使用 | d.for_each_disk (g, virDomainGetConnect (dom), doc, add_disk, &data); | a.解析xml文档 | b.add_disk(代码中add_disk为参数f) add_disk (guestfs_h *g,const char *filename, const char *format, int readonly_in_xml, const char *protocol, char *const *server, const char *username,const char *secret, void *datavp) | guestfs_add_drive_opts_argv (g, filename, &optargs); | guestfs_impl_add_drive_opts (g, filename, optargs); | add_drive_to_handle (g, drv); /* drv is now owned by the handle drv是struct derive类型的结构体,添加支持多种协议
如ftp、ftps、gluster、http、iscsi等*/
这梁行代码主要是用户调试跟踪,不用太多关注,调用位置可以紧跟guestfs_create之后,这样可以更早的跟踪调试信息。
guestfs_set_trace (g, 0); guestfs_set_verbose (g,0);
guestfs_launch
guestfs_launch最终调用的是launch_libvirt和launch_direct二者之一
关于launch_libvirt
Libguestfs的性能分析
Libguestfs应用是IO绑定的,可以并行运行多个appliances。假设有充足的内存,1个appliance和多个appliances之间的时间性能仅仅有细微的差别。
在一个2核(4线程),16G内存的物理机上进行测试,下图展示了1个到20个appliances并行运行时良好的时间性能表现:

从表中可以看出,大量部署虚机时,可以并行部署3台,与运行单个appliance的时间性能几乎是一样的,可以大大节约部署的时间。
撰写过程中......

浙公网安备 33010602011771号