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 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。