SPICE协议之RedWorker线程

来源:互联网 发布:阿里云深圳分公司地址 编辑:程序博客网 时间:2024/05/16 09:47

RedWorker是SPICE协议的架构的核心,该线程处理所有的display/cursor相关消息。RedWorker主函数red_worker_main()使用了异步事件处理框架,事件来源有:

1、驱动消息,包括绘图指令、Surface创建销毁、UpdateArea等等。

2、客户端display/cursor信道连接、断链

3、migrate消息

4、socket事件

5、定时器

red_worker.c中的register_callbacks()函数,这里是为控制面和驱动消息注册回调函数。具体的消息类型可以去看看。最新版本的SPICE对red_worker作了大幅重构,代码可维护性上升了不少,但是流程、算法和功能不受影响,本文以老版本为基准来分析。

首先发生的流程是surface_create。显示驱动加载时触发primary_surface创建。offscreen创建流程与primary surface略有差异。red_create_surface()是主要流程,初始化了worker->surfaces[],这个RedSurface结构体非常重要,对于一个surface上的所有draw command会顺序添加到RedSurface 上的current_list结构上。

第二步客户端display信道连接,对RED_WORKER_MESSAGE_DISPLAY_CONNECT消息的处理。这里创建了DCC(display channel client)数据结构,初始化了编解码器,并在on_new_display_channel_client()传送了第一副主屏图像到客户端。每个DCC都有一个pipe,发往客户端的所有消息都需要先挂在pipe上,当red_push()时候会从pipe tail取出来编码后发送。

无论客户端是否连接,主循环都每次都回调用red_process_commands()中处理来自驱动的绘图指令。该函数会轮询来自驱动的DRAW_COMMAND,其中QXL_CMD_DRAW的处理是重点,会进行渲染树的维护。渲渲染树的作用是重复数据消除和流媒体维护,它是RedWorker的核心,也是SPICE的核心。渲染树的数据结构是一个树型结构,每个surface都有自己的渲染树。所有的draw command到来时会往渲染树上添加节点。节点类型分析三种:

drawable:相当于叶节点;

container:包含多个drawable,相当于父结点;

shadow:当drawable类型是QXL_COPY_BITS(从surface自身的一块src区域copy到目标区域),创建shadow标识src区域。shadow被覆盖的部分会加入on_hold区,这个区域用于维护exclude_rgn。on_hold区缩小时,exclude_rgn需要相应扩大,这样就可以将shadow的区域的drawable正确地保留或者消除。为简化起见先不分析shadow。

渲染树操作的核心函数是red_current_add(),分为两个阶段:

1、第一阶段,主要是Container维护,也就是red_current_add()前半部分。首先进行region_test(),有三种结果。

第一种结果是新来的drawable与旧drawable(后面称为sibling)的region完全相同,如果新drawable类型是不透明的(QXL_EFFECT_OPAQUE)就会直接覆盖sibling,并将其从pipe中删除,这样就减少了重复数据传输。如果不是OPAQUE的,那么就会判断能不能覆盖删除了。在这里还会进行流媒体维护,因为同样的地方反复发生绘制容易进入流。

第二种结果是sibling被完全覆盖了而新来的drawable还有部分独立不重叠,且新drawable opaque,那么就删除sibling继续进行判断。

第三种结果是新drawable被sibling完全包围,且sibling是Container或是opaque drawable,就要进入Container继续比较或者创建Container后继续比较,相当于树节点向下延伸一级。

exclude_base是比较的起点,exclude_region都需要以此为起点进行重复数据消除,也就是说前面(sibling链表前面的部分以及上一级Container部分)的已经比较完了,后继渲染树比较操作从这里开始。exclude_region()本质上是搜索渲染树,如果遇到重叠就调用__exclude_region()把重叠部分消除掉。如果旧的drawable被多个新的 drawable叠加在一起才完全覆盖了,这里也能进行消除。

2、第二阶段是red_current_add() while循环之后的半部分,这里就只会消除而不会再新建Container了。如果drawable 是opaque的,就要再进行剩余的节点的搜索消除,然后维护流。如果不是opaque,那么就需要将与之重叠的stream detach。最后把drawble挂到渲染树的合适位置。

在渲染树操作完成之后,就要往pipe中添加了,需要关注red_handle_drawable_surfaces_client_synced(),这个函数会把客户端缺失的offscreen surface创建传送过去。我们再往回看red_handle_surfaces_dependencies(),这个函数会把drawable依赖其他surface的关系维护好,并将与主surface中依赖区域中重叠的stream detach掉。

现在再来关注red_push(),它的作用是把DCC级别将pipe数据编码发送给客户端。之前push到pipe的item会从队尾取出来,在display_channel_send_item()中分发到各个处理函数中。研究编解码先关注red_marshall_qxl_draw_copy(),这个函数编码出来的数据量是占据最大流量的地方。具体流程就不再详细分析了,总体而言,这里根据客户端能力配置、测速信息、图形GRADUAL级别来决定采用有损压缩还是无损压缩,构造好消息和压缩数据,都送入marshaller,然后通过marshaller发送给客户端。marshaller发送使用的是非阻塞socket,进行异步传送。marshaller是一个巧妙的零拷贝发送机制,如果有人感兴趣想自己编写一套协议,这个机制也是可以重用的。

另一个关注点是update_area。update_area的作用是将指定surface中的指定区域绘制到“显存”中,此功能的触发可能来自于驱动需求,也可能来自于内部绘制的需求。update_area之后,渲染树上的drawable就会被摘除了,但是pipe可能还没有发送出去,所以这些drawable后继就无法通过渲染树消除重复数据了。

总的来说SPICE很优秀,但是我们看到其重复数据消除过于简单,仅仅依赖于渲染树,所以无法降低流量到ICA或者SPLASHTOP的水准,如果要进行优化需要添加其他流控机制。





0 0