虚拟机配置中:
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
With qemu, if name is "org.qemu.guest_agent.0", then libvirt can interact with a guest agent installed in the guest, for actions such as guest shutdown or file system quiescing.
虚拟机启动命令行:
/usr/libexec/qemu-kvm
-chardev socket,id=charmonitor,fd=35,server,nowait
-mon chardev=charmonitor,id=monitor,mode=control
-chardev socket,id=charchannel0,fd=42,server,nowait
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0
启动虚拟机时libvirt会创建一个qemu monitor的unix socket和一个qemu agent的unix socket:
srwxrwxr-x 1 root root 0 Jan 11 16:13 /var/lib/libvirt/qemu/domain-28-Centos7/monitor.sock
srwxrwxr-x 1 qemu qemu 0 Jan 11 16:13 /var/lib/libvirt/qemu/channel/target/domain-28-Centos7/org.qemu.guest_agent.0
libvirt启动虚拟机时会打开两个unix socket,作为server,一个用作qemu monitor,一个用作qemu agent,然后启动qemu子进程。
qemuProcessStart
->qemuProcessPrepareDomain
->for循环执行qemuDomainPrepareChannel # 设置channel->source->data.nix.path,例如:/var/lib/libvirt/qemu/channel/target/domain-28-Centos7/org.qemu.guest_agent.0
qemuProcessLaunch
->qemuBuildCommandLine
->qemuBuildMonitorCommandLine
->qemuBuildChrChardevStr
->fd = qemuOpenChrChardevUNIXSocket # dev->type == VIR_DOMAIN_CHR_TYPE_UNIX
->virCommandPassFD(cmd, fd, VIR_COMMAND_PASS_FD_CLOSE_PARENT); # 将要传递的fd放在cmd->passfd中
->qemuBuildChannelsCommandLine
->qemuBuildChrChardevStr # VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO
->fd = qemuOpenChrChardevUNIXSocket(dev); # VIR_DOMAIN_CHR_TYPE_UNIX,打开unix socker listen
->virCommandPassFD(cmd, fd, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
->qemuBuildChrDeviceCommandLine # 生成-device参数
->virCommandRun # 启动qemu子进程,之后libvirt进程关闭cmd->passfd中的fd
->qemuProcessWaitForMonitor
->qemuConnectMonitor
->qemuMonitorOpen # 连接unix socket
->qemuProcessInitMonitor # 发送qmp_capabilities
->qemuMigrationCapsCheck # 发送query-migrate-capabilities
->qemuMonitorGetChardevInfo # 发送query-chardev
->qemuProcessRefreshChannelVirtioState # 增加domain agent生命周期事件
->qemuConnectAgent
->agent = qemuAgentOpen(...)
->agent->fd = qemuAgentOpenUnix(config->data.nix.path); # 通过unix socket连接到qemu agent,config->data.nix.path在之前的qemuDomainPrepareChannel 函数中已经设置好
->qemuAgentRegister(agent); # 使用glib事件循环监测qemu agent事件,处理函数为qemuAgentIO
qemu_init
->for循环执行chardev_init_func
->qemu_chr_new_from_opts
->backend = qemu_chr_parse_opts(opts, errp);
->qemu_chr_parse_socket # SOCKET_ADDRESS_LEGACY_KIND_FD
->chr = qemu_chardev_new(...)
->chardev_new
->qemu_char_open
->qmp_chardev_open_socket
->qmp_chardev_open_socket_server
全局变量qmp_dispatcher_bh用于进行qmp命令
/* Bottom half to dispatch the requests received from I/O thread */
QEMUBH *qmp_dispatcher_bh;
函数monitor_init_globals_core初始化qmp_dispatcher_bh并将其处理函数设置为monitor_qmp_bh_dispatcher
void monitor_init_globals_core(void)
{
monitor_qapi_event_init();
qemu_mutex_init(&monitor_lock);
/*
* The dispatcher BH must run in the main loop thread, since we
* have commands assuming that context. It would be nice to get
* rid of those assumptions.
*/
qmp_dispatcher_bh = aio_bh_new(iohandler_get_aio_context(),
monitor_qmp_bh_dispatcher,
NULL);
}
qmp命令的入口函数是qmp_dispatch:
virsh qemu-monitor-command --hmp info block调用堆栈如下:
Breakpoint 1, 0x000055fdab6f88a0 in hmp_info_block ()
(gdb) bt
#0 0x000055fdab6f88a0 in hmp_info_block ()
#1 0x000055fdab7e3dd6 in handle_hmp_command ()
#2 0x000055fdab674d12 in qmp_human_monitor_command ()
#3 0x000055fdab807019 in qmp_marshal_human_monitor_command ()
#4 0x000055fdab8c14b0 in qmp_dispatch ()
#5 0x000055fdab7e1111 in monitor_qmp_dispatch ()
#6 0x000055fdab7e17d0 in monitor_qmp_bh_dispatcher ()
#7 0x000055fdab9081e3 in aio_bh_poll ()
#8 0x000055fdab90b59e in aio_dispatch ()
#9 0x000055fdab90807e in aio_ctx_dispatch ()
#10 0x00007f25d5f15099 in g_main_context_dispatch () at /lib64/libglib-2.0.so.0
#11 0x000055fdab90a8e3 in main_loop_wait ()
#12 0x000055fdab67b269 in qemu_main_loop ()
#13 0x000055fdab59c08e in main ()
virsh qemu-monitor-command '{"execute": "query-block"}'调用堆栈如下:
#0 0x000055fdab87b5a0 in qmp_query_block ()
#1 0x000055fdab7fc797 in qmp_marshal_query_block ()
#2 0x000055fdab8c1536 in qmp_dispatch ()
#3 0x000055fdab7e1111 in monitor_qmp_dispatch ()
#4 0x000055fdab7e17d0 in monitor_qmp_bh_dispatcher ()
#5 0x000055fdab9081e3 in aio_bh_poll ()
#6 0x000055fdab90b59e in aio_dispatch ()
#7 0x000055fdab90807e in aio_ctx_dispatch ()
#8 0x00007f25d5f15099 in g_main_context_dispatch () at /lib64/libglib-2.0.so.0
#9 0x000055fdab90a8e3 in main_loop_wait ()
#10 0x000055fdab67b269 in qemu_main_loop ()
#11 0x000055fdab59c08e in main ()
qga命令的入口函数是tcp_chr_read:
tcp_chr_read
->qemu_chr_be_write
->chr_read # virtconsole_realize中调用qemu_chr_fe_set_handlers将backend的读函数设置为chr_read(virtio-console.c)
->virtio_serial_write
->virtqueue_push
->virtio_notify
之后就是通过virtio机制通知到虚拟机内部,虚拟机内部的qemu guest agent打开的virtio串口收到消息进行处理。