一、為什么要學習Bionic
Bionic庫是Android的基礎(chǔ)庫之一菌仁,也是連接Android系統(tǒng)和Linux系統(tǒng)內(nèi)核的橋梁诵姜,Bionic中包含了很多基本的功能模塊,這些功能模塊基本上都是源于Linux冻记,但是就像青出于藍而勝于藍睡毒,它和Linux還是有一些不一樣的的地方。同時冗栗,為了更好的服務(wù)Android演顾,Bionic中也增加了一些新的模塊,由于本次的主題是Androdi的跨進程通信贞瞒,所以了解Bionic對我們更好的學習Android的跨進行通信還是很有幫助的偶房。
Android除了使用ARM版本的內(nèi)核外和傳統(tǒng)的x86有所不同,谷歌還自己開發(fā)了Bionic庫军浆,那么谷歌為什么要這樣做那?
二棕洋、谷歌為什么使用Bionic庫
谷歌使用Bionic庫主要因為以下三點:
- 1、谷歌沒有使用Linux的GUN Libc乒融,很大一部分原因是因為GNU Libc的授權(quán)方式是GPL 授權(quán)協(xié)議有限制掰盘,因為一旦軟件中使用了GPL的授權(quán)協(xié)議,該系統(tǒng)所有代碼必須開元赞季。
- 2愧捕、谷歌在BSD的C庫的基礎(chǔ)上加入了一些Linux特性從而生成了Bionic。Bionic名字的來源就是BSD和Linux的混合申钩。而且不受限制的開源方式次绘,所以在現(xiàn)代的商業(yè)公司中比較受歡迎。
- 3、還有就是因為性能的原因邮偎,因為Bionic的核心設(shè)計思想就是"簡單"管跺,所以Bionic中去掉了很多高級功能。這樣Bionic庫僅為200K左右禾进,是GNU版本體積的一半豁跑,這意味著更高的效率和低內(nèi)存的使用,同時配合經(jīng)過優(yōu)化的Java VM Dalvik才可以保證高的性能泻云。
三艇拍、Bionic庫簡介
Bionic 音標為 bī??nik,翻譯為"仿生"
Bionic包含了系統(tǒng)中最基本的lib庫宠纯,包括libc卸夕,libm,libdl征椒,libstd++娇哆,libthread_db,以及Android特有的鏈接器linker勃救。
四碍讨、Bionic庫的特性
Bionic庫的特性很多,受篇幅限制蒙秒,我挑幾個和大家平時接觸到的說下
(一)勃黍、架構(gòu)
Bionic 當前支持ARM、x86和MIPS執(zhí)行集晕讲,理論上可以支持更多覆获,但是需要做些工作,ARM相關(guān)的代碼在目錄arch-arm中瓢省,x86相關(guān)代碼在arch-x86中弄息,mips相關(guān)的代碼在arch-mips中。
(二)勤婚、Linux核心頭文件
Bionic自帶一套經(jīng)過清理的Linxu內(nèi)核頭文件摹量,允許用戶控件代碼使用內(nèi)核特有的聲明(如iotcls,常量等)這些頭文件位于目錄:
bionic/libc/kernel/common
bionic/libc/kernel/arch-arm
bionic/libc/kernel/arch-x86
bionic/libc/kernel/arch-mips
(三)馒胆、DNS解析器
雖然Bionic 使用NetBSD-derived解析庫缨称,但是它也做了一些修改。
- 1祝迂、不實現(xiàn)name-server-switch特性
- 2睦尽、讀取/system/etc/resolv.conf而不是/etc/resolv.config
- 3、從系統(tǒng)屬性中讀取服務(wù)器地址列表型雳,代碼中會查找'net.dns1'当凡,'net.dns2'山害,等屬性。每個屬性都應(yīng)該包含一個DNS服務(wù)器的IP地址宁玫。這些屬性能被Android系統(tǒng)的其它進程修改設(shè)置粗恢。在實現(xiàn)上,也支持進程單獨的DNS服務(wù)器列表欧瘪,使用屬性'net.dns1.<pid>'、'net.dns2.<pid>'等匙赞,這里<pid> 表示當前進程的ID號佛掖。
- 4、在執(zhí)行查詢時涌庭,使用一個合適的隨機ID(而不是每次+1)芥被,以提升安全性。
- 5坐榆、在執(zhí)行查詢時拴魄,給本地客戶socket綁定一個隨機端口號,以提高安全性席镀。
- 6匹中、刪除了一些源代碼,這些源代碼會造成了很多線程安全的問題
(四)豪诲、二進制兼容性
由于Bionic不與GNU C庫顶捷、ucLibc,或者任何已知的Linux C相兼容屎篱。所以意味著不要期望使用GNU C庫頭文件編譯出來的模塊能夠正常地動態(tài)鏈接到Bionic
(五)服赎、Android特性
Bionict提供了少部分Android特有的功能
1、訪問系統(tǒng)特性
Android 提供了一個簡單的"共享鍵/值 對" 空間給系統(tǒng)中的所有進程交播,用來存儲一定數(shù)量的"屬性"重虑。每個屬性由一個限制長度的字符串"鍵"和一個限制長度的字符串"值"組成。
頭文件<sys/system_properties.h>中定義了讀系統(tǒng)屬性的函數(shù)秦士,也定義了鍵/值對的最大長度缺厉。
2、Android用戶/組管理
在Android中沒有etc/password和etc/groups 文件伍宦。Android使用擴展的Linux用戶/組管理特性芽死,以確保進程根據(jù)權(quán)限來對不同的文件系統(tǒng)目錄進行訪問。
Android的策略是:
- 1次洼、每個已經(jīng)安裝的的應(yīng)用程序都有自己的用戶ID和組ID关贵。ID從10000(一萬)開始,小于10000(一萬)的ID留給系統(tǒng)的守護進程卖毁。
- 2揖曾、tpwnam()能識別一些硬編碼的子進程名(如"radio")落萎,能將他們翻譯為用戶id值,它也能識別"app_1234"炭剪,這樣的組合名字练链,知道將后面的1234和10000(一萬)相加,得到的ID值為11234.getgrname()也類似奴拦。
- 3媒鼓、getservent() Android中沒有/etc/service,C庫在執(zhí)行文件中嵌入只讀的服務(wù)列表作為代替错妖,這個列表被需要它的函數(shù)所解析绿鸣。所見文件bionic/libc/netbsd/net/getservent.c和bionic/libc/netbsd/net/service.h。
這個內(nèi)部定義的服務(wù)列表暂氯,未來可能有變化潮模,這個功能是遺留的,實際很少使用痴施。getservent()返回的是本地數(shù)據(jù)擎厢,getservbyport()和getservbyname()也按照同樣的方式實現(xiàn)。- 4辣吃、getprotoent() 在Android中沒有/etc/protocel动遭,Bionic目前沒有實現(xiàn)getprotocent()和相關(guān)函數(shù)。如果增加的話齿尽,很可能會以getervent()相同的方式沽损。
五、Bionic庫的模塊簡介
Bionic目錄下一共有5個庫和一個linker程序
5個庫分別是:
- 1循头、libc
- 2绵估、libm
- 3、libdl
- 4卡骂、libstd++
- 5国裳、libthread_db
(一)、Libc庫
Libc是C語言最基礎(chǔ)的庫文件全跨,它提供了所有系統(tǒng)的基本功能缝左,這些功能主要是對系統(tǒng)調(diào)用的封裝,Libc是應(yīng)用和Linux內(nèi)核交流的橋梁浓若,主要功能如下:
- 進程管理:包括進程的創(chuàng)建渺杉、調(diào)度策略和優(yōu)先級的調(diào)整
- 線程管理:包括線程的創(chuàng)建和銷毀,線程的同步/互斥等
- 內(nèi)存管理:包括內(nèi)存分配和釋放等
- 時間管理:包括獲取和保存系統(tǒng)時間挪钓、獲取當前系統(tǒng)運行時長等
- 時區(qū)管理:包括時區(qū)的設(shè)置和調(diào)整等
- 定時器管理:提供系統(tǒng)的定時服務(wù)
- 文件系統(tǒng)管理:提供文件系統(tǒng)的掛載和移除功能
- 文件管理:包括文件和目錄的創(chuàng)建增刪改
- 網(wǎng)絡(luò)套接字:創(chuàng)建和監(jiān)聽socket是越,發(fā)送和接受
- DNS解析:幫助解析網(wǎng)絡(luò)地址
- 信號:用于進程間通信
- 環(huán)境變量:設(shè)置和獲取系統(tǒng)的環(huán)境變量
- Android Log:提供和Android Log驅(qū)動進行交互的功能
- Android 屬性:管理一個共享區(qū)域來設(shè)置和讀取Android的屬性
- 標準輸入/輸出:提供格式化的輸入/輸出
- 字符串:提供字符串的移動、復制和比較等功能
- 寬字符:提供對寬字符的支持碌上。
(二)倚评、Libm庫
Libm 是數(shù)學函數(shù)庫浦徊,提供了常見的數(shù)學函數(shù)和浮點運算功能,但是Android浮點運算時通過軟件實現(xiàn)的天梧,運行速度慢盔性,不建議頻繁使用。
(三)呢岗、libdl庫
libdl庫原本是用于動態(tài)庫的裝載冕香。很多函數(shù)實現(xiàn)都是空殼,應(yīng)用進程使用的一些函數(shù)敷燎,實際上是在linker模塊中實現(xiàn)暂筝。
(四)、libstd++庫
libstd++ 是標準的C++的功能庫硬贯,但是,Android的實現(xiàn)是非常簡單的陨收,只是new饭豹,delete等少數(shù)幾個操作符的實現(xiàn)。
(五)务漩、libthread_db庫
libthread_db 用來支持對多線程中動態(tài)庫的調(diào)試拄衰。
(六)、Linker模塊
Linux系統(tǒng)上其實有兩種并不完全相同的可執(zhí)行文件
- 一種是靜態(tài)鏈接的可執(zhí)行程序饵骨。靜態(tài)可執(zhí)行程序包含了運行需要的所有函數(shù)翘悉,可以不依賴任何外部庫來運行。
- 另一種是動態(tài)鏈接的可執(zhí)行程序居触。動態(tài)鏈接的可執(zhí)行程序因為沒有包含所需的庫文件妖混,因此相對于要小很多。
靜態(tài)可執(zhí)行程序用在一些特殊場合轮洋,例如制市,系統(tǒng)初始化時,這時整個系統(tǒng)還沒有準備好弊予,動態(tài)鏈接的程序還無法使用祥楣。系統(tǒng)的啟動程序Init就是一個靜態(tài)鏈接的例子。在Android中汉柒,會給程序自動加上兩個".o"文件误褪,分別是"crtbegin_static.c"和"certtend_android.o",這兩個".o"文件對應(yīng)的源文件位于bionic/libc/arch-common/bionic目錄下碾褂,文件分別是crtbegin.c和certtend.S兽间。_start()函數(shù)就位于cerbegin.c中。
在動態(tài)鏈接時斋扰,execuve()函數(shù)會分析可執(zhí)行文件的文件頭來尋找鏈接器渡八,Linux文件就是ld.so啃洋,而Android則是Linker。execuve()函數(shù)將會將Linker載入到可執(zhí)行文件的空間屎鳍,然后執(zhí)行Linker的_start()函數(shù)宏娄。Linker完成動態(tài)庫的裝載和符號重定位后再去運行真正的可執(zhí)行文件的代碼。
六逮壁、Bionic庫的內(nèi)存管理函數(shù)
(一)內(nèi)存管理函數(shù)
對于32位的操作系統(tǒng)孵坚,能使用的最大地址空間是4GB,其中地址空間3GB分配給用戶進程使用窥淆,但是用戶進程并不是在啟動時就獲取了所有的0~3GB地址空間的訪問權(quán)利卖宠,而是需要事先向內(nèi)核申請對模塊地址空間的讀寫權(quán)利。而且申請的只是地址空間而已忧饭,此時并沒有分配真正的物理地址扛伍。只有當進程訪問某個地址時,如果該地址對應(yīng)的物理頁面不存在词裤,則由內(nèi)核產(chǎn)生缺頁中斷刺洒,在中斷中才會分配物理內(nèi)存并建立頁表。如果用戶進程不需要某塊空間了吼砂,可以通過內(nèi)核釋放掉它們逆航,對應(yīng)的物理內(nèi)存也釋放掉。
但是由于缺頁中斷會導致運行緩慢渔肩,如果頻繁的地由內(nèi)核來分配和釋放內(nèi)存將會降低整個體統(tǒng)的性能因俐,因此,一般操作系統(tǒng)都會在用戶進程中提供地址空間的分配和回收機制周偎。用戶進程中的內(nèi)存管理會預先向內(nèi)核申請一塊大的地址空間抹剩,稱為堆。當用戶進程需要分配內(nèi)存時栏饮,由內(nèi)存管理器從堆中尋找一塊空閑的內(nèi)存分配給用戶進程使用吧兔。當用戶進程釋放某塊內(nèi)存時,內(nèi)存管理器并不會立刻將它們交給內(nèi)核釋放袍嬉,而是放入空閑列表中境蔼,留待下次分配使用。
內(nèi)存管理器會動態(tài)的調(diào)整堆的大小伺通,如果堆的空間使用完了箍土,內(nèi)存管理器會向堆內(nèi)存申請更多的地址空間,如果堆中空閑太多罐监,內(nèi)存管理器也會將一部分空間返給內(nèi)核吴藻。
(二) Bionic的內(nèi)存管理器——dlmalloc
dlmalloc是一個十分流行的內(nèi)存分配器。dlmalloc位于bionic/libc/upstream-dlmalloc下弓柱,只有一個C文件malloc.c沟堡。由于本次主題是跨進程通信侧但,后續(xù)有時間就Android的內(nèi)存回收單獨作為一個課題去講解,今天就不詳細說了航罗,就簡單的說下原理禀横。
dlmalloc的原理:
- dlmalloc內(nèi)部是以鏈表的形式將"堆"的空閑空間根據(jù)尺寸組織在一起。分配內(nèi)存時通過這些鏈表能快速地找到合適大小的空閑內(nèi)存粥血。如果不能找到滿足要求的空閑內(nèi)存柏锄,dlmalloc會使用系統(tǒng)調(diào)用來擴大堆空間。
- dlmalloc內(nèi)存塊被稱為"trunk"复亏。每塊大小要求按地址對齊(默認8個字節(jié))趾娃,因此,trunk塊的大小必須為8的倍數(shù)缔御。
- dlmalloc用3種不同的的鏈表結(jié)構(gòu)來組織不同大小的空閑內(nèi)存塊抬闷。小于256字節(jié)的塊使用malloc_chunk結(jié)構(gòu),按照大小組織在一起耕突。由于尺寸小于的塊一共有256/8=32饶氏,所以一共使用了32個malloc_chunk結(jié)構(gòu)的環(huán)形鏈表來組織小于256的塊。大小大于256字節(jié)的塊由結(jié)構(gòu)malloc_tree_chunk組成鏈表管理有勾,這些塊根據(jù)大小組成二叉樹。而更大的尺寸則由系統(tǒng)通過mmap的方式單獨分配一塊空間古程,并通過malloc_segment組成的鏈表進行管理
- 當dlmalloc分配內(nèi)存時蔼卡,會通過查找這些鏈表來快速找到一塊和要求的尺寸大小最匹配的空閑內(nèi)存塊(這樣做事為了盡量避免內(nèi)存碎片)。如果沒有合適大小的塊挣磨,則將一塊大的分成兩塊雇逞,一塊分配出去,另一塊根據(jù)大小再加入對應(yīng)的空閑鏈表中茁裙。
- 當dlmalloc釋放內(nèi)存時塘砸,會將相鄰的空閑塊合并成一個大塊來減少內(nèi)存碎片。如果空閑塊過多晤锥,超過了dlmaloc內(nèi)存的閥值,dlmalloc就開始向系統(tǒng)返回內(nèi)存。
- dlmalloc除了能管理進程的"堆"空間煎娇,還能提供私有堆管理巧婶,就是在堆外單獨分配一塊地址空間,由dlmalloc按照同樣的方式進行管理壕翩。dlmalloc中用來管理進程的"堆"空間的函數(shù)蛉迹,都帶有"dl"前綴,如"dlmalloc"放妈,"dlfree"等北救,而私有堆的管理函數(shù)則帶有前綴"msspace_"荐操,如"msspace_malloc"
Dalvk虛擬機中使用了dlmalloc進行私有堆管理。
七珍策、線程
Bionic中的線程管理函數(shù)和通用的Linux版本的實現(xiàn)有很多差異托启,Android根據(jù)自己的需要做了很多裁剪工作。
(一)膛壹、Bionic線程函數(shù)的特性
1驾中、pthread的實現(xiàn)基于Futext,同時盡量使用簡單的代碼來實現(xiàn)通用操作模聋,特征如下:
pthread_mutex_t肩民,pthread_cond_t類型定義只有4字節(jié)。
- 支持normal,recursive and error-check 互斥量链方〕痔担考慮到通常大多數(shù)的時候都使用normal,對normal分支下代碼流程做了很細致的優(yōu)化
- 目前沒有支持讀寫鎖祟蚀,互斥量的優(yōu)先級和其他高級特征工窍。在Android還不需要這些特征,但是在未來可能會添加進來前酿。
2患雏、Bionic不支持pthread_cancel(),因為加入它會使得C庫文件明顯變大罢维,不太值得淹仑,同時有以下幾點考慮
- 要正確實現(xiàn)pthread_cancel(),必須在C庫的很多地方插入對終止線程的檢測肺孵。
- 一個好的實現(xiàn)匀借,必須清理資源,例如釋放內(nèi)存平窘,解鎖互斥量吓肋,如果終止恰好發(fā)生在復雜的函數(shù)里面(比如gthosbyname()),這會使許多函數(shù)正常執(zhí)行也變慢瑰艘。
- pthread_cancel()不能終止所有線程是鬼。比如無窮循環(huán)中的線程。
- pthread_cancel()本身也有缺點磅叛,不太容易移植屑咳。
- Bionic中實現(xiàn)了pthread_cleanup_push()和pthread_cleanup_pop()函數(shù),在線程通過調(diào)用pthread_exit()退出或者從它的主函數(shù)中返回到時候弊琴,它們可以做些清理工作兆龙。
3、不要在pthread_once()的回調(diào)函數(shù)中調(diào)用fork(),這么做會導致下次調(diào)用pthread_once()的時候死鎖紫皇。而且不能在回調(diào)函數(shù)中拋出一個C++的異常慰安。
4、不能使用_thread關(guān)鍵詞來定義線程本地存儲區(qū)聪铺。
(二)化焕、創(chuàng)建線程和線程的屬性
1、創(chuàng)建線程
函數(shù)pthread_create()用來創(chuàng)建線程铃剔,原型是:
int pthread_create((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
其中撒桨,pthread_t在android中等同于long
typedef long pthread_t;
- 參數(shù)thread是一個指針,pthread_create函數(shù)成功后键兜,會將代表線程的值寫入其指向的變量凤类。
- 參數(shù) args 一般情況下為NULL,表示使用缺省屬性普气。
- 參數(shù)start_routine是線程的執(zhí)行函數(shù)
- 參數(shù)arg是傳入線程執(zhí)行函數(shù)的參數(shù)
若線程創(chuàng)建成功谜疤,則返回0,若線程創(chuàng)建失敗现诀,則返回出錯編號夷磕。
PS:要注意的是,pthread_create調(diào)用成功后線程已經(jīng)創(chuàng)建完成仔沿,但是不會立刻發(fā)生線程切換坐桩。除非調(diào)用線程主動放棄執(zhí)行,否則只能等待線程調(diào)度封锉。
2撕攒、線程的屬性
結(jié)構(gòu) pthread_atrr_t用來設(shè)置線程的一些屬性,定義如下:
typedef struct
{
uint32_t flags;
void * stack_base; //指定棧的起始地址
size_t stack_size; //指定棧的大小
size_t guard_size;
int32_t sched_policy; //線程的調(diào)度方式
int32_t sched_priority; //線程的優(yōu)先級
}
使用屬性時要先初始化烘浦,函數(shù)原型是:
int pthread_attr_init(pthread_attr_t* attr)
通過pthread_attr_init()函數(shù)設(shè)置的缺省屬性值如下:
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; //普通調(diào)度方式
attr0->sched_priority=0; //中等優(yōu)先級
return 0;
}
下面介紹每項屬性的含義萍鲸。
- 1闷叉、flag 用來表示線程的分離狀態(tài)
Linux線程有兩種狀態(tài):分離(detch)狀態(tài)和非分離(joinable)狀態(tài),如果線程是非分離狀態(tài)(joinable)狀態(tài)脊阴,當線程函數(shù)退出時或者調(diào)用pthread_exit()時都不會釋放線程所占用的系統(tǒng)資源握侧。只有當調(diào)用了pthread_join()之后這些資源才會釋放。如果是分離(detach)狀態(tài)的線程嘿期,這些資源在線程函數(shù)退出時調(diào)用pthread_exit()時會自動釋放- 2品擎、stack_base: 線程棧的基地址
- 3、stack_size: 線程棧的大小备徐√汛基地址和棧的大小。
- 4蜜猾、guard_size: 線程的棧溢出保護區(qū)大小秀菱。
- 5振诬、sched_policy:線程的調(diào)度方式。
線程一共有3種調(diào)度方式:SCHED_NORMAL衍菱,SCHED_FIFO赶么,SCHED_RR。其中SCHED_NORMAL代表分時調(diào)度策略脊串,SCHED_FIFO代表實時調(diào)度策略辫呻,先到先服務(wù),一旦占用CPU則一直運行琼锋,一直運行到有更高優(yōu)先級的任務(wù)到達放闺,或者自己放棄。SCHED_RR代表實時調(diào)度策略:時間片輪轉(zhuǎn)斩例,當前進程時間片用完雄人,系統(tǒng)將重新分配時間片,并置于就緒隊尾- 6念赶、sched_priority:線程的優(yōu)先級
Bionic雖然也實現(xiàn)了pthread_attr_setscope()函數(shù)础钠,但是只支持PTHREAD_SCOP_SYSTEM屬性,也就意味著Android線程將在全系統(tǒng)的范圍內(nèi)競爭CPU資源叉谜。
3旗吁、退出線程的方法
(1)、調(diào)用pthread_exit函數(shù)退出
一般情況下停局,線程運行函數(shù)結(jié)束時線程才退出很钓。但是如果需要,也可以在線程運行函數(shù)中調(diào)用pthread_exit()函數(shù)來主動退出線程運行董栽。函數(shù)原型如下:
void pthread_exit( void * retval) ;
其中參數(shù)retval用來設(shè)置返回值
(2)码倦、設(shè)置布爾的全局變量
但是如果希望在其它線程中結(jié)束某個線程?前面介紹了Android不支持pthread_cancel()函數(shù)锭碳,因此袁稽,不能在Android中使用這個函數(shù)來結(jié)束線程。通俗的方法是擒抛,如果線程在一個循環(huán)中不停的運行推汽,可以在每次循環(huán)中檢查一個初始值為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诊胞; //青坡線程退出
}
這種方法實現(xiàn)起來簡單可靠暖夭,在編程中經(jīng)常使用。但它的缺點是:如果線程處于掛起等待狀態(tài),這種方法就不適用了鳞尔。
另外一種方式是使用pthread_kill()函數(shù)嬉橙。pthread_kill()函數(shù)的作用不是"殺死"一個線程,而是給線程發(fā)送信號寥假。函數(shù)如下:
int pthread_kill(pthread tid,int sig);
即使線程處于掛起狀態(tài)市框,也可以使用pthead_kill()函數(shù)來給線程發(fā)送消息并使得線程執(zhí)行處理函數(shù),使用pthread_kill()函數(shù)的問題是:線程如果在信號處理函數(shù)中退出糕韧,不方便釋放在線程的運行函數(shù)中分配的資源枫振。
(3)、通過管道
更復雜的方法是:創(chuàng)建一個管道萤彩,在線程運行函數(shù)中對管道"讀端"用select()或epoll()進行監(jiān)聽粪滤,沒有數(shù)據(jù)則掛起線程,通過管道的"寫端"寫入數(shù)據(jù)雀扶,就能喚起線程杖小,從而釋放資源,主動退出愚墓。
4予权、線程的本地存儲TLS
線程本地存儲(TLS)用來保存、傳遞和線程有關(guān)的數(shù)據(jù)浪册。例如在前面說道的使用pthread_kill()函數(shù)關(guān)閉線程的例子中扫腺,需要釋放的資源可以使用TLS傳遞給信號處理函數(shù)。
(1)村象、TLS介紹
TLS在線程實例中是全局可見的笆环,對某個線程實例而言TLS是這個線程實例的私有全局變量。同一個線程運行函數(shù)的不同運行實例厚者,他們的TLS是不同的躁劣。在這個點上TLS和線程的關(guān)系有點類似棧變量和函數(shù)的關(guān)系。棧變量在函數(shù)退出時會消失库菲,TLS也會在線程結(jié)束時釋放习绢。Android實現(xiàn)了TLS的方式是在線程棧的頂開辟了一塊區(qū)域來存放TLS項,當然這塊區(qū)域不再受線程棧的控制蝙昙。
TLS內(nèi)存區(qū)域按數(shù)組方式管理,每個數(shù)組元素稱為一個slot梧却。Android 4.4中的TLS一共有128 slot奇颠,這和Posix中的要求一致(Android 4.2是64個)
(2)、TLS注意事項
TLS變量的數(shù)量有限放航,使用前要申請一個key烈拒,這個key和內(nèi)部的slot關(guān)聯(lián)一起,使用完需要釋放。
申請一個key的函數(shù)原型:
int pthread_key_create(pthread_key_t *key荆几,void (*destructor_function) (void *) );
pthread_key_create()函數(shù)成功返回0吓妆,參數(shù)key中是分配的slot,如果將來放入slot中的對象需要在線程結(jié)束的時候由系統(tǒng)釋放吨铸,則需要提供一個釋放函數(shù)行拢,通過第二個函數(shù)destructor_function傳入。
釋放 TLS key的函數(shù)原型是:
int pthread_key_delete ( pthread_key_t) ;
pthread_key_delete()函數(shù)并不檢查當前是否還有線程正在使用這個slot诞吱,也不會調(diào)用清理函數(shù)舟奠,只是將slot釋放以供下次調(diào)用pthread_key_create()使用。
利用TLS保存數(shù)據(jù)中函數(shù)原型:
int pthread_setspecific(pthread_key_t key房维,const void *value) ;
讀取TLS保存數(shù)據(jù)中的函數(shù)原型:
void * pthread_getsepcific (pthread_key_t key);
5沼瘫、線程的互斥量(Mutex)函數(shù)
Linux線程提供了一組函數(shù)用于線程間的互斥訪問,Android中的Mutex類實質(zhì)上是對Linux互斥函數(shù)的封裝咙俩,互斥量可以理解為一把鎖耿戚,在進入某個保護區(qū)域前要先檢查是否已經(jīng)上鎖了。如果沒有上鎖就可以進入阿趁,否則就必須等待膜蛔,進入后先將鎖鎖上,這樣別的線程就無法再進入了歌焦,退出保護區(qū)后要解鎖飞几,其它線程才可以繼續(xù)使用.
(1)、Mutex在使用前需要初始化
初始化函數(shù)是:
int pthread_mutex_init(pthread_mutext_t *mutex, const pthread_mutexattr_t *attr);
成功后函數(shù)返回0独撇,metex被初始化成未鎖定的狀態(tài)屑墨。如果參數(shù)attr為NULL,則使用缺省的屬性MUTEX_TYPE-BITS_NORMAL纷铣。
互斥量的屬性主要有兩種卵史,類型type和范圍scope,設(shè)置和獲取屬性的函數(shù)如下:
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種
- PTHREAD_MUTEX_NORMAL:該類型的的互斥量不會檢測死鎖搜立。如果線程沒有解鎖(unlock)互斥量的情況下再次鎖定該互斥量以躯,會產(chǎn)生死鎖。如果線程嘗試解鎖由其他線程鎖定的互斥量會產(chǎn)生不確定的行為啄踊。如果嘗試解鎖未鎖定的互斥量忧设,也會產(chǎn)生不確定的行為。** 這是Android目前唯一支持的類型 **颠通。
- PTHREAD_MUTEX_ERRORCHECK:此類型的互斥量可提供錯誤檢查址晕。如果線程在沒有解鎖互斥量的情況下嘗試重新鎖定該互斥量,或者線程嘗試解鎖的互斥量由其他線程鎖定顿锰。** Android目前不支持這種類型 ** 谨垃。
- PTHREAD_MUTEX_RECURSIVE启搂。如果線程沒有解鎖互斥量的情況下重新鎖定該互斥量,可成功鎖定該互斥量刘陶,不會產(chǎn)生死鎖情況胳赌,但是多次鎖定該互斥量需要進行相同次數(shù)的解鎖才能釋放鎖,然后其他線程才能獲取該互斥量匙隔。如果線程嘗試解鎖的互斥量已經(jīng)由其他線程鎖定疑苫,則會返回錯誤。如果線程嘗試解鎖還未鎖定的互斥量牡直,也會返回錯誤缀匕。** Android目前不支持這種類型 **
互斥量Mutex的作用范圍(scope) 有2種
- PTHREAD_PROCESS_PRIVATE:互斥量的作用范圍是進程內(nèi),這是缺省屬性碰逸。
- PTHREAD_PROCESS_SHARED:互斥量可以用于進程間線程的同步乡小。Android文檔中說不支持這種屬性,但是實際上支持饵史,在audiofliger和surfacefliger都有用到满钟,只不過在持有鎖的進程意外死亡的情況下,互斥量(Mutex)不能釋放掉胳喷,這是目前實現(xiàn)的一個缺陷湃番。
6、線程的條件量(Condition)函數(shù)
(1)為什么需要條件量Condition函數(shù)
條件量Condition是為了解決一些更復雜的同步問題而設(shè)計的吭露》痛椋考慮這樣的一種情況,A和B線程不但需要互斥訪問某個區(qū)域讲竿,而且線程A還必須等待線程B的運行結(jié)果泥兰。如果僅使用互斥量進行保護,在線程B先運行的的情況下沒有問題题禀。但是如果線程A先運行鞋诗,拿到互斥量的鎖,往下忘無法進行迈嘹。
條件量就是解決這類問題的削彬。在使用條件量的情況下,如果線程A先運行秀仲,得到鎖以后融痛,可以使用條件量的等待函數(shù)解鎖并等待,這樣線程B得到了運行的機會神僵。線程B運行完以后通過條件量的信號函數(shù)喚醒等待的線程A雁刷,這樣線程A的條件也滿足了,程序就能繼續(xù)執(zhí)行力額
(2)Condition函數(shù)
條件量在使用前需要先初始化挑豌,函數(shù)原型是:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr *attr);
使用完需要銷毀安券,函數(shù)原型是:
int pthread_cond_destroy(pthread_cond_t *cond);
條件量的屬性只有 "共享(share)" 一種,下面是屬性相關(guān)函數(shù)原型氓英,下面是屬性相關(guān)的函數(shù)原型:
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)" 屬性的值有兩種
- PTHREAD_PROCESS_PRIVATE:條件量的作用范圍是進程內(nèi)侯勉,這是缺省的屬性。
- PTHREAD_PROCESS_SHARED:條件量可以用于進程間線程同步铝阐。
條件量的等待函數(shù)的原型如下:
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);
條件量的等待函數(shù)會先解鎖互斥量址貌,因此,使用前一定要確保mutex已經(jīng)上鎖徘键。鎖上后線程將掛起练对。pthread_cond_timedwait()用在希望線程等待一段時間的情況下,如果時間到了線程就會恢復運行吹害。
使用函數(shù)pthread_cond_signal()來喚醒等待隊列中的一個線程螟凭,原型如下:
int pthread_cond_signal (pthread_cond_t *__cond);
也可以通過pthread_cond_broadcast()喚醒所有等待的線程
int pthread_cond_broadcast (pthread_cond_t *__cond);
(三)、Futex同步機制
- Futex 是 fast userspace mutext的縮寫它呀,意思是快速用戶控件互斥體螺男。這里討論Futex是因為在Android中不但線程函數(shù)使用了Futex,甚至一些模塊中也直接使用了Futex作為進程間同步手段纵穿,了解Futex的原理有助于我們理解這些模塊的運行機制下隧。
- Linux從2.5.7開始支持Futex。在類Unix系統(tǒng)開發(fā)中谓媒,傳統(tǒng)的進程同步機制都是通過對內(nèi)核對象進行操作來完成淆院,這個內(nèi)核對象在需要同步的進程中都是可見的。這種同步方法因為涉及用戶態(tài)和內(nèi)核態(tài)的切換句惯,效率比較低土辩。使用了傳統(tǒng)的同步機制時,進入臨界區(qū)即使沒有其他進程競爭也會切到內(nèi)核態(tài)檢查內(nèi)核同步對象的狀態(tài)宗弯,這種不必要的切換明顯降低了程序的執(zhí)行效率脯燃。
- Futex就是為了解決這個問題而設(shè)計的。Futex是一種用戶態(tài)和內(nèi)核態(tài)混合的同步機制蒙保,使用Futex同步機制辕棚,如果用于進程間同步,需要先調(diào)用mmap()創(chuàng)建一塊共享內(nèi)存邓厕,F(xiàn)utex變量就位于共享區(qū)逝嚎。同時對Futex變量的操作必須是原子的,當進程駛?cè)脒M入臨界區(qū)或者退出臨界區(qū)的時候详恼,首先檢查共享內(nèi)存中的Futex變量补君,如果沒有其他進程也申請了使用臨界區(qū),則只修改Futex變量而不再執(zhí)行系統(tǒng)調(diào)用昧互。如果同時有其他進程也申請使用臨界區(qū)挽铁,還是需要通過系統(tǒng)調(diào)用去執(zhí)行等待或喚醒操作伟桅。這樣通過用戶態(tài)的Futex變量的控制,減少了進程在用戶態(tài)和內(nèi)核態(tài)之間切換的次數(shù)叽掘,從而最大程度的降低了系統(tǒng)同步的開銷楣铁。
1、Futex的系統(tǒng)調(diào)用
在Linux中更扁,F(xiàn)utex系統(tǒng)調(diào)用的定義如下:
#define _NR_futex 240
(1) Fetex系統(tǒng)調(diào)用的原型是:
int futex(int *uaddr, int cp, int val, const struct timespec *timeout, int *uaddr2, int val3);
- uaddr是Futex變量盖腕,一個共享的整數(shù)計數(shù)器。
- op表示操作類型浓镜,有5中預定義的值溃列,但是在Bionic中只使用了下面兩種:① FUTEX_WAIT,內(nèi)核將檢查uaddr中家屬器的值是否等于val,如果等于則掛起進程膛薛,直到uaddr到達了FUTEX_WAKE調(diào)用或者超時時間到听隐。②FUTEXT_WAKE:內(nèi)核喚醒val個等待在uaddr上的進程。
- val存放與操作op相關(guān)的值
- timeout用于操作FUTEX_WAIT中相叁,表示等待超時時間遵绰。
- uaddr2和val3很少使用。
(1) 在Bionic中增淹,提供了兩個函數(shù)來包裝Futex系統(tǒng)調(diào)用:
extern int _futex_wait(volatile void *ftx,int val, const struct timespec *timespec );
extern int _futex_wake(volatile void *ftx, int count);
(2) Bionic還有兩個類似的函數(shù)椿访,它們的原型如下:
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);
這兩個函數(shù)多了一個參數(shù)pshared,pshared的值為true 表示wake和wait操作是用于進程間的掛起和喚醒;值為false表示操作于進程內(nèi)線程的掛起和喚醒虑润。當pshare的值為false時成玫,執(zhí)行Futex系統(tǒng)調(diào)用的操作碼為
FUTEX_WAIT|FUTEX_PRIVATE_FLAG
內(nèi)核如何檢測到操作有FUTEX_PRIVATE_FLAG標記,能以更快的速度執(zhí)行七掛起和喚醒操作拳喻。
_futex_wait 和_futex_wake函數(shù)相當于pshared等于true的情況哭当。
(3) 在Bionic中,提供了兩個函數(shù)來包裝Futex系統(tǒng)調(diào)用:
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()钦勘。這兩個函數(shù)與前面的區(qū)別是能指定操作碼op作為參數(shù)。操作碼可以是FUTEX_WAIT_FUTEX_WAKE或者它們和FUTEX_PRIVATE_FLAG的組合亚亲。
2彻采、Futex的用戶態(tài)操作
Futex的系統(tǒng)調(diào)用FUTEX_WAIT和FUTEX_WAKE只是用來掛起或者喚醒進程,F(xiàn)utex的同步機制還包括用戶態(tài)下的判斷操作捌归。用戶態(tài)下的操作沒有固定的函數(shù)調(diào)用肛响,只是一種檢測共享變量的方法。Futex用于臨界區(qū)的算法如下:
- 首先創(chuàng)建一個全局的整數(shù)變量作為Futex變量惜索,如果用于進程間的同步特笋,這個變量必須位于共享內(nèi)存。Futex變量的初始值為0巾兆。
- 當進程或線程嘗試持有鎖的時候猎物,檢查Futex變量的值是否為0虎囚,如果為0,則將Futex變量的值設(shè)為1蔫磨,然后繼續(xù)執(zhí)行溜宽;如果不為0,將Futex的值設(shè)為2以后质帅,執(zhí)行FUTEX_WAIT 系統(tǒng)調(diào)用進入掛起等待狀態(tài)。
- Futex變量值為0表示無鎖狀態(tài)留攒,1表示有鎖無競爭的狀態(tài)煤惩,2表示有競爭的狀態(tài)。
- 當進程或線程釋放鎖的時候炼邀,如果Futex變量的值為1魄揉,說明沒有其他線程在等待鎖,這樣講Futex變量的值設(shè)為0就結(jié)束了拭宁;如果Futex變量的值2洛退,說明還有線程等待鎖,將Futex變量值設(shè)為0杰标,同時執(zhí)行FUTEX_WAKE()系統(tǒng)調(diào)用來喚醒等待的進程兵怯。
對Futex變量操作時,比較和賦值操作必須是原子的腔剂。