无与伦比

libhybris及EGL Platform-在Glibc生态中重用Android的驱动

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

0. 概述

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下载。编译过程可参见《Sailfish OS Hardware Adaptation Development Kit Documentation》。
它的一些主要目录如下:

值得注意的是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库为例:

对于这个库中的每个函数,都会调用android_dlsym来找到其地址。看来神奇之处是android_dlopen()和android_dlsym(),它们的定义都在自带的linker中。先来看看android_dlopen():

其中的reloc_library()中做了libc中symbol的hook:

可以看到,这个包含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目录如下:

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层。

(3)另外,这个libEGL的wrapper还会增加一些额外的扩展函数,如eglBindWaylandDisplayWL()。它通过hook eglQueryString()和eglGetProcAddress()函数来实现。前者会返回EGL_WL_bind_wayland_display的字符串,表示支持该扩展。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中会将之转化为:

对于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。

退出移动版