Bionic库是Android的基础库之一,也是链接Android系统和Linux系统内核的桥梁,Bionic中包含了不少基本的功能模块,这些功能模块基本上都是源于Linux,可是就像青出于蓝而胜于蓝,它和Linux仍是有一些不同的的地方。同时,为了更好的服务Android,Bionic中也增长了一些新的模块,因为本次的主题是Androdi的跨进程通讯,因此了解Bionic对咱们更好的学习Android的跨进行通讯仍是颇有帮助的。android
Android除了使用ARM版本的内核外和传统的x86有所不一样,谷歌还本身开发了Bionic库,那么谷歌为何要这样作那?算法
谷歌使用Bionic库主要由于如下三点:编程
Bionic 音标为 bīˈänik,翻译为"仿生"数组
Bionic包含了系统中最基本的lib库,包括libc,libm,libdl,libstd++,libthread_db,以及Android特有的连接器linker。安全
Bionic库的特性不少,受篇幅限制,我挑几个和你们平时接触到的说下服务器
Bionic 当前支持ARM、x86和MIPS执行集,理论上能够支持更多,可是须要作些工做,ARM相关的代码在目录arch-arm中,x86相关代码在arch-x86中,mips相关的代码在arch-mips中。网络
Bionic自带一套通过清理的Linxu内核头文件,容许用户控件代码使用内核特有的声明(如iotcls,常量等)这些头文件位于目录:多线程
bionic/libc/kernel/common
bionic/libc/kernel/arch-arm
bionic/libc/kernel/arch-x86
bionic/libc/kernel/arch-mips架构
虽然Bionic 使用NetBSD-derived解析库,可是它也作了一些修改。app
因为Bionic不与GNU C库、ucLibc,或者任何已知的Linux C相兼容。因此意味着不要指望使用GNU C库头文件编译出来的模块可以正常地动态连接到Bionic
Bionict提供了少部分Android特有的功能
一、访问系统特性
Android 提供了一个简单的"共享键/值 对" 空间给系统的中的全部进程,用来存储必定数量的"属性"。每一个属性由一个限制长度的字符串"键"和一个限制长度的字符串"值"组成。
头文件<sys/system_properties.h>中定义了读系统属性的函数,也定义了键/值对的最大长度。
二、Android用户/组管理
在Android中没有etc/password和etc/groups 文件。Android使用扩展的Linux用户/组管理特性,以确保进程根据权限来对不一样的文件系统目录进行访问。
Android的策略是:
Bionic目录下一共有5个库和一个linker程序
5个库分别是:
Libc是C语言最基础的库文件,它提供了全部系统的基本功能,这些功能主要是对系统调用的封装,是Libc是应用和Linux内核交流的桥梁,主要功能以下:
Libm 是数学函数库,提供了常见的数学函数和浮点运算功能,可是Android浮点运算时经过软件实现的,运行速度慢,不建议频繁使用。
libdl库本来是用于动态库的装载。不少函数实现都是空壳,应用进程使用的一些函数,其实是在linker模块中实现。
libstd++ 是标准的C++的功能库,可是,Android的实现是很是简单的,只是new,delete等少数几个操做符的实现。
libthread_db 用来支持对多线程的中动态库的调试。
Linux系统上其实有两种并不彻底相同的可执行文件
静态可执行程序用在一些特殊场合,例如,系统初始化时,这时整个系统尚未准备好,动态连接的程序还没法使用。系统的启动程序Init就是一个静态连接的例子。在Android中,会给程序自动加上两个".o"文件,分别是"crtbegin_static.c"和"certtend_android.o",这两个".o"文件对应的源文件位于bionic/libc/arch-common/bionic目录下,文件分别是crtbegin.c和certtend.S。_start()函数就位于cerbegin.c中。
在动态连接时,execuve()函数会分析可执行文件的文件头来寻找连接器,Linux文件就是ld.so,而Android则是Linker。execuve()函数将会将Linker载入到可执行文件的空间,而后执行Linker的_start()函数。Linker完成动态库的装载和符号重定位后再去运行真正的可执行文件的代码。
对于32位的操做系统,能使用的最大地址空间是4GB,其中地址空间03GB分配给用户进程使用,地址空间3GB4GB由内核使用,可是用户进程并非在启动时就获取了全部的0~3GB地址空间的访问权利,而是须要事先向内核申请对模块地址空间的读写权利。并且申请的只是地址空间而已,此时并无分配真是的物理地址。只有当进程访问某个地址时,若是该地址对应的物理页面不存在,则由内核产生缺页中断,在中断中才会分配物理内存并创建页表。若是用户进程不须要某块空间了,能够经过内核释放掉它们,对应的物理内存也释放掉。
可是因为缺页中断会致使运行缓慢,若是频繁的地由内核来分配和释放内存将会下降整个体统的性能,所以,通常操做系统都会在用户进程中提供地址空间的分配和回收机制。用户进程中的内存管理会预先向内核申请一块打的地址空间,称为堆。当用户进程须要分配内存时,由内存管理器从堆中寻找一块空闲的内存分配给用户进程使用。当用户进程释放某块内存时,内存管理器并不会马上将它们交给内核释放,而是放入空闲列表中,留待下次分配使用。
内存管理器会动态的调整堆的大小,若是堆的空间使用完了,内存管理器会向堆内存申请更多的地址空间,若是堆中空闲太多,内存管理器也会将一部分空间返给内核。
dlmalloc是一个十分流行的内存分配器。dlmalloc位于bionic/libc/upstream-dlmalloc下,只有一个C文件malloc.c。因为本次主题是跨进程通讯,后续有时间就Android的内存回收单独做为一个课题去讲解,今天就不详细说了,就简单的说下原理。
dlmalloc的原理:
Dalvk虚拟机中使用了dlmalloc进行私有堆管理。
Bionic中的线程管理函数和通用的Linux版本的实现有不少差别,Android根据本身的须要作了不少裁剪工做。
一、pthread的实现基于Futext,同时尽可能使用简单的代码来实现通用操做,特征以下:
二、Bionic不支持pthread_cancel(),由于加入它会使得C库文件明显变大,不太值得,同时有如下几点考虑
三、不要在pthread_once()的回调函数中调用fork(),这么作会致使下次调用pthread_once()的时候死锁。并且不能在回调函数中抛出一个C++的异常。
四、不能使用_thread关键词来定义线程本地存储区。
一、建立线程
函数pthread_create()用来建立线程,原型是:
int pthread_create((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
其中,pthread_t在android中等同于long
typedef long pthread_t;
若线程建立成功,则返回0,若线程建立失败,则返回出错编号。
PS:要注意的是,pthread_create调用成功后线程已经建立完成,可是不会马上发生线程切换。除非调用线程主动放弃执行,不然只能等待线程调度。
二、线程的属性
结构 pthread_atrr_t用来设置线程的一些属性,定义以下:
typedef struct { uint32_t flags; void * stack_base; //指定栈的起始地址 size_t stack_size; //指定栈的大小 size_t guard_size; int32_t sched_policy; //线程的调度方式 int32_t sched_priority; //线程的优先级 }
使用属性时要先初始化,函数原型是:
int pthread_attr_init(pthread_attr_t* attr)
经过pthread_attr_init()函数设置的缺省属性值以下:
int pthread_attr_init(pthread_attr_t* attr){ attr->flag=0; attr->stack_base=null; attr->stack_szie=DEFAULT_THREAD_STACK_SIZE; //缺省栈的尺寸是1MB attr0->quard_size=PAGE_SIZE; //大小是4096 attr0->sched_policy=SCHED_NORMAL; //普通调度方式 attr0->sched_priority=0; //中等优先级 return 0; }
下面介绍每项属性的含义。
Bionic虽然也实现了pthread_attr_setscope()函数,可是只支持PTHREAD_SCOP_SYSTEM属性,也就意味着Android线程将在全系统的范围内竞争CPU资源。
三、退出线程的方法
(1)、调用pthread_exit函数退出
通常状况下,线程运行函数结束时线程才退出。可是若是须要,也能够在线程运行函数中调用pthread_exit()函数来主动退出线程运行。函数原型以下:
void pthread_exit( void * retval) ;
其中参数retval用来设置返回值
(2)、设备布尔的全局变量
可是若是但愿在其它线程中结束某个线程?前面介绍了Android不支持pthread_cancel()函数,所以,不能在Android中使用这个函数来结束线程。通俗的方法是,若是线程在一个循环中不停的运行,能够在每次循环中检查一个初始值为false的全局变量,一旦这个变量的值为ture,则主动退出,这样其它线程就能够铜鼓改变这个全局变量的值来控制线程的退出,示例以下:
bool g_force_exit =false; void * thread_func(void *){ for(;;){ if(g_force_exit){ break; } ..... } return NULL; } int main(){ ..... q_force_exit=true; //青坡线程退出 }
这种方法实现起来简单可靠,在编程中常用。但它的缺点是:若是线程处于挂起等待状态,这种方法就不适用了。
另一种方式是使用pthread_kill()函数。pthread_kill()函数的做用不是"杀死"一个线程,而是给线程发送信号。函数以下:
int pthread_kill(pthread tid,int sig);
即便线程处于挂起状态,也可使用pthead_kill()函数来给线程发送消息并使得线程执行处理函数,使用pthread_kill()函数的问题是:线程若是在信号处理函数中退出,不方便释放在线程的运行函数中分配的资源。
(3)、经过管道
更复杂的方法是:建立一个管道,在线程运行函数中对管道"读端"用select()或epoll()进行监听,没有数据则挂起线程,经过管道的"写端"写入数据,就能唤起线程,从而释放资源,主动退出。
四、线程的本地存储TLS
线程本地存储(TLS)用来保存、传递和线程有关的数据。例如在前面说道的使用pthread_kill()函数关闭线程的例子中,须要释放的资源可使用TLS传递给信号处理函数。
(1)、TLS介绍
TLS在线程实例中是全局可见的,对某个线程实例而言TLS是这个线程实例的私有全局变量。同一个线程运行函数的不一样运行实例,他们的TLS是不一样的。在这个点上TLS和线程的关系有点相似栈变量和函数的关系。栈变量在函数退出时会消失,TLS也会在线程结束时释放。Android实现了TLS的方式是在线程栈的顶开辟了一块区域来存放TLS项,固然这块区域再也不受线程栈的控制。
TLS内存区域按数组方式管理,每一个数组元素称为一个slot。Android 4.4中的TLS一共有128 slot,这和Posix中的要求一致(Android 4.2是64个)
(2)、TLS注意事项
int pthread_key_create(pthread_key_t *key,void (*destructor_function) (void *) );
pthread_key_create()函数成功返回0,参数key中是分配的slot,若是未来放入slot中的对象须要在线程结束的时候由系统释放,则须要提供一个释放函数,经过第二个函数destructor_function传入。
int pthread_key_delete ( pthread_key_t) ;
pthread_key_delete()函数并不检查当前是否还有线程正在使用这个slot,也不会调用清理函数,只是将slot释放以供下次调用pthread_key_create()使用。
int pthread_setspecific(pthread_key_t key,const void *value) ;
void * pthread_getsepcific (pthread_key_t key);
五、线程的互斥量(Mutex)函数
Linux线程提供了一组函数用于线程间的互斥访问,Android中的Mutex类实质上是对Linux互斥函数的封装,互斥量能够理解为一把锁,在进入某个保护区域前要先检查是否已经上锁了。若是没有上锁就能够进入,不然就必须等待,进入后现将锁锁上,这样别的线程就没法再进入了,退出保护区后腰解锁,其它线程才能够继续使用
(1)、Mutex在使用前须要初始化
初始化函数是:
int pthread_mutex_init(pthread_mutext_t *mutex, const pthread_mutexattr_t *attr);
成功后函数返回0,metex被初始化成未锁定的状态。若是参数attr为NULL,则使用缺省的属性MUTEX_TYPE-BITS_NORMAL。
互斥量的属性主要有两种,类型type和范围scope,设置和获取属性的函数以下:
int pthread_mutexattr_settype (pthread_mutexattr_t * attr, type); int pthread_mutexattr_gettype (const pthread_mutexattr_t * attr, int *type); int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared ); int pthread_mutexattrattr_ setpshared (pthread_mutexattr_t *attr,int pshared);
互斥量Mutex的类型(type) 有3种
互斥量Mutex的做用范围(scope) 有2种
六、线程的条件量(Condition)函数
(1)为何须要条件量Condition函数
条件量Condition是为了解决一些更复杂的同步问题而设计的。考虑这样的一种状况,A和B线程不但须要互斥访问某个区域,并且线程A还必须等待线程B的运行结果。若是仅使用互斥量进行保护,在线程B先运行的的状况下没有问题。可是若是线程A先运行,拿到互斥量的锁,往下忘没法进行。
条件量就是解决这类问题的。在使用条件量的状况下,若是线程A先运行,获得锁之后,可使用条件量的等待函数解锁并等待,这样线程B获得了运行的机会。线程B运行完之后经过条件量的信号函数唤醒等待的线程A,这样线程A的条件也知足了,程序就能继续执行力额。
(2)Condition函数
1️⃣ 条件量在使用前须要先初始化,函数原型是:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr *attr);
使用完须要销毁,函数原型是:
int pthread_cond_destroy(pthread_cond_t *cond);
条件量的属性只有 "共享(share)" 一种,下面是属性相关函数原型,下面是属性相关的函数原型:
int pthread_condattr_init(pthread_condattr_t *attr); int pthread_condattr_getpshared(pthread_condattr_t *attr,int *pshared); int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared) int pthread_condattr_destroy (pthread_condattr_t *__attr);
"共享(shared)" 属性的值有两种
2️⃣条件量的等待函数的原型以下:
int pthread_cond_wait (pthread_cond_t *__restrict __cond,pthread_mutex_t *__restrict __mutex); int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,pthread_mutex_t *__restrict __mutex, __const struct timespec *__restrict __abstime);
条件量的等待函数会先解锁互斥量,所以,使用前必定要确保mutex已经上锁。锁上后线程将挂起。pthread_cond_timedwait()用在但愿线程等待一段时间的状况下,若是时间到了线程就会恢复运行。
3️⃣ 可使用函数pthread_cond_signal()来唤醒等待队列中的一个线程,原型以下:
int pthread_cond_signal (pthread_cond_t *__cond);
也能够经过pthread_cond_broadcast()唤醒全部等待的线程
int pthread_cond_broadcast (pthread_cond_t *__cond);
一、Futex的系统调用
在Linux中,Futex系统调用的定义以下:
#define _NR_futex 240
(1) Fetex系统调用的原型是:
int futex(int *uaddr, int cp, int val, const struct timespec *timeout, int *uaddr2, int val3);
(1) 在Bionic中,提供了两个函数来包装Futex系统调用:
extern int _futex_wait(volatile void *ftx,int val, const struct timespec *timespec ); extern int _futex_wake(volatile void *ftx, int count);
(2) Bionic还有两个相似的函数,它们的原型以下:
extern int _futex_wake_ex(volatile void *ftx,int pshared,int val); extern int _futex_wait_ex(volatile void *fex,int pshared,int val, const stuct timespec *timeout);
这两个函数多了一个参数pshared,pshared的值为true 表示wake和wait操做是用于进程间的挂起和唤醒;值为false表示操做于进程内线程的挂起和唤醒。当pshare的值为false时,执行Futex系统调用的操做码为
FUTEX_WAIT|FUTEX_PRIVATE_FLAG
内核如何检测到操做有FUTEX_PRIVATE_FLAG标记,能以更快的速度执行七挂起和唤醒操做。
_futex_wait 和_futex_wake函数至关于pshared等于true的状况。
(3) 在Bionic中,提供了两个函数来包装Futex系统调用:
extern int _futex_syscall3(volatile void *ftx,int pshared,int val); extern int _futex_syscall4(volatile void *ftx,int pshared,int val, const struct timespec *timeout);
_futex_syscall3()至关于 _futex_wake(),而 _futex_system4()至关于 _futex_wait()。这两个函数与前面的区别是能指定操做码op做为参数。操做码能够是FUTEX_WAIT_FUTEX_WAKE或者它们和FUTEX_PRIVATE_FLAG的组合。
二、Futex的用户态操做
Futex的系统调用FUTEX_WAIT和FUTEX_WAKE只是用来挂起或者唤醒进程,Futex的同步机制还包括用户态下的判断操做。用户态下的操做没有固定的函数调用,只是一种检测共享变量的方法。Futex用于临界区的算法以下:
对Futex变量操做时,比较和赋值操做必须是原
做者:隔壁老李头 连接:https://www.jianshu.com/p/25a908c7eefa 來源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。