QGA工作原理
qga简介
qemu通过串口设备(串口设备的速率较低,适合小数据量的交互)的模拟,为宿主机和虚拟机提供了一个数据通道(channel),这个通道的两端分别为 VM中的串口和 HOST中的unix socket文件。
-chardev socket,id=charchannel0,fd=22,server=on,wait=off -device {"driver":"virtserialport","bus":"virtio-serial0.0","nr":1,"chardev":"charchannel0","id":"channel0","name":"org.qemu.guest_agent.0"}
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/org.qemu.test_agent'/>
<target type='virtio' name='org.qemu.guest_agent.0' state='connected'/>
<alias name='channel0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
- -chardev socket 参数指定了一个chardev设备,其后端设备为socket,fd为对应的unix socket(path为/var/lib/libvirt/qemu/org.qemu.test_agent)的文件描述符
- -device {…} 创建一个virtio-serial-port设备,其对应的chardev为charchannel0,其对应的名字为org.qemu.guest_agent.0,该设别会被挂载在virtio-serial-bus(virtio-serial0.0)上。在虚拟机启动时则会创建一个/dev/vport1p1设备,并为其创建一个符号链接/dev/virtio-ports/org.qemu.guest_agent.0
host:
# file /var/lib/libvirt/qemu/org.qemu.test_agent
/var/lib/libvirt/qemu/org.qemu.test_agent: socket
guest:
# systemctl status qemu-guest-agent.service
● qemu-guest-agent.service - QEMU Guest Agent
Loaded: loaded (/usr/lib/systemd/system/qemu-guest-agent.service; enabled; preset: enabled)
...
CGroup: /system.slice/qemu-guest-agent.service
└─802 /usr/bin/qemu-ga --method=virtio-serial --path=/dev/virtio-ports/org.qemu.guest_agent.0 --allow-rpcs=guest-sync-delimited,guest-sync,guest
# readlink /dev/virtio-ports/org.qemu.guest_agent.0
../vport2p1
# file /dev/vport2p1
/dev/vport2p1: character special (241/1)
因此宿主机端可以和/var/lib/libvirt/qemu/org.qemu.test_agent这个unix socket建立连接,从而与虚拟机内部的qga进行通信。
qga与qemu之间的关系如下所示
qemu创建一个virtserialport virtio串口设备,该设备有一个chardev设备,该设备的后端为unix socket,对应的文件为/var/lib/libvirt/qemu/org.qemu.test_agent,qemu会将该unix socket对应的fd加入到事件监听的主循环中。chardev设备主要用于提供虚拟机与外部的连接,chardev设备的后端用于表示数据传输的方式(有的chardev基于文件,有的网络,有的基于管道)。在内核驱动和该virtio串口设备匹配时则会在sysfs下创建一个/dev/vport1p1设备提供给用户态,并将设备状态设置为已连接。
当虚拟机外部需要向qga发出请求,使其设置或者获取虚拟机内部的一些信息时,就要向/var/lib/libvirt/qemu/org.qemu.test_agent这个Unix socket文件写入请求数据,请求数据的格式与qmp一样,也是Json格式。当该unix socket接收到数据时,就会唤醒主循环,串口设备读取数据,然后填写virtio中的vring结构,向设备注入一个中断。
设备接收到这个中断后会读取数据,并唤醒用户态的qga进程。qga本身是一个事件循环监听串口/dev/vport2p1的数据,当它由于串口数据被唤醒时就会按照请求进行处理。请求应答的数据格式与qmp一样也是json格式,当应答数据生成好以后就通过virtio串口设备向qemu发送数据。QEMU则通过chardev设备向连接/var/lib/libvirt/qemu/org.qemu的对端发送其对应的请求应答数据。
qga源码解析
GAState和GAConfig分别用于表示qga的整个状态和配置文件,
qemu/qga/main.c
int main(int argc, char **argv)
{
int ret = EXIT_SUCESS;
GAState *s;
GAConfig *config = g_new0(GAConfig, 1);
int socket_activation;
...
qga_qmp_init_marshal(&ga_commands); //将qga支持的命令注册到系统中
...
config_parse(config, argc, argv);
...
config_dump(config);
...
// 该函数中会调用json_message_parser_init(&s->parser, process_event, s, NULL)
// 将s->parser的emit函数注册为process_event
s = initialize_agent(config, socket_activation);
...
ret = run_agent(s);
...
cleanup_agent(s);
}
struct GAState {
JSONMessageParser parser; // 解析json对象,并调用设置的回调函数
GMainLoop *main_loop; // 表示qga的主循环
GAChannel *channel; // 表示virtio serial端口设备的通道
bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
GACommandState *command_state;
GLogLevelFlags log_level;
FILE *log_file;
bool logging_enabled;
#ifdef _WIN32
GAService service;
HANDLE wakeup_event;
HANDLE event_log;
#endif
bool delimit_response;
bool frozen;
GList *blockedrpcs;
GList *allowedrpcs;
char *state_filepath_isfrozen;
struct {
const char *log_filepath;
const char *pid_filepath;
} deferred_options;
#ifdef CONFIG_FSFREEZE
const char *fsfreeze_hook;
#endif
gchar *pstate_filepath;
GAPersistentState pstate;
GAConfig *config;
int socket_activation;
bool force_exit;
};
struct GAConfig {
char *channel_path; //串口端口的路径
char *method; // 串口端口的类型,可以是virtio-serial或isa
char *log_filepath;
char *pid_filepath;
#ifdef CONFIG_FSFREEZE
char *fsfreeze_hook;
#endif
char *state_dir;
#ifdef _WIN32
const char *service;
#endif
gchar *bliststr; /* blockedrpcs may point to this string */
gchar *aliststr; /* allowedrpcs may point to this string */
GList *blockedrpcs;
GList *allowedrpcs;
int daemonize; // 是否守护进程运行
GLogLevelFlags log_level;
int dumpconf;
bool retry_path;
};
在qmp_command的构造完成以及程序的初始化完成后,开始调用run_agent函数,
static int run_agent(GAState *s)
{
int ret = EXIT_SUCCESS;
s->force_exit = false;
do {
ret = run_agent_once