zoukankan      html  css  js  c++  java
  • (转)libhybris及EGL Platform-在Glibc生态中重用Android的驱动

    原文地址:http://blog.csdn.net/jinzhuojun/article/details/41412587

    libhybris主要作用是为了解决libc库的兼容问题,目的是为了在基于GNU C library的系统运行那些用bionic编译的库(主要是Android下的闭源HAL库)。它在Ubuntu touch, WebOS, Jolla Sailfish OS等系统中都有使用。因为这些系统都是基于glibc生态的,然而现有的硬件厂商提供的driver多是为Android而写的,自然也是用bionic编译的。那么问题来了,说服厂商再写一套驱动不是那么容易的,就算写出来也需要经过一段时间才能变得成熟。那如何让基于glibc的系统能够重用现有Android的driver呢?这就需要像libhybris这样的兼容层。

    libhybris的源码可以从https://github.com/mer-hybris/libhybris.git下载。编译过程可参见《Sailfish OS Hardware Adaptation Development Kit Documentation》。它的一些主要目录如下:
         compat // 一些核心模块的compatibility layer。它主要用在Ubuntu Touch中,和hybris目录下的相应目录一起,组成实现Android模块抽象接口的跳板库的两端。这下面的都是基于bionic编译的。
         hybris // 主要功能实现。这下面的都是基于glibc编译的。
              common // glibc/bionic的处理,包含一个自定义的Android版本相关的linker。
              egl // EGL platform,这是一个backend无关的egl抽象。其中还包含若干个具体backend的实现。
              glesv1/glesv2 // GLES库的wrapper
              hardware // libhardware的wrapper。
              sf/camera/ui/input // 跳板库的glibc端。它们会利用libhybris来打开compat目录下对应的bionic版本的跳板库。
              libnfc_nxp/libnfc_ndef_nxp/vibrator // 利用libhybris打开基于bionic的HAL库。
         utils //  Utility脚本 ,如抽取用于编译的Android的头文件等。
    值得注意的是libhybris有好几个系统在用,每个系统用法还不太一样。代码的贡献者也分好几波。而这些代码都放在同个git中,所以阅读代码时还得留意下文件头。写着Canonical的一般是for Ubuntu Touch的。写着Jolla一般是for Sailfish OS的。写着Collabora一般是为了添加Wayland的支持。

    总得来说,libhybris在解决libc库兼容性问题的基础上,还有Android模块抽象兼容层(主要是Ubuntu Touch在用)和EGL platform(主要是Sailfish OS在用)的部分。下面分这几个部分简单阐述。

    1. Bionic/glibc兼容

    前面提到过,libhybris的主要作用是libc的兼容,说白了就是把库中bionic的symbol替换成glibc中相应兼容的版本(当然对于其中一些会加些glue code)。它是主要工作原理是先为那些基于bionic编译的库写一个wrapper,当上层调用到这些库时,会首先load这个wrapper,这个wrapper就会使用一个自带的linker去load那些真正我们想load的库。这个自带的linker是一个基于Android中linker修改而成的版本,其中加入了对libc中symbol的特殊处理。下图是原理图。

    图片来源: http://events.linuxfoundation.org/sites/events/files/slides/Ubuntu%20Touch%20Internals_1.pdf  

    我们知道libc和bionic在pthread, IPC, exception, STL, wchar等方面都是有差异的,两者并不完全兼容。这就意味着简单的symbol mapping不能解决所有问题。在libhybris,它是通过linker中维护的symbol映射hash表和在bionic中打一些patch来共同解决的。bionic中打的patch具体可参见《Sailfish OS Hardware Adaptation Development Kit Documentation》Release 1.0.2-EA2 中的12.2节或者mer-hybris的git。

    对于一个基于bionic编译的库,其加载过程大致是这样的:首先wrapper库中会调用android_dlopen()来加载bionic编译的目标库。以gles库为例:
     64 static void  __attribute__((constructor)) _init_androidglesv2()  {
     65     _libglesv2 = (void *) android_dlopen(getenv("LIBGLESV2") ? getenv("LIBGLESV2") : "libGLESv2.so", RTLD_NOW);
     66     GLES2_LOAD(glBlendColor);
    其中GLES2_LOAD宏为:
     50 #define GLES2_LOAD(sym)  { *(&_ ## sym) = (void *) android_dlsym(_libglesv2, #sym);  } 


    对于这个库中的每个函数,都会调用android_dlsym来找到其地址。看来神奇之处是android_dlopen()和android_dlsym(),它们的定义都在自带的linker中。先来看看android_dlopen():
    android_dlopen()
         find_library()
              load_library() // 加载lib。
                   open_library()  // open, read, lseek, etc.
                   load_segments()
              init_library() // 初始化lib。
                   link_image()
                        find_library() // 递归加载所需要的库。
                        reloc_library() // 处理symbol重定位。
         call_constructors_recursive()
              // 忽略bionic版本的libc。

    其中的reloc_library()中做了libc中symbol的hook:
    reloc_library()
         get_hooked_symbol()
              hooks_install() 
                   hook_add() // 将 hybris/common/hooks.c中定义的hooks数组加入到hash表中(如没有加过),待查。
              hook_find() // 在hash表中查找该symbol,找到会直接返回,找不到会将尝试将其添加到hash表中。
              //白名单中的symbol会fallback到bionic中的版本。
              hook_add_from_lib(sym, "librt.so") // 在librt.so中检查该symbol。
                   dlopen(),  dlsym()
                   hook_add() // 加到hash表中,待查。
              hook_add_from_lib(sym, "libc.so.6") // 在libc.so.6中检查该symbol。
                   dlopen(), dlsym()
                   hook_add() // 加到hash表中,待查。

    可以看到,这个包含symbol映射关系的hash表中包含了两部分:一部分是hooks这个数组中定义的映射。在这里面的多半是将symbol地址指向libhybris中实现的wrapper函数。wrapper中执行一些glue code来处理兼容性问题,然后调用到glibc版本的对应版本。 另一部分是bionic的symbol到libc.so.6和 librt.so中对应版本的直接映射。前者是静态定义的,后者是runtime生成的。

    2. Android Abstraction Compatibility Layer

    除了通过自带linker直接打开bionic的库这种方法外,还有种截在比较高层的方法就是在glibc和bionic世界通过跳板库来桥接。这种方法适用于有一个访问HAL的service,并且Client通过IPC申请服务的情况。下图是Ubuntu Touch早期的做法。它将Android作为Host,而Ubuntu作为Guest跑在Container里(2013.7后就倒过来了)。libhybris定义了一套Android模块抽象接口,用于Ubuntu世界(基于glibc)调用Android世界(基于bionic)的服务。

    图片来源:http://sysmagazine.com/posts/180505/ 

    可以看到,对于一个模块,有两个实现了模块抽象接口的跳板库。这对跳板库有两个对应的so组成(如文章前面提到的),一个是glibc的,一个是bionic的。前者会通过libhybris来load后者,两个世界就通过它们桥接起来了。然后bionic那个会通过IPC向service申请服务,而service可以是链接bionic的。通过这套机制,glibc世界就间接地使用了HAL层。

    3. EGL Platform

    除此之外,在Graphics方面,libhybris还实现了EGL platform,这是一套backend无关的遵循egl接口的图形平台,以及多个backend的实现(如fbdev, hwcomposer, wayland, null)。它们的共同接口是ws_module(其中有init_module(), CreateWindow(), eglQueryString(), finishSwap()等)。hybris/egl/platform目录下,每个backend实现都会有一个名为ws_module_info的函数指针数组,其中是ws_module这个接口的实现函数。因此,上层要使用backend首先要获得这个结构。几个backend目录如下:
         * common(libhybris-eglplatformcommon.so):各个backend通用的部分,如BaseNativeWindow,BaseNativeWindowBuffer等,还有Wayland的支持。
         * fbdev(eglplatform_fbdev.so):定义了FbDevNativeWindow,其中打开framebuffer HAL,通过fbPost()往FB上绘制。对于Wayland compositor来说,一般设成fbdev(如果直接操作framebuffer)或者下面的hwcomposer(如果用hwcomposer合成)。
         * null(eglplatform_null.so):通过android_createDisplaySurface()创建FramebufferNativeWindow,FramebufferNativeWindow是Android早期用于操作FB的类。
         * wayland(eglplatform_wayland.so):创建WaylandNativeWindow,其中finishSwap()时向Wayland compositor提交buffer。对于Client来说,如果想要用egl接口来向compositor请求绘制,设成wayland即可。
         * hwcomposer(eglplatform_hwcomposer.so):定义了HWComposerNativeWindow。真正用时,会创建其实现类HWComposer。调用eglSwapBuffers() -> queueBuffer()时会调用HWComposer的present()函数。最终调用hwcomposer HAL的prepare()和set()函数。如qt5-qpa-hwcomposer-plugin是hwcomposer backend的实现,源码位于https://github.com/mer-hybris/qt5-qpa-hwcomposer-plugin.git

    hybris/egl/目录中为libEGL的实现,它是真正libEGL(基于bionic那个)的wrapper,它会load真正的libEGL库,同时也会hook某些函数,同时也会增加一些扩展函数。具体来说,上层对EGL接口的调用,分三种情况:
    1. 对于大多数函数,由于它们是backend无关的,因此通过之前的android_dlopen(), android_dlsym()得到真实libEGL库中的地址,映射过去进行调用就行。
    2. 对于几个关键函数,如eglCreateWindowSurface(),eglSwapBuffers()等,这些是backend相关的。需要对它们进行hook,加进glue code。以eglCreateWindowSurface()为例,首先会调用eglplatform的接口(ws_module)的wrapper函数(ws_CreateWindow())。这个wrapper首先会调用_init_ws()初始化真正的backend,具体基于哪个backend是在_init_ws()里根据环境变量HYBRIS_EGLPLATFORM或EGL_PLATFORM选择的。初始化后会调用相应backend的实现函数中(如wayland backend的话是waylandws_CreateWindow())。在其中创建完本地窗口对象后,接着还是会调用真实的eglCreateWindowSurface()来把创建的本地窗口传给EGL层。
    eglCreateWindowSurface(dpy, config, win, ...) // win的类型为wl_egl_window。
         EGL_DLSYM(&_eglCreateWindowSurface, ...) // 找到真实的那个eglCreateWindowSurface()地址。
         win = ws_CreateWindow(win, ...) // 为wl_egl_window创建本地窗口WaylandNativeWindow。
              _init_ws()
                   ws = dlsym(wsmod, "ws_module_info"); // load backend-specifc module
              ws->CreateWindow() // backend-specific implementation
        (*_eglCreateWindowSurface)(win,...) // 调用真实的eglCreateWindowSurface()函数。

    3. 另外,这个libEGL的wrapper还会增加一些额外的扩展函数,如eglBindWaylandDisplayWL()。它通过hook eglQueryString()和eglGetProcAddress()函数来实现。前者会返回EGL_WL_bind_wayland_display的字符串,表示支持该扩展。eglGetProcAddress()则会返回其地址:
    eglGetProcAddress()
         ws_eglGetProcAddress()
              return ws->eglGetProcAddress(procname) // eglplatformcommon_eglGetProcAddress()
    这个函数实现在eglplatformcommon.c,这里就可以加很多标准EGL不支持的函数,如eglBindWaylandDisplayWL()/eglUnbindWaylandDisplayWL()/eglQueryWaylandBufferWL()等。

    由此,可以看到libhybris中的EGL platform结构大体如下:



    下面以wayland backend为例,大体描述下客户端(simple-egl)通过EGL platform向Wayland compositor(Weston)提交渲染申请的过程。

    首先,在Server端,会调用刚才提到的EGL扩展函数eglBindWaylandDisplayWL(),它会调用server_wlegl_create(),其作用是在Server端注册server_wlegl的global资源对象,该对象接口为android_wlegl_interface(其中包含了create_handle(), create_buffer()接口)。以后Client就可以通过Wayland扩展协议向Server端提交图形缓冲区,然后通过Wayland协议通知Compositor渲染新buffer。

    Server端的初始化后,在Client端,先调用wl_compsitor_create_surface()通过Wayland协议创建窗口的代理对象wl_surface,然后调用wl_egl_window_create()创建硬件渲染窗口结构wl_egl_window,它在之后的eglCreateWindowSurface()中作为参数传入。在Wayland backend中,接着会调用waylandws_CreateWindow()。它创建WaylandNativeWindow,前面创建的wl_egl_window会设到这个WaylandNativeWindow的成员变量中。这个WaylandNativeWindow继承自ANativeWindow,因此它可以被传到真正的EGL库的eglCreateWindowSurface()中。可以看到,wl_egl_window是一个Wayland下硬件渲染窗口的抽象,它一头连着类型EGLNativeWindowType的WaylandNativeWindow与下层EGL连接,一头连着wl_surface与Wayland compositor连接。



    之后Client进行绘制,绘制完后调用eglSwapBuffers(),libhybris提供的libEGL wrapper中会将之转化为:

    ws_prepareSwap()
         waylandws_prepareSwap()
              WaylandNativeWindow::prepareSwap()
    (*_eglSwapBuffers)(); // 真实libEGL中的eglSwapBuffers()函数
         WaylandNativeWindow::queueBuffer()
         WaylandNativeWindow::dequeueBuffer()
    ws_finishSwap()
         waylandws_finishSwap()
              WaylandNativeWindow::finishSwap()
                   wlbuffer_from_native_handle() // 通过Wayland协议向Wayland compositor传输图形缓冲区句柄。这里用到了前面在Server端创建的server_wlegl资源对象。
                   wl_surface_attach/damage/commit() // 通过Wayland协议向Wayland compositor提交更新buffer和重绘请求。


    对于Wayland协议相关通信简介,可以参考此文(http://blog.csdn.net/jinzhuojun/article/details/40264449)。下图简单画了Client通过libhybris中的wayland backend来渲染的过程。

    对于其它的backend,原理也是一样的,只是ANativeWindow的具体实现类不同而已。如hwcomposer backend的话,WaylandNativeWindow的位置就被替换为HWComposerNativeWindow,当queueBuffer()时,会调用其实现继承类(如test_hwcomposer或qt5-qpa-hwcomposer-plugin中的HWComposer)的present()函数将该帧交由hwcomposer处理。再如fbdev backend,用的就是FbDevNativeWindow,它在queueBuffer()时会调用framebuffer HAL的post()将该帧放到FB中。null backend就更省事了,直接借用了Android中的FramebufferNativeWindow。

  • 相关阅读:
    第36课 经典问题解析三
    第35课 函数对象分析
    67. Add Binary
    66. Plus One
    58. Length of Last Word
    53. Maximum Subarray
    38. Count and Say
    35. Search Insert Position
    28. Implement strStr()
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/lihaiping/p/5570268.html
Copyright © 2011-2022 走看看