好贷网好贷款

VDI接口工作方式及具体实现

发布时间:2016-12-5 4:28:37 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"VDI接口工作方式及具体实现",主要涉及到VDI接口工作方式及具体实现方面的内容,对于VDI接口工作方式及具体实现感兴趣的同学可以参考一下。

转载自:http://www.cnblogs.com/D-Tec/archive/2013/03/01/2939311.html 〇、VDI接口工作方式 Spice中的VDI(Virtual Device Interfaces)是一个经过专门设计的接口规范,其设计哲学可以推广到很多需要做功能扩展的软件项目中。此类接口设计的主要目标是在尽量不改变原有代码的情况下,通过动态库的方式来为原有软件提供全新的功能组件。这种设计哲学在以开源代码为基础的商业软件开发中,其价值尤为明显,动态库的代码是完全私有的,因此可以不公开代码。 具体到VDI,其设计目标是提供一个标准方法,能够将虚拟设备的接口通过软件组件直接开放给其他软件组件使用。这里提到了两次“软件组件”,是VDI接口标准的两大组成部分,前者被称作back-end,后者被称作front-end。在Spice中,虚拟设备即为back-end,如虚拟显卡、虚拟声卡等,LibSpice则充当front-end角色:                         VDI接口规范本身非常简单,它仅为开发人员提供一种标准的开发方式,具体的VDI接口功能则由程序员自己负责。具体的约束总结如下: 1、必须包含一个固定的BaseInterface结构体成员。 2、Back-end与Front-end的交互关系初始化工作由Back-end负责发起。 3、Back-end负责实现与Back-end内部强相关且Front-end感兴趣的功能接口。 4、Front-end负责实现与Front-end内部强相关且Back-end感兴趣的功能接口。 所有的约束条件都很容易理解: 约束1的目的是确保所有VDI可以共用统一的接口,BaseInterface相当于基类功能。 约束2的原因则是由于Front-end通常以动态库的方式提供,属于被动执行体。 约束3和4则纯粹是软件设计的必然。 VDI接口规范除了规定了BaseInterface以外,没有规定具体的接口名称和数量,由程序员自己自由发挥。即:程序员只需要按照上述约束分别实现Back-end和Front-end,并将两边的接口在初始化时告知对方即可。 Spice实现的VDI接口主要有: SpiceCoreInterface:    Spice与Qemu内部交互用的接口 QXLInterface:          显示数据交互接口 SpiceKbdInstance:      键盘输入交互接口 SpiceMouseInterface:   鼠标输入交互接口 SpicePlaybackInterface:音频播放交互接口 SpiceRecordInterface:  音频录制交互接口 一、Qemu虚拟设备实现及初始化方法 VDI的Back-end是虚拟设备,因此有必要先简要介绍Qemu的虚拟设备实现方法。Qemu实现了非常多的设备类型,常见的有PCI、ISA、IDE、I2C等,每种设备类型都以一个struct来表示,如:struct PCIDeviceInfo定义了一个PCI设备的具体内容。尽管每种设备的功能接口各不相同,但所有设备类型都必须实现一个init函数,该函数供Qemu来初始化此设备。 Qemu内部维护了一个全局的设备列表,所有Qemu内部实现的虚拟设备结构对象如显卡、声卡等都注册到该全局设备列表中。Qemu的main函数执行以前,该全局设备列表已经初始化完成,因此在Qmeu的main函数执行过程中是找不到设备列表初始化的语句的,该特性必须由gcc编译器支持。Qemu的main函数负责所有注册到全局设备列表中的设备的初始化工作,即调用这些设备对象的init函数。 虚拟设备需要实现对应的物理设备所支持的所有基本功能,以虚拟显卡为例,虚拟设备必须实现标准VGA支持的所有I/0端口、显存、ROM等,所有这些工作都必须在init中完成,以保证设备能正常工作。 二、SpiceCoreInterface 每个VDI接口都有对应的Back-end和Front-end,Spice中所有的Back-end都是虚拟设备,CoreInterface的Back-end也不例外,唯一不同的是CoreInterface实现的是一个假的虚拟设备,即:它以虚拟设备的形式实现,以使Qemu在初始化时可以以统一的方式调用init函数来初始化VDI的Back-end,但不往全局设备列表中注册设备,不具备任何设备功能。 在介绍CoreInterface前先看看BaseInterface的定义: struct SpiceBaseInterface {     const char *type;     const char *description;     uint32_t major_version;     uint32_t minor_version; }; 接下来是CoreInterface定义如下: struct SpiceCoreInterface {     SpiceBaseInterface base;       SpiceTimer *(*timer_add)(SpiceTimerFunc func, void *opaque);     void (*timer_start)(SpiceTimer *timer, uint32_t ms);     void (*timer_cancel)(SpiceTimer *timer);     void (*timer_remove)(SpiceTimer *timer);       SpiceWatch *(*watch_add)(int fd, int event_mask, SpiceWatchFunc func, void *opaque);     void (*watch_update_mask)(SpiceWatch *watch, int event_mask);     void (*watch_remove)(SpiceWatch *watch);       void (*channel_event)(int event, SpiceChannelEventInfo *info); };     BaseInterface只定义了最基本的几个信息字段,用以描述此VDI接口的基本信息。另外代码中有很多地方直接将BaseInterface类型的指针指针转换为具体的VDI类型,其依据就是每个VDI的第一个数据成员为BaseInterface对象,类似这种转换非常多。     CoreInterface最主要的工作是定义了2组函数:Timer和Watch,其具体功能由Qemu实现,供libspice调用。这两组函数的作用分别是用来在Qemu中设置定时器和维护网络事件。下面以Watch函数组来详细介绍CoreInterface的工作方式。     Back-end:     Qemu中会定义SpiceCoreInterface的一个静态全局实例: static SpiceCoreInterface core_interface = {     .base.type          = SPICE_INTERFACE_CORE,     .base.description   = "qemu core services",     .base.major_version = SPICE_INTERFACE_CORE_MAJOR,     .base.minor_version = SPICE_INTERFACE_CORE_MINOR,       .timer_add          = timer_add,     .timer_start        = timer_start,     .timer_cancel       = timer_cancel,     .timer_remove       = timer_remove,       .watch_add          = watch_add,     .watch_update_mask  = watch_update_mask,     .watch_remove       = watch_remove,   #if SPICE_INTERFACE_CORE_MINOR >= 3     .channel_event      = channel_event, #endif }; 以上定义并初始化了一个SpiceCoreInterface实例,所有的数据成员和函数指针都做了初始化,其中就包括我们关心的Watch函数组:watch_add、watch_update_mask、watch_remove。core_interface是一个全局静态数据对象,这一点属于编码细节的关键。Libspice是以动态库的方式加载到Qemu的进程空间中的,因此可以将Qemu的全局数据对象直接给libspice使用。 与设备对象的初始化类似,利用gcc编译器的特性,在main函数执行之前,Qemu会调用qemu_spice_init函数来执行CoreInterface的初始化,该函数内部会调用libspice对外的几个有限的接口之一:spice_server_init函数来执行具体的CoreInterface初始化工作。 Front-end: spice_server_init函数在libspice中实现,实际工作之一就是将Back-end传递过来的core_interface对象的地址赋值给在libspice中定义的一个全局SpiceCoreInterface指针core。这样,libspice就可以使用CoreInterface中的各个回调函数了。 VDI中的成员函数都是以函数指针方式定义的,因此在编码及编译过程中,libspice都不需要实际的函数体,只需要在运行时正确的初始化这些函数指针就可以了。 对于CoreInterface来说,Back-end不需要Front-end提供服务,因此只在Qemu端为libspice初始化了响应的接口。 以上完成了CoreInterface的定义和初始化,下面来看Watch接口如何被使用的: spice_server_init初始化完core指针后,立即会调用reds_init_net来初始化spice的网络监听服务,实现函数如下: static void reds_init_net() {     if (spice_port != -1) {         reds->listen_socket = reds_init_socket();         reds->listen_watch = core->watch_add(reds->listen_socket,                                              SPICE_WATCH_EVENT_READ,                                              reds_accept, NULL);         if (reds->listen_watch == NULL) {             red_error("set fd handle failed");         }     } } 上面函数中调用core指针指向的watch_add函数来在Qemu增加一个socket事件。watch_add由Qemu实现,具体工作就是在一个socket队列中增加一个新网络socket,并注册其对应的网络事件处理函数reds_accept。当此socket可读或可写时,Qemu便调用reds_accept函数来处理网络事件。第二个参数则定义了此socket关心的网络事件,SPICE_WATCH_EVENT_READ表明当前socket只关心输入数据,因为该socket是负责网络监听的。 Qemu中针对Spice的网络处理是移植的关键,因此CoreInterface中Watch函数组的工作机制相对来说是一个框架性的东西,且具有分非常好的灵活性。 三、QXLInterface QXLInterface是最主要也是最复杂的一个VDI,并且与其他VDI不同的是,Front-end有一个单独的网络处理模型, QXLInterface在Front-end有自己单独的处理线程。Back-end端需要实现Qxl device,同时需要Guest OS的Driver配合工作。另外,Qemu需要Spice为其提供相关的接口来完成一些交互工作。 但是单从VDI接口本身来看,QXLInterface还是比较简单的,复杂性全部隐藏在各自模块内部。QXLInterface定义如下: struct QXLInterface {     SpiceBaseInterface base;       void (*attache_worker)(QXLInstance *qin, QXLWorker *qxl_worker);     void (*set_compression_level)(QXLInstance *qin, int level);     void (*set_mm_time)(QXLInstance *qin, uint32_t mm_time);       void (*get_init_info)(QXLInstance *qin, QXLDevInitInfo *info);     int (*get_command)(QXLInstance *qin, struct QXLCommandExt *cmd);     int (*req_cmd_notification)(QXLInstance *qin);     void (*release_resource)(QXLInstance *qin, struct QXLReleaseInfoExt release_info);     int (*get_cursor_command)(QXLInstance *qin, struct QXLCommandExt *cmd);     int (*req_cursor_notification)(QXLInstance *qin);     void (*notify_update)(QXLInstance *qin, uint32_t update_id);     int (*flush_resources)(QXLInstance *qin); };     以上结构中的函数指针由Qemu实现,并在调用spice_server_add_interface时初始化并传递给libspice。所有函数均与视频处理紧密相关,最重要的命令:get_command,用来从虚拟显卡设备中获取显示处理命令。     上面是Back-end提供给Front-end的接口,对于显示处理,Front-end还需要为Back-end提供接口,以使Back-end可以访问Front-end,该接口通过如下数据结构封装: struct QXLWorker {     uint32_t minor_version;     uint32_t major_version;     void (*wakeup)(QXLWorker *worker);     void (*oom)(QXLWorker *worker);     void (*start)(QXLWorker *worker);     void (*stop)(QXLWorker *worker);     void (*update_area)(QXLWorker *qxl_worker, uint32_t surface_id,                        struct QXLRect *area, struct QXLRect *dirty_rects,                        uint32_t num_dirty_rects, uint32_t clear_dirty_region);     void (*add_memslot)(QXLWorker *worker, QXLDevMemSlot *slot);     void (*del_memslot)(QXLWorker *worker, uint32_t slot_group_id, uint32_t slot_id);     void (*reset_memslots)(QXLWorker *worker);     void (*destroy_surfaces)(QXLWorker *worker);     void (*destroy_primary_surface)(QXLWorker *worker, uint32_t surface_id);     void (*create_primary_surface)(QXLWorker *worker, uint32_t surface_id,                                    QXLDevSurfaceCreate *surface);     void (*reset_image_cache)(QXLWorker *worker);     void (*reset_cursor)(QXLWorker *worker);     void (*destroy_surface_wait)(QXLWorker *worker, uint32_t surface_id);     void (*loadvm_commands)(QXLWorker *worker, struct QXLCommandExt *ext, uint32_t count); };     当libspice收到Qemu的QXLInterface接口增加消息后,需要回填此结构给Qemu。显示处理在libspice中是以单独的线程进行工作的,因此上面的接口中有一部分就是用来控制线程运转的命令,另一部分则是内存数据交互、图像处理相关的控制接口。     四、SpiceKbdInstance、SpiceMouseInterface 键盘鼠标的VDI接口非常简单: struct SpiceKbdInterface {     SpiceBaseInterface base;       void (*push_scan_freg)(SpiceKbdInstance *sin, uint8_t frag);     uint8_t (*get_leds)(SpiceKbdInstance *sin); }; struct SpiceMouseInterface {     SpiceBaseInterface base;       void (*motion)(SpiceMouseInstance *sin, int dx, int dy, int dz,                    uint32_t buttons_state);     void (*buttons)(SpiceMouseInstance *sin, uint32_t buttons_state); }; 五、SpicePlaybackInterface、SpiceRecordInterface 声音处理的两个VDI接口实现方式则自成体系,代码处理风格上与上面略有不同,先看这两个VDI接口的声明: struct SpicePlaybackInterface {     SpiceBaseInterface base; }; struct SpiceRecordInterface {     SpiceBaseInterface base; }; 什么也没有,只是封装了一下BaseInterface! 这与音频数据处理的实现方式有关系。虚拟显卡产生视频数据后,libspice需要主动调用get_command函数去获取数据,然后在libspice中进行相关的处理后发送到client。而音频处理则是在虚拟声卡的实现中直接调用libspice导出的api,将音频数据发送到client。因此Qemu不需要为libspice开放接口。 音频处理API接口如下: spice_server_playback_put_samples spice_server_record_get_samples

上一篇:[置顶] 大学新生寄语
下一篇:RSS协议中,时间字段格式要求

相关文章

相关评论