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的时间性能几乎是一样的,可以大大节约部署的时间。


撰写过程中......

 

posted @ 2017-06-22 19:44  Cloud2020  阅读(939)  评论(0)    收藏  举报