libvirt-virsh代码解读

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014104588/article/details/51613958
virsh是libvirt的一个命令行工具。相当于libvirt的一个客户端(libvirtd是服务器)。每次执行virsh命令,程序是从virsh.c中的main函数开始执行。

在virsh中有几个比较重要的结构体,其一如下:

struct _vshControl {
    const char *name;           /* hardcoded name of the binary that cannot
                                 * be changed without recompilation compared
                                 * to program name */
    char *connname;             /* connection name */
    char *progname;             /* program name */
    vshCmd *cmd;                /* the current command */
    char *cmdstr;               /* string with command */
    bool imode;                 /* interactive mode? */
    bool quiet;                 /* quiet mode */
    bool timing;                /* print timing info? */
    int debug;                  /* print debug messages? */
    char *logfile;              /* log file name */
    int log_fd;                 /* log file descriptor */
    char *historydir;           /* readline history directory name */
    char *historyfile;          /* readline history file name */
    virThread eventLoop;
    virMutex lock;
    bool eventLoopStarted;
    bool quit;
    int eventPipe[2];           /* Write-to-self pipe to end waiting for an
                                 * event to occur */
    int eventTimerId;           /* id of event loop timeout registration */

    int keepalive_interval;     /* Client keepalive interval */
    int keepalive_count;        /* Client keepalive count */

# ifndef WIN32
    struct termios termattr;    /* settings of the tty terminal */
# endif
    bool istty;                 /* is the terminal a tty */

    const vshClientHooks *hooks;/* mandatory client specific hooks */
    void *privData;             /* client specific data */
};

从virsh.c中main函数开始分析

    if (!(progname = strrchr(argv[0], '/')))
        progname = argv[0];
    else
        progname++;
    ctl->progname = progname;

该部分是获取命令的名字,命令行启动virsh有两种方法,一是直接virsh xxx 二是利用绝对路径启动。对于virsh命令,progname就是“virsh”

    if (isatty(STDIN_FILENO)) {
        ctl->istty = true;
        if (tcgetattr(STDIN_FILENO, &ctl->termattr) < 0)
            ctl->istty = false;
    }

该部分就是获取标准输入设备的一些信息。

    if (virMutexInit(&ctl->lock) < 0) {
        vshError(ctl, "%s", _("Failed to initialize mutex"));
        return EXIT_FAILURE;
    }

初始化锁。

    if (virInitialize() < 0) {
        vshError(ctl, "%s", _("Failed to initialize libvirt"));  
        return EXIT_FAILURE;
    }

virInitialize函数的实现如下

int
virInitialize(void)
{
    if (virOnce(&virGlobalOnce, virGlobalInit) < 0)
        return -1;

    if (virGlobalError)
        return -1;
    return 0;
}

在多线程环境里面,virGlobalInit函数只执行一次,该函数对于virsh端主要初始化如下

virErrorInitialize();
virLogSetFromEnv();
remoteRegister();

virErrorInitialize函数用于初始化错误信息变量,该变量是线程特定数据。具体调用方式virThreadLocalInit(&virLastErr, virLastErrFreeData);virLastErr就是一个线程特定数据。
virLogSetFromEnv函数的实现如下

void
virLogSetFromEnv(void)
{
    const char *debugEnv;

    if (virLogInitialize() < 0)
        return;

    debugEnv = virGetEnvAllowSUID("LIBVIRT_DEBUG");
    if (debugEnv && *debugEnv)
        virLogParseDefaultPriority(debugEnv); /*virLogDefaultPriority*/
    debugEnv = virGetEnvAllowSUID("LIBVIRT_LOG_FILTERS");
    if (debugEnv && *debugEnv)
        virLogParseFilters(debugEnv);  /*LOGµÈ¼¶*/
    debugEnv = virGetEnvAllowSUID("LIBVIRT_LOG_OUTPUTS");
    if (debugEnv && *debugEnv)
        virLogParseOutputs(debugEnv);  /*Êä³öµ½ÏµÍ³ÈÕÖ¾»¹ÊÇ×Ô¼ºµÄ?*/
}

可以看见virLogSetFromEnv函数是用来从系统获取LIBVIRT_DEBUG,LIBVIRT_LOG_FILTERS,LIBVIRT_LOG_OUTPUTS三个配置信息,如果获取到则将配置信息赋值给对应的全局变量。最终LIBVIRT_DEBUG的值会赋值给virLogDefaultPriority。LIBVIRT_LOG_FILTERS的值赋值给virLogFilters。LIBVIRT_LOG_OUTPUTS的值赋值给virLogOutputs。

remoteRegister函数是初始化远程模块。在virsh端没有注册真正的虚拟化模块,知识注册了一个远程模块。

int
remoteRegister(void)
{
    if (virRegisterConnectDriver(&connect_driver,
                                 false) < 0)
        return -1;
    if (virRegisterStateDriver(&state_driver) < 0)
        return -1;

    return 0;
}

virRegisterConnectDriver函数是将connect_driver变量赋值给virConnectDriverTab数组,该数组存储已经注册的驱动。connect_driver结构中包含了针对remote模块各种操作的接口。connect_driver的初始化如下

static virConnectDriver connect_driver = {
    .hypervisorDriver = &hypervisor_driver,
    .interfaceDriver = &interface_driver,
    .networkDriver = &network_driver,
    .nodeDeviceDriver = &node_device_driver,
    .nwfilterDriver = &nwfilter_driver,
    .secretDriver = &secret_driver,
    .storageDriver = &storage_driver,
};

重新回到main函数中,接下来是

    if ((defaultConn = virGetEnvBlockSUID("VIRSH_DEFAULT_CONNECT_URI")))
        ctl->connname = vshStrdup(ctl, defaultConn);

该部分从系统环境里面获取默认连接的名字。比如qemu://system

    if (!vshInit(ctl, cmdGroups, NULL))
        exit(EXIT_FAILURE);

该部是初始化操作函数数组。cmdGroups是一个全局的变量,该变量存储了和通过命令行敲入操作匹配的操作函数。该函数里面还调用了vshInitDebug(ctl); vshInitDebug函数是获取日志等级和日志文件。

接下来是virshParseArgv函数,该函数会解析命令行传进来的参数。先解析有没有传入配置项,最后再调用vshCommandStringParse函数或者vshCommandArgvParse函数去解析真正的操作参数。

    if (argc == optind) {  /*no option , command is onli "virsh"*/
        ctl->imode = true;
    } else {
        /* parse command */
        ctl->imode = false;
        if (argc - optind == 1) {
            vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
            return vshCommandStringParse(ctl, argv[optind]);
        } else {
            return vshCommandArgvParse(ctl, argc - optind, argv + optind);
        }
    }

经过解析过会,程序会调用vshCmddefSearch函数从cmdGroups数组中寻找对应的操作函数。然后再获取传进来的操作的参数。并将参数存储在opts链表里,获取到的操作函数则存储在def里。opts和def是在_vshCmd结构体中定义的,如下:

struct _vshCmd {
    const vshCmdDef *def;       /* command definition */
    vshCmdOpt *opts;            /* list of command arguments */
    vshCmd *next;      /* next command */
};

最后将该vshCmd结构体变量存储在ctl->cmd中。

参数解析完毕后回到main中开始执行virshInit函数。
该函数中会执行virEventRegisterDefaultImpl函数,该函数中先执行virEventPollInit来创建一个管道,并将管道的读端加入到eventLoop数组中。读端的事件服务函数仅仅是从管道中读取一字节,然后丢弃。
然后再调用virEventRegisterImpl函数去初始化全局变量addHandleImpl、updateHandleImpl、removeHandleImpl、addTimeoutImpl、updateTimeoutImpl、removeTimeoutImpl。

virEventRegisterDefaultImpl函数执行完后,开始调用如下函数

    if (virThreadCreate(&ctl->eventLoop, true, vshEventLoop, ctl) < 0)
        return false;

该部分先创造一个线程,该线程会监听eventLoop中的所有描述符。如果有期待时间发生,则调用事件处理函数。
最后调用virshReconnect(ctl)函数去连接到对应的模块。

最后去调用vshCommandRun(ctl, ctl->cmd);
该函数里会调用cmd->def->handler(ctl, cmd),最总就会调用的寻找到的remote端的操作函数。然后解析返回值来判断操作是否正确。

猜你喜欢

转载自blog.csdn.net/u014104588/article/details/51613958