`
jinghuainfo
  • 浏览: 1524179 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

DirectFB运行机制介绍

 
阅读更多
<meta http-equiv="content-type" content="text/html;charset=UTF-8"> DirectFB运行机制介绍
转载时请注明出处和作者联系方式 作者联系方式:李先静 <xianjimli at hotmail dot com>

事实上我对DirectFB的理解是比较肤浅的,几年前的确花了一些时间研究去DirectFB的实现,但只是集中在一部分功能的代码上。后来GUI(DirectFB/GTK)由一位同事接手了,我就没有花太多时间在上面了。可能是因为前面写过几篇关于DirectFB的文章,结果还真有不少网友把我当专家了。答应过一位网友写篇介绍DirectFB运行机制的文章,这个周末花了点时间写了这篇短文,放在这里供有需要的朋友参考。文中若有不当之处,望大家不吝指正。

DirectFBInit 这个函数其实并没有做什么特殊的初始化工作,只是加载了配置文件而已。加载的顺序如下,重复的配置内容以后加载的为优先:

  • 系统全局配置文件,老版本文件为/etc/directfbrc,新版本文件为SYSCONFDIR"/directfbrc"
  • 用户全局配置文件,文件为~/.directfbrc
  • (系统)应用程序特定的配置文件 SYSCONFDIR"/directfbrc.应用程序名"
  • (用户)应用程序特定的配置文件 ~/.directfbrc.应用程序名
  • 命令行参数
DirectFBCreate 真正的初始化是在这里面进行的,严格的说这也不叫初始化了,因为这是创建DirectFB对象,对象当然是可以创建/销毁多次,而初始化通常只能做一次了。考虑到在一个进程中DirectFBCreate通常只会调一次,进程退出时才销毁,所以我们还是把它看作初始化。现在我们来看看DirectFBCreate做了什么: direct_initialize direct是一个基础库,这个函数主要做了两件事情:
  • 初始化log。 direct实现了三种LOG方法,第一种是输出到stderr上,第二种是输出到指定的文件里,第三种是通过UDP输出到网络上。这种方法对嵌入式环境是特别有用的,因为很多时候没有机会看终端上的信息。
  • 安装信号处理函数。主要是针对像SIGTERM这类会导致程序退出的SIGNAL,DirectFB希望在程序退出之前做些善后处理。值得学习的是direct是一个基础库,它不能直接调用上层组件的函数,所以提供了direct_signal_handler_add之类的函数让上层组件注册/注销回调函数,避免了下层函数与上层函数的耦合。
dfb_core_create 核心组件初始化。核心组件都是以共享库形式存在的,这里主要是加载这些共享库,并调用相应的初始化函数。为了避免框架与组件之间的耦合,DirectFB是通过遍历目录去加载的,删除不必要插件可以提高加载速度。
  • dfb_system_lookup 其实system在这里并没有表示出它真正的意义,这里的system实际上是指显示方式,比如基于framebuffer的fbdev和基于XWindow的x11等等。它先调用direct_modules_explore_directory在找出目录中所有的system实现,然后与配置中指定的名称匹配,找到指定的system后调用GetSystemInfo得到一些system信息。
  • direct_find_best_memcpy 因为GUI里面memcpy的效率对系统整体的效率影响很大,所以DirectFB针对不同的系统实现了一些优化过的memcpy函数。
  • fusion_enter 初始化fusion,fusion是用于进程间通信的机制,/dev/fusion/n是用于进程间消息传递的文件,每个进程都有这样一个文件。在本函数中会打开这个文件,并用ioctrl发送FUSION_ENTER命令。随后映射/初始共享内存。最后创建消息分发线程,其线程入口函数为fusion_dispatch_loop。
  • fusion_arena_enter 我没有直接用过arena,它的主要功能似乎只是对共享数据的管理,通过一个名称与一块数据关联起来,放到hash表中,每个进程都可以通过名字取相应的数据。
IDirectFB_Construct构造一个IDirectFB对象。
  • 通过dfb_layer_at_translated获取primary layer.
  • 通过dfb_layer_get_primary_context获取primary layer, CoreLayerContext里面最重要的东西可能就是CoreWindowStack,CoreWindowStack管理着所有窗口,对应用程序产生直接的影响。
  • 通过dfb_layer_context_windowstack获取窗口栈。
  • 初始化IDirectFB接口的虚函数,让函数指针指到具体的函数上。随便说一句,最近还有一些冒充专家的外行说C语言只能按面向过程的方法开发软件,甚至说C语言只适合开发小项目,真是无语了。建议他们看看DirectFB的代码,update一下他们陈旧的观念。
  • 如果是master进程,还要调用InitLayers, 它的主要功能是根据配置初始化layer,包括显示区域大小 ,调色板,透明色,窗口栈的显示区域和绘制背景(背景图片或者颜色填充)等等。
dfb_wm_post_init如果是master进程,还要执行窗口栈的PostInit,我看了几个窗口管理器的实现,这个函数都是空的。 dfb_core_activate初始化完成后激活dfb_core。这也是很重要的,可以避免初始化完成之前dfb_core被其它线程使用。 IDirectFB_CreateInputEventBuffer这个函数创建一个EventBuffer,通过EventBuffer可以获取输入事件,不过通常应该通过Window去创建EventBuffer,那样可以收到更高层的事件。InputEventBuffer实际上主要是对InputDevice的包装,不要小看这个包装,包装后有三个重要变化:
  1. Event被缓冲,不用担心事件丢失或者阻塞驱动程序。包装前的InputDevice事件是来一个处理一个,包装后则是先放到缓冲中,然后一个一个的处理。
  2. 由被动变为主动,更符合GUI的事件处理的惯例。包装前是上层设置回调函数,在事件到来时回调函数被下层调用,包装后是上层主动调用GetEvevnt 获取事件。
  3. 由多线程变为单线程,简化了处理。包装前是由输入线程回调上层设置的回调函数,每个输入设备都是一个独立的线程,所以回调函数要考虑多线程并发执行的问题,包装后多线程问题由EventBuffer处理掉了,上层通过GetEvent获取事件并处理,处理函数始终是在主线程中执行的,所以不用考虑与输入线程的并发问题了。

IDirectFB_SetCooperativeLevel设置指定的IDirectFB对象与其它IDirectFB对象的协作级别,DFSCL_FULLSCREEN和DFSCL_EXCLUSIVE的处理方式是一样的,与DFSCL_NORMAL的差别是它们有自己独立的Context,也就是说设置为DFSCL_FULLSCREEN的DirectFB对象自成一个体系,不会与其它DirectFB对象干扰。在多进程情况下,一般是设置为DFSCL_NORMAL的,它保证所有窗口由同一个窗口器管理。(题外话,这个函数里调用了drop_window函数,drop_window的实现是有点问题的,它始终调用dfb_windowstack_cursor_enable去打开光标,这是不对的,应该根据据情况而定,在IDirectFB_Destruct里调用时就不应该打开光标,因为这可能会造成程序不正常退出。)

输入设备事件所有GUI系统都是基于事件驱动模型的,搞清楚DirectFB事件处理过程对理解DirectFB是很有帮助的。这里介绍一下DirectFB的输入事件处理。

  • 输入设备驱动程序 输入设备驱动程序的代码是放在inputdrivers目录中的,编译之后安装到$(PREFIX)/lib/directfb-1.1-0/inputdrivers中。运行时每个驱动程序都会创建一个线程,它们通常都是挂在设备文件上,当有输入事件时就会被唤醒。唤醒后先读取输入事件,然后调用dfb_input_dispatch把事件分发给上层组件。

    要注意的是,输入设备线程是在driver_open_device里创建的,而driver_open_device是在dfb_core_create函数里调用的,也就是说线程起来的时候dfb_core可能还没有完成初始化,如果在这个时候有输入事件上来,可以会造成程序死掉。为了避免这个问题,应该在dfb_input_dispatch里检查一下dfb_core初始化是否完毕。

  • dfb_input_dispatch先调用fixup_xxx函数对输入事件做些转换和映射,再调用core_input_filter对一些特殊键处理,比如屏幕截图等,再调用fusion_reactor_dispatch分发事件。分发时先通过函数_dfb_windowstack_inputdevice_listener把事件分发给窗口管理,然后再分发给其它全局回调函数,最后通过reactor把分发给用户注册的回调函数。
  • _dfb_windowstack_inputdevice_listener通过dfb_wm_process_input调窗口管理器的wm_process_input函数。default窗口管理器是在这个函数里对输入事件处理的,它的实现比较简单,我们这里不讨论了。unique窗口管理里却不是以这种方式实现的,它调用dfb_input_add_global把_unique_device_listener注册为全局的输入事件的回调函数。其实unique也完全可以用dfb_wm_process_input来实现的,可能是实现者的想法不一样而已。
  • _unique_device_listener则按输入设备类别把事件分发给相应的处理函数,这些函数是在devices中实现的。我们broncho平台的手写就是在pointer.c中拦截笔点事件的,现在想来,手写完全可以独立出来的。这些函数把DFBInputEvent事件转换为UniqueInputEvent事件,再调用unique_device_dispatch分发事件。
  • unique_device_dispatch这里消息分为两路,一路经_unique_cursor_device_listener去更新当前光标的位置,一路经_unique_input_switch_device_listener把消息投递到焦点窗口。
  • _unique_input_switch_device_listener消息经unique_input_channel_dispatch到_unique_window_input_channel_listener到dfb_window_post_event到IDirectFBEventBuffer_WindowReact到IDirectFBEventBuffer_AddItem,最后放入EventBuffer中。当然如果是多进程的情况,要经过内核模块fusion才能到达焦点窗口的EventBuffer。
  • IDirectFBEventBuffer_GetEvent前面的函数都是都被动调用,由输入线程或者fusion事件线程调过来的。GetEvent则是上层主动调用的,它从EventBuffer的消息队列中获取事件,调用之前最好先调用WaitForEvent或者WaitForEventWithTimeout等待事件发生。
  • IDirectFBEventBuffer_CreateFileDescriptor输入事件并不是唯一的事件,可能还有定时器,管道和网络事件等,WaitForEvent/WaitForEventWithTimeout会让主线程阻塞,按Unix下通用的做法是用select/poll挂在多事件源上,只要有事件发生select/poll就会唤醒,这样单线程就可以处理多事件源,CreateFileDescriptor创建一个文件描述符,可以用select/poll来等待事件。

显示输出DirectFB可以输出到不同的目标上面,这个可以通过配置中的system指定。

  • Surface Surface是一个概念简单而实现复杂的东西,它在功能上和Windows中的DeviceContext类似,提供基本绘图功能。每个窗口都和一个Surface关联,窗口上的控件与一个SubSurface关联。绘制结束后调用Flip更新到物理屏幕上。Surface一般是双缓冲,这主要是避免闪烁,Flip时并不总是BACK和FRONT交换。如果只是更新一个区域则是直接从BACK向FRONT拷贝这个区域的内容。
  • IDirectFBSurface_Window_Flip实际上,普通Surface是不随便Flip的,因为它没有层次关系,直接刷新到屏幕上,整个显示就会乱了,所以只有DSCAPS_FLIPPING能力的特殊Surface才允许Flip。与窗口关联的Surface重载了Flip函数,用IDirectFBSurface_Window_Flip代替了IDirectFBSurface_Flip,这样Surface Flip的时候,它会调用dfb_window_repaint更新整个窗口栈中的窗口。
  • wm_update_window这是窗口栈中的update函数,由dfb_window_repaint调用过来。它会根据窗口栈中窗口的层次和位置关系,决定哪些窗口的区域要更新,这部分代码相对来说是有点复杂的。
  • dfb_layer_region_flip_update这是真正Flip了,它会调用primaryUpdateRegion去Flip。
  • 谁能更新物理屏幕?对于fbdev,master和slave都可以更新。对于x11只有master可以更新,slave通过fusion_call_execute请求master更新。

~~end~~

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics