AOSP-FDBUS通信中间件编译、配置、通信测试
智能座舱域主流采用Android+QNX组合的方式,其中QNX作为仪表信息系统,而Android作为IVI车载娱乐系统。两者不可避免需要进行通信。
基于安全性和独立性要求,不同的系统之间数据不能直接访问。但是不同域下的不同系统之间,同一域下的不同系统之间的通信需求现实存在。现有的解决方案主要是基于TCP/IP协议栈的Socket(套接字)通讯。
大众在FDBUS基础上开发通信中间件完成系统间的通信。
FDBUS介绍
FDBus:https://gitee.com/jeremyczhen/fdbus
Fast Distributed Bus,基于Socket(Unix Domain和TCP)的快速分布式总线。
UDS:Unix Domain Socket,专用于IPC。Zygote中的Local Socket即为UDS。
FDBUS为了更方便的进行寻址,允许不通过IP+端口或者固定UDS地址,而是采用域名的方式进行寻址。类似于在浏览器输入 www.baidu.com ,与百度完成通信,则需要进行DNS解析,将域名转化为IP地址。
FDBUS同样提供了 name-server ,负责管理server名字(域名)与地址(IP)的映射,和DNS服务器一样,完成
server名字到IP的解析。
编译
将FDBUS源码下载后放入Android AOSP源码 /frameworks/native/services
中:
不是必须放入该目录,这里以此目录为例。
配置fdbus的android.bp文件
打开Android.bp文件,将其修改为如下:
1 | //===================================================================================== |
上述脚本会编译出libfdbus.so、libfdbus-jni.so与name-server可执行文件。
libfdbus.so、libfdbus-jni.so的输出目录为:
/home/jia/AOSP/aosp/out/target/product/emulator_x86_64/system/lib
name-server的输出目录为:
/home/jia/AOSP/aosp/out/target/product/emulator_x86_64/system/bin
部署启动与权限配置
配置product
在Android12中在 /build/target/product/base_system.mk 中配置:
1 | # Base modules and settings for the system partition. |
name-server启动配置
为了让name-server开机启动,需要在 init.rc 中配置启动脚本。
打开 /system/core/rootdir/init.rc 在文件最后加入:
1 | #1、在挂载 /data 后创建/data/misc/fdbus 并设置system用户组权限 |
1、因为FDBUS支持uds与tcp。在当前系统中其他进程需要连接name-server会采用UDS的方式,/data/misc/fdbus 则为name-server的uds固定地址。
1 mkdir /data/misc/fdbus 0755 system system:创建文件并设置权限。2、-u 参数后文解释,-n 参数为当前name-server的别名可以随意传递。
1
2 class core:当前服务属主,系统能根据属主统一管理同属主下所有的进程,同一个class的所有服务必须同时启动或者停止。
group inet:当前服务的用户组。 inet 组表示允许网络访问!在
/framework/base/data/etc/platform.xml
中可以查看权限对应的用户组:
name-server的SELinux权限配置
完成上述配置后,系统启动就会拉起name-server,但是此时name-server还无法正常工作。Android是建立在标准
的Linux Kernel基础上,通过Linux的 SELinux(SEAndroid)进行访问权限控制。比如name-server需要访问
tcp_socket、unix_stream_socket则必须开启SElinux的访问限制。
修改 system/sepolicy/prebuilts/api/31.0/private
与 system/sepolicy/private
目录下同
样的:file_contexts、netd.te与untrusted_app_all.te文件,并在两个目录下都创建name-server.te文件。
在file_contexts的最后一行加入:
1 | /system/bin/name-server u:object_r:name-server_exec:s0 |
新建name-server.te 文件中内容为:
1 | type name-server, domain, coredomain, mlstrustedsubject; |
同时name-server需要与netd进程(网络管理进程)交互,还需要在netd.te 增加:
1 | typeattribute netd coredomain; |
如果需要普通APP使用name-server, 还需要在 untrusted_app_all.te 中加入:
1 | allow untrusted_app_all app_api_service:service_manager find; |
FDBUS-API Framework-SDK集成
在上一步,我们不仅完成了name-server的部署,同时编译出libfdbus-jni.so,该动态库为FDBUS的Java层API的JNI封装。为了在Java中完成FDBUS的使用,可以将FDBUS集成进入Framework与SDK中。
将FDBUS中的 jni\src\java 下的Java代码放入AOSP源码中的 /frameworks/base/core/java 中
修改AOSP源码中的 build/soong/scripts/check_boot_jars/package_allowed_list.txt 。
修改 /framework/base/Android.bp
中的metalava_framework_docs_args与packages_to_document 。前者表示忽略我们的Java代码中的lint检查;后者则代表将我们的Java API打包进入文档,这样才能在SDK中可见,也就是说APP能够调用新增的Java API。
1 | metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) "+ |
编译与集成过程中的错误及解决
clang 编译错误
在CBaseClient.cpp的118行存在如下代码:
1 | LOG_E("CClientSocket: client %s shutdown but try to request address of and connect again...\n",client->name().c_str(), client->nsName().c_str()); |
Android Log库输出日志,但是代码中传递了两个参数 client->name().c_str() 与 client->nsName().c_str() ,
然而只存在一个 %s 接收,此时参数不匹配。解决该问题的方式有两种:
修改源码
在第一个参数中加入一个 %s ,让参数与占位符数量匹配。
1 | LOG_E("CClientSocket: client %s shutdown but try to request address of and connect %s again...\n",client->name().c_str(), client->nsName().c_str()); |
配置android.bp
在Android.bp中的name-server配置处的cflags与cppflags处加入:”-Wno-format-extra-args”,表示忽略该问题。
其他clang错误
继续编译,会出现很多与上一个类似的问题日志,都是因为Android12在编译时cflags/cppflags设置了-Werror,将
原本默认为警告的类型指定为了错误。
1
2
3
4
5
6 -Werror:把警告当作错误,出现任何警告就放弃编译;
-Werror=【type】:将指定类型的警告设置为error,如-Werror=unused-variable(当存在未使用的变量时
中断编译);
-Wno-error:忽略所有报错;
-Wno-error=【type】:忽略指定类型错误;
-Wno-【type】:忽略指定类型的警告;
按照日志提示,在Android.bp中所有模块的cflag与cppflags中加入:
1 | "-Wno-non-virtual-dtor", |
最终Android.bp中所有的cflag与cpflags为:
jni.h not found
由于libfdbus-jni中需要使用jni,此时在Android12中编译会报出:
1 | In file included from frameworks/base/core/jni/fdbus/CJniAFComponent.cpp:17: |
通过学习 framework/base/core/jni 下的Android.bp(编译libandroid_runtime.so)发现,其中存在如下配置:
1 | //...... |
可以发现libandroid_runtime.so依赖于libnativehelper_lazy.a,同时会导出libnativehelper_lazy的头文件。在
源码根目录下找到libnativehelper中的Android.bp,其中部分内容如下:
1 | //头文件模块:jni_headers,包含include_jni下所有的头文件(该目录下存在一个头文件jni.h) |
很显然, framework/base/core/jni 下的libandroid_runtime.so 依赖静态库libnativehelper_lazy。同时在libnativehelper_lazy的配置中会导出 include_jni ,因此能使用 jni.h 。而我们的libfdbus-jni.so不需要依赖nativehelper,因此可以直接在libfdbus-jni的编译配置中加入:
1 | header_libs: ["jni_headers"] |
如果需要依赖nativehelper可以写成:
1 | //依赖libnativehelper_lazy.a |
因为在静态库和动态库中都将 include_jni 目录导出,因此依赖这些库即可使用 jni.h 。
package_allowed_list.txt
由于我们在Framework中增加了对应的Java API,此时编译报错如下错误: ipc.fdbus 包不允许打包进入Framework。
按照提示,在 build/soong/scripts/check_boot_jars/package_allowed_list.txt 中加入我们的包名:
selinux file_contexts错误
1 | Error: could not load context file from out/target/product/emulator_x86_64/obj/ETC/plat_file_contexts_intermediates/plat_file_contexts |
这个错误最终定位到原因,挺扯淡的。是因为在file_contexts最后一行增加name_server上下文后没有增加空行引起的……
1 | # ...... |
抓取开机日志排查问题
- 先用命令
adb reboot
重启设备模拟器 - 然后立即输入命令
adb root
- 然后
adb wait-for-device shell dmesg >dmesg.txt
FDBUS部署与跨系统通信测试
车载系统中,由于Hypervisor的采用,有的域内可能会有多个节点。如智能座舱域,一个SOC芯片上可能会同时运行QNX和Android,虽然位于同一个SOC上,但还是被认为是两个节点。这两个节点必须打通,才能相互通信!
在Android与QNX系统中各自部署name-server,Android与QNX的name-server只能管理系统自身内部服务,为了
打通两个系统,FDBUS提供了host-server。
就好像DNS解析,本地DNS服务器无法解析,就会请求远程服务器。如果说name-server是本地DNS服务器,那么
host-server就是远程DNS服务器。
host-server部署
host-server可以搭建在域内,也可以搭建在域外,只要保证Android与QNX都能够访问即可。
为了方便测试,我们模拟一个通信环境,大致描述一下环境:
在ubuntu系统上跑一个android模拟器。
android模拟器可以理解为车载的android系统,内部运行有name-server
ubuntu系统可以理解为车载的QNX系统,内部运行了name-server、host-server和一个真正处理业务的service
我们需要在Ubuntu上搭建name-server和host-server服务,然后启动一个测试的service server
fdbus的完整编译参照文章 https://blog.csdn.net/u012739527/article/details/124011570
编译完成后,启动name-server、host-server、fdbxserver(fdbus提供的测试服务)
1 | jia@jia-virtual-machine:~/work/fdbus/fdbus/build$ ./host_server & |
客户端测试app
需要先将上文中aosp集成fdbus后编译出的SDK更新添加到studio的platform中以便app compile使用。
然后编写测试代码如下:
1 | package com.example.myapplication |
调用代码
1 | package com.example.myapplication |
运行app并点击测试
控制台输出
1 | qnx响应: xxxxxx |
fdbxserver收到响应并回复