曂沅仴駦 发表于 2024-8-17 11:16:01

libvir服务机制与通信原理

libvir服务机制

前言

libvirt服务机制是一个复杂的布局,里面包含了event事件,rpc网络通信,线程池以及相关的job机制,哪一个单拿出来都是一个复杂的模型布局,更何况libvirt服务是这几个机制之间相互协作的复杂布局,因此更难以阐明清晰,但是万变不离其宗,本质上还是service创建fd并相互发送消息,所以本篇文章的目标是在代码端阐明libvirt服务通信的实现,着重是代码实现。为此我们按模块进行对布局进行拆解,层层递进,以了解基础原件为开始慢慢的再此基础上解说会用到该基础模块的其他模块,即以逻辑次序从底层往上层解说,这样有利于整理的逻辑理解,以及各个模块之间的关系以及相互作用。
1模块解说


[*]
[*]libvirt事件机制

1.1.1 事件操作函数初始化

事件机制是libvirt服务中创建监视事件的一种基础原件,本身并未实现具体的功能,必要结合具体的fd来实现对特定事件的监视,其本质是glib事件循环机制的包装,让事件监视的回调函数更一样平常化,即为一个通用的中心函数用于调用参数中的回调函数。
https://i-blog.csdnimg.cn/direct/d94abdc4f1564733b3359cc92598e7a5.png
为此在libvirt中设计了两类事件处理函数接口。1:平凡事件处理函数接口;2:超时事件处理函数接口。都包罗了增加,删除,更新相应事件的接口,即统共6个接口。
首先通过virEventGLibRegister->virEventGLibRegister->virEventRegisterImpl初始化全集变量回调函数。
https://i-blog.csdnimg.cn/direct/c62ee3609f1a44638b3b393388d95f19.png
对应的函数virEventGLibHandleAdd、virEventGLibHandleUpdate、virEventGLibHandleRemove、virEventGLibTimeoutAdd、virEventGLibTimeoutUpdate、virEventGLibTimeoutRemove初始化为全局变量addHandleImpl、updateHandleImpl、removeHandleImpl、addTimeoutImpl、updateTimeoutImpl、removeTimeoutImpl。这些全局函数指针变量用于被通用的接口virEventAddHandle、virEventUpdateHandle、virEventRemoveHandle、virEventAddTimeout、virEventUpdateTimeout、virEventRemoveTimeout调用,换言之这些接口调用的时间函数就是初始化的函数。
https://i-blog.csdnimg.cn/direct/0769e21b008d4b619ea488534562d3ba.png
https://i-blog.csdnimg.cn/direct/c4dbaf437e0e42f0a2ff3f5e9b0fc260.png
这是一种初始化参数的方式,只要知道终极调用的是谁就好。接下来就看看这些函数到底是干了什么。
1.1.2 事件操作函数

事件操作函数顾名思义是操作监视操作事件的函数,有添加、删除、更新,本质是glib事件机制的封装。我们着重分析一下添加事件函数,以及添加超时事件函数,其他的就根据glib事件处理机制简略分析。
事件添加:
https://i-blog.csdnimg.cn/direct/fe0915083f5b408bb550676e8909762a.png
首先要设置要监视的事件cont,为四类G_IO_IN、G_IO_OUT、G_IO_ERR、G_IO_HUP。很明显也就是四类fd事件用于通信用的。
接着创建一个事件回调函数用的参数data,由前面所说该参数实际就是真正的回调函数所在的对象。data里有watch表示在hanldes中的序列便于查找,fd为事件监视的fd,cb为事件相应时实际调用的回调函数,opaque为cb的参数,ff为释放函数。然后由virEventGLibAddSocketWatch创建一个glib source再赋值到data中。到这里我们可以明显的感知到,data对象包含了全部的创建事件的元素,只要根据watch在全局变量handles中找到data,我们可以对事件做相应的处理。最后把data在handles中的次序watch返回,即把data所在的次序传递出去,供其他元素查找。
总结一个下添加事件做的几件事:
1:创建事件source,监视fd特定事件;
2:把传入的回调函数,回调函数对应参数,fd,创建的source,监视的事件,创建data在handles中的次序,释放函数,等整合到data中。
3:返回创建data在hanldes中的次序。
事件删除:
事件删除的逻辑为:烧毁data中的source,把data从hanldes中删除。
事件更新的逻辑为:事件更新目的是对同一个fd监视其他的事件,为此烧毁data中的source,依据新的cout创建新的source赋值到data中。
事件超时机制与事件机制类似,不再累述。
总结:libvirt事件机制本质是glib source机制,为了方便调用使用了类似于父类的方式同一函数添加函数接口根据不同的data调用对应的回调函数。事件机制实际也就是个添加方便的glib事件机制。

[*]
[*]libvirt通信创建

libvirt服务涵盖的通信方式有很多,有tcp、udp、ssh、tls、unix等。libvirt要为各种方式创建相应的服务为了后续客户端的连接。虽然有如此多中通信方式在团体看来便是回调函数的不同,为此我们就比力熟悉的tcp为例来分析libvirt服务端创建于client端连接的流程。

[*]
[*]
[*]libvirt tcp服务创建


我们都知道服务创建分为两层:一层是socket listen层,一层是socket accept层。同样的libvirt服务创建主要是对connet对应fd设置回调函数与accept对应fd的回调函数。
服务创建:srv
https://i-blog.csdnimg.cn/direct/6345dbbebe564d328130ef28198dd884.png
创建服务实际就是创建一个线程池,后面会被用到的函数是:virNetServerHandleJob该函数服务处理的核心函数:
https://i-blog.csdnimg.cn/direct/66c019cb4ae94172811d4650fb57797c.png
线程池实际就是创建了指定多个线程执行不同的任务:
https://i-blog.csdnimg.cn/direct/ab2621d0321142219119b896033f2307.png
创建了gain个线程执行virThreadPoolWorker
https://i-blog.csdnimg.cn/direct/54652ca485684384b5979d95b45a92b3.png
virThreadHelper函数里主要是设置一些线程属性而已
https://i-blog.csdnimg.cn/direct/97af5a964c46425e8459e67335bbeabf.png
https://i-blog.csdnimg.cn/direct/cc9923e9d118493e87c0f9464b845243.png
可以看到每个线程只是while循环然后阻塞等候信号,当信号过来进行叫醒时执行(pool->jobFunc)(job->data, pool->jobOpaque);
而(pool->jobFunc)(job->data, pool->jobOpaque)为virNetServerHandleJob
https://i-blog.csdnimg.cn/direct/629508bf43744200baecaadef2e0ef52.png
可以看到核心函数为virNetServerProcessMsg
https://i-blog.csdnimg.cn/direct/6125c9035aa54ffcb2e1746d146e1c4e.png
https://i-blog.csdnimg.cn/direct/b4b691f454c542a78a3619416bf584bd.png
https://i-blog.csdnimg.cn/direct/4d9760aeb1ba4710b1b1af34c63c7432.png
virNetServerClientSendMessage就不用看了,看看virNetServerProgramDispatchCall吧
https://i-blog.csdnimg.cn/direct/754db31ac0d5481495ae35a89a23269a.png
https://i-blog.csdnimg.cn/direct/dbb8227d239146809bfc22a2c24f6628.png
https://i-blog.csdnimg.cn/direct/3b32ffeaf25a4893b474125e5fa258ca.png

这个函数很长但是核心不多:
最主要的目的是找到对应的回调函数
dispatcher = virNetServerProgramGetProc(prog, msg->header.proc);
执行相应的回调函数
rv = (dispatcher->func)(server, client, msg, &rerr, arg, ret);
好比经典的函数migrate都是通过call函数通过客户端往服务端发送信息在virNetServerProgram对象中找到相应的类似于qemuxxx的回调函数然后执行。
TCP服务创建:
函数调用关系:daemonSetupNetworking->virNetServerAddServiceTCP
https://i-blog.csdnimg.cn/direct/f9ae626f84244060a6662762209d419d.png
创建的为virNetServerService类型对象,该对象为virNetServer类型对象的成员变量,且该成员变量是virNetServerService类型的数组,因此srv是一个总的服务描述,而每中类型的服务所创建的src都为其中的一个元素。
  该函数为两个模块return 0之前的是重启模块,之后的是新创建服务的模块,区别是重启服务要连接已有的fd,而新创建的只需创建等候连接即可。virNetServerAddServiceActivation函数中包含的就是virNetServerServiceNewTCPs和virNetServerAddService逻辑因此我们以这两个函数为着重介绍对象。

[*]virNetServerServiceNewTCPs核心目的是创建socket对象sockt,核心函数是socket,bind, virNetSocketNew, virNetSocketNew函数用fd创建cocket对象。
[*]virNetServerServiceNewSocket函数核心目标是listen fd并把fd添加到事件中监视fd,以及设置listen后的回调函数。核心函数是virNetSocketListen、virNetSocketAddIOCallback
https://i-blog.csdnimg.cn/direct/e061cf6f651d402e9b23367adb020d5c.png

https://i-blog.csdnimg.cn/direct/190bb31370f8404c87243b24505f3d8d.png
https://i-blog.csdnimg.cn/direct/999ab1cc21eb4466ab4e426a7feb5056.png
https://i-blog.csdnimg.cn/direct/5df6f336026e428f925712436dba3b95.png
由上面两个函数具体实现可知当sock->fd产生事件的时间终极调用的是svc->dispatchfunc()函数,调用逻辑为:sockt->fd触发回调-> virNetSocketEventHandle->(sockt->func())=virNetServerServiceAccept->(svc->dispatchFunc())同里这些调用的目的还是为了统一化接口因为virNetServerServiceAccept的回调函数调用的dispatchFunc会根据传输类型不同而有不同的回调函数。在virNetServerServiceAccept中
https://i-blog.csdnimg.cn/direct/655acdbbfcbf4241a11204d5e4d4aeca.png
virNetSocketAccept主要目的就是因为listen的fd收到的事件,因此必要accept吸收并生成IO fd并用virNetSocketNew创建一个virNetSocket对象。

https://i-blog.csdnimg.cn/direct/c7351166525d4bb183c3533fda9ca3f7.png
https://i-blog.csdnimg.cn/direct/851fcbc80aec41a7a3852ceb645a1465.png
由virNetSocketAccept创建的virNetSocket来创建一个virNetServerClient对象,该对象是服务与client通信的终极对象。

virNetServerClientNew函数任务就是依据listen的fd clientsock创建一个client对象。
https://i-blog.csdnimg.cn/direct/6e0b9f5c159545579c7658621b00eb22.png
https://i-blog.csdnimg.cn/direct/b96c89a540114e9090ee40144e1819ca.png
https://i-blog.csdnimg.cn/direct/4bd68f8dc945491ca45c0c35fd64ef61.png
virNetServerAddClient有两个比力重要的函数:virNetServerClientInit、virNetServerClientSetDispatcher
先说virNetServerClientSetDispatcher因为该函数也是一个初始化函数,且会被前面初始化的回调函数调用。
https://i-blog.csdnimg.cn/direct/7a3ccf1441e742ed8801f4acbcf149d4.png
由代码可知函数实际就是初始化了client->dispatchFunc为virNetServerDispatchNewMessage。
https://i-blog.csdnimg.cn/direct/ca50b65b526a49bab29104a3e968c744.png
再看virNetServerClientInit函数,可以从函数布局上可以看出函数的核心调用为virNetServerClientRegisterEvent即为client设置读写回调函数:
https://i-blog.csdnimg.cn/direct/a509ffd0be964e6f97c06b20bef48a57.png
根据事件机制我们知道这里即为把sock到场到事件循环中进行监视,且回调函数为virNetServerClientDispatchEvent:
https://i-blog.csdnimg.cn/direct/ff06d38d93034037b1a049e0ec1f7a17.png
该函数的核心函数为virNetServerClientDispatchWrite和virNetServerClientDispatchRead,当然如果是virNetServerClientDispatchRead则会执行virNetServerClientDispatchMessage进行处理传过来的信息。
https://i-blog.csdnimg.cn/direct/377dab6778044b679af297aa12329247.png
好比client如果往服务端发送各种指令必要执行相应的任务之类的。我们已经知道dispatchOpaque具体函数是什么了。
https://i-blog.csdnimg.cn/direct/45c68f116ea546b9afb8b8ee7af3b67d.png
看到这我们应该已经明确srv中的线程池用来干什么的了。
这里会创建一个job发送给服务其中比力重要的几个元素,client 、msg然后把job发送给服务的线程池。
https://i-blog.csdnimg.cn/direct/8f91ba57e29d4b8e8706246527d3ab3e.png
首先会把virNetServerJob类型的对象赋值给virThreadPoolJob类型的对象job,并把该job添加到线程池中的joblist中,virCondSignal发送信号给线程池。
然后线程里执行相应的disptch函数。
这时间事件监视机制,线程池,网络通信就全部用上了。
1.3客户端创建

客户端是个比力广泛的应用,因为客户不确定以及服务不确定,客户可以时ivirsh指令可以是libvirt服务大概其他服务。服务端可以是libvirt服务也可以是其他服务好比qemu服务等。
创建客户端的开始就是创建连接打开:
https://i-blog.csdnimg.cn/direct/6a0c34f0ba7f4a0c98189c1acf14fa4e.png
该函数完成两个任务,virInitialize初始化和virConnectOpenInternal创建连接;
初始化中比力重要的是对remotedriver的初始化,供后面的客户端调研访问libvirt服务,里面基本包含了全部客户端的操作。
https://i-blog.csdnimg.cn/direct/6fe68371c03a4d5188809878fa6bd177.png
virConnectOpenInternal->remoteConnectOpen->doRemoteOpen->virNetClientNewTCP
服务端与客户端虽然都是创建fd进行监视,不同的就是client创建的网络传输对象就是virNetClient
相应的创建函数:
virNetServerClientNew<->virNetClientNew
创建的对象virNetServerClient<->virNetClient
virNetServerServiceNewTCP<->virNetClientNewTCP
所在文件virnetservice.c<->virnetclient.c
提及这些区别的目的是为了阐明,如果见到service的对象基本可以确定是服务端的传输的描述符否则就是client端;
创建连接从connectopen开始
看看client端的创建virNetClient
https://i-blog.csdnimg.cn/direct/8183712c93964e75996067ce86cd6f01.png
也就两个函数了:
https://i-blog.csdnimg.cn/direct/4dc2ddb788c641d49a76427d21f445ff.png
https://i-blog.csdnimg.cn/direct/ab98e6df369c4e58bb6e2145be5dce16.png
从代码中可以看出没做啥事,就是创建连接创建一个virNetSocket保存建立连接的fd
https://i-blog.csdnimg.cn/direct/73fd7f0511324c1e925f09e1dc8697c0.png
这个也没做啥事,就是创建一个loop和context保存在client中
对应的也有fd的回调函数加到事件循环列表中:
https://i-blog.csdnimg.cn/direct/365f2d5a777f4f0f8db671d085ea418f.png
https://i-blog.csdnimg.cn/direct/c0c543a5deba448bafc06b964134b499.png
熟悉的函数virNetSocketAddIOCallback目的是设置对应fd的回调函数为virNetClientIncomingEvent
https://i-blog.csdnimg.cn/direct/00195048b97046579a9dfa97d29abc1f.png
https://i-blog.csdnimg.cn/direct/c11b2a1d56734a4d8e7a4b732c6a468e.png
显然两个回调函数virNetClientIOHandleOutput,virNetClientIOHandleInput代表着读写操作。通过virNetClientIOHandleOutput->virNetClientIOWriteMessage->virNetSocketWrite->virNetSocketWriteWire->write把数据传到目的端。
来看一个接口怎样实现向服务端发送指令的,还举迁徙的例子
virDomainMigrate3->virDomainMigrateVersion3->virDomainMigrateVersion3Full->
domain->conn->driver->domainMigrateBegin3Params ||domain->conn->driver->domainMigratePerform3Params ||domain->conn->driver->domainMigrateConfirm3Params
好比domain->conn->driver->domainMigrateBegin3Params 对应的是remoteDomainMigrateBegin3Params
remote接口都是统一的标准纵然用call函数把数据发送到目的端:call发送的参数即为remoteDomainMigrateBegin3Params函数在服务驱动中函数数组所对应的序号。
call->callFull->virNetClientProgramCall->virNetClientSendWithReply->virNetClientSendInternal->virNetClientIO->virNetClientIOEventLoop->virNetClientIOHandleOutput->virNetSocketWrite


1.3 libvirt服务与qemu的连接

libvirt要作为client端与qemu进行通信,而且qemu也必要把假造机的状态返回给libvirt让其做出相应处理。
注册:
daemonInitialize:
https://i-blog.csdnimg.cn/direct/8e00ca9c1c3947179cfa149ca7e592b4.png
该函数调用qemuRegister:
https://i-blog.csdnimg.cn/direct/ab10c81d91b0460f95d43819d596734e.png
终极把qemuHypervisorDriver注册到与qemu进程连接的驱动中
qemuStateInitialize
https://i-blog.csdnimg.cn/direct/b05787e00fbf40ba8c54600aaee28dc8.png
设置与qemu通信fd
qemuProcessLaunch-> qemuProcessWaitForMonitor()->qemuConnectMonitor
https://i-blog.csdnimg.cn/direct/2944b99378ef460a922955add85d8d5c.png
https://i-blog.csdnimg.cn/direct/aba5cfd9360d4d30b2743b12253c26c5.png
https://i-blog.csdnimg.cn/direct/d9597cd887694b4d80627e08abbb0f29.png
qemuMonitorOpenUnix只是创建一个fd;
qemuMonitorOpenInternal为这个fd设置cb为monitorCallbacks
qemuMonitorOpenInternal->qemuMonitorRegister把fd事件放到事件循环中进行监视。
https://i-blog.csdnimg.cn/direct/bbc2ddd44dcf4bafb9f01288daa15d18.png
回调函数为qemuMonitorIO
https://i-blog.csdnimg.cn/direct/1645086341c9455aaea1739e9994ca81.png
https://i-blog.csdnimg.cn/direct/759daf938b4449f5bb8a9fda7aa39fdb.png
如果从fd中读出指令信息则qemuMonitorIOProcess->qemuMonitorJSONIOProcess->qemuMonitorJSONIOProcessLine->qemuMonitorJSONIOProcessEvent
https://i-blog.csdnimg.cn/direct/e2bda449e9574d3eae5f0c361e8fcec7.png
如果吸收到事件
https://i-blog.csdnimg.cn/direct/7c7e2c35cb5642a08f668ce8dd734157.png

qemuMonitorJSONIOProcessEvent –》qemuMonitorEmitEvent
https://i-blog.csdnimg.cn/direct/217c10b16e434797ad76f1ea7ed01157.png
找到domainEvent中的回调函数并调用
https://i-blog.csdnimg.cn/direct/05520ba2cb854fca80c416a6f05554dc.png
找到对应的事件调用相应的回调函数进行处理
其中某些monitorCallBackes回调函数会叫醒qemu_driver创建的线程池去完成某些任务:好比
https://i-blog.csdnimg.cn/direct/dac2d65df0a84259b76c8b586e18e510.png
https://i-blog.csdnimg.cn/direct/0ebbc96cadbe4a28be9be43292b59a60.png
用qemuProcessEventSubmit通过调用virThreadPoolSendJob叫醒driver->workerPool去执行qemuProcessEventHandler即对产生事件的处理
https://i-blog.csdnimg.cn/direct/b6557bfe6cf7456db2763b498c072845.png




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: libvir服务机制与通信原理