NDK编译——ABI管理

文章的内容是从Android开发者官网扒的,为了防止再登不上去,记录一下。官网网址:https://developer.android.com/ndk/guides/abis.html


ABI 管理

不同 Android 手机使用不同的 CPU,因此支持不同的指令集。CPU 与指令集的每种组合都有其自己的应用二进制界面(或 ABI)。 ABI 可以非常精确地定义应用的机器代码在运行时如何与系统交互。 您必须为应用要使用的每个 CPU 架构指定 ABI。

典型的 ABI 包含以下信息:

  • 机器代码应使用的 CPU 指令集。
  • 运行时内存存储和加载的字节顺序。
  • 可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。
  • 用于解析内容与系统之间数据的各种约定。这些约定包括对齐限制,以及系统如何使用堆栈和在调用函数时注册。
  • 运行时可用于机器代码的函数符号列表 - 通常来自非常具体的库集。

本页枚举了 NDK 支持的 ABI,并且提供每个 ABI 如何运行的信息。

支持的 ABI


每个 ABI 支持一个或多个指令集。表 1 提供每个 ABI 支持的指令集概览。

表 1. ABI 和支持的指令集。

ABI 支持的指令集 说明
armeabi
  • ARMV5TE 和更高版本
  • Thumb-1
无硬浮点。
armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • 其他(可选)
与 ARMv5、v6 设备不兼容。
arm64-v8a
  • AArch-64
x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
不支持 MOVBE 或 SSE4。
x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1、4.2
  • POPCNT
mips
  • MIPS32r1 及更高版本
使用硬浮点,并且假设 CPU:FPU 时钟比率为 2:1 以获取最大兼容性。 不提供 micromips 或 MIPS16。
mips64
  • MIPS64r6
 

有关下面每个 ABI 的更多详细信息。

armeabi

此 ABI 适用于基于 ARM、至少支持 ARMv5TE 指令集的 CPU。 请参阅以下文档了解详情:

AAPCS 标准将 EABI 定义为类似但不同 ABI 的系列。 此外,Android 还采用小字节序 ARM GNU/Linux ABI

此 ABI 不支持硬件辅助的浮点计算。 相反,所有浮点运算都使用编译器 libgcc.a 静态库中的软件帮助程序函数。

armeabi ABI 支持 ARM 的 Thumb(亦称 Thumb-1)指令集。NDK 默认生成 Thumb 代码,除非您在 Android.mk 文件中使用 LOCAL_ARM_MODE 变量指定不同的行为。

armeabi-v7a

此 ABI 可扩展 armeabi 以包含多个 CPU 指令集扩展。 此 Android 特定 ABI 支持的指令扩展包括:

  • Thumb-2 指令集扩展,其性能堪比 32 位 ARM 指令,简洁性类似于 Thumb-1。
  • VFP 硬件 FPU 指令。更具体一点,包括 VFPv3-D16,它除了 ARM 核心中的 16 个 32 位寄存器之外,还包含 16 个专用 64 位浮点寄存器。

v7-a ARM 规格描述的其他扩展,包括 高级 SIMD(亦称 NEON)、VFPv3-D32 和 ThumbEE,都是此 ABI 可选的。 由于不能保证它们存在,因此系统在运行时应检查扩展是否可用。 如果不可用,则必须使用替代代码路径。此检查类似于系统在检查或使用 MMXSSE2 及 x86 CPU 上其他专用指令集时所执行的检查。

如需了解有关如何执行这些运行时检查的信息,请参阅 cpufeatures 库。另外,有关 NDK 支持为 NEON 构建机器代码的信息,请参阅 NEON 支持

armeabi-v7a ABI 使用 -mfloat-abi=softfp 开关强制实施规则,要求编译器在函数调用时必须传递核心寄存器对中的所有双精度值,而不是专用浮点值。 系统可以使用 FP 寄存器执行所有内部计算。 这样可极大地加速计算。

arm64-v8a

此 ABI 适用于基于 ARMv8、支持 AArch64 的 CPU。它还包含 NEON 和 VFPv4 指令集。

如需了解详细信息,请参阅 ARMv8 技术预览,并联系 ARM 了解进一步的详细信息。

x86

此 ABI 适用于支持通常称为“x86”或“IA-32”的指令集的 CPU。 此 ABI 的特性包括:

  • 指令一般由具有编译器标志的 GCC 生成,如下所示:
    -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
    

    这些标志指向 Pentium Pro 指令集,伴随 MMXSSESSE2SSE3 及 SSSE3 指令集扩展。生成的代码在顶层 Intel 32 位 CPU 之间进行了均衡优化。

    如需了解有关编译器标志的详细信息,特别是与性能优化相关的信息,请参阅 GCC x86 性能提示

  • 使用标准 Linux x86 32 位调用约定,与 SVR 使用的约定相反。如需了解详细信息,请参阅不同 C++ 编译器和操作系统的调用约定的第 6 节“寄存器的使用”。

ABI 不含任何其他可选的 IA-32 指令集扩展,例如:

  • MOVBE
  • SSE4 的任何变体。

您仍可使用这些扩展,只要您使用运行时功能探测来启用它们,并且为不支持它们的设备提供备用方法。

NDK 工具链假设在函数调用之前进行 16 位栈对齐。默认工具和选项强制执行此规则。 如果编写的是汇编代码,必须确保栈对齐,而且其他编译器也遵守此规则。

请参阅以下文档了解详情:

x86_64

此 ABI 适用于支持通常称为“x86-64”的指令集的 CPU。 它支持 GCC 通常使用以下编译器标志生成的指令:

-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel

这些标志指向 x86-64 指令集(根据 GCC 文档),伴随 MMXSSESSE2SSE3SSSE3SSE4.1SSE4.2 和 POPCNT 指令集扩展。 生成的代码在顶层 Intel 64 位 CPU 之间进行了均衡优化。

如需了解有关编译器标志的详细信息,特别是与性能优化相关的信息,请参阅 GCC x86 性能

此 ABI 不含任何其他可选的 x86-64 指令集扩展,例如:

  • MOVBE
  • SHA
  • AVX
  • AVX2

您仍可使用这些扩展,只要您使用运行时功能探测来启用它们,并且为不支持它们的设备提供备用方法。

请参阅以下文档了解详情:

mips

此 ABI 适用于基于 MIPS、至少支持 MIPS32r1 指令集的 CPU。它包含以下功能:

  • MIPS32 修订版 1 ISA
  • 小字节序
  • O32
  • 硬浮点
  • 无 DSP 应用特定的扩展

如需了解详细信息,请参阅以下文档:

如需了解更具体的详细信息,请参阅 MIPS32 架构。常见问答请参阅 MIPS FAQ

mips64

此 ABI 适用于 MIPS64 R6。如需了解详细信息,请参阅 MIPS64 架构

为特定 ABI 生成代码


默认情况下,NDK 为 armeabi ABI 生成机器代码。但您可以通过向 Application.mk 文件添加以下行生成 ARMv7-a 兼容的机器代码。

APP_ABI := armeabi-v7a

要为两个或更多不同的 ABI 构建机器代码,请使用空格作为分隔符。例如:

APP_ABI := armeabi armeabi-v7a

此设置指示 NDK 为机器代码构建两个版本:此行中所列的每个 ABI 一个。 如需了解有关可以为 APP_ABI 变量指定的值的详细信息,请参阅 Android.mk

构建多个机器代码版本时,构建系统会将库复制到应用项目路径,并最终将它们封装到 APK 中,从而创建一个胖二进制文件。 胖二进制文件大于只包含一个系统的机器代码的二进制文件;权衡方式是兼容性更广,但 APK 更大。

在安装时,软件包管理器只解包最适合目标设备的机器代码。 如需了解详细信息,请参阅安装时自动解压缩原生代码

Android 平台上的 ABI 管理


本节详细说明 Android 平台如何管理 APK 中的原生代码。

应用包中的原生代码

Play 商店和软件包管理器专家预期在 APK 中符合以下模式的文件路径上查找 NDK 生成的库:

/lib/<abi>/lib<name>.so

这里的 <abi> 是支持的 ABI 下面列出的 ABI 名称之一,<name> 是您为 Android.mk 文件中的 LOCAL_MODULE 变量定义库时使用的库名称。 由于 APK 文件只是 zip 文件,因此打开它们并确认它们属于哪些共享原生库很简单。

如果系统在预期位置找不到原生共享库,便无法使用它们。 在这种情况下,应用本身必须复制这些库,然后执行 dlopen()

在胖二进制文件中,每个库位于其名称与相应 ABI 匹配的目录下。例如,胖二进制文件可能包含:

/lib/armeabi/libfoo.so
/lib/armeabi-v7a/libfoo.so
/lib/arm64-v8a/libfoo.so
/lib/x86/libfoo.so
/lib/x86_64/libfoo.so
/lib/mips/libfoo.so
/lib/mips64/libfoo.so

:运行 4.0.3 或更早版本、基于 ARMv7 的 Android 设备从 armeabi 目录(而非 armeabi-v7a 目录,如果两个目录都存在)安装原生库。 这是因为在 APK 中,/lib/armeabi/ 在 /lib/armeabi-v7a/ 后面。 从 4.0.4 开始,此问题已修复。

Android 平台 ABI 支持

Android 系统在运行时知道它支持哪些 ABI,因为版本特定的系统属性会指示:

  • 设备的主要 ABI,与系统映像本身使用的机器代码对应。
  • 可选的辅助 ABI,与系统映像也支持的另一个 ABI 对应。

此机制确保系统在安装时从软件包提取最佳机器代码。

为实现最佳性能,应直接针对主要 ABI 进行编译。例如,基于 ARMv5TE 的典型设备只会定义主要 ABI:armeabi。 相反,基于 ARMv7 的典型设备将主要 ABI 定义为 armeabi-v7a,而将辅助 ABI 定义为 armeabi,因为它可以运行为每个 ABI 生成的应用原生二进制文件。

许多基于 x86 的设备也可运行 armeabi-v7a 和 armeabi NDK 二进制文件。对于这些设备,主要 ABI 将是 x86,辅助 ABI 是 armeabi-v7a

基于 MIPS 的典型设备只定义主要 ABI:mips

安装时自动解压缩原生代码

安装应用时,软件包管理器服务将扫描 APK,查找以下形式的任何共享库:

lib/<primary-abi>/lib<name>.so

如果未找到,并且您已定义辅助 ABI,该服务将扫描以下形式的共享库:

lib/<secondary-abi>/lib<name>.so

找到所需的库时,软件包管理器会将它们复制到应用的 data 目录 (data/data/<package_name>/lib/) 下的 /lib/lib<name>.so

如果根本没有共享对象文件,应用也会构建并安装,但在运行时会崩溃。


### 使用 NDK 编译针对 armeabi 架构的库 为了使用 NDK 编译适用于 `armeabi` 架构的目标库,需要完成一系列配置和操作。以下是详细的说明: #### 1. **设置 Application.mk 文件** 在构建过程中,`Application.mk` 文件用于指定目标 ABI 和其他编译选项。对于 `armeabi` 架构的支持,可以在该文件中添加如下内容: ```makefile APP_ABI := armeabi ``` 此行代码表示仅为目标架构 `armeabi` 进行编译[^3]。 需要注意的是,`armeabi` 已被标记为过时(deprecated),官方推荐迁移到更现代的架构如 `armeabi-v7a` 或 `arm64-v8a`。然而,在某些特定场景下仍可能需要支持它。 --- #### 2. **创建 Android.mk 文件** NDK 默认会查找位于项目根目录下的 `jni/Android.mk` 文件来描述源码结构及其依赖关系。一个典型的 `Android.mk` 文件示例如下所示: ```makefile LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-world LOCAL_SRC_FILES := main.cpp include $(BUILD_SHARED_LIBRARY) ``` 上述脚本定义了一个共享库模块 (`hello-world`) 并指定了其对应的源文件位置。如果希望进一步优化或扩展功能,则可以在此基础上调整变量声明[^4]。 --- #### 3. **利用 Standalone Toolchain 方法** 另一种方法是通过独立工具链来进行交叉编译。这种方式允许开发者更加灵活地控制整个流程而不必受限于 Gradle 插件或其他高层框架的影响。具体步骤包括但不限于下载最新的 Android NDK 版本以及执行 Python 脚本来初始化所需的环境变量等。 假设我们打算基于 Clang 创建一套专门服务于 ARMv5TE 设备(即对应传统意义上的 “armeabi” 定义)的解决方案,则可尝试运行下面这条指令: ```bash $ python $NDK_HOME/tools/make_standalone_toolchain.py \ --arch arm --api 16 --stl=gnustl --install-dir=/path/to/toolchains/arm-eabi/ ``` 这里特别强调了 API Level 参数的选择应当依据实际需求而定;同时也要注意 STL 库形式的不同可能会带来兼容性差异等问题[^5]。 --- #### 4. **集成到 Build System 中** 最后一步就是把前面准备好的材料整合进现有的工程体系里去。如果是采用纯命令行的方式作业的话,那么可以直接调用 ndk-build 命令启动任务; 而对于那些倾向于图形界面或者自动化程度较高的场合而言,则需参照相应 IDE 的文档指南来做额外适配工作——比如 CLion 用户就需要按照之前提到过的那篇博文里的指导去做必要的前期准备工作[^2]。 综上所述,以上便是关于如何借助 Android Native Development Kit 来生成匹配指定 CPU 类型动态链接库的一些基本概念和技术要点概述。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值