原文地址: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》。
它的一些主要目录如下:
- –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库为例:
1 2 3 4 5 |
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():
1 2 3 4 5 6 7 8 9 |
|--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:
1 2 3 4 5 6 7 8 9 10 11 |
|--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层。
1 2 3 4 5 6 7 |
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()则会返回其地址:
1 2 3 |
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中会将之转化为:
1 2 3 4 5 6 7 8 9 10 11 |
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。