libvirt源码分析——libvirtd的初始化

libvirtd的初始化在libvirtd.c中main函数里。
首先是分析命令行传进来的参数,常用的是libvirtd -d,即启动libvirtd守护进程。

c = getopt_long(argc, argv, "ldf:p:t:vVh", opts, &optidx);
    case 'd':
        godaemon = 1;  

然后执行

config = daemonConfigNew(privileged)

该函数是根据是初始化一些配置,比如最大的客户端连接数,宿主机的主机名。
接下来就是确定配置文件的路径,当privileged = geteuid() == 0为true时,则privileged=true,remote_config_file=”/libvirt/libvirtd.conf”

daemonConfigFilePath(privileged, &remote_config_file) < 0)

紧接着就是从配置文件中读取数据,并分析数据,将一些配置赋值在config结构体中,比如有日志等级,日志过滤器,默认的连接uri

daemonConfigLoadFile(config, remote_config_file, implicit_conf)

下面是日志的初始化

daemonSetupLogging(config, privileged, verbose, godaemon)

daemonSetupLogging会先从环境变量读取日志的一些配置信息,若读取不到,则会将从配置文件读取到的配置赋值给全局变量,比如,其函数里有下面的一个调用
virLogSetDefaultPriority(config->log_level);
就是将从配置文件读取到的日志等级log_level赋值给全局变量virLogDefaultPriority,以后打印日志就会根据virLogDefaultPriority的值来决定是否打印日志。
同时它还会确定日志的输出位置,是系统的日志文件,还是自己的日志文件(从配置文件或者环境变量读取到日志文件的路径)。

daemonSetupAccessManager(config)        /*没有看懂*/

接下来是确定pid文件的路径

virPidFileConstructPath(privileged, LOCALSTATEDIR, "libvirtd", &pid_file)

对于root用户,最总确定的pid文件路径是LOCALSTATEDIR/run/libvirtd.pid。
接下来就是根据从文件读取到的配置来确定unix套接字的路径

daemonUnixSocketPaths(config, privileged, &sock_file, &sock_file_ro, &sock_file_adm)

daemonUnixSocketPaths会根据前面读取到的config信息来赋值sock_file,sock_file_ro,sock_file_adm三个值,sock_file是unix套接字的路径,三个值具体为config->unix_sock_dir/libvirt-sock,config->unix_sock_dir/libvirt-sock-ro , config->unix_sock_dir/libvirt-admin-sock。

statuswrite = daemonForkIntoBackground(argv[0])

daemonForkIntoBackground是将启动的Libvirtd进程变为后台的守候进程。
进入该函数,可以看到该函数的调用大概如下

pipe(statuspipe) 
pid = fork()
switch (pid)
{
case 0: /*child 进程 */
    setsid()
    nextpid = fork();
            switch (nextpid) {
            case 0: /* grandchild */
                return statuspipe[1];  /*grand child 进程返回描述符的写端*/
            default:  
                _exit(EXIT_SUCCESS); /*子进程退出*/
            }
default:
    virProcessWait(pid, NULL, false)
    ret = read(statuspipe[0], &status, 1);
}

可以知道daemonForkIntoBackground,是将该进程的grand child进程变为守护进程,同时,该进程等待grand child给其发消息,若收到成功消息,则退出。grand child进程返回管道的写端,如果后面初始化完成,就会往管道写数据。

再往下走

virPidFileAcquirePath(pid_file, false, getpid())

virPidFileAcquirePath是为了防止重复的运行libvirtd进程,该函数中,先尝试设置独占锁给pid_file,如果失败,则说明已经有libvirtd进程运行了,如果成功,则将pid写到pid_file中。

dmn = virNetDaemonNew()

virNetDaemonNew中首先会调用virEventRegisterDefaultImpl,该函数会创建一个管道eventLoop.wakeupfd,并设置管道的读端的回调函数(仅仅从读端读取一个字符)和期待事件。然后virNetDaemonNew再调用virEventRegisterImpl给全局变量addHandleImpl,updateHandleImpl,removeHandleImpl赋值函数地址,这些函数都是为了更改描述符事件和回调函数。

接下来会调用daemonInitialize函数来注册libvirtd侧的驱动。

daemonInitialize()

qemu_driver就是在libvirtd侧注册的,所谓的注册就是将存储了qemu相关的两个结构体qemuConnectDriver和qemuStateDriver填充到全局数组virConnectDriverTab和virStateDriverTab中。

daemonSetupNetworking(srv, srvAdm, 
                              config,
                              sock_file,
                              sock_file_ro,
                              sock_file_adm,
                              ipsock, privileged) 

sock_file等socket的路径在前面已经初始化了,该函数根据路径去创建socket,以sock_file unix套接字为例,会首先创建一个套接字,然后给监听描述符添加回调函数virNetServerServiceAccept和virObjectFreeCallback,virNetServerServiceAccept函数会在每次有连接到来时去处理连接请求,同时调用virNetServerDispatchNewClient函数,该函数会开辟每个已连接描述符对应的缓存,用来存储接收的数据,同时也会添加数据读写函数virNetServerClientDispatchEvent,数据读写函数中会调用数据分析函数virNetServerDispatchNewMessage,数据分析函数会根据消息的头类型,去srv(virNetServerPtr)结构中寻找符合的工程结构virNetServerProgramProc(对于qemu就是qemuProcs),该工程中包含相应的函数,去进一步调用注册的驱动函数。

至此,Libvirtd的基本初始化已经完成,开始向grand father进程发送消息,让其退出

write(statuswrite, &status, 1)

后面的就是初始化驱动

daemonStateInit(dmn)

该函数会调用virStateDriverTab数组中的每个注册的函数,来初始化各个驱动。

最后

virNetDaemonRun(dmn)

virNetDaemonRun会循环调用virEventRunDefaultImpl,该函数会运行poll去检测eventloop.handles中的每个描述符的事件,如果有事件发生就会调用回调函数去处理事件。

你可能感兴趣的:(虚拟化,libvirt)