libvirt事件机制

1 libvirt事件机制

事件机制是libvirt最为核心的一种机制。libvirt通常可以看做两个部分,libvirt客户端和libvirt服务端。libvirt客户端就是我们平时所熟识的libvirt接口部分,是通过提供libvirt共享库以及头文件的形式直接给客户使用的部分,相当于libvirt的眼睛和耳朵,接受客户的各种命令;libvirt服务端就是我们称作libvirtd的守护进程,也是libvirt的大脑,libvirtd接到客户端传来的命令后,调用相应驱动中注册的各种相应动作来完成接收到的命令,这里驱动就相当于人的身体来完成各种动作。事件机制就好比是神经网络,串联起一个有血有肉,能跑能跳的libvirt。libvirt接口和libvirtd用于通信的RPC机制和事件上报功能其本质都是libvirt事件机制的一种应用。

1. 注册默认事件处理函数。
事件机制本质上就是一个分发器,接收到事件,并根据事件找到与之匹配的动作,完成动作后,再去接收事件,将事件分发到对应的动作函数。要支撑这一系列行为,客户需要首先注册默认事件处理函数,这些处理函数都是支持事件机制运行的基础,此动作相当于事件机制本身的初始化。
函数原型:
int virEventRegisterDefaultImpl(void);

2. 打开连接。
客户建立与libvirt的连接。这里面包括打开syslog日志、libvirt接口相关驱动初始化(如remote_driver)等动作。大体上都是为后续的libvirt接口调用获得准入资格,这里我们是为了客户下一步注册并使用事件机制做准备。打开成功后,我们会得到一个virConnectPtr类型的连接指针,就像一把开启libvirt功能的钥匙。俗话说“一把钥匙开一把锁”,每一个连接指针背后都是一套完整的复杂数据结构。
函数原型:
virConnectPtr          virConnectOpenAuth      (const char *name,
                                                virConnectAuthPtr auth,
                                                unsigned int flags);

3. 注册事件回调函数。
注册指定事件的回调函数,当事件处理系统收到指定事件时,就会触发对应的回调函数,完成客户的指定动作。
函数原型一:默认只能注册生命周期类型事件(life cycle)的回调函数,不推荐使用。
int virConnectDomainEventRegister(virConnectPtr conn,
                                 virConnectDomainEventCallback cb,
                                 void *opaque,
                                 virFreeCallback freecb);
函数原型二:可以注册指定事件类型的回调函数,推荐使用。
/* Use VIR_DOMAIN_EVENT_CALLBACK() to cast the 'cb' parameter  */
int virConnectDomainEventRegisterAny(virConnectPtr conn,
                                    virDomainPtr dom, /* Optional, to filter */
                                    int eventID,
                                    virConnectDomainEventGenericCallback cb,
                                    void *opaque,
                                    virFreeCallback freecb);

4. 启动事件处理功能。
要求客户注册事件回调函数后,通过while循环来调用事件处理函数,从而启动事件处理功能。
函数原型:
int virEventRunDefaultImpl(void);

5. 触发事件上报。
事件上报通常是在一些libvirt接口调用的过程中,由libvirtd将事件压入队列,从而触发事件从libvirtd经由RPC回传到libvirt接口,一直传送到由客户开启的事件处理系统中,触发客户注册的回调函数。注意:前提是,客户注册了对应被触发事件类型的回调函数,事件才会上报,否则,事件在libvirtd端就会被丢弃掉。
以虚拟机的重启流程为例,其整个过程中要多次触发我们测试用例中注册的事件上报。
函数原型:
int                    virDomainReboot         (virDomainPtrdomain,
                                                unsigned int flags);

6. 事件上报处理。
事件上报处理实际上是从客户注册的事件在libvirtd端被触发,到客户注册的回调函数在客户端被调用的整个流程。其中会经历两个完整的事件处理系统,libvirtd端开启的和客户自己开启的两套独立的系统。
7. 注销事件回调函数。
将指定的事件回调函数相关资源释放掉。
函数原型一:与virConnectDomainEventRegister配对使用,不推荐使用。
int virConnectDomainEventDeregister(virConnectPtr conn,
                                   virConnectDomainEventCallback cb);
函数原型二:与virConnectDomainEventRegisterAny配对使用,推荐使用。
int virConnectDomainEventDeregisterAny(virConnectPtr conn,
                                      int callbackID);

8. 关闭连接。
将连接相关资源释放掉。
函数原型:
int                    virConnectClose         (virConnectPtrconn);

1.1.2 
 
event-test.c是libvirt社区提供的客户如何应用事件机制的一个用例。我们可以顺着这条主线理清事件机制的功能和原理。下面我们解析下其中的main函数:
main
int main(int argc, char **argv)
{
    int callback1ret = -1;
    int callback2ret = -1;
    int callback3ret = -1;
    int callback4ret = -1;
    int callback5ret = -1;
    int callback6ret = -1;
    int callback7ret = -1;
    int callback8ret = -1;
    int callback9ret = -1;
    struct sigaction action_stop;

    memset(&action_stop, 0, sizeofaction_stop);
    /*定义收到SIGTERM信号和收到SIGINT信号所要响应的停止动作*/
    action_stop.sa_handler = stop;

    if (argc > 1 && STREQ(argv[1],"--help")) {
        usage(argv[0]);
        return -1;
    }
    /*注册默认的事件处理函数*/
    virEventRegisterDefaultImpl();
virConnectPtr dconn = NULL;
    /*打开连接*/
    dconn = virConnectOpenAuth(argc > 1 ? argv[1] : NULL,
                              virConnectAuthPtrDefault,
                              VIR_CONNECT_RO);
    if (!dconn) {
        printf("error opening\n");
        return -1;
    }

    sigaction(SIGTERM, &action_stop, NULL);
    sigaction(SIGINT, &action_stop, NULL);

    VIR_DEBUG("Registering domain eventcbs");

/* Add 2 callbacks to prove this works with more than just one */
/*只能默认注册VIR_DOMAIN_EVENT_ID_LIFECYCLE类型的事件回调函数myDomainEventCallback1*/
    callback1ret = virConnectDomainEventRegister(dconn,myDomainEventCallback1,
                                                strdup("callback 1"), myFreeFunc);
/*
注册VIR_DOMAIN_EVENT_ID_LIFECYCLE 类型的事件回调函数myDomainEventCallback2
那问题就来了,如果对同一类型的事件同时注册了多个不同的回调函数,会执行哪一个呢?
答案是所有的都会执行,在 XX 小节,我们会看到,这是一个for循环处理的流程。
另外:我们不难发现virConnectDomainEventRegisterAny要比virConnectDomainEventRegister多了2个参数,第二个参数是域指针,标明针对哪个域的事件进行注册,此处为NULL,表明该注册针对所有的域生效;第三个参数是事件类型,可以注册多种事件类型的回调函数。virConnectDomainEventRegisterAny其实是virConnectDomainEventRegister的一个加强升级版。
*/
    callback2ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_LIFECYCLE,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventCallback2),
                                                   strdup("callback 2"), myFreeFunc);

    /* 注册VIR_DOMAIN_EVENT_ID_REBOOT类型的事件回调函数myDomainEventRebootCallback */
    callback3ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_REBOOT,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventRebootCallback),
                                                   strdup("callback reboot"), myFreeFunc);
    /* 注册VIR_DOMAIN_EVENT_ID_RTC_CHANGE类型的事件回调函数myDomainEventRTCChangeCallback */
    callback4ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_RTC_CHANGE,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventRTCChangeCallback),
                                                   strdup("callback rtcchange"), myFreeFunc);

    /* 注册VIR_DOMAIN_EVENT_ID_WATCHDOG类型的事件回调函数myDomainEventWatchdogCallback */
    callback5ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_WATCHDOG,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventWatchdogCallback),
                                                   strdup("callback watchdog"), myFreeFunc);
    /* 注册VIR_DOMAIN_EVENT_ID_IO_ERROR 类型的事件回调函数myDomainEventIOErrorCallback */
    callback6ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_IO_ERROR,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventIOErrorCallback),
                                                   strdup("callback io error"), myFreeFunc);

    /* 注册VIR_DOMAIN_EVENT_ID_GRAPHICS类型的事件回调函数myDomainEventGraphicsCallback */
    callback7ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_GRAPHICS,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventGraphicsCallback),
                                                   strdup("callback graphics"), myFreeFunc);
    /* 注册VIR_DOMAIN_EVENT_ID_CONTROL_ERROR类型的事件回调函数myDomainEventControlErrorCallback */
    callback8ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_CONTROL_ERROR,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventControlErrorCallback),
                                                   strdup("callback control error"), myFreeFunc);

    /* 注册VIR_DOMAIN_EVENT_ID_DISK_CHANGE类型的事件回调函数myDomainEventDiskChangeCallback */
    callback9ret = virConnectDomainEventRegisterAny(dconn,
                                                   NULL,
                                                   VIR_DOMAIN_EVENT_ID_DISK_CHANGE,
                                                   VIR_DOMAIN_EVENT_CALLBACK(myDomainEventDiskChangeCallback),
                                                   strdup("disk change"), myFreeFunc);

    if ((callback1ret != -1) &&
        (callback2ret != -1) &&
        (callback3ret != -1) &&
        (callback4ret != -1) &&
        (callback5ret != -1) &&
        (callback6ret != -1) &&
        (callback7ret != -1) &&
        (callback9ret != -1)) {

       /*
设置心跳检测,此处是为了检查服务端libvirtd是否死掉了,设定后,客户端会定时向libvirtd发送信息,并等待libvirtd的响应,如果libvirtd长时间没响应,则认为libvirtd死掉了。第二个参数是心跳检测的时间间隔,此处为5秒发一次心跳;第三个参数是发心跳的次数,此处是发3次心跳。那么我们此处的设定就是每隔5秒向libvirtd发送一次消息,如果连发3个消息都没有得到libvirtd的响应,当发第四个消息的时候会断开libvirt连接。详见心跳检测机制一节。
*/
        if (virConnectSetKeepAlive(dconn, 5,3) < 0) {
            virErrorPtrerr = virGetLastError();
           fprintf(stderr, "Failed to start keepalive protocol: %s\n",
                   err && err->message ? err->message : "Unknown error");
            run = 0;
        }
        /*
启动事件处理功能。通过while循环来反复执行virEventRunDefaultImpl,实现事件处理的功能。
stop函数中会将run置0,即程序收到外面发来的终止信号的时候,会退出while循环;或者是,通过心跳检测,发现libvirtd没有响应,连接被自动断开了,也会跳出while循环。
        */
        while (run &&virConnectIsAlive(dconn) == 1) {
            if(virEventRunDefaultImpl() < 0) {
               virErrorPtr err = virGetLastError();
               fprintf(stderr, "Failed to run event loop: %s\n",
                       err && err->message ? err->message : "Unknown error");
            }
        }

       VIR_DEBUG("Deregistering event handlers");
/*注销事件回调函数*/
       virConnectDomainEventDeregister(dconn, myDomainEventCallback1);
       virConnectDomainEventDeregisterAny(dconn, callback2ret);
       virConnectDomainEventDeregisterAny(dconn, callback3ret);
       virConnectDomainEventDeregisterAny(dconn, callback4ret);
       virConnectDomainEventDeregisterAny(dconn, callback5ret);
       virConnectDomainEventDeregisterAny(dconn, callback6ret);
       virConnectDomainEventDeregisterAny(dconn, callback7ret);
        virConnectDomainEventDeregisterAny(dconn,callback9ret);
        if (callback8ret != -1)
           virConnectDomainEventDeregisterAny(dconn, callback8ret);
    }

VIR_DEBUG("Closing connection");
/*关闭连接*/
    if (dconn && virConnectClose(dconn) < 0) {
        printf("error closing\n");
    }

    printf("done\n");
    return 0;
}

1.2 注册默认事件处理函数

1. 重置最近一次错误。
这个最近一次错误是指捕捉到的库函数调用失败产生的error,这个信息是保存在线程的存储结构中的,不同的线程可以分别访问自己的错误码,重置的也只是自己线程的错误码。
函数原型:
void   virResetLastError (void);

2. 事件轮询初始化。
所谓的事件轮询,简单的来说就是一个线程没事儿的时候阻塞在那里,监控一堆用户指定的句柄,看其中是否有句柄准备好进行IO操作,有就退出阻塞,没有的话就继续等待或者是超时退出等待。我们的事件机制是利用了建立管道的两端,读句柄和写句柄来做文章的。向写句柄里写信息,管道另一端的读句柄就会收到。我们的事件处理线程监控的句柄之一就是管道的读句柄。管道写句柄那边一写,这边读句柄就会有IO,从而让事件处理线程退出阻塞,进行事件处理。说白了,就相当于一个电子自来水开关,我要洗手了(有事件要处理了),就把手靠近(向写句柄写信息),水出来了(退出阻塞),我开始洗手(事件处理),洗完手转身闪人(事件处理完了),水又停了(进入下一轮阻塞),又有人来洗手(新一轮事件处理);另外,除了主动写管道结束阻塞之外,还可以通过设定超时时间,让下一轮事件处理的轮询超时时间为0,即没有等待,直接向下走,进行事件处理。实际上,由于libvirtd端的大部分事件触发点都是在事件处理线程中,即事件处理线程并不是处于阻塞状态,而是处于事件处理状态,那么我们要触发的事件只能等当前事件处理线程结束,进入下一轮事件处理时,立即响应了。这时,我们会通过设定超时时间来达到下一轮不阻塞,或者短暂阻塞,立即响应的目的。我们大部分的事件处理都是采用这种方式。
言归正传,事件轮询初始化简言之就是一个建立管道,将管道的读句柄加入轮询监控的过程。关于管道和轮询,详情见pipe2和poll
函数原型:
/**
 * virEventPollInit: Initialize the event loop
 *
 * returns -1 if initialization failed
 */
int virEventPollInit(void);

3. 事件执行动作注册。
将全局静态变量的函数指针指向具体的事件处理函数入口。由于是所有事件处理所共用的工具函数,因此采用全局静态变量,方便使用。
函数原型:
void virEventRegisterImpl(virEventAddHandleFunc addHandle,
                         virEventUpdateHandleFunc updateHandle,
                         virEventRemoveHandleFunc removeHandle,
                         virEventAddTimeoutFunc addTimeout,
                         virEventUpdateTimeoutFunc updateTimeout,
                         virEventRemoveTimeoutFunc removeTimeout);

1.2.2 
virEventRegisterDefaultImpl
int virEventRegisterDefaultImpl(void)
{
    VIR_DEBUG("registering default event implementation");
    /*重置最近一次错误*/
    virResetLastError();
    /*事件轮询初始化*/
    if (virEventPollInit() < 0) {
        virDispatchError(NULL);
        return -1;
    }
    /*事件执行动作注册*/
    virEventRegisterImpl(
        virEventPollAddHandle,
        virEventPollUpdateHandle,
        virEventPollRemoveHandle,
        virEventPollAddTimeout,
        virEventPollUpdateTimeout,

virResetLastError
/**
 * virResetLastError:
 *
 * Reset the last error caught at the library level.
 *
 * The error object is kept in thread local storage, so separate
 * threads can safely access this concurrently, only resetting
 * their own error object.
 */
void
virResetLastError(void)
{
    /*获得指向最近一次错误实例的指针*/
    virErrorPtr err = virLastErrorObject();
if (err)
    /*重置错误,即将err清零*/
        virResetError(err);
}
virLastErrorObject
static virErrorPtr
virLastErrorObject(void)
{
virErrorPtr err;
/*获得当前线程的最近一次错误,virLastErr为全局变量*/
err = virThreadLocalGet(&virLastErr);
/*如果err为NULL,说明是第一次,要为err分配内存空间,并将virLastErr的值传给err*/
    if (!err) {
        if (VIR_ALLOC(err) < 0)
            return NULL;
        if(virThreadLocalSet(&virLastErr, err) < 0)
           VIR_FREE(err);
    }
    return err;
}

virEventPollInit
int virEventPollInit(void)
{
    /*事件轮询锁的初始化*/
    if (virMutexInit(&eventLoop.lock) < 0) {
        virReportSystemError(errno,"%s",
                            _("Unable to initialize mutex"));
        return -1;
    }
    /*创建事件轮询管道,作为控制事件轮询的一个开关*/
    if (pipe2(eventLoop.wakeupfd, O_CLOEXEC | O_NONBLOCK) <0) {
        virReportSystemError(errno,"%s",
                            _("Unable to setup wakeup pipe"));
        return -1;
    }

    if(virEventPollAddHandle(eventLoop.wakeupfd[0],
                             VIR_EVENT_HANDLE_READABLE,
                             virEventPollHandleWakeup, NULL, NULL) < 0) {
       virReportError(VIR_ERR_INTERNAL_ERROR,
                      _("Unable to add handle %d to event loop"),
                      eventLoop.wakeupfd[0]);
       VIR_FORCE_CLOSE(eventLoop.wakeupfd[0]);
        VIR_FORCE_CLOSE(eventLoop.wakeupfd[1]);
        return -1;
    }

    return 0;
}

virEventPollAddHandle
/*
 * Register a callback for monitoring file handle events.
 * NB, it *must* be safe to call this from within a callback
 * For this reason we only ever append to existing list.
 */
int virEventPollAddHandle(int fd, int events,
                         virEventHandleCallback cb,
                         void *opaque,
                         virFreeCallback ff) {
    int watch;
virMutexLock(&eventLoop.lock);
/*
/* Allocate extra slots for virEventPollHandle/virEventPollTimeout
   records in this multiple */
#define EVENT_ALLOC_EXTENT 10
*/
    if (eventLoop.handlesCount == eventLoop.handlesAlloc) {
        EVENT_DEBUG("Used %zu handleslots, adding at least %d more",
                   eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT);
        /*重新划定eventLoop.handles指向内存块大小 */
        if (VIR_RESIZE_N(eventLoop.handles,eventLoop.handlesAlloc,
                        eventLoop.handlesCount, EVENT_ALLOC_EXTENT) < 0) {
           virMutexUnlock(&eventLoop.lock);
            return -1;
        }
    }

    watch = nextWatch++;

/* 
将事件的相关信息初始化到事件轮询的handles数组结构体中
watch是监控号;
fd是被监控句柄;
events是被监控事件;
cb是执行功能回调函数的指针;
ff是释放回调函数的指针;
deleted表明该事件是否被删除了,0表示否,1表示是
*/
    eventLoop.handles[eventLoop.handlesCount].watch = watch;
    eventLoop.handles[eventLoop.handlesCount].fd = fd;
    eventLoop.handles[eventLoop.handlesCount].events =
                                        virEventPollToNativeEvents(events);
/* cb 指向 virEventPollHandleWakeup */
    eventLoop.handles[eventLoop.handlesCount].cb = cb;
    eventLoop.handles[eventLoop.handlesCount].ff = ff;
    eventLoop.handles[eventLoop.handlesCount].opaque = opaque;
    eventLoop.handles[eventLoop.handlesCount].deleted = 0;

/* 记录监控句柄数 */
    eventLoop.handlesCount++;

    virEventPollInterruptLocked();

    PROBE(EVENT_POLL_ADD_HANDLE,
          "watch=%d fd=%devents=%d cb=%p opaque=%p ff=%p",
          watch, fd, events, cb,opaque, ff);
    virMutexUnlock(&eventLoop.lock);

    return watch;
}

virEventPollToNativeEvents
int
virEventPollToNativeEvents(int events)
{
int ret = 0;
/* 转化成本地事件*/
    if(events & VIR_EVENT_HANDLE_READABLE)
        ret |= POLLIN;
    if(events & VIR_EVENT_HANDLE_WRITABLE)
        ret |= POLLOUT;
    if(events & VIR_EVENT_HANDLE_ERROR)
        ret |= POLLERR;
    if(events & VIR_EVENT_HANDLE_HANGUP)
        ret |= POLLHUP;
    return ret;
}

virEventRegisterImpl
/**
 * virEventRegisterImpl:
 * @addHandle: the callback to add fd handles
 * @updateHandle: the callback to update fd handles
 * @removeHandle: the callback to remove fd handles
 * @addTimeout: the callback to add a timeout
 * @updateTimeout: the callback to update a timeout
 * @removeTimeout: the callback to remove a timeout
 *
 * Registers an event implementation, to allow integration
 * with an external event loop. Applications would use this
 * to integrate with the libglib2 event loop, or libevent
 * or the QT event loop.
 *
 * If an application does not need to integrate with an
 * existing event loop implementation, then the
 * virEventRegisterDefaultImpl method can be used to setup
 * the generic libvirt implementation.
 */
void virEventRegisterImpl(virEventAddHandleFunc addHandle,
                         virEventUpdateHandleFunc updateHandle,
                         virEventRemoveHandleFunc removeHandle,
                         virEventAddTimeoutFunc addTimeout,
                         virEventUpdateTimeoutFunc updateTimeout,
                         virEventRemoveTimeoutFunc removeTimeout)
{
    VIR_DEBUG("addHandle=%p updateHandle=%p removeHandle=%p"
             "addTimeout=%p updateTimeout=%p removeTimeout=%p",
             addHandle, updateHandle, removeHandle,
             addTimeout, updateTimeout, removeTimeout);
/*
紫色变量定义为静态全局变量
*/
    addHandleImpl = addHandle;// virEventPollAddHandle
    updateHandleImpl = updateHandle;// virEventPollUpdateHandle
    removeHandleImpl = removeHandle;// virEventPollRemoveHandle
    addTimeoutImpl = addTimeout;// virEventPollAddTimeout
    updateTimeoutImpl = updateTimeout;//virEventPollUpdateTimeout
    removeTimeoutImpl = removeTimeout;//virEventPollRemoveTimeout
}

1. 初始化。
1) 日志设定。
设定日志级别,打开syslog日志的一系列动作。
2) 初始化gnutls库。
使用virNetTLS需要用到,我们似乎没有用过。
3) 注册驱动。
客户端对我们来说最主要的是对remote驱动的注册,注册就相当于一个全局驱动变量初始化的过程,是一个从通用到指定驱动的过程,只有注册了驱动,后续在调用libvirt接口时,我们才能找到对应的驱动程序的动作,说白了,就是一个函数指针赋值过程。remote驱动是连接libvirt客户端和libvirt服务端的重要驱动,是RPC机制的重要载体,相当于血肉,无论libvirtd底层用户使用何种驱动,libxl也好,xen也好,qemu也好,中间都要经过remote驱动,才能将信息从libvirt接口(客户端)传到libvirtd(服务端)。
2. 打开动作。
1) 获得连接。
创建连接结构的实例,并进行初始化。
2) 获得配置文件。
解析配置文件信息/etc/libvirt/libvirt.conf
3) 打开驱动。
这里面主要经历了RPC流程和事件处理流程。实际在libvirtd端就赋值了REMOTE_AUTH_NONE,其它也没啥。重点关注代码流程和结构,尤其是如何在libvirtd端找到对应的处理函数。

virConnectOpenAuth
/**
 * virConnectOpenAuth:
 * @name: URI of the hypervisor
 * @auth: Authenticate callback parameters
 * @flags: bitwise-OR of virConnectFlags
 *
 * This function should be called first to get a connection to the
 * Hypervisor. If necessary, authentication will be performed fetching
 * credentials via the callback
 *
 * See virConnectOpen for notes about environment variables which can
 * have an effect on opening drivers
 *
 * Returns a pointer to the hypervisor connection or NULL in case of error
 *
 * URIs are documented at http://libvirt.org/uri.html
 */
virConnectPtr
virConnectOpenAuth(const char *name,
                  virConnectAuthPtr auth,
                  unsigned int flags)
{
    virConnectPtr ret = NULL;
/*初始化*/
    if (virInitialize() < 0)
        goto error;

VIR_DEBUG("name=%s, auth=%p, flags=%x", NULLSTR(name),auth, flags);
/*重置错误*/
virResetLastError();
/*打开动作*/
    ret = do_open (name, auth, flags);
    if (!ret)
        goto error;
    return ret;

error:
    virDispatchError(NULL);
    return NULL;
}

virInitialize
/**
 * virInitialize:
 *
 * Initialize the library.
 *
 * This method is invoked automatically by any of the virConnectOpen API
 * calls. Since release 1.0.0, there is no need to call this method even
 * in a multithreaded application, since initialization is performed in
 * a thread safe manner.
 *
 * The only time it would be necessary to call virInitialize is if the
 * application did not invoke virConnectOpen as its first API call.
 *
 * Returns 0 in case of success, -1 in case of error
 */
int
virInitialize(void)
{
/*
全局初始化,注册部分驱动等,如remote驱动(传入的是驱动的指针,指向的是实例化的驱动结构,里面对每个功能针对不同驱动实例化,实例化后是一个个具体的函数指针)
*/
    if (virOnce(&virGlobalOnce, virGlobalInit) < 0)
        return -1;

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

virGlobalInit
static void
virGlobalInit(void)
{
    /* 线程初始化:什么都没做,直接返回0;错误初始化:注册了错误结构体释放函数*/
    if (virThreadInitialize() < 0 ||
        virErrorInitialize() < 0)
        goto error;

    gcry_control(GCRYCTL_SET_THREAD_CBS,&virTLSThreadImpl);
    gcry_check_version(NULL);
/*日志设定*/
    virLogSetFromEnv();
/*初始化gnutls库*/
    virNetTLSInit();

#if HAVE_LIBCURL
    curl_global_init(CURL_GLOBAL_DEFAULT);
#endif

    VIR_DEBUG("register drivers");

#if HAVE_WINSOCK2_H
    if (winsock_init () == -1)
        goto error;
#endif

    if (!bindtextdomain(PACKAGE, LOCALEDIR))
        goto error;

    /*
     * Note that the order is important: the first oneshave a higher
     * priority when calling virConnectOpen.
     */
/*
如果有对应驱动的宏定义,就引入对应驱动注册流程,里面会对一些驱动相关的全局变量数组进行初始化。下面我们以remote驱动为例讲解。
*/
#ifdef WITH_TEST
    if (testRegister() == -1)
        goto error;
#endif
#ifdef WITH_OPENVZ
    if (openvzRegister() == -1)
        goto error;
#endif
#ifdef WITH_VMWARE
    if (vmwareRegister() == -1)
        goto error;
#endif
#ifdef WITH_PHYP
    if (phypRegister() == -1)
        goto error;
#endif
#ifdef WITH_VBOX
    if (vboxRegister() == -1)
        goto error;
#endif
#ifdef WITH_ESX
    if (esxRegister() == -1)
        goto error;
#endif
#ifdef WITH_HYPERV
    if (hypervRegister() == -1)
        goto error;
#endif
#ifdef WITH_XENAPI
    if (xenapiRegister() == -1)
        goto error;
#endif
#ifdef WITH_PARALLELS
    if (parallelsRegister() == -1)
        goto error;
#endif
#ifdef WITH_REMOTE

    if (remoteRegister () == -1)
        goto error;
#endif

    return;

error:
    virGlobalError = true;
}

virLogSetFromEnv
/**
 * virLogSetFromEnv:
 *
 * Sets virLogDefaultPriority, virLogFilters and virLogOutputs based on
 * environment variables.
 */
void
virLogSetFromEnv(void)
{
    char *debugEnv;

debugEnv = getenv("LIBVIRT_DEBUG");
/*设定日志级别*/
    if (debugEnv && *debugEnv)
        virLogParseDefaultPriority(debugEnv);
    debugEnv = getenv("LIBVIRT_LOG_FILTERS");
    if (debugEnv && *debugEnv)
        virLogParseFilters(debugEnv);
debugEnv = getenv("LIBVIRT_LOG_OUTPUTS");
/*解析syslog日志的路径,并打开syslog文件*/
    if (debugEnv && *debugEnv)
        virLogParseOutputs(debugEnv);
}

virLogParseDefaultPriority
/**
 * virLogParseDefaultPriority:
 * @priority: string defining the desired logging level
 *
 * Parses and sets the default log priority level. It can take a string or
 * number corresponding to the following levels:
 *    1: DEBUG
 *    2: INFO
 *    3: WARNING
 *    4: ERROR
 *
 * Returns the parsed log level or -1 on error.
 */
int
virLogParseDefaultPriority(const char *priority)
{
    int ret = -1;

    if (STREQ(priority, "1") ||STREQ(priority, "debug"))
        ret =virLogSetDefaultPriority(VIR_LOG_DEBUG);
    else if (STREQ(priority, "2") || STREQ(priority,"info"))
        ret =virLogSetDefaultPriority(VIR_LOG_INFO);
    else if (STREQ(priority, "3") || STREQ(priority,"warning"))
        ret =virLogSetDefaultPriority(VIR_LOG_WARN);
    else if (STREQ(priority, "4") || STREQ(priority,"error"))
        ret = virLogSetDefaultPriority(VIR_LOG_ERROR);
    else
        VIR_WARN("Ignoring invalid loglevel setting");

    return ret;
}

virLogSetDefaultPriority
/**
 * virLogSetDefaultPriority:
 * @priority: the default priority level
 *
 * Set the default priority level, i.e. any logged data of a priority
 * equal or superior to this level will be logged, unless a specific rule
 * was defined for the log category of the message.
 *
 * Returns 0 if successful, -1 in case of error.
 */
int
virLogSetDefaultPriority(virLogPriority priority)
{
    if ((priority < VIR_LOG_DEBUG) || (priority >VIR_LOG_ERROR)) {
        VIR_WARN("Ignoring invalid loglevel setting.");
        return -1;
}

/*日志初始化*/
/*
VIR_ONCE_GLOBAL_INIT(virLog)
virLogInitialize ->virLogOnce->virLogOnceInit
# define VIR_ONCE_GLOBAL_INIT(classname)                               \
    static virOnceControl classname ## OnceControl =VIR_ONCE_CONTROL_INITIALIZER; \
    static virErrorPtr classname ## OnceError =NULL;                  \
                                                                       \
    static void classname ##Once(void)                                \
   {                                                                  \
        if (classname ## OnceInit() <0)                               \
            classname ##OnceError =virSaveLastError();               \
   }                                                                  \
                                                                       \

    static int classname ## Initialize(void)                           \
   {                                                                  \
        if (virOnce(&classname ##OnceControl, classname ## Once) < 0)  \
            return-1;                                                 \
                                                                       \
        if (classname ## OnceError){                                  \
           virSetError(classname ## OnceError);        \
            return-1;              \
       }                     \
                                   \
        return0;            \
    }
*/
    if (virLogInitialize() < 0)
        return -1;

    virLogDefaultPriority = priority;
    return 0;
}

int
virLogParseOutputs(const char *outputs)
{
/* 
(gdb) p outputs
$2 = 0x7fffffffef1d "1:file:/var/log/libvirt/syslog"
 */
    const char *cur = outputs, *str;
    char *name;
    char *abspath;
    virLogPriority prio;
    int ret = -1;
    int count = 0;

    if (cur == NULL)
        return -1;

    VIR_DEBUG("outputs=%s", outputs);

    virSkipSpaces(&cur);
    while (*cur != 0) {
        prio= virParseNumber(&cur);
        if ((prio < VIR_LOG_DEBUG) ||(prio > VIR_LOG_ERROR))
            goto cleanup;
        if (*cur != ':')
            goto cleanup;
        cur++;
        if (STREQLEN(cur,"stderr", 6)) {
            cur += 6;
            if(virLogAddOutputToStderr(prio) == 0)
               count++;
        } else if (STREQLEN(cur,"syslog", 6)) {
            cur += 6;
            if (*cur !=':')
               goto cleanup;
            cur++;
            str = cur;
            while ((*cur!= 0) && (!IS_SPACE(cur)))
               cur++;
            if (str ==cur)
               goto cleanup;
#if HAVE_SYSLOG_H
            name =strndup(str, cur - str);
/*
(gdb) p name
$10 = 0x615640 "/var/log/libvirt/syslog"
*/

           if (name == NULL)
               goto cleanup;
            if(virLogAddOutputToSyslog(prio, name) == 0)
               count++;
           VIR_FREE(name);
#endif /* HAVE_SYSLOG_H */
        } else if (STREQLEN(cur,"file", 4)) {
            cur += 4;
            if (*cur !=':')
               goto cleanup;
            cur++;
            str = cur;
            while ((*cur!= 0) && (!IS_SPACE(cur)))
               cur++;
            if (str ==cur)
               goto cleanup;
            name =strndup(str, cur - str);
            if (name ==NULL)
               goto cleanup;
            if(virFileAbsPath(name, &abspath) < 0) {
               VIR_FREE(name);
               return -1; /* skip warning here because setting was fine */
            }
            if(virLogAddOutputToFile(prio, abspath) == 0)
               count++;
           VIR_FREE(name);
           VIR_FREE(abspath);
        } else if (STREQLEN(cur,"journald", 8)) {
            cur += 8;

#if USE_JOURNALD
            if(virLogAddOutputToJournald(prio) == 0)
               count++;
#endif /* USE_JOURNALD */
        } else {
            gotocleanup;
        }
        virSkipSpaces(&cur);
    }
    ret = count;
cleanup:
    if (ret == -1)
        VIR_WARN("Ignoring invalid logoutput setting.");
    return ret;
}
virNetTLSInit
/*
 * This function MUST be called before any
 * virNetTLS* because it initializes
 * underlying GnuTLS library. According to
 * it's documentation, it's safe to be called
 * many times, but is not thread safe.
 *
 * There is no corresponding "Deinit" / "Cleanup"
 * function because there is no safe way to call
 * 'gnutls_global_deinit' from a multi-threaded
 * library, where other libraries linked into the
 * application may also be using gnutls.
 */
void virNetTLSInit(void)
{
    gnutls_global_init();
}

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值