Android Bionic Libc 原理与实现综述
Bionic 是 Android 平台的 C 标准库实现,在生态位上对应 GNU/Linux 常见的 glibc (GNU Libc),以及传统嵌入式场景 中曾广泛使用的 uClibc ;近年的嵌入式与容器化环境里,musl 或裁剪版 glibc 也很常见。传统 Unix/Linux 上,Libc 是最基础的运行时库;glibc / uClibc 等常与 GPL/LGPL 类许可绑定。Android 未直接采用 glibc/uClibc,而是自研 Bionic 作为系统 C 库。
命名与词源(非官方定论) :从实现来源 看,Bionic 确实混合了 部分 BSD C 库 与 面向 Linux 的自研代码 (线程、进程、信号等)。至于英文名称 Bionic 的词源,公开资料中未见 Google/AOSP 的正式、统一解释 ,不宜写成「官方定论」。社区普遍推测 之一为 BSD 与 Ionic (或相近音节)的组合简称;也有人从 bionics(仿生学) 角度理解,强调其作为 Android 的「人造基础组件」。本文仅在技术含义上使用 Bionic 一词。
下文从设计动机、与 glibc 及工具链差异、源码结构、系统调用桩、时间与文件偏移、内核头文件、pthread、Android 特有接口、DNS、定时器与动态链接等方面归纳其实现要点。文中 AOSP 路径以经典树形为例,新版本目录可能调整,以当前官方源码为准。
文档说明与沿革(阅读前请看)
本文在整理 约 2011 年前后 出版物对 Bionic 的叙述基础上,按 当前 AOSP 公开资料 做了勘误与补充 。Bionic 随 Android API 级别 持续演进,任何具体「有无某 API、符号数量、路径」均应以 你目标设备 / NDK 版本对应的源码与文档为准。
建议同步阅读(官方) :bionic/docs/status.md(libc/libm 行为、POSIX 缺口、按 API level 的新增符号等;镜像站如 aosp-mirror/platform_bionic 可浏览同文)。
Bionic 在系统中的位置(示意)
用户态
应用 / NDK 共享库
Bionic:libc / libm / libdl 等
动态链接器 linker / linker64
Linux 内核
旧资料语境 vs 当前实践(勘误总表)
下列对照帮助读者把「十几年前的读本」与 现今 AOSP 主线趋势 对齐;不替代官方 release note。
| 主题 | 旧资料常见说法 | 更新说明 |
|---|---|---|
| 体积 | libc 约 200KB、远小于 glibc | 仍可作为「设计初衷:偏轻量」的历史背景;今日 libc 随 API 累积与 FORTIFY、新 syscall 封装 等远大于该数量级。官方 status.md 用 nm 统计导出符号数 作演进参考(如 API 34 约 1392 个 libc 符号),不宜 与当年单一 .so 镜像大小直接对比。 |
| 目标架构 | 主要 ARM + x86 | 真机主流为 ARM64(aarch64) ;模拟器常见 x86_64 ;32 位 ARMv7 逐步边缘化;新主线中出现 RISC-V 相关 syscall/工具支持(随版本扩展)。 |
time_t / Y2038 |
强调 32 位 time_t |
**64 位 Android(LP64)**上 time_t 多为 64 位 ,与 POSIX 更一致;仅 32 位 ABI 仍须关注 2038,并可用 time64_t / <time64.h> 等路径。 |
| 读写锁 | 「无 pthread_rwlock」 |
API 24(Android N) 起已补齐 pthread_rwlock* 等(见 status.md N 段)。 |
TLS / __thread |
不支持线程局部存储扩展 | API 28(Android P) 起支持 ELF TLS (Whole-program TLS 模型);thread_local / __thread 在符合 NDK/链接选项下可用,细节见 AOSP elf-tls.md。 |
| 互斥与调度 | 无优先级继承等 | API 28 起含 pthread_mutexattr_getprotocol / setprotocol (优先级继承)等;具体以 status.md P 段为准。 |
| 动态链接器 | /system/bin/linker |
64 位进程对应 linker64 ;现代系统还有 命名空间(namespace) 、库可见性策略,与桌面 ld.so 差异更大。 |
pthread_cancel |
不支持 | 至今仍不支持 ;官方表述为 极不可能实现 (实现成本与正确性),见 status.md POSIX 段。 |
| 本地化 | 常笼统写「POSIX 不全」 | Bionic 实际仅 C/POSIX UTF-8 场景 ;完整 i18n 多在应用层用 ICU 或 Java/Kotlin。 |
| libm | 旧书或指「不完整」 | 按 status.md,C11/POSIX libm 已无缺失项(持续随版本维护)。 |
目录
- 文档说明与沿革(阅读前请看)
- [API 级别与 libc 规模(参考)](#API 级别与 libc 规模(参考))
- 设计目标与体积
- [与 glibc 的特性对比](#与 glibc 的特性对比)
- 配套二进制工具
- 实现思想与许可
- 架构与指令集
- [系统调用桩(Syscall stub)](#系统调用桩(Syscall stub))
- [时间、时区与 off_t](#时间、时区与 off_t)
- [Linux 内核头文件](#Linux 内核头文件)
- [Pthread 实现](#Pthread 实现)
- pthread_cancel
- pthread_once
- 线程本地存储(TLS)
- 多核与内存屏障
- [Android 特有行为](#Android 特有行为)
- [DNS 解析器](#DNS 解析器)
- [POSIX 定时器与 SIGEV_THREAD](#POSIX 定时器与 SIGEV_THREAD)
- 二进制兼容性与动态链接器
- [C++ 异常、System V IPC 与头文件包含路径](#C++ 异常、System V IPC 与头文件包含路径)
- [FORTIFY 与 targetSdk 行为差异(补充)](#FORTIFY 与 targetSdk 行为差异(补充))
- 小结
API 级别与 libc 规模(参考)
以下为 AOSP status.md 中 按 API level 统计的 libc 导出符号数量(工具链与统计方式见原文),仅作「功能面持续扩张」的直观参考:
| API Level | 约略 libc 符号数(T 类) |
|---|---|
| 21 | 1016 |
| 24 | 1147 |
| 28 | 1298 |
| 30 | 1368 |
| 34 | 1392 |
设计目标与体积
业界公开论述中,自研 Bionic 的常见动机包括:
| 考量 | 说明 |
|---|---|
| 许可 | 采用 BSD License ,避免 glibc 的 GPL 对产品形态与分发策略的约束 |
| 轻量与性能 | glibc / uClibc 对手机等资源受限环境偏重;Bionic 在实现 C 库能力的同时 裁剪 少用、耗资源的接口;早期 资料常给出 约 200KB 量级作为与 glibc 对比的意象,不能直接等同于当前发行产物大小 |
| 线程 | 提供更小、更快的 pthread 实现(同时持续向 POSIX 靠拢,见上文勘误表) |
应用与 native 开发需要明确:并非完整 POSIX ,部分接口缺失或语义与桌面 Linux 不同,避免按「桌面 glibc 行为」做假设。权威缺口列表见 bionic/docs/status.md。
与 glibc 的特性对比
相对 glibc,Bionic 常见特征可概括为:
| 项目 | Bionic | 说明 |
|---|---|---|
| 许可 | BSD License | 非 GPL |
| 体积与演进 | 历史上强调「远小于 glibc」 | 现今 以 API level 滚动增加符号与行为;对比应看 目标 API + status.md |
| pthread | 独立实现 | pthread_cancel 仍无;读写锁、时钟等待等已随 API 增加 |
| Android 专用 API | 如 getprop、LOGI 等 |
非 POSIX |
| POSIX | 不完全支持 | 如 Locale 仅 C/POSIX UTF-8;pthread_cancel、robust mutex 等见官方说明 |
| libthread_db | 与 glibc 调试模型不同 | 调试流程依赖 Android 调试栈(lldb、ptrace、tombstone 等),以当前文档为准 |
| libm | 曾相对薄弱 | 当前主线 宣称 C11/POSIX libm 无缺失 (status.md) |
配套二进制工具
除 C 库本身外,Android 构建与运行时的部分工具也与常见 Linux 桌面不同(路径为典型 AOSP/历史工具链示例):
| 环节 | 常见 Linux | Android(典型描述) |
|---|---|---|
| 动态库加载 | /lib/ld.so 等 |
/system/bin/linker (32 位)与 linker64 (64 位);并配合 命名空间 策略 |
| 预链接 | prelink |
历史上 apriori (build/tools/apriori);现代构建链路以官方当前工具为准 |
| 剥离调试符号 | 常规 strip |
构建中可能使用 soslim 等;亦与交叉 arm-eabi-strip 等路径并存过,以当前 AOSP 为准 |
text
应用进程
│
▼
linker(64) ← 解析 DT_NEEDED、命名空间、共享库路径
│
▼
libc.so / libm.so ...(Bionic)
实现思想与许可
- 设计思想 :尽可能 简单、轻量 ------对内核提供 薄封装 ,控制体积,不追求覆盖所有边缘 POSIX 细节(同时随 Android 版本逐步补全常用 POSIX/Linux 扩展)。
- 许可 :源自 BSD 的部分遵循相应 BSD 声明;Bionic 专有部分遵循 Android Open Source Project(AOSP) 许可证;发行物以仓库内 LICENSE 文件为准。
架构与指令集
- Bionic 为 多架构 维护:ARM64、ARMv7、x86_64、x86 等;源码树中常见
bionic/libc/arch-arm64、arch-arm、arch-x86_64、arch-x86(具体目录以 checkout 为准)。 - 扩展到新架构或新 syscall 时,往往要在
bionic/libc/SYSCALLS.TXT(及架构子文件)中维护系统调用号,并配合 linker 与内核一致。 - 重要 :面向 x86/x86_64 的 Bionic 构建服务于 Android 设备或模拟器 ;不能假设可把该 libc 随意换到任意桌面 Linux 发行版上使用。
- RISC-V:在新版 AOSP 中出现相关 syscall/支持条目,属持续演进中的架构线,以官方发布为准。
系统调用桩(Syscall stub)
- 每个系统调用对应一小段汇编 stub。
- 生成方式:脚本
bionic/libc/tools/gensyscalls.py读取bionic/libc/SYSCALLS.TXT(及按架构拆分的数据),自动生成桩代码。 SYSCALLS.TXT内容包含:待生成的系统调用列表、各 ABI 各自的调用号、函数签名等。该文件仍是理解 Bionic 与内核契约的入口之一。
时间、时区与 off_t
| 主题 | 行为 |
|---|---|
| time_t | LP64(64 位) 上多为 64 位 ,Y2038 压力主要落在 LP32(32 位) 场景。Bionic 仍提供 <time64.h> 、time64_t 与 mktime64()、localtime64() 等,供跨 32 位或显式 64 位时间线使用 |
| 时区 | 时区名优先取环境变量 TZ ;若未设置,则使用系统属性 persist.sys.timezone |
| off_t | LP32 上常见 32 位限制;_FILE_OFFSET_BITS=64 等在 API 24+ 已按官方说明补全(见 status.md N 段)。移植大文件场景请显式使用 64 位 API 或确认宏与 API 级别 |
Linux 内核头文件
Bionic 自带一套面向用户空间的 「干净」Linux 内核头文件 ,用于暴露 ioctl、结构体、常量 等与驱动/内核协议相关的声明,而避免直接包含完整、易变的主线内核树。
典型布局示例:
bionic/libc/kernel/commonbionic/libc/kernel/arch-arm、arch-arm64、arch-x86、arch-x86_64等
(目录名随 AOSP 版本扩展。)
Pthread 实现
与其它 C 库相比,Bionic 的 pthread 常见特点(历史设计 与现行能力请结合 API 级别):
| 点 | 说明 |
|---|---|
| 链接 | 仍通过 -lpthread;链接器仅导出少量入口符号,利于动态链接 |
实时能力(原 -lrt) |
相关能力 并入 libc;基于 futex 等实现短小路径 |
| mutex / cond | 历史上强调 4 字节等紧凑实现;具体布局以当前头文件/ABI 为准 |
| 互斥类型 | 支持 Normal、recursive、error-check;Normal 路径优化多 |
| 进程共享 | 不支持 PTHREAD_PROCESS_SHARED 的互斥量/条件变量(实现复杂且上层多用其它 IPC) |
| 读写锁 | API 24+ 已提供 pthread_rwlock*(旧书若写「无」,应作废) |
| 时钟等待 | API 30+ 等引入 pthread_mutex_clocklock 、pthread_cond_clockwait 等(见 status.md) |
targetSdk 行为 :例如 API 26+ 对非法 pthread_t 、API 28+ 对已销毁 mutex 的调用,可能 直接 abort(FORTIFY) 而非仅返回错误码------移植老代码时须注意(详见 status.md「Target API level behavioral differences」)。
pthread_cancel
Bionic 不提供 pthread_cancel(),以避免 libc 体积膨胀与行为难以推理。官方 status.md 明确:极不可能实现。
常见技术理由包括:
- 实现取消点需在 libc 多处插入检测,调试与维护成本高。
- 取消时需释放内存、解锁互斥量等;若在
gethostbyname()、fprintf()等复杂路径上触发取消,易使大量函数变慢或行为复杂。 - 取消 无法终止任意线程(例如死循环线程)。
- POSIX 取消语义本身在可移植性与边界情况上存在争议;可参考 LWN 等社区文章 「This is why we can't have safe cancellation points」。
替代做法 :协作式退出(标志位、pthread_cond、关闭任务队列等)。
pthread_once
使用约束:
- 禁止 在
pthread_once的初始化回调里调用fork();否则子进程中再次调用pthread_once()可能 死锁。 - 禁止 在回调中抛出 C++ 异常(与异常安全、libc 约定相关)。
- 早期实现曾被讨论 双重检查锁定 严谨性;是否已完全强化以 当前源码 为准。
线程本地存储(TLS)
pthread_key_t槽位 :历史上略 少于 POSIX 128 的要求;精确数以 当前pthread实现 为准。- TLS 布局 (栈顶、
pthread_internal_t等)见bionic/libc/bionic/pthread.c等源文件注释。 __thread/thread_local:API 28+ 支持 ELF TLS 后,在 NDK/链接模型 正确的前提下可用;旧书「完全不支持」已过时。
多核与内存屏障
- Bionic 不像 glibc 那样提供一整套
mb/rmb/wmb风格封装供随手调用。 - 多核可见性与排序应优先使用 C11
stdatomic.h、编译器内建原子 或内核/NDK 文档推荐的原语。 - 具体「是否导出某内存模型辅助符号」以 符号表与头文件 为准。
Android 特有行为
系统属性(Property)
全进程可见的 键/值字符串 空间,键与值长度均受限。头文件 <sys/system_properties.h> 提供读接口并定义长度上限。
用户与组
- 无 桌面式
/etc/passwd、/etc/group的多用户文本库(单用户设备模型为主)。 - 已安装应用通常拥有独立
uid_t/gid_t,从 10000 起编号;小于 10000 的 id 多预留给系统进程。 getpwnam():识别硬编码子系统名(如radio),以及形如app_1234的组合名(与 10000 相加得到 uid,如 11234)。getgrnam():类似规则。
getservent() / getprotoent()
- 历史上无完整桌面
/etc/services、/etc/protocols模型。 - API 28 起
status.md记载补全了一批getprotoent/setservent等相关符号(以官方列表为准);行为仍可能与 glibc+NIS/NSS 不同。
DNS 解析器
Bionic 的解析器基于 NetBSD 衍生 代码,相对典型桌面 glibc+NSS 行为,传统 改动如下(系统层 还可能叠加 netd、Private DNS、VPN 等策略,实际解析路径以设备与 Android 版本为准):
| 项 | 行为 |
|---|---|
| NSS | 不实现 name-service-switch(<nsswitch.h>) |
| 配置文件 | 常涉及 /system/etc/resolv.conf ,而非桌面 /etc/resolv.conf |
| 系统属性 | 从 net.dns1、net.dns2、... 读取 DNS 服务器 IP(可由 dhcpd 等组件写入) |
| 每进程 DNS | 支持 net.dns1.<pid>、net.dns2.<pid> 等形式,<pid> 为进程号 |
| 安全 | 查询使用 随机查询 ID ;本地 socket 绑定 随机源端口 |
| 线程安全 | 删减过易引发竞态的代码路径 |
| 头文件 | <arpa/nameser.h> 曾故意留空、不暴露解析器内部结构;是否仍完全空白以当前头文件为准 |
POSIX 定时器与 SIGEV_THREAD
Bionic 支持 timer_create()、timer_gettime()、timer_settime()、timer_getoverrun() ,并支持 SIGEV_THREAD 形式的实时定时器。
与 glibc 的对比要点:
- Bionic 侧模型相对 直白 :常见描述为 一线程对应一定时器 (大量
SIGEV_THREAD定时器时 内存占用可能明显上升)。 - glibc 侧可能使用 启发式 ,在多个定时器属性相同时 合并线程 以降低开销。
- 若应用需要 大量定时器 ,可考虑基于
timeout/事件循环 的单一或少量线程模型。 SIGEV_SIGNAL等由内核路径处理的定时器,系统资源占用通常更小。
二进制兼容性与动态链接器
- Bionic 不保证 与 glibc、uClibc 或其它 Linux libc 的 二进制兼容(ABI 级别即不同)。
- Android 使用 自有 linker / linker64 ,并配合 命名空间 ;不能 假设用任意第三方工具链生成的
.so可直接按桌面 Linux 方式加载。
C++ 异常、System V IPC 与头文件包含路径
- C++ 异常 :现代 NDK 使用 libc++ ;与
-fexceptions等组合下可在 native 使用异常,但 跨 JNI、与pthread_once回调等场景仍需谨慎,以 NDK 发行说明为准。 - System V IPC :Bionic 不提供 System V 消息队列、信号量、共享内存;动机包括缩小攻击面、降低 拒绝服务 类滥用风险。
- 头文件路径 :Soong / NDK CMake toolchain 会为模块 自动注入 libc 头文件搜索路径,应用开发一般无需手写全局
-I到 Bionic 源码树。
FORTIFY 与 targetSdk 行为差异(补充)
Android 在 Bionic 中实现了增强的 _FORTIFY_SOURCE :除缓冲区检查外,还可检测 destroyed mutex、非法 pthread_t、危险 printf %n 等(部分检查 不依赖 应用是否打开 FORTIFY)。高 targetSdkVersion 下,若干历史「宽松」返回值会改为 abort 。细节见 status.md 的 FORTIFY 与 Target API level behavioral differences 章节。
小结
| 维度 | 一句话 |
|---|---|
| 定位 | Android 专用 C 库,BSD 许可,非 glibc 替代品;随 API level 持续增厚 |
| 系统调用 | SYSCALLS.TXT + gensyscalls.py 生成桩,多架构维护 |
| 线程 | pthread_cancel 仍无 ;读写锁、时钟等待等 已随 API 增加 ;注意 targetSdk + FORTIFY |
| TLS | API 28+ ELF TLS :__thread/thread_local 在正确工具链下可用 |
| 系统接口 | Property、uid/app_ 命名、无传统桌面 passwd 模型 |
| 网络 | 解析器仍偏 Bionic 定制 ;真实设备还受 netd / Private DNS 等影响 |
| 链接 | linker64 + 命名空间 ,与桌面 Linux .so 不通用 |
| 自查 | 以 bionic/docs/status.md 为第一手变更清单 |
主题:Android Bionic Libc、AOSP 沿革勘误、与 glibc 差异、pthread、DNS、动态链接。