VNC(Virtual Network Computer)是虚拟网络计算机的缩写,它使用RFB(Remote Frame Buffer)协议来控制另外一台计算机,Qemu使用该协议来进行图形界面的操作与维护。
10.3.1 Qemu中VNC工作架构
main(vl.c)==> vnc_display_init(ds);
voidvnc_display_init(DisplayState *ds) {
VncDisplay *vs = g_malloc0(sizeof(*vs));
dcl =g_malloc0(sizeof(DisplayChangeListener)); //dcl为全局变量
ds->opaque= vs;
dcl->idle = 1;
vnc_display = vs;
vs->lsock = -1;
vs->ds = ds;
QTAILQ_INIT(&vs->clients);
vs->expires = TIME_MAX;
if (keyboard_layout)
vs->kbd_layout =init_keyboard_layout(name2keysym, keyboard_layout);
else
vs->kbd_layout = init_keyboard_layout(name2keysym,"en-us");
。。。。。。。
qemu_mutex_init(&vs->mutex);
//启动vnc线程,其函数为vnc_worker_thread
vnc_start_worker_thread();
//这一组为图形显示回调
dcl->dpy_copy = vnc_dpy_copy;
dcl->dpy_update = vnc_dpy_update;
dcl->dpy_resize = vnc_dpy_resize;
dcl->dpy_setdata = vnc_dpy_setdata;
register_displaychangelistener(ds,dcl);
ds->mouse_set = vnc_mouse_set;//鼠标位置的更新
ds->cursor_define =vnc_dpy_cursor_define;
}
main==> vnc_display_open
该函数根据用户参数初始化VNC Server,最后调用qemu_set_fd_handler2(vs->lsock,NULL, vnc_listen_read, NULL, vs);等待客户端连接,并调用vnc_connect,其流程如下:
a. qemu_set_fd_handler2(vs->csock,NULL, vnc_client_read, NULL, vs);处理客户端发送的请求
b. vga_hw_update==> active_console->hw_update
c.发送RFB协议的版本到客户端,并注册protocol_version的回调
d. 初始化键盘输入为空reset_keys
e. 注册鼠标位置改变的回调
vs->mouse_mode_notifier.notify =check_pointer_type_change;
qemu_add_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
f. vnc_init_timer注册一个timer定期用于屏幕的更新,更新回调为vnc_refresh。
vnc_refresh==> vnc_update_client
a. vnc_job_new 建立一个新的job
b. 将上次图形的更新区域提交到job中
vnc_worker_thread==》 vnc_worker_thread_loop其流程如下:
a. 等待有任务到来qemu_cond_wait(&queue->cond, &queue->mutex);
b. 取一个jobjob =QTAILQ_FIRST(&queue->jobs);
c. 根据job向客户端发送更新的区域
vnc_send_framebuffer_update(&vs,entry->rect.x, entry->rect.y,
entry->rect.w, entry->rect.h);
下一个问题是vnc模块如何接收鼠标与键盘数据,vnc_client_read用于读取客户端的数据,并响应。通过vs->read_handler读取客户端数据并数据拷贝到memmove(vs->input.buffer,vs->input.buffer + len, (vs->input.offset - len));
根据客户与服务器的连接装填, 该回调在不同时期为不同函数,但彻底建立连接后为protocol_client_msg
VNC_MSG_CLIENT_KEY_EVENT用户响应键盘
key_event==》 do_key_event ==> press_key ==> kbd_put_keycode==> qemu_put_kbd_event
VNC_MSG_CLIENT_POINTER_EVENT用于响应鼠标
pointer_event==> kbd_mouse_event ==> entry->qemu_put_mouse_event
下一节我们将分析虚拟驱动的数据和vnc数据的关联
10.3.2虚拟驱动的数据输入输出
下面以显示数据更新为例来看看显卡的数据如何到达vnc layer.
cirrus_do_copy==》 qemu_console_copy ==> dpy_copy
staticinline void dpy_copy(struct DisplayState *s, int src_x, int src_y,
int dst_x, intdst_y, int w, int h) {
struct DisplayChangeListener *dcl =s->listeners;
while (dcl != NULL) {
if (dcl->dpy_copy)
dcl->dpy_copy(s, src_x, src_y,dst_x, dst_y, w, h);
else /* TODO */
dcl->dpy_update(s, dst_x, dst_y,w, h);
dcl = dcl->next;
}
}
dcl->dpy_copy==> vnc_dpy_copy ==> vnc_copy
其中vnc_framebuffer_update会更新dirty区域,下次refresh时会使用该区域
下面在来分析键盘qemu_add_kbd_event_handler会注册实际的处理函数,
下面分析最简单的ps2键盘:
ps2_kbd_init==》 qemu_add_kbd_event_handler(ps2_put_keycode,s);
最后来分析鼠标,其注册函数为qemu_add_mouse_event_handler
ps2_mouse_init==》 qemu_add_mouse_event_handler(ps2_mouse_event,s, 0, "QEMU PS/2 Mouse");
vnc最终调用到鼠标与键盘的回调,虚拟驱动就能得到数据,并向虚拟机返回数据了