原文地址: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在用)的部分。下面分这几个部分简单阐述。
图片来源: 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生成的。
图片来源:http://sysmagazine.com/posts/180505/
可以看到,对于一个模块,有两个实现了模块抽象接口的跳板库。这对跳板库有两个对应的so组成(如文章前面提到的),一个是glibc的,一个是bionic的。前者会通过libhybris来load后者,两个世界就通过它们桥接起来了。然后bionic那个会通过IPC向service申请服务,而service可以是链接bionic的。通过这套机制,glibc世界就间接地使用了HAL层。
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结构大体如下:
首先,在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。