介绍:
核心体系:
Bionic设计的核心思想是:尽量简单。
这意味这这个C库只围绕着内核提供轻量级的包装,我们让它尽量小,不去处理一些细枝末页的事情。
取Bionic这个名字,是因为它由部分BSD和部分linux组成:
它的代码由BSD C库和自定义的linux代码(用于处理线程,进程,信号,和其他事情)混合而成。
所有原始的BSD块,遵循BSD版权声明。特定的Bionic部分遵循Android open source project版权声明。一切发行版都遵循BSd版权。
架构:
Bionic 当前支持ARM and x86指令集。理论上可以支持更多,但是这需要做点工作(比如,
向SYSCALLS.TXT文件中添加系统调用IDS,如下面所述,或者修改动态链接器)。
ARM相关代码在arch-arm/下面,x86相关代码在arch-x86下面。
注意,x86版本只能运行在x86 android设备上。我们并没有声明说,你能够在普通 x86 linux发布版上使用Bionic,(尽管这会很”酷”,因此欢迎大家做这样的补丁程序);
Syscall stub:
每个系统调用函数都由一个小的汇编代码段实现(称为”syscall stub”),这是由tools/gensyscalls.py工具自动生成的,它从SYSCALL.TXT中取得输入参数。
SYSCALLS.TXT包含了一份要生成的系统调用块列表,和相应的系统调用数字标识符(ARM和X86不一样),以及它的签署。
如果你要修改这个文件,你可能要使用tools/checksyscalls.py工具,检查它里面是否包含官方linux核心头文件,如果有不合法的syscall ids,它会报告错误。
有时,C库函数其实就是一个包装,它内部调用相应的syscall名称,例如,exit函数由C库提供,它调用了_exit()这个syscall stub.
详细内容请参考SYSCALLS.TXT文件。
time_t:
time_t在32位CPU内核上定义为32位,一个64位的版本需要避免Y2038bug,但是内核的维护者认为,此刻并不需要。相反,Bionic提供了一个<time64.h>头文件,定义了一个time64_t类型,和相关的函数,如mktime64(), localtime64(),等等。
时区管理:
当前时区的名字取决与TZ环境变量,如果TZ未定义,将会使用名为'persist.sys.timezone'的系统属性。
时区数据库和索引文件放在/system/usr/share/zoneinfo下面,而不是在posix兼容路径/usr/share/zoneinfo下面。
Off_t:
出于相同的原因,off_t也是32位的,由于BSD的继承关系,我们定义loff_t型为64位变量,但是
可以使用typedef 定义off64_t类型,以便使得当前linux代码移植更简单。
Linux核心头文件:
Bionic带来一套自己的”clean”Linux内核头文件,允许用户空间代码使用内核特有的声明(比如,
IOCTLs, structure declarations, constants, 等等... ).他们位于:
./kernel/common,
./kernel/arch-arm
./kernel/arch-x86
这些头文件由一个工具(kernel/tools/update-all.py)产生,只包含原始Linux内核头文件中的公有定义。
如果你想知道为什么和怎么做,请参考kernel/README.TXT文件。
Pthread实现:
Bionic's C库使用他自己的pthread程序包,与其他C库相比有如下不同点:
-放在一个外部库中(-lpthread)
-开放含有少量符号的链接器入口用于动态链接
运行时特征支持(a.k.a. -lrt),也打包在C库中。基于futexes的实现,尽量提供很短小的代码实现通用操作,有以下几点显著的特征:
- pthread_mutex_t, pthread_cond_t 每各类型都只有4个字节.
- 支持Normal, recursive and error-check互斥体,对Normal case下的代码流程做了很细致的优化,通常大多数的时候都使用Normal。
- 进程共享互斥和条件变量不被支持,它们的实现很复杂,而且Android绝对用不到(android使用其他的进程内同步组件)。
注意,通过精心调整代码流程,他们可能会在未来添加进来,而且不会破坏ABI,(尽管这会使代码执行变得有点儿慢)。
-
当前没有读写锁,互斥体的优先权,和其他高级特征的支持。再说一句,在Android中根本不需要这些,但是在未来可能会被添加进来。
pthread_cancel():
pthread_cancel()在Bionic中不被支持,因为这将使得C库变得庞大起来,就为了这点好处不值得。
基于一下几点考虑:
- 要正常实现,必须在C库的多个地方插入pthread cancellation检测,这会使代码的统一调试变得非常困难。
- 要正常实现,也必须清理资源,象释放内存,解锁互斥体,如果cancel恰好发生在complex函数里(比如在gethostbyname() or fprintf() + 复杂格式化规则里面)。这会使许多函数的执行变慢。
- pthread cancellation不能停止所有的线程:比如,对于无穷循环,它就无能为力。
- pthread cancellation本身也有缺陷,不好移植。
(see http://advogato.org/person/slamb/diary.html?start=49 for example).
所有这些都与Bionic的设计目标相反,如果你的代码依赖thread cancellation,请好好考虑下吧。
注意Bionic确实实现了pthread_cleanup_push()和pthread_cleanup_pop(),在线程通过pthread_exit()调用退出或者从它的主函数中返回的时候,它可作些清理工作,
NND,不想支持就别支持嘛,非要编这么多理由来敷衍我们。
pthread_once():
不要在pthread_once()的回调函数中调用fork(),这么做的话,会在下次调用pthread_once()的时候,在子进程中导致死锁。
而且,你不能在回调函数中throw一个C++ Exception(参见下面的C++ Exception支持).
当前pthread_once()实现缺少必要的多核安全双重检查锁定(屏蔽读写操作)
线程本地数据:
线程本地存储区仅为每个进程提供了略小于64个pthread_key_t对象,在实现上,提供了64个实时slot, 而且自己使用了大约5个(实际数量依赖于实现)。(比如,两个 slot由C库预先分配,以启动Android OpenGL子系统加速)。
注意,Posix要求至少128个slot,但是我们不打算与Posix兼容。
除了主线城,在堆栈顶部也存储了TLS区,详细资料请参考bionic/libc/bionic/pthread.c文件中的注释部分。
当前,使用__thread关键词来定义线程本地存储区,还不被Bionic C库和动态链接器支持。
多核支持:
目前,Bionic不提供或使用读/写内存屏蔽。这意味着可能在某些多核系统中不被支持,这取决于实际的CPU架构。
Android特性:
Android提供了少部分特殊的功能。
- 访问系统属性:
Android向系统中所有的进程提供了一个简单共享的键/值对空间。它提供了properties上的字符号码,每一个都是限制大小的字符串,与一个限制大小的字符串值相关联。
头文件<sys/system_properties.h>能够被用于读系统属性,也定义键/值对的最大大小。
-Android的用户/组管理:
在Android中没有/etc/passwd 或 /etc/groups,按照设计,只打算给一个手机用户使用。另一方面,Android使用扩展的Linux用户/组管理特性,以确保进程权限,象对不同文件系统目录的访问。
Android策略是这样的,每个已安装的应用程序都有自己的uid_t/gid_t(从10000开始),小于10000的ids保留给系统守护进程.
getpwnam()认得一些硬编码的子系统名(比如.”radio”),会把他们翻译为用户id值,它也认得
"app_1234"是一个组合名字,会把它和10000相加,得到11234. getgrnam()也一样。
getgrouplist()总是返回用户名所属的组,它取用户名做输入参数。
同样地,getgrgid()只会返回一个结构体,它包含一个单元素的成员列表,与用户组的数值一样的用户相对应,
预知详情,参考bionic/libc/bionic/stubs.c。
- getservent()
在Android中没有/etc/services, C库在可执行文件中嵌入只读的服务列表作为代替,它们被依赖于它的不同函数所解析。可以参考bionic/libc/netbsd/net/getservent.c和bionic/libc/netbsd/net/services.h.
内部定义的服务列表可能在未来有变化。这项特性是历史性的,很少使用。
getservent()返回线程本地数据。getservbyport()和getservbyname()也按照同样的方式实现。
- getprotoent()
在Android中没有/etc/protocol,当前不实现getprotoent()和相关函数。如果加入的话,很有可能会与getservent()相同的方式实现。
DNS解析器:
Bionic 使用NetBSD-derived 解析库,按以下方式修改:
- 不实现name-server-switch特性(a.k.a. <nsswitch.h>)
- 读取/system/etc/resolv.conf而不是/etc/resolv.conf
- 从系统属性中读取服务列表。代码查找'net.dns1', 'net.dns2',等等。每个属性应包含一个DNS服务器的IP地址。
这些属性被Android系统的其他部分修改设置(比如dhcpd进程)
在实现上,也支持每进程的DNS服务器列表,使用属性'net.dns1.<pid>', 'net.dns2.<pid>',等等。这里<pid>代表当前进程的ID号。
- 在执行查询时,使用一个适当的随机查询ID(而不是递增1),以提高安全性。
- 在执行查询时,给本地客户socket绑定一个随机端口号,以提高安全性。
- 删除一些源代码,这些源代码会造成许多不幸的线程安全问题。
Bionic不暴露它的DNS解析器的实现细节;<arpa/nameser.h>中的内容故意空白。解析器的实现可能会在未来完全改变。
PThread 实时定时器:
支持timer_create(), timer_gettime(), timer_settime() and timer_getoverrun()。
Bionic现在也支持SIGEV_THREAD实时定时器(参见timer_create())。
在实现上,简单地使用一个线程一个定时器的机制,不像Glibc使用复杂的启发式技术,在多个定时器具有相同属性的情况下,尽可能减少使用的线程数。
这意味着,如果你的代码使用许多SIGEV_THREAD定时器,你的程序可能会消耗太多内存。无论如何,如果你的程序需要许多定时器,最好直接使用timeout事件代替。
其他定时器(如SIGEV_SIGNAL)由内核处理,使用更少的系统资源。
二进制兼容性:
Bionic 不与GNU C库,ucLibc,或任何已知的Linux C库相兼容。这意味一下几件事情:
- 你不能指望依赖GNU C库头文件编译出来的东东,能够正常地动态链接到Bionic.
- 你应该*确实地*依赖Bionic并且使用Android工具链来编译你的程序,工具链处理一些至关重要的细节问题,让你的程序能正常运行。
如果不这样做,经常会导致无法运行或者链接,甚至运行崩溃。互联网上有些网页也描述了怎样使用ARM GNU 工具链成功地写一个"hello-world"程序。这些示例有时也会正常运行,如果不是情非得以,你不应该按照那些指令去做,除非你想浪费时间。
注意,不管怎样,你*能够*依赖GNU C库头文件并且通过静态链接的方式生成一个程序。相应的程序应该能够运行。(如果你不使用dlopen()/dlsym()的话)
动态链接器:
Bionic有它自己的动态链接器(就像在Linux中的ld.so一样),这个链接器不支持由其他GCC ARM工具链生成的重定位。
C++异常支持:
目前,Bionic不支持C++异常,这意味着下面的事实:
- 如果在pthread_once()的回调函数中抛出异常,C库将保持对应的pthread_once_t互斥器的锁定状态。再次调用pthread_once()将导致死锁。
- 一个适当的实现是能够在回调之前注册一个C++异常清理器,以便正确地为pthread_once_t解锁。不幸地是这需要高度依赖编译器的汇编代码。
这个特性在最近不会得到支持。
- 如果你从C库的回调函数中抛出异常,会发生同样的问题。幸运地是,这些情况非常少,但是你向C库的提供的回调不应抛出异常。
- Bionic缺乏一些能让异常正常运行的支持函数。
System V IPCs:
Bionic有意不提供System-V IPCs机制的支持,象由semget(), shmget(), msgget()提供的那些。原因是为了避免”拒绝服务”.想要了解这个详细的理由,请阅读文档docs/SYSV-IPCS.TXT.
包含路径:
Android编译系统会自动提供必要的C库头文件的包含路径。然而,如果你想要亲自动手的话,必须要添加:
libc/arch-$ARCH/include
libc/include
libc/kernel/common
libc/kernel/arch-$ARCH
到C代码的包含路径。