x86
此 ABI 适用于支持通常称为“x86”或“IA-32”的指令集的 CPU。此 ABI 的特性包括:
- 指令一般由具有编译器标记的 GCC 生成,如下所示:
-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
这些标记指向 Pentium Pro 指令集,以及 MMX、SSE、SSE2、SSE3 及 SSSE3 扩展指令集。生成的代码在顶层 Intel 32 位 CPU 之间进行了均衡优化。
如需了解有关编译器标记的更多信息,特别是与性能优化相关的信息,请参阅 GCC x86 性能提示。
- 使用标准 Linux x86 32 位调用规范,与 SVR 使用的规范相反。详情请参阅不同 C++ 编译器和操作系统的调用规范的第 6 部分“寄存器的使用”。
ABI 不含任何其他可选 IA-32 扩展指令集,例如:
- MOVBE
- SSE4 的任何变体。
您仍可使用这些扩展指令集,只要您使用运行时功能探测来启用它们,并且为不支持它们的设备提供回退机制。
NDK 工具链假设在函数调用之前进行 16 字节堆栈对齐。默认工具和选项会强制实施此规则。如果编写的是汇编代码,必须确保堆栈对齐,而且其他编译器也遵守此规则。
请参阅以下文档了解更多详情:
- GCC 在线文档:Intel 386 和 AMD x86-64 选项
- 不同 C++ 编译器和操作系统的调用规范
- Intel IA-32 Intel 架构软件开发者手册第 2 卷:指令集参考
- Intel IA-32 Intel 架构软件开发者手册第 3 卷:系统编程指南
- System V 应用二进制接口:Intel386 处理器架构补充
x86_64
此 ABI 适用于支持通常称为“x86-64”的指令集的 CPU。它支持 GCC 通常使用以下编译器标记生成的指令:
-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel
这些标记指向 x86-64 指令集(根据 GCC 文档),以及 MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2 和 POPCNT 扩展指令集。生成的代码在顶层 Intel 64 位 CPU 之间进行了均衡优化。
如需了解有关编译器标记的更多信息,特别是与性能优化相关的信息,请参阅 GCC x86 性能。
此 ABI 不含任何其他可选的 x86-64 扩展指令集,例如:
- MOVBE
- SHA
- AVX
- AVX2
您仍可使用这些扩展指令集,只要您使用运行时功能探测来启用它们,并且为不支持它们的设备提供回退机制。
请参阅以下文档了解更多详情:
为特定 ABI 生成代码
默认情况下,NDK 指向所有非弃用 ABI。您可通过在 Application.mk
文件中设置 APP_ABI
来指向单个 ABI。以下代码段演示了使用 APP_ABI
的几个示例
APP_ABI := arm64-v8a # Target only arm64-v8a
APP_ABI := all # Target all ABIs, including those that are deprecated.
APP_ABI := armeabi-v7a x86_64 # Target only armeabi-v7a and x86_64.
要详细了解您可以为 APP_ABI
变量指定的值,请参阅 Android.mk。
编译系统的默认行为是将每个 ABI 的二进制文件包括在单个 APK(也称为胖 APK)内。与仅包含单个 ABI 的二进制文件的 APK 相比,胖 APK 要大得多;要权衡的是兼容性更广,但 APK 更大。强烈建议您利用拆分 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()
。
在胖 APK 中,每个库位于名称与相应 ABI 匹配的目录下。例如,胖 APK 可能包含:
/lib/armeabi/libfoo.so
/lib/armeabi-v7a/libfoo.so
/lib/arm64-v8a/libfoo.so
/lib/x86/libfoo.so
/lib/x86_64/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 生成的应用原生二进制文件。
64 位设备也支持其 32 位变体。以 arm64-v8a 设备为例,该设备也可以运行 armeabi 和 armeabi-v7a 代码。但请注意,如果应用以 arm64-v8a 为目标,而非依赖于运行 armeabi-v7a 版应用的设备,应用在 64 位设备上的性能要好得多。
许多基于 x86 的设备也可运行 armeabi-v7a
和 armeabi
NDK 二进制文件。对于这些设备,主要 ABI 将是 x86
,辅助 ABI 是 armeabi-v7a
。
安装时自动解压缩原生代码
安装应用时,软件包管理器服务将扫描 APK,并查找以下形式的任何共享库:
lib/<primary-abi>/lib<name>.so
如果未找到,并且您已定义辅助 ABI,该服务将扫描以下形式的共享库:
lib/<secondary-abi>/lib<name>.so
找到所需的库时,软件包管理器会将它们复制到应用的 data
目录 (data/data/<package_name>/lib/
) 下的 /lib/lib<name>.so
。
如果根本没有共享对象文件,应用也会编译并安装,但在运行时会崩溃。
三、处理CPU功能
ABI:使用预处理器的预定义宏
通常,在编译时使用 #ifdef
及以下各项确定 ABI 最为方便:
- 对于 32 位 ARM,使用
__arm__
- 对于 64 位 ARM,使用
__aarch64__
- 对于 32 位 X86,使用
__i386__
- 对于 64 位 X86,使用
__x86_64__
请注意:32 位 X86 称为 __i386__
,而不是 __x86__
,这可能与您预想的有所不同!
CPU 核心计数:使用 libc 的 sysconf(3)
sysconf(3) 既可以查询 _SC_NPROCESSORS_CONF
(系统中的 CPU 核心数),又可以查询 _SC_NPROCESSORS_ONLN
(当前在线的 CPU 核心数)。
功能:使用 libc 的 getauxval(3)
自 API 级别 18 开始,Android 的 C 库开始支持 etauxval(3)。AT_HWCAP
和 AT_HWCAP2
参数会返回列出特定 CPU 功能的位掩码。请参阅 NDK 中的各种 hwcap.h
头文件以获取要进行比较的常量,如用于 arm64 SHA512 指令的 HWCAP_SHA512
,或用于 arm Thumb 整数除法指令的 HWCAP_IDIVT
。
Google cpu_features 库
AT_HWCAP
的一个问题是有时设备会出错。例如,一些旧设备宣称拥有整数除法指令,但实际上并没有。
Google 的 cpu_features 库凭借其对特定 SoC 的了解(通过解析 /proc/cpuinfo
掌握相应的 SoC),解决了此类问题。
另一个解决方案是为 SIGILL
安装信号处理程序,然后直接尝试执行相关指令。例如,BoringSSL/OpenSSL 使用的就是这种方法。
NDK cpufeatures 库
NDK 提供了一个名为 cpufeatures
的小型库,其功能类似于 getauxval(3),但它也可用于 API 级别 18 之前的版本。与其他 cpu_features 库不同,它对特定 SoC 并无额外的了解。
NDK cpufeatures API
uint64_t android_getCpuFeatures();
返回一组位标记,每个标记代表一个 CPU 系列特定的功能。本部分其余内容介绍了各个系列的功能。
32 位 ARM CPU 系列
以下标记适用于 32 位 ARM CPU 系列:
ANDROID_CPU_ARM_FEATURE_VFPv2
表示设备的 CPU 支持 VFPv2 指令集。大多数 ARMv6 CPU 都支持此指令集。
ANDROID_CPU_ARM_FEATURE_ARMv7
表示设备的 CPU 支持 armeabi-v7a ABI 所支持的 ARMv7-A 指令集。此指令集同时支持 Thumb-2 和 VFPv3-D16 指令。此返回值还表示支持 VFPv3 硬件 FPU 扩展指令集。
ANDROID_CPU_ARM_FEATURE_VFPv3
表示设备的 CPU 支持 VFPv3 硬件 FPU 扩展指令集。
此值等同于 VFPv3-D16 指令集,后者只提供 16 个硬件双精度 FP 寄存器。
ANDROID_CPU_ARM_FEATURE_VFP_D32
表示设备的 CPU 支持 32 个(而不是 16 个)硬件双精度 FP 寄存器。即使有 32 个硬件双精度 FP 寄存器,也只有 32 个单精度寄存器映射至同一寄存器组。
ANDROID_CPU_ARM_FEATURE_NEON
表示设备的 CPU 支持 ARM Advanced SIMD (NEON) 向量扩展指令集。请注意,ARM 要求这些 CPU 也要实现 VFPv3-D32,VFPv3-D32 提供 32 个硬件 FP 寄存器(与 NEON 单元共享)。
ANDROID_CPU_ARM_FEATURE_VFP_FP16
表示设备的 CPU 支持在 16 位寄存器上执行浮点运算的指令。该功能是 VFPv4 规范的一部分。
ANDROID_CPU_ARM_FEATURE_VFP_FMA
表示设备的 CPU 支持 VFP 指令集的融合乘积累加扩展指令集。它也是 VFPv4 规范的一部分。
ANDROID_CPU_ARM_FEATURE_NEON_FMA
表示设备的 CPU 支持 NEON 指令集的融合乘积累加扩展指令集。它也是 VFPv4 规范的一部分。
ANDROID_CPU_ARM_FEATURE_IDIV_ARM
表示设备的 CPU 在 ARM 模式下支持整数除法。仅适用于更高型号的 CPU,例如 Cortex-A15。
ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2
表示设备的 CPU 在 Thumb-2 模式下支持整数除法。仅适用于更高型号的 CPU,例如 Cortex-A15。
ANDROID_CPU_ARM_FEATURE_iWMMXt
表示设备的 CPU 支持可添加 MMX 寄存器和指令的扩展指令集。该功能仅适用于少数几个基于 XScale 的 CPU。
ANDROID_CPU_ARM_FEATURE_LDREX_STREX
表示设备的 CPU 支持自 ARMv6 后可用的 LDREX 和 STREX 指令。借助专用监视器,这些指令结合起来可在内存中提供原子级更新。
64 位 ARM CPU 系列
以下标记适用于 64 位 ARM CPU 系列:
ANDROID_CPU_ARM64_FEATURE_FP
表示设备的 CPU 具有浮点单元 (FPU)。所有 Android ARM64 设备都必须支持此功能。
ANDROID_CPU_ARM64_FEATURE_ASIMD
表示设备的 CPU 具有高级 SIMD (ASIMD) 单元。所有 Android ARM64 设备都必须支持此功能。
ANDROID_CPU_ARM64_FEATURE_AES
表示设备的 CPU 支持 AES 指令。
ANDROID_CPU_ARM64_FEATURE_CRC32
表示设备的 CPU 支持 CRC32 指令。
ANDROID_CPU_ARM64_FEATURE_SHA1
表示设备的 CPU 支持 SHA1 指令。
ANDROID_CPU_ARM64_FEATURE_SHA2
表示设备的 CPU 支持 SHA2 指令。
ANDROID_CPU_ARM64_FEATURE_PMULL
表示设备的 CPU 支持 64 位 PMULL 和 PMULL2 指令。
32 位 x86 CPU 系列
以下标记适用于 32 位 x86 CPU 系列。
ANDROID_CPU_X86_FEATURE_SSSE3
表示设备的 CPU 支持 SSSE3 扩展指令集。
ANDROID_CPU_X86_FEATURE_POPCNT
表示设备的 CPU 支持 POPCNT 指令。
ANDROID_CPU_X86_FEATURE_MOVBE
表示设备的 CPU 支持 MOVBE 指令。此指令特定于某些 Intel IA-32 CPU,例如 Atom。
android_getCpuFeatures() 针对没有列出扩展指令集的 CPU 系列返回 0。
int android_getCpuCount(void);
注意:考虑到 sysconf(3),此函数通常无用。
返回系统中的 CPU 核心数,可能高于实际在线的核心数。
AndroidCpuFamily android_getCpuFamily();
注意:考虑到预处理器宏,此函数通常无用。
返回以下其中一个常量,代表设备所支持的 CPU 系列/架构:
ANDROID_CPU_FAMILY_ARM
ANDROID_CPU_FAMILY_X86
最后
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
hina.csdn.net/m0_60958482/android_p7)**
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
[外链图片转存中…(img-xQt35gdU-1630936442299)]
【算法合集】
[外链图片转存中…(img-zrBNMjk0-1630936442301)]
【延伸Android必备知识点】
[外链图片转存中…(img-FQx9eh4p-1630936442303)]