Rust 在 Android 的编程实践——技术驱动的车云一体化解决方案探索
Greptime 车云一体化解决方案颠覆了从前传统的车云协同模式,采用更加低成本、高效率的方案来满足当前的市场需求。其中 GreptimeDB Edge 作为核心组件,专为车机环境量身打造。本文旨在详尽探讨在 Android 平台利用 Rust 语言进行开发过程中所积累的经验和教训。
交叉编译
在车机场景下,GreptimeDB Edge 通常以服务的形式部署在 Android 环境上,这要求我们将其编译成适用于 Android 平台的可执行文件。一个初步的方案可能是购置一款 Android 开发板,安装 Rust 工具链以进行编译工作。然而,这种做法可能会面临以下挑战:
在 Android 开发板上配置 Rust 编译环境可能比较复杂(作者实际也没配置过);
多数 Android 开发板的 CPU 性能较弱,编译大型项目时速度缓慢,效率低下;
本地 Android API 版本可能与目标设备上的 API 版本存在差异,甚至 CPU 架构也可能不同,从而导致兼容性问题。
相对而言,交叉编译提供了一个更为高效的替代方案。它允许开发者在一个系统平台上(例如 x86 的 PC)编译出能在另一种系统平台上(例如 ARM 移动设备)运行的程序,这在目标系统上直接编译困难时尤其有用。
Rust 对交叉编译的支持非常出色,加之 Android NDK 提供了必要的工具链和库,这进一步简化了交叉编译的过程。由于我们的开发或编译环境通常是 macOS 或 Linux,所以选择通过交叉编译的方式来生成 Android 可执行文件是一个理想的解决方案。
Rust 编译
首先,我们需要大致了解一下 Rust 编译过程。Rustc 先把 Rust 代码编译为 LLVM-IR
,然后再由 LLVM 将 LLVM-IR
编译为各个平台的二进制,最终由 linker 链接在一起,生成最终的二进制文件。
Rustc 是 Rust 的编译器,以 LLVM 作为后端(也可以说 Rustc 是 LLVM 的前端)。
下面是一个简化版本的 Rust 编译架构图:
(图 1 :简化版本的 Rust 编译架构图)
GreptimeDB 交叉编译实战
GreptimeDB Edge 是以开源版本的 GreptimeDB 为内核进行构建。所以,我们接下来,以开源版本的 GreptimeDB 为例, 一步一步向大家展示如何在 x86 Linux
上进行交叉编译,生成 aarch64-linux-android
架构的可执行文件。
首先安装 Android NDK,下载地址为: https://developer.android.com/ndk/downloads?hl=zh-cn。此外设置一个环境变量,方便后续操作,如下所示:
接下来,从 GitHub 上拉取 GreptimeDB 的源码:
然后,添加 Target 到 Rust 工具链是实现跨平台编译的关键步骤。这允许 Rustc 将中间表示层 LLVM-IR 代码编译成目标平台的机器语言。在这个例子中,目标平台架构是 aarch64-linux-android
,在 GreptimeDB 项目根目录下执行以下命令:
Rust 平台支持详见这里。
这时候,尝试编译可能会报错: “-lgcc” 找不到。原因是:Android NDK 的 libgcc.a
已经被 libunwind.a
替代,解决方案是复制一份 libunwind.a
并重命名为 libgcc.a
,详见 Rust blog。
在仅涉及 Rust 语言的项目中,开发者通常需要配置链接器(Linker)和归档器(AR)。然而,Rust 支持在构建脚本(build.rs)中执行构建任务,因此在 Rust 项目的编译过程中,可能需要集成 C 和 C++ 等其他语言的编译工作。这通常需要向编译工具(如 cc 或 cmake 提供一些必要的信息,包括编译器路径(CC 和 CXX)、库文件和头文件的位置等,这一过程往往较为复杂。
cargo-ndk 这个项目帮我们解决了大部分问题。通过执行以下命令,就可以编译出适用于 aarch64-linux-android
平台的 GreptimeDB 二进制程序。
此外,针对那些不兼容特定目标平台的库,处理起来确实较为棘手。一种解决方案是替换为兼容的库;如果涉及到功能并非必需,可以使用 feature guard ,在编译阶段将其去掉。
在编译过程中,如果遇到错误提示缺少
protobuf
库或其他,正确安装即可。
常见问题
之前遇到一个问题,当开启 LTO 优化时,交叉编译 GreptimeDB 就会失败。报错信息如下所示:
当 API level >= 21 时,Android 会提供一个 pthread_atfork
的声明。而 tikv-jemallocator 中也有一个 pthread_atfork
的声明,都是强符号类型,当开启 LTO 优化时,就会导致了符号冲突。解决方案:将 tikv-jemallocator
中 pthread_atfork
设置为弱符号类型。
最新版本的 tikv-jemallocator
已经解决了这个问题,详见这里。
Backtrace on Android
在开发 GreptimeDB Edge 项目的过程中,我们观察到 Rust 语言的标准库的 backtrace
在 Android 环境中无法提供预期的堆栈信息。具体来说,当程序 panic
时,相关的堆栈信息未能正确捕获,而是显示为 unknown
,这为问题的诊断带来了极大的困扰。
问题复现
为了复现这一问题,我们编写了一个简化的示例程序。
在
main
方法中触发了一个panic
,模拟程序出现异常:
指定
rust-toolchain
为stable 1.81
或者更低的版本:
交叉编译, 生成在 Android 上运行的二进制文件。在此过程中,我们可以回顾并巩固上一节的内容:
将二进制 push 到 Android 虚拟机,执行:
执行结果表明,未能成功获取到预期的
backtrace
信息。问题复现了!
解决方案
我们先介绍一下解决方案,以便对问题原因不感兴趣的的小伙伴可以跳过下一节。
升级 Rust 工具链版本: 建议将 rust-toolchain
版本升级至 1.82
或更高。这个问题已经在 1.82
中被修复了(下一小节会介绍修复方法)。
自定义 Panic Hook: Rust 支持通过注册自定义的 panic hook
函数来替代默认行为。若无法升级 Rust 版本,可利用 backtrace-rs
库设置自定义 panic hook
函数。
Rust 默认的
panic hook
函数可能无法满足特定环境下的需求,例如,在 Android 平台上,可能倾向于将 panic 信息输出到文件或 logcat, 而默认的panic hook
函数只是把 panic 信息输出到标准错误中。因此,很多场景下都需要我们自定义panic hook
函数。
下面提供一个实现示例:
输出的堆栈信息如下所示(在编译选项中去掉了 debug info
, 且保留了符号表):
补充说明:输出的堆栈信息与编译选项也有关系。如果把二进制中的符号表和
debug info
都去掉,会生成 unknown 的堆栈。如果保留debug info
,堆栈信息将更详细,但二进制的体积会增加很多。
问题原因
接下来,我们将基于 Rust 1.81,来探究一下之前提出的问题。
前置知识
Rust 标准库的 backtrace 依赖了
backtrace-rs
库,并以 git submodule 的形式集成到了 Rust 标准库中,详见这里。backtrace-rs
在编译构建时,会判断 Android 的API
版本。如果大于等于 21,则会启用dl_iterate_phdr
特性。详见这里(注:backtrace-rs
的版本是 Rust 1.81 依赖的版本,并不是最新版本)。
综合以上两点,Rust 标准库以 git submodule
的形式引入了 backtrace-rs
,但是并没有执行 backtrace-rs
中的 build.rs
的构建逻辑,导致 dl_iterate_phdr
特性未能启用。那么标准库的 backtrace
就无法在 Android 上正常工作了。
破案了!
解决方法
实际上,我们只需在标准库中启用 backtrace-rs
的 dl_iterate_phdr
特性即可。但是从 #120593 开始,Rust 对 Android 的最小支持 API 版本从 19 提升至 21,并且 从 21 开始,Android 就支持了 dl_iterate_phdr
,具体信息可以查看这里。所以我们可以在 backtrace-rs
库中直接默认开启 dl_iterate_phdr
特性,无需检测 Android 的 API 版本(Rust 1.82 也是这么修复的)。
相关 PR 链接
总结
交叉编译一直是非常棘手的,可能会碰到各种各样的问题,并没有什么固定的解决方案,我们总是要针对特定的问题进行处理。幸运的是,Cargo NDK 和 Android NDK 提供了一套便捷的解决方案,帮助我们有效地应对了大部分的编译问题。
通过本文的探讨,我们认识到交叉编译在 Android 环境中的重要性,以及 Rust 编译机制的优势。虽然理想的编译过程在实践中会遇到诸多挑战,但希望我们的经验能为后续的开发提供一些实用的参考和启发。
关于 Greptime
Greptime 格睿科技专注于为可观测、物联网及车联网等领域提供实时、高效的数据存储和分析服务,帮助客户挖掘数据的深层价值。目前基于云原生的时序数据库 GreptimeDB 已经衍生出多款适合不同用户的解决方案,更多信息或 demo 展示请联系下方小助手(微信号:greptime)。
欢迎对开源感兴趣的朋友们参与贡献和讨论,从带有 good first issue 标签的 issue 开始你的开源之旅吧~期待在开源社群里遇见你!添加小助手微信即可加入“技术交流群”与志同道合的朋友们面对面交流哦~
Star us on GitHub Now: https://github.com/GreptimeTeam/greptimedb
Twitter: https://twitter.com/Greptime
Slack: https://greptime.com/slack
评论