QEMU中VNC Server架构分析

QEMU的入口main函数所在的源文件vl.c中针对QEMU_OPTION_vnc对vnc参数进行解析,vnc选项支持的具体参数在ui/vnc.c中进行定义,如下所示:

static QemuOptsList qemu_vnc_opts = {
    .name = "vnc",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_vnc_opts.head),
    .implied_opt_name = "vnc",
    .desc = {
        {
            .name = "vnc",
            .type = QEMU_OPT_STRING,
        },{
            .name = "websocket",
            .type = QEMU_OPT_STRING,
        },{
            .name = "tls-creds",
            .type = QEMU_OPT_STRING,
        },{
            /* Deprecated in favour of tls-creds */
            .name = "x509",
            .type = QEMU_OPT_STRING,
        },{
            .name = "share",
            .type = QEMU_OPT_STRING,
        },{  
            .name = "display",
            .type = QEMU_OPT_STRING,
        },{  
            .name = "head",
            .type = QEMU_OPT_NUMBER,
        },{  
            .name = "connections",
            .type = QEMU_OPT_NUMBER,
        },{  
            .name = "to",
            .type = QEMU_OPT_NUMBER,
        },{  
            .name = "ipv4",
            .type = QEMU_OPT_BOOL,
        },{  
            .name = "ipv6",
            .type = QEMU_OPT_BOOL,
        },{  
            .name = "password",
            .type = QEMU_OPT_BOOL,
        },{  
            .name = "reverse",
            .type = QEMU_OPT_BOOL,
        },{  
            .name = "lock-key-sync",
            .type = QEMU_OPT_BOOL,
        },{  
            .name = "key-delay-ms",
            .type = QEMU_OPT_NUMBER,
        },{  
            .name = "sasl",
            .type = QEMU_OPT_BOOL,
        },{  
            /* Deprecated in favour of tls-creds */
            .name = "tls",
            .type = QEMU_OPT_BOOL,
        },{ 
            /* Deprecated in favour of tls-creds */
            .name = "x509verify",
            .type = QEMU_OPT_STRING,
        },{
            .name = "acl",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "lossy",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "non-adaptive",
            .type = QEMU_OPT_BOOL,
        },
        { /* end of list */ }
    },
};

QEMU的VNC Server架构中主要包含以下几个概念:

1. 显卡,即VNC Server提供的图像所对应的显卡,可以是qxl显卡,也可以是cirrus等,这是VNC Server图像的来源。

2. VncDisplay,即QEMU中定义的代表一个VNC Server的结构体,即一个VncDisplay代表一个VNC Server

3. VncState,即VNC Server中针对每个VNC Client保存的一个状态信息

4. VncClient,即对应到VNC客户端

5. VNCJobQueue,即VNC Server中用于存放更新VNC客户端的工作队列

其具体关系如下图所示:

QEMU中VNC Server架构分析_第1张图片

在QEMU的源码中入口函数vl.c:main()中,先会调用module_call_init(MODULE_INIT_OPTS),该函数会调用到ui/vnc.c:vnc_register_config()添加QEMU对VNC参数的支持。然后调用qemu_opts_foreach(qemu_find_opts("vnc"), vnc_init_func, NULL, NULL);对QEMU命令行中的VNC参数进行解析,在解析的过程中会调用vnc_init_func()建立VNC Server,并对VNC Server进行相应的初始化。

vnc_init_func()的具体源码如下所示:

QEMU中VNC Server架构分析_第2张图片

vnc_init_func()函数的具体操作如下所示:

vnc_init_func()

  1. vnc_display_init(),对VNC Server进行初始化
    1. 创建VncDisplay结构体,并对其进行一定的初始化,如id,expire时间,键盘layout,共享策略,连接数量限制等。
    2. vnc_start_worker_thread(),创建VncJobQueue和worker线程--vnc_worker_thread(),该线程循环执行vnc_worker_thread_loop()函数
      1. 如果VncJobQueue为空,则等待。
      2. 将图像数据从VncJobQueue中转换到VncState中。
      3. 调度VncState的bh对更新的VncState的数据进行处理。当有新的VNC Client连接的时候,会调用vnc_connect()函数建立VNC连接,即创建VncState,然后将相应的bh设置为vnc_jobs_bh() -> vnc_jobs_consume_buffer()。
        1. 将需要更新的数据从VncState的jobs_buffer转移到ouput。
        2. 调用vnc_flush() -> vnc_client_write_locked() -> vnc_client_write_plain(),将output buffer中的数据写到vnc连接对应的socket中,并且设置vnc_client_io()函数用于监视VNC Client在接收到写入的数据后的反应。
    3. register_displaychangelistener(),将dcl_ops相关操作注册到VNCDisplay中。
      1. 创建一个console的DisplayState,并创建一个相应的timer更新鼠标,该timer会调用text_console_update_cursor()函数用于更新鼠标图像。
      2. 将dcl_ops操作插入到创建的DisplayState的display change listener list中
      3. gui_setup_refresh(),决定是否启用gui timer对GUI界面进行定时更新,更新函数为gui_update() -> dpy_refresh() -> vnc_refresh(),以QXL显卡为例:
        1. graphic_hw_update() -> qxl_hw_update() -> qxl_render_update() QXL显卡更新显示图像数据。
        2. vnc_refresh_server_surface(),更新VNC Server的surface
        3. vnc_update_client(),针对VncState,创建一个一个的VncJob,然后将VncJob插入到VncJobQueue中 。等到vnc_worker_thread在后台进行处理。
        4. 根据是否有数据需要更新对VNC更新的频率进行调整。
  2. vnc_display_open(),让VNC Server准备好接收VNC Client的连接。
    1. vnc_display_close(),先对VncDisplay中的连接相关设置做一下基本的初始化,确保目前没有VNC连接。
    2. 根据vnc参数获取VNC连接的地址、是否使用密码、键盘设置、传输加密、加速机制等参数,并对这些参数进行具体的解析转化到VncDisplay的具体设置上。
    3. qemu_console_lookup_by_device_name()将VncDisplay和相应的显示console设备关联起来,如qxl显卡对应的console。
    4. vnc_display_listen() -> vnc_display_listen_addr(),监听VNC客户端的连接请求,Unix socket或者网络socket。通过qio_channel_add_watch()添加对新创建的socket的检测,当检测到G_IO_IN事件的时候,就会调用vnc_listen_io(),根据socket接收到的连接请求,调用vnc_connect()建立VNC连接。
      1. 分配并初始化VncState结构体的基本内容,每个VncState都代表一个VNC连接
      2. 分配lossy_rect缓存,用于存放VNC客户端的图像数据
      3. 设置VncState的bh为vnc_jobs_bh
      4. graphic_hw_update() 调用对应显卡设备的gfx_update()函数,如qxl显卡的qxl_hw_update(),对图像做最初的初始化更新。

总的来说,vnc_init_func()函数会在QEMU的VNC Server上建立如下结构的系统结构:

QEMU中VNC Server架构分析_第3张图片

首先是在显卡和VncDisplay之间建立起两个定时器,用于更新VNC Server画面的鼠标和图像的显示,将需要更新的图像拆分成一个个的VncJob,放到VncJobQueue中。VNC Server中会对客户端连接的IO端口进行监听,如果有新的连接请求,则会创建新的VNC State,用于代表一个VNC客户端的连接。另外VNC Server中会起一个VNC worker thread的线程,该worker thread会一直检测VncJobQueue中是否有需要处理的VncJob,如果有,则会将VncJob进行处理和转换,将转换后的数据放到代表VNC客户端连接的VncState结构体中,等待进一步处理。最后VNC worker thread会对VNC的bh进行调度,该bh的处理函数会将VncState output buffer中的数据写到具体的io socket中。

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