(转)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。
(转)libhybris及EGL Platform-在Glibc生态中重用Android的驱动的更多相关文章
- 关于Android Studio 3.2 运行应用时提示 “Instant Run requires that the platform corresponding to your target device (Android 7.0 (Nougat)) is installed.” 的说明
点击"Run",运行App后,Android Studio显示如图1-1界面: 图1-1 这是因为你连接的外部设备(比如Android手机或AVD)的SDK版本在你的电脑上没有安装 ...
- 大数据与 AI 生态中的开源技术总结
本文由云+社区发表 作者:堵俊平 在数据爆炸与智能革命的新时代,新的平台与应用层出不穷,开源项目推动了前沿技术和业界生态快速发展.本次分享将以技术和生态两大视角来看大数据和人工智能技术的发展,通过分析 ...
- Linux platform平台总线、平台设备、平台驱动
平台总线(platform_bus)的需求来源? 随着soc的升级,S3C2440->S3C6410->S5PV210->4412,以前的程序就得重新写一遍,做着大量的重复工作, 人 ...
- 上海仪电Azure Stack技术深入浅出系列1:谈Azure Stack在私有云/混合云生态中的定位
2.2 Azure Stack Azure Stack到2017年7月才提供GA版本,但目前还是可以通过技术预览版了解该技术.Azure Stack本质上是核心Azure服务的一个私有实例. Micr ...
- Hadoo生态中pHive HBase 项目的区别
http://jenmhdn.iteye.com/blog/1678789 导读:Apache Hive是一个构建于Hadoop(分布式系统基础架构)顶层的数据仓库,Apache HBase是运行于H ...
- Spring生态简介
目录 概述 项目说明 主要项目 社区项目 保留项目 最后总结 概述 做Java开发的人一提起Spring,首先在脑海中浮现出的就是"IoC","AOP",&qu ...
- 将Win8.1/WP8.1应用迁移到Universal Windows Platform
在上一篇在VS2015 RC打开CTP中创建的工程,我们介绍了怎么在RC中打开CTP中创建的Universal 工程,这一篇我们来讲下怎么将Windows 8.1/WP8.1的应用迁移到Univers ...
- 互联网云生态下DDOS安全产品的一些考虑和测试方法(一)
DDOS攻击简介 安全的三要素——“保密性”.“完整性”和“可用性”中,DOS(Denial of Service拒绝服务攻击)所针对的目标是服务的“可用性”.这种攻击方式利用目标系统的网络服务功能缺 ...
- 为AM335x移植Linux内核主线代码(35)使用platform中的GPIO
http://www.eefocus.com/marianna/blog/15-02/310352_46e8f.html 使用GPIO,当然可以自己编写驱动,比如之前的第34节,也可以使用Kernel ...
随机推荐
- python(40):利用utf-8编码判断中文英文字符
#!/usr/bin/env Python # -*- coding:GBK -*- """汉字处理的工具: 判断unicode是否是汉字,数字,英文,或者其他字符. 全 ...
- git push remote error解决办法
通常在用git clone了remote端(服务器)的git仓库后,再进行了自己一系列修改后,会将自己测试后稳定的状态push到remote端,以更新源仓库,使 其他人在pull的时候得到自己的修改. ...
- 关于SimpleMsgPack中swap引发的问题大端法和小端法研究笔记
今天diocp裙中[珠海]-芒果反应了一个关于SimpleMsgPack的问题 msgPack.AsFloat = 2.507182; 经过编码再解码后,会直接触发异常. 因为msgPack的标准,在 ...
- 【ARM】2440裸机系列-RTC数字时钟
功能 裸机程序,实现LCD显示数字时钟 主要代码 1)背景绘制 void Brush_ U32 c) { int x,y ; for ( y = 0 ; y < LCD_HEIGHT ; y ...
- 解决chrome extension无法下载的问题
由于GFW把谷歌应用商店给屏蔽了,下载chrome扩展变得很困难. 我使用的是版本30.0.1599.101 m. 那么你需要做的第一个处理是,修改host文件,保证chrome应用商店可以登录.如下 ...
- Quo JS多种触摸手势轻量级JavaScript库
http://www.uedsc.com/quo-js.html http://quojs.tapquo.com/
- Windows下Python3+nose+appium自动化测试之Android篇
[本文出自天外归云的博客园] 简介 以下用来做自动化测试的这款app叫最爱抓娃娃,以后会改名为网易抓娃娃. 下文提到的appiumier项目里会包含用来测试的apk包以及自动化测试代码. 先说一个坑 ...
- Redis之 命令行 操作
一.key pattern 查询相应的key (1)redis允许模糊查询key 有3个通配符 *.?.[] (2)randomkey:返回随机key (3)type key:返回key存储的类型 ...
- visualstudio学习
https://docs.microsoft.com/zh-CN/visualstudio/ide/get-started-with-visual-studio
- C#使用System.IO.Path获取文件路径、文件名
class Program { static void Main(string[] args) { //获取当前运行程序的目录 string fileDir = Environment.Current ...