移步系列Android跨進(jìn)程通信IPC系列
Bionic庫(kù)是Android的基礎(chǔ)庫(kù)之一烦粒,也是連接Android系統(tǒng)和Linux系統(tǒng)內(nèi)核的橋梁,Bionic中包含了很多基本的功能模塊府阀,這些功能模塊基本上都是源于Linux,但是就像青出于藍(lán)而勝于藍(lán)芽突,它和Linux還是有一些不一樣的的地方试浙。
1 谷歌為什么使用Bionic庫(kù)
谷歌使用Bionic庫(kù)主要因?yàn)橐韵氯c(diǎn)
- 1、谷歌沒(méi)有使用Linux的GUN Libc寞蚌,很大一部分原因是因?yàn)镚NU Libc的授權(quán)方式是GPL 授權(quán)協(xié)議有限制田巴,因?yàn)橐坏┸浖惺褂昧薌PL的授權(quán)協(xié)議,該系統(tǒng)所有代碼必須開(kāi)元挟秤。
- 2壹哺、谷歌在BSD的C庫(kù)上的基礎(chǔ)上加入了一些Linux特性從而生成了Bionic。Bionic名字的來(lái)源就是BSD和Linux的混合艘刚。而且不受限制的開(kāi)源方式管宵,所以在現(xiàn)代的商業(yè)公司中比較受歡迎奕删。
- 3呻逆、還有就是因?yàn)樾阅艿脑颍驗(yàn)锽ionic的核心設(shè)計(jì)思想就是"簡(jiǎn)單",所以Bionic中去掉了很多高級(jí)功能赴肚。這樣Bionic庫(kù)僅為200K左右,是GNU版本體積的一半最欠,這意味著更高的效率和低內(nèi)存的使用翠勉,同時(shí)配合經(jīng)過(guò)優(yōu)化的Java VM Dalvik才可以保證高的性能。
2 Bionic庫(kù)簡(jiǎn)介
- Bionic 音標(biāo)為 bī??nik埠居,翻譯為"仿生"
- Bionic包含了系統(tǒng)中最基本的lib庫(kù)诞丽,包括libc,libm拐格,libdl僧免,libstd++,libthread_db捏浊,以及Android特有的鏈接器linker懂衩。
3 Bionic庫(kù)的特性
3.1 架構(gòu)
Bionic 當(dāng)前支持ARM、x86和MIPS執(zhí)行集金踪,理論上可以支持更多浊洞,但是需要做些工作,ARM相關(guān)的代碼在目錄arch-arm中胡岔,x86相關(guān)代碼在arch-x86中法希,mips相關(guān)的代碼在arch-mips中。
3.2 Linux核心頭文件
Bionic自帶一套經(jīng)過(guò)清理的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
3.3 DNS解析器
雖然Bionic 使用NetBSD-derived解析庫(kù),但是它也做了一些修改怨咪。
- 1屋剑、不實(shí)現(xiàn)name-server-switch特性
- 2、讀取/system/etc/resolv.conf而不是/etc/resolv.config
- 3诗眨、從系統(tǒng)屬性中讀取服務(wù)器地址列表唉匾,代碼中會(huì)查找'net.dns1','net.dns2'匠楚,等屬性巍膘。每個(gè)屬性都應(yīng)該包含一個(gè)DNS服務(wù)器的IP地址。這些屬性能被Android系統(tǒng)的其它進(jìn)程修改設(shè)置芋簿。在實(shí)現(xiàn)上峡懈,也支持進(jìn)程單獨(dú)的DNS服務(wù)器列表,使用屬性'net.dns1.<pid>'益咬、'net.dns2.<pid>'等逮诲,這里<pid> 表示當(dāng)前進(jìn)程的ID號(hào)帜平。
- 4、在執(zhí)行查詢時(shí)梅鹦,使用一個(gè)合適的隨機(jī)ID(而不是每次+1)裆甩,以提升安全性。
- 5齐唆、在執(zhí)行查詢時(shí)嗤栓,給本地客戶socket綁定一個(gè)隨機(jī)端口號(hào),以提高安全性箍邮。
6茉帅、刪除了一些源代碼,這些源代碼會(huì)造成了很多線程安全的問(wèn)題
3.4 二進(jìn)制兼容性
由于Bionic不與GNU C庫(kù)锭弊、ucLibc堪澎,或者任何已知的Linux C相兼容。所以意味著不要期望使用GNU C庫(kù)頭文件編譯出來(lái)的模塊能夠正常地動(dòng)態(tài)鏈接到Bionic
3.5 Android特性
Bionict提供了少部分Android特有的功能
3.5.1 訪問(wèn)系統(tǒng)特性
Android 提供了一個(gè)簡(jiǎn)單的"共享鍵/值 對(duì)" 空間給系統(tǒng)的中的所有進(jìn)程味滞,用來(lái)存儲(chǔ)一定數(shù)量的"屬性"樱蛤。每個(gè)屬性由一個(gè)限制長(zhǎng)度的字符串"鍵"和一個(gè)限制長(zhǎng)度的字符串"值"組成。
頭文件<sys/system_properties.h>中定義了讀系統(tǒng)屬性的函數(shù)剑鞍,也定義了鍵/值對(duì)的最大長(zhǎng)度昨凡。
3.5.2 Android用戶/組管理
在Android中沒(méi)有etc/password和etc/groups 文件。Android使用擴(kuò)展的Linux用戶/組管理特性蚁署,以確保進(jìn)程根據(jù)權(quán)限來(lái)對(duì)不同的文件系統(tǒng)目錄進(jìn)行訪問(wèn)便脊。
Android的策略是:
- 1、每個(gè)已經(jīng)安裝的的應(yīng)用程序都有自己的用戶ID和組ID光戈。ID從10000(一萬(wàn))開(kāi)始哪痰,小于10000(一萬(wàn))的ID留給系統(tǒng)的守護(hù)進(jìn)程。
- 2田度、tpwnam()能識(shí)別一些硬編碼的子進(jìn)程名(如"radio")妒御,能將他們翻譯為用戶id值解愤,它也能識(shí)別"app_1234"镇饺,這樣的組合名字,知道將后面的1234和10000(一萬(wàn))相加送讲,得到的ID值為11234.getgrname()也類(lèi)似奸笤。
- 3、getservent() Android中沒(méi)有/etc/service哼鬓,C庫(kù)在執(zhí)行文件中嵌入只讀的服務(wù)列表作為代替监右,這個(gè)列表被需要它的函數(shù)所解析。所見(jiàn)文件bionic/libc/netbsd/net/getservent.c和bionic/libc/netbsd/net/service.h异希。
這個(gè)內(nèi)部定義的服務(wù)列表健盒,未來(lái)可能有變化,這個(gè)功能是遺留的,實(shí)際很少使用扣癣。getservent()返回的是本地?cái)?shù)據(jù)惰帽,getservbyport()和getservbyname()也按照同樣的方式實(shí)現(xiàn)。 - 4父虑、getprotoent() 在Android中沒(méi)有/etc/protocel该酗,Bionic目前沒(méi)有實(shí)現(xiàn)getprotocent()和相關(guān)函數(shù)。如果增加的話士嚎,很可能會(huì)以getervent()相同的方式呜魄。
4 Bionic庫(kù)的模塊簡(jiǎn)介
Bionic目錄下一共有5個(gè)庫(kù)和一個(gè)linker程序
5個(gè)庫(kù)分別是:
- 1、libc
- 2莱衩、libm
- 3爵嗅、libdl
- 4、libstd++
- 5笨蚁、libthread_db
4.1 Libc庫(kù)
Libc是C語(yǔ)言最基礎(chǔ)的庫(kù)文件操骡,它提供了所有系統(tǒng)的基本功能,這些功能主要是對(duì)系統(tǒng)調(diào)用的封裝赚窃,是Libc是應(yīng)用和Linux內(nèi)核交流的橋梁册招,主要功能如下:
- 進(jìn)程管理:包括進(jìn)程的創(chuàng)建、調(diào)度策略和優(yōu)先級(jí)的調(diào)整
- 線程管理:包括線程的創(chuàng)建和銷(xiāo)毀勒极,線程的同步/互斥等
- 內(nèi)存管理:包括內(nèi)存分配和釋放等
- 時(shí)間管理:包括獲取和保存系統(tǒng)時(shí)間是掰、獲取當(dāng)前系統(tǒng)運(yùn)行時(shí)長(zhǎng)等
- 時(shí)區(qū)管理:包括時(shí)區(qū)的設(shè)置和調(diào)整等
- 定時(shí)器管理:提供系統(tǒng)的定時(shí)服務(wù)
- 文件系統(tǒng)管理:提供文件系統(tǒng)的掛載和移除功能
- 文件管理:包括文件和目錄的創(chuàng)建增刪改
- 網(wǎng)絡(luò)套接字:創(chuàng)建和監(jiān)聽(tīng)socket,發(fā)送和接受
- DNS解析:幫助解析網(wǎng)絡(luò)地址
- 信號(hào):用于進(jìn)程間通信
- 環(huán)境變量:設(shè)置和獲取系統(tǒng)的環(huán)境變量
- Android Log:提供和Android Log驅(qū)動(dòng)進(jìn)行交互的功能
- Android 屬性:管理一個(gè)共享區(qū)域來(lái)設(shè)置和讀取Android的屬性
- 標(biāo)準(zhǔn)輸入/輸出:提供格式化的輸入/輸出
- 字符串:提供字符串的移動(dòng)辱匿、復(fù)制和比較等功能
- 寬字符:提供對(duì)寬字符的支持键痛。
4.2 Libm庫(kù)
Libm 是數(shù)學(xué)函數(shù)庫(kù),提供了常見(jiàn)的數(shù)學(xué)函數(shù)和浮點(diǎn)運(yùn)算功能匾七,但是Android浮點(diǎn)運(yùn)算時(shí)通過(guò)軟件實(shí)現(xiàn)的絮短,運(yùn)行速度慢,不建議頻繁使用昨忆。
4.3 libdl庫(kù)
libdl庫(kù)原本是用于動(dòng)態(tài)庫(kù)的裝載丁频。很多函數(shù)實(shí)現(xiàn)都是空殼,應(yīng)用進(jìn)程使用的一些函數(shù)邑贴,實(shí)際上是在linker模塊中實(shí)現(xiàn)席里。
4.4 libstd++
libstd++ 是標(biāo)準(zhǔn)的C++的功能庫(kù),但是拢驾,Android的實(shí)現(xiàn)是非常簡(jiǎn)單的奖磁,只是new,delete等少數(shù)幾個(gè)操作符的實(shí)現(xiàn)繁疤。
4.5 libthread_db庫(kù)
libthread_db 用來(lái)支持對(duì)多線程的中動(dòng)態(tài)庫(kù)的調(diào)試咖为。
4.6 Linker模塊
Linux系統(tǒng)上其實(shí)有兩種并不完全相同的可執(zhí)行文件
- 一種是靜態(tài)鏈接的可執(zhí)行程序秕狰。靜態(tài)可執(zhí)行程序包含了運(yùn)行需要的所有函數(shù),可以不依賴任何外部庫(kù)來(lái)運(yùn)行躁染。
- 另一種是動(dòng)態(tài)鏈接的可執(zhí)行程序封恰。動(dòng)態(tài)鏈接的可執(zhí)行程序因?yàn)闆](méi)有包含所需的庫(kù)文件,因此相對(duì)于要小很多褐啡。
靜態(tài)可執(zhí)行程序用在一些特殊場(chǎng)合诺舔,例如,系統(tǒng)初始化時(shí)备畦,這時(shí)整個(gè)系統(tǒng)還沒(méi)有準(zhǔn)備好低飒,動(dòng)態(tài)鏈接的程序還無(wú)法使用。系統(tǒng)的啟動(dòng)程序Init就是一個(gè)靜態(tài)鏈接的例子懂盐。在Android中褥赊,會(huì)給程序自動(dòng)加上兩個(gè)".o"文件,分別是"crtbegin_static.c"和"certtend_android.o"莉恼,這兩個(gè)".o"文件對(duì)應(yīng)的源文件位于bionic/libc/arch-common/bionic目錄下拌喉,文件分別是crtbegin.c和certtend.S。_start()函數(shù)就位于cerbegin.c中俐银。
在動(dòng)態(tài)鏈接時(shí)尿背,execuve()函數(shù)會(huì)分析可執(zhí)行文件的文件頭來(lái)尋找鏈接器,Linux文件就是ld.so捶惜,而Android則是Linker田藐。execuve()函數(shù)將會(huì)將Linker載入到可執(zhí)行文件的空間,然后執(zhí)行Linker的_start()函數(shù)吱七。Linker完成動(dòng)態(tài)庫(kù)的裝載和符號(hào)重定位后再去運(yùn)行真正的可執(zhí)行文件的代碼汽久。
5 Bionic庫(kù)的內(nèi)存管理函數(shù)
5.1 內(nèi)存管理函數(shù)
- 對(duì)于32位的操作系統(tǒng),能使用的最大地址空間是4GB踊餐,其中地址空間03GB分配給用戶進(jìn)程使用景醇,地址空間3GB4GB由內(nèi)核使用,但是用戶進(jìn)程并不是在啟動(dòng)時(shí)就獲取了所有的0~3GB地址空間的訪問(wèn)權(quán)利吝岭,而是需要事先向內(nèi)核申請(qǐng)對(duì)模塊地址空間的讀寫(xiě)權(quán)利三痰。
- 而且申請(qǐng)的只是地址空間而已,此時(shí)并沒(méi)有分配真是的物理地址苍碟。
- 只有當(dāng)進(jìn)程訪問(wèn)某個(gè)地址時(shí)酒觅,如果該地址對(duì)應(yīng)的物理頁(yè)面不存在,則由內(nèi)核產(chǎn)生缺頁(yè)中斷微峰,在中斷中才會(huì)分配物理內(nèi)存并建立頁(yè)表。
- 如果用戶進(jìn)程不需要某塊空間了抒钱,可以通過(guò)內(nèi)核釋放掉它們蜓肆,對(duì)應(yīng)的物理內(nèi)存也釋放掉颜凯。
- 但是由于缺頁(yè)中斷會(huì)導(dǎo)致運(yùn)行緩慢,如果頻繁的地由內(nèi)核來(lái)分配和釋放內(nèi)存將會(huì)降低整個(gè)體統(tǒng)的性能仗扬,因此症概,一般操作系統(tǒng)都會(huì)在用戶進(jìn)程中提供地址空間的分配和回收機(jī)制。
- 用戶進(jìn)程中的內(nèi)存管理會(huì)預(yù)先向內(nèi)核申請(qǐng)一塊打的地址空間早芭,稱為堆彼城。當(dāng)用戶進(jìn)程需要分配內(nèi)存時(shí),由內(nèi)存管理器從堆中尋找一塊空閑的內(nèi)存分配給用戶進(jìn)程使用退个。
- 當(dāng)用戶進(jìn)程釋放某塊內(nèi)存時(shí)募壕,內(nèi)存管理器并不會(huì)立刻將它們交給內(nèi)核釋放,而是放入空閑列表中语盈,留待下次分配使用舱馅。
- 內(nèi)存管理器會(huì)動(dòng)態(tài)的調(diào)整堆的大小,如果堆的空間使用完了刀荒,內(nèi)存管理器會(huì)向堆內(nèi)存申請(qǐng)更多的地址空間代嗤,如果堆中空閑太多,內(nèi)存管理器也會(huì)將一部分空間返給內(nèi)核缠借。
5.2 Bionic的內(nèi)存管理器——dlmalloc
dlmalloc是一個(gè)十分流行的內(nèi)存分配器干毅。
dlmalloc位于bionic/libc/upstream-dlmalloc下,只有一個(gè)C文件malloc.c泼返。
dlmalloc內(nèi)部是以鏈表的形式將"堆"的空閑空間根據(jù)尺寸組織在一起溶锭。分配內(nèi)存時(shí)通過(guò)這些鏈表能快速地找到合適大小的空閑內(nèi)存。如果不能找到滿足要求的空閑內(nèi)存符隙,dlmalloc會(huì)使用系統(tǒng)調(diào)用來(lái)擴(kuò)大堆空間趴捅。
dlmalloc內(nèi)存塊被稱為"trunk"。每塊大小要求按地址對(duì)齊(默認(rèn)8個(gè)字節(jié))霹疫,因此拱绑,trunk塊的大小必須為8的倍數(shù)。
dlmalloc用3種不同的的鏈表結(jié)構(gòu)來(lái)組織不同大小的空閑內(nèi)存塊丽蝎。小于256字節(jié)的塊使用malloc_chunk結(jié)構(gòu)猎拨,按照大小組織在一起。由于尺寸小于的塊一共有256/8=32屠阻,所以一共使用了32個(gè)malloc_chunk結(jié)構(gòu)的環(huán)形鏈表來(lái)組織小于256的塊红省。大小大于256字節(jié)的塊由結(jié)構(gòu)malloc_tree_chunk組成鏈表管理,這些塊根據(jù)大小組成二叉樹(shù)国觉。而更大的尺寸則由系統(tǒng)通過(guò)mmap的方式單獨(dú)分配一塊空間吧恃,并通過(guò)malloc_segment組成的鏈表進(jìn)行管理。
當(dāng)dlmalloc分配內(nèi)存時(shí)麻诀,會(huì)通過(guò)查找這些鏈表來(lái)快速找到一塊和要求的尺寸大小最匹配的空閑內(nèi)存塊(這樣做事為了盡量避免內(nèi)存碎片)痕寓。如果沒(méi)有合適大小的塊傲醉,則將一塊大的分成兩塊,一塊分配出去呻率,另一塊根據(jù)大小再加入對(duì)應(yīng)的空閑鏈表中硬毕。
當(dāng)dlmalloc釋放內(nèi)存時(shí),會(huì)將相鄰的空閑塊合并成一個(gè)大塊來(lái)減少內(nèi)存碎片礼仗。如果空閑塊過(guò)多吐咳,超過(guò)了dlmaloc內(nèi)存的閥值,dlmalloc就開(kāi)始向系統(tǒng)返回內(nèi)存元践。
dlmalloc除了能管理進(jìn)程的"堆"空間韭脊,還能提供私有堆管理,就是在堆外單獨(dú)分配一塊地址空間卢厂,由dlmalloc按照同樣的方式進(jìn)行管理乾蓬。dlmalloc中用來(lái)管理進(jìn)程的"堆"空間的函數(shù),都帶有"dl"前綴慎恒,如"dlmalloc"任内,"dlfree"等,而私有堆的管理函數(shù)則帶有前綴"msspace_"融柬,如"msspace_malloc"
Dalvk虛擬機(jī)中使用了dlmalloc進(jìn)行私有堆管理死嗦。
6 線程
Bionic中的線程管理函數(shù)和通用的Linux版本的實(shí)現(xiàn)有很多差異,Android根據(jù)自己的需要做了很多裁剪工作粒氧。
6.1 Bionic線程函數(shù)的特性
6.1.1 pthread的實(shí)現(xiàn)基于Futext越除,同時(shí)盡量使用簡(jiǎn)單的代碼來(lái)實(shí)現(xiàn)通用操作,特征如下
- pthread_mutex_t外盯,pthread_cond_t類(lèi)型定義只有4字節(jié)摘盆。
- 支持normal,recursive and error-check 互斥量”ス叮考慮到通常大多數(shù)的時(shí)候都使用normal孩擂,對(duì)normal分支下代碼流程做了很細(xì)致的優(yōu)化
- 目前沒(méi)有支持讀寫(xiě)鎖,互斥量的優(yōu)先級(jí)和其他高級(jí)特征箱熬。在Android還不需要這些特征类垦,但是在未來(lái)可能會(huì)添加進(jìn)來(lái)。
6.1.2 Bionic不支持pthread_cancel()城须,因?yàn)榧尤胨鼤?huì)使得C庫(kù)文件明顯變大蚤认,不太值得,同時(shí)有以下幾點(diǎn)考慮
- 要正確實(shí)現(xiàn)pthread_cancel()糕伐,必須在C庫(kù)的很多地方插入對(duì)終止線程的檢測(cè)砰琢。
- 一個(gè)好的實(shí)現(xiàn),必須清理資源,例如釋放內(nèi)存氯析,解鎖互斥量亏较,如果終止恰好發(fā)生在復(fù)雜的函數(shù)里面(比如gthosbyname())莺褒,這會(huì)使許多函數(shù)正常執(zhí)行也變慢掩缓。
- pthread_cancel()不能終止所有線程。比如無(wú)窮循環(huán)中的線程遵岩。
- pthread_cancel()本身也有缺點(diǎn)你辣,不太容易移植。
- Bionic中實(shí)現(xiàn)了pthread_cleanup_push()和pthread_cleanup_pop()函數(shù)尘执,在線程通過(guò)調(diào)用pthread_exit()退出或者從它的主函數(shù)中返回到時(shí)候舍哄,它們可以做些清理工作。
6.1.3 不要在pthread_once()的回調(diào)函數(shù)中調(diào)用fork()誊锭,這么做會(huì)導(dǎo)致下次調(diào)用pthread_once()的時(shí)候死鎖表悬。而且不能在回調(diào)函數(shù)中拋出一個(gè)C++的異常。
6.1.4 不能使用_thread關(guān)鍵詞來(lái)定義線程本地存儲(chǔ)區(qū)丧靡。
6.2 創(chuàng)建線程和線程的屬性
6.2.1 創(chuàng)建線程
函數(shù)pthread_create()用來(lái)創(chuàng)建線程蟆沫,原型是:
int pthread_create((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
其中,pthread_t在android中等同于long
- 參數(shù)thread是一個(gè)指針温治,pthread_create函數(shù)成功后饭庞,會(huì)將代表線程的值寫(xiě)入其指向的變量。
- 參數(shù) args 一般情況下為NULL熬荆,表示使用缺省屬性舟山。
- 參數(shù)start_routine是線程的執(zhí)行函數(shù)
- 參數(shù)arg是傳入線程執(zhí)行函數(shù)的參數(shù)
若線程創(chuàng)建成功,則返回0卤恳,若線程創(chuàng)建失敗累盗,則返回出錯(cuò)編號(hào)。
PS:要注意的是突琳,pthread_create調(diào)用成功后線程已經(jīng)創(chuàng)建完成若债,但是不會(huì)立刻發(fā)生線程切換。除非調(diào)用線程主動(dòng)放棄執(zhí)行本今,否則只能等待線程調(diào)度拆座。
6.2.2 線程的屬性
結(jié)構(gòu) pthread_atrr_t用來(lái)設(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)先級(jí)
}
使用屬性時(shí)要先初始化冠息,函數(shù)原型是:
int pthread_attr_init(pthread_attr_t* attr)
通過(guò)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)先級(jí)
return 0挪凑;
}
下面介紹每項(xiàng)屬性的含義
- 1、flag 用來(lái)表示線程的分離狀態(tài)
Linux線程有兩種狀態(tài):分離(detch)狀態(tài)和非分離(joinable)狀態(tài)逛艰,如果線程是非分離狀態(tài)(joinable)狀態(tài)躏碳,當(dāng)線程函數(shù)退出時(shí)或者調(diào)用pthread_exit()時(shí)都不會(huì)釋放線程所占用的系統(tǒng)資源。只有當(dāng)調(diào)用了pthread_join()之后這些資源才會(huì)釋放散怖。如果是分離(detach)狀態(tài)的線程菇绵,這些資源在線程函數(shù)退出時(shí)調(diào)用pthread_exit()時(shí)會(huì)自動(dòng)釋放 - 2肄渗、stack_base: 線程棧的基地址
- 3、stack_size: 線程棧的大小咬最◆岬眨基地址和棧的大小。
- 4永乌、guard_size: 線程的棧溢出保護(hù)區(qū)大小惑申。
- 5、sched_policy:線程的調(diào)度方式翅雏。
線程一共有3中調(diào)度方式:SCHED_NORMAL圈驼,SCHED_FIFO,SCHED_RR望几。其中SCHED_NORMAL代表分時(shí)調(diào)度策略绩脆,SCHED_FIFO代表實(shí)時(shí)調(diào)度策略,先到先服務(wù)橄抹,一旦占用CPU則一直運(yùn)行靴迫,一直運(yùn)行到有更高優(yōu)先級(jí)的任務(wù)到達(dá),或者自己放棄害碾。SCHED_RR代表實(shí)時(shí)調(diào)度策略:時(shí)間片輪轉(zhuǎn)矢劲,當(dāng)前進(jìn)程時(shí)間片用完,系統(tǒng)將重新分配時(shí)間片慌随,并置于就緒隊(duì)尾芬沉。 - 6、sched_priority:線程的優(yōu)先級(jí)阁猜。
Bionic雖然也實(shí)現(xiàn)了pthread_attr_setscope()函數(shù)丸逸,但是只支持PTHREAD_SCOP_SYSTEM屬性,也就意味著Android線程將在全系統(tǒng)的范圍內(nèi)競(jìng)爭(zhēng)CPU資源剃袍。
6.3 退出線程的方法
6.3.1 調(diào)用pthread_exit函數(shù)退出
一般情況下黄刚,線程運(yùn)行函數(shù)結(jié)束時(shí)線程才退出。但是如果需要民效,也可以在線程運(yùn)行函數(shù)中調(diào)用pthread_exit()函數(shù)來(lái)主動(dòng)退出線程運(yùn)行憔维。函數(shù)原型如下:
void pthread_exit( void * retval) ;
其中參數(shù)retval用來(lái)設(shè)置返回值
6.3.2 設(shè)備布爾的全局變量
但是如果希望在其它線程中結(jié)束某個(gè)線程?前面介紹了Android不支持pthread_cancel()函數(shù)畏邢,因此业扒,不能在Android中使用這個(gè)函數(shù)來(lái)結(jié)束線程。通俗的方法是舒萎,如果線程在一個(gè)循環(huán)中不停的運(yùn)行程储,可以在每次循環(huán)中檢查一個(gè)初始值為false的全局變量,一旦這個(gè)變量的值為ture,則主動(dòng)退出章鲤,這樣其它線程就可以銅鼓改變這個(gè)全局變量的值來(lái)控制線程的退出摊灭,示例如下:
bool g_force_exit =false;
void * thread_func(void *){
for(;;){
if(g_force_exit){
break;
}
.....
}
return NULL;
}
int main(){
.....
q_force_exit=true; //青坡線程退出
}
這種方法實(shí)現(xiàn)起來(lái)簡(jiǎn)單可靠败徊,在編程中經(jīng)常使用帚呼。但它的缺點(diǎn)是:如果線程處于掛起等待狀態(tài),這種方法就不適用了集嵌。
另外一種方式是使用pthread_kill()函數(shù)萝挤。pthread_kill()函數(shù)的作用不是"殺死"一個(gè)線程御毅,而是給線程發(fā)送信號(hào)根欧。函數(shù)如下:
int pthread_kill(pthread tid,int sig);
即使線程處于掛起狀態(tài),也可以使用pthead_kill()函數(shù)來(lái)給線程發(fā)送消息并使得線程執(zhí)行處理函數(shù)端蛆,使用pthread_kill()函數(shù)的問(wèn)題是:線程如果在信號(hào)處理函數(shù)中退出凤粗,不方便釋放在線程的運(yùn)行函數(shù)中分配的資源。
6.3.3 通過(guò)管道
更復(fù)雜的方法是:創(chuàng)建一個(gè)管道今豆,在線程運(yùn)行函數(shù)中對(duì)管道"讀端"用select()或epoll()進(jìn)行監(jiān)聽(tīng)嫌拣,沒(méi)有數(shù)據(jù)則掛起線程,通過(guò)管道的"寫(xiě)端"寫(xiě)入數(shù)據(jù)呆躲,就能喚起線程异逐,從而釋放資源,主動(dòng)退出插掂。
6.4 線程的本地存儲(chǔ)TLS
線程本地存儲(chǔ)(TLS)用來(lái)保存灰瞻、傳遞和線程有關(guān)的數(shù)據(jù)。例如在前面說(shuō)道的使用pthread_kill()函數(shù)關(guān)閉線程的例子中辅甥,需要釋放的資源可以使用TLS傳遞給信號(hào)處理函數(shù)酝润。
6.4.1 TLS介紹
- TLS在線程實(shí)例中是全局可見(jiàn)的,對(duì)某個(gè)線程實(shí)例而言TLS是這個(gè)線程實(shí)例的私有全局變量璃弄。
- 同一個(gè)線程運(yùn)行函數(shù)的不同運(yùn)行實(shí)例要销,他們的TLS是不同的。在這個(gè)點(diǎn)上TLS和線程的關(guān)系有點(diǎn)類(lèi)似棧變量和函數(shù)的關(guān)系夏块。
- 棧變量在函數(shù)退出時(shí)會(huì)消失疏咐,TLS也會(huì)在線程結(jié)束時(shí)釋放。Android實(shí)現(xiàn)了TLS的方式是在線程棧的頂開(kāi)辟了一塊區(qū)域來(lái)存放TLS項(xiàng)脐供,當(dāng)然這塊區(qū)域不再受線程棧的控制浑塞。
- TLS內(nèi)存區(qū)域按數(shù)組方式管理,每個(gè)數(shù)組元素稱為一個(gè)slot患民。Android 4.4中的TLS一共有128 slot缩举,這和Posix中的要求一致(Android 4.2是64個(gè))
6.4.2 TLS注意事項(xiàng)
- TLS變量的數(shù)量有限,使用前要申請(qǐng)一個(gè)key,這個(gè)key和內(nèi)部的slot關(guān)聯(lián)一起仅孩,使用完需要釋放托猩。
申請(qǐng)一個(gè)key的函數(shù)原型:
int pthread_key_create(pthread_key_t *key,void (*destructor_function) (void *) );
pthread_key_create()函數(shù)成功返回0辽慕,參數(shù)key中是分配的slot京腥,如果將來(lái)放入slot中的對(duì)象需要在線程結(jié)束的時(shí)候由系統(tǒng)釋放,則需要提供一個(gè)釋放函數(shù)溅蛉,通過(guò)第二個(gè)函數(shù)destructor_function傳入公浪。
- 釋放 TLS key的函數(shù)原型是:
int pthread_key_delete ( pthread_key_t) ;
pthread_key_delete()函數(shù)并不檢查當(dāng)前是否還有線程正在使用這個(gè)slot,也不會(huì)調(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);
6.5 線程的互斥量(Mutex)函數(shù)
Linux線程提供了一組函數(shù)用于線程間的互斥訪問(wèn)镜撩,Android中的Mutex類(lèi)實(shí)質(zhì)上是對(duì)Linux互斥函數(shù)的封裝预柒,互斥量可以理解為一把鎖,在進(jìn)入某個(gè)保護(hù)區(qū)域前要先檢查是否已經(jīng)上鎖了袁梗。如果沒(méi)有上鎖就可以進(jìn)入宜鸯,否則就必須等待,進(jìn)入后現(xiàn)將鎖鎖上遮怜,這樣別的線程就無(wú)法再進(jìn)入了淋袖,退出保護(hù)區(qū)后腰解鎖,其它線程才可以繼續(xù)使用
6.5.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涝桅。
互斥量的屬性主要有兩種拜姿,類(lèi)型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的類(lèi)型(type) 有3種
- PTHREAD_MUTEX_NORMAL:該類(lèi)型的的互斥量不會(huì)檢測(cè)死鎖冯遂。如果線程沒(méi)有解鎖(unlock)互斥量的情況下再次鎖定該互斥量蕊肥,會(huì)產(chǎn)生死鎖。如果線程嘗試解鎖由其他線程鎖定的互斥量會(huì)產(chǎn)生不確定的行為蛤肌。如果嘗試解鎖未鎖定的互斥量壁却,也會(huì)產(chǎn)生不確定的行為。這是Android目前唯一支持的類(lèi)型裸准。
- PTHREAD_MUTEX_ERRORCHECK:此類(lèi)型的互斥量可提供錯(cuò)誤檢查展东。如果線程在沒(méi)有解鎖互斥量的情況下嘗試重新鎖定該互斥量,或者線程嘗試解鎖的互斥量由其他線程鎖定炒俱。Android目前不支持這種類(lèi)型 盐肃。
- PTHREAD_MUTEX_RECURSIVE爪膊。如果線程沒(méi)有解鎖互斥量的情況下重新鎖定該互斥量,可成功鎖定該互斥量砸王,不會(huì)產(chǎn)生死鎖情況推盛,但是多次鎖定該互斥量需要進(jìn)行相同次數(shù)的解鎖才能釋放鎖,然后其他線程才能獲取該互斥量谦铃。如果線程嘗試解鎖的互斥量已經(jīng)由其他線程鎖定耘成,則會(huì)返回錯(cuò)誤。如果線程嘗試解鎖還未鎖定的互斥量驹闰,也會(huì)返回錯(cuò)誤瘪菌。Android目前不支持這種類(lèi)型 。
互斥量Mutex的作用范圍(scope) 有2種
- PTHREAD_PROCESS_PRIVATE:互斥量的作用范圍是進(jìn)程內(nèi)嘹朗,這是缺省屬性师妙。
- PTHREAD_PROCESS_SHARED:互斥量可以用于進(jìn)程間線程的同步。Android文檔中說(shuō)不支持這種屬性骡显,但是實(shí)際上支持疆栏,在audiofliger和surfacefliger都有用到,只不過(guò)在持有鎖的進(jìn)程意外死亡的情況下惫谤,互斥量(Mutex)不能釋放掉,這是目前實(shí)現(xiàn)的一個(gè)缺陷珠洗。
6.6 線程的條件量(Condition)函數(shù)
6.6.1 為什么需要條件量Condition函數(shù)
- 條件量Condition是為了解決一些更復(fù)雜的同步問(wèn)題而設(shè)計(jì)的溜歪。考慮這樣的一種情況许蓖,A和B線程不但需要互斥訪問(wèn)某個(gè)區(qū)域蝴猪,而且線程A還必須等待線程B的運(yùn)行結(jié)果。如果僅使用互斥量進(jìn)行保護(hù)膊爪,在線程B先運(yùn)行的的情況下沒(méi)有問(wèn)題自阱。但是如果線程A先運(yùn)行,拿到互斥量的鎖米酬,往下忘無(wú)法進(jìn)行沛豌。
- 條件量就是解決這類(lèi)問(wèn)題的。在使用條件量的情況下赃额,如果線程A先運(yùn)行加派,得到鎖以后,可以使用條件量的等待函數(shù)解鎖并等待跳芳,這樣線程B得到了運(yùn)行的機(jī)會(huì)芍锦。線程B運(yùn)行完以后通過(guò)條件量的信號(hào)函數(shù)喚醒等待的線程A,這樣線程A的條件也滿足了飞盆,程序就能繼續(xù)執(zhí)行力額娄琉。
6.6.2 Condition函數(shù)
- 條件量在使用前需要先初始化次乓,函數(shù)原型是:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr *attr);
使用完需要銷(xiāo)毀,函數(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:條件量的作用范圍是進(jìn)程內(nèi),這是缺省的屬性匈棘。
- PTHREAD_PROCESS_SHARED:條件量可以用于進(jìn)程間線程同步丧慈。
- 2 條件量的等待函數(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ù)會(huì)先解鎖互斥量,因此主卫,使用前一定要確保mutex已經(jīng)上鎖逃默。鎖上后線程將掛起。pthread_cond_timedwait()用在希望線程等待一段時(shí)間的情況下簇搅,如果時(shí)間到了線程就會(huì)恢復(fù)運(yùn)行完域。
- 3 可以使用函數(shù)pthread_cond_signal()來(lái)喚醒等待隊(duì)列中的一個(gè)線程,原型如下:
int pthread_cond_signal (pthread_cond_t *__cond);
也可以通過(guò)pthread_cond_broadcast()喚醒所有等待的線程
int pthread_cond_broadcast (pthread_cond_t *__cond);
6.6.3 Futex同步機(jī)制
- Futex 是 fast userspace mutext的縮寫(xiě)瘩将,意思是快速用戶控件互斥體吟税。這里討論Futex是因?yàn)樵贏ndroid中不但線程函數(shù)使用了Futex,甚至一些模塊中也直接使用了Futex作為進(jìn)程間同步手段姿现,了解Futex的原理有助于我們理解這些模塊的運(yùn)行機(jī)制肠仪。
- Linux從2.5.7開(kāi)始支持Futex。在類(lèi)Unix系統(tǒng)開(kāi)發(fā)中备典,傳統(tǒng)的進(jìn)程同步機(jī)制都是通過(guò)對(duì)內(nèi)核對(duì)象進(jìn)行操作來(lái)完成异旧,這個(gè)內(nèi)核對(duì)象在需要同步的進(jìn)程中都是可見(jiàn)的。這種同步方法因?yàn)樯婕坝脩魬B(tài)和內(nèi)核態(tài)的切換提佣,效率比較低吮蛹。使用了傳統(tǒng)的同步機(jī)制時(shí),進(jìn)入臨界區(qū)即使沒(méi)有其他進(jìn)程競(jìng)爭(zhēng)也會(huì)切到內(nèi)核態(tài)檢查內(nèi)核同步對(duì)象的狀態(tài)拌屏,這種不必要的切換明顯降低了程序的執(zhí)行效率潮针。
- Futex就是為了解決這個(gè)問(wèn)題而設(shè)計(jì)的。Futex是一種用戶態(tài)和內(nèi)核態(tài)混合的同步機(jī)制倚喂,使用Futex同步機(jī)制每篷,如果用于進(jìn)程間同步,需要先調(diào)用mmap()創(chuàng)建一塊共享內(nèi)存务唐,F(xiàn)utex變量就位于共享區(qū)雳攘。同時(shí)對(duì)Futex變量的操作必須是原子的,當(dāng)進(jìn)程駛?cè)脒M(jìn)入臨界區(qū)或者退出臨界區(qū)的時(shí)候枫笛,首先檢查共享內(nèi)存中的Futex變量吨灭,如果沒(méi)有其他進(jìn)程也申請(qǐng)了使用臨界區(qū),則只修改Futex變量而不再執(zhí)行系統(tǒng)調(diào)用刑巧。如果同時(shí)有其他進(jìn)程也申請(qǐng)使用臨界區(qū)喧兄,還是需要通過(guò)系統(tǒng)調(diào)用去執(zhí)行等待或喚醒操作无畔。這樣通過(guò)用戶態(tài)的Futex變量的控制,減少了進(jìn)程在用戶態(tài)和內(nèi)核態(tài)之間切換的次數(shù)吠冤,從而最大程度的降低了系統(tǒng)同步的開(kāi)銷(xiāo)浑彰。
1 Futex的系統(tǒng)調(diào)用
在Linux中,F(xiàn)utex系統(tǒng)調(diào)用的定義如下:
#define _NR_futex 240
Fetex系統(tǒng)調(diào)用的原型是:
int futex(int *uaddr, int cp, int val, const struct timespec *timeout, int *uaddr2, int val3);
- uaddr是Futex變量拯辙,一個(gè)共享的整數(shù)計(jì)數(shù)器郭变。
- op表示操作類(lèi)型,有5中預(yù)定義的值涯保,但是在Bionic中只使用了下面兩種:① FUTEX_WAIT,內(nèi)核將檢查uaddr中家屬器的值是否等于val诉濒,如果等于則掛起進(jìn)程,直到uaddr到達(dá)了FUTEX_WAKE調(diào)用或者超時(shí)時(shí)間到夕春。②FUTEXT_WAKE:內(nèi)核喚醒val個(gè)等待在uaddr上的進(jìn)程未荒。
- val存放與操作op相關(guān)的值
- timeout用于操作FUTEX_WAIT中,表示等待超時(shí)時(shí)間及志。
- uaddr2和val3很少使用片排。
(1) 在Bionic中,提供了兩個(gè)函數(shù)來(lái)包裝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還有兩個(gè)類(lèi)似的函數(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);
這兩個(gè)函數(shù)多了一個(gè)參數(shù)pshared,pshared的值為true 表示wake和wait操作是用于進(jìn)程間的掛起和喚醒率寡;值為false表示操作于進(jìn)程內(nèi)線程的掛起和喚醒。當(dāng)pshare的值為false時(shí)锌畸,執(zhí)行Futex系統(tǒng)調(diào)用的操作碼為
FUTEX_WAIT|FUTEX_PRIVATE_FLAG
內(nèi)核如何檢測(cè)到操作有FUTEX_PRIVATE_FLAG標(biāo)記勇劣,能以更快的速度執(zhí)行七掛起和喚醒操作。
_futex_wait 和_futex_wake函數(shù)相當(dāng)于pshared等于true的情況潭枣。
2、Futex的用戶態(tài)操作
Futex的系統(tǒng)調(diào)用FUTEX_WAIT和FUTEX_WAKE只是用來(lái)掛起或者喚醒進(jìn)程幻捏,F(xiàn)utex的同步機(jī)制還包括用戶態(tài)下的判斷操作盆犁。用戶態(tài)下的操作沒(méi)有固定的函數(shù)調(diào)用,只是一種檢測(cè)共享變量的方法篡九。Futex用于臨界區(qū)的算法如下:
- 首先創(chuàng)建一個(gè)全局的整數(shù)變量作為Futex變量谐岁,如果用于進(jìn)程間的同步,這個(gè)變量必須位于共享內(nèi)存榛臼。Futex變量的初始值為0伊佃。
- 當(dāng)進(jìn)程或線程嘗試持有鎖的時(shí)候,檢查Futex變量的值是否為0沛善,如果為0航揉,則將Futex變量的值設(shè)為1,然后繼續(xù)執(zhí)行金刁;如果不為0帅涂,將Futex的值設(shè)為2以后议薪,執(zhí)行FUTEX_WAIT 系統(tǒng)調(diào)用進(jìn)入掛起等待狀態(tài)。
- Futex變量值為0表示無(wú)鎖狀態(tài)媳友,1表示有鎖無(wú)競(jìng)爭(zhēng)的狀態(tài)斯议,2表示有競(jìng)爭(zhēng)的狀態(tài)。
- 當(dāng)進(jìn)程或線程釋放鎖的時(shí)候醇锚,如果Futex變量的值為1哼御,說(shuō)明沒(méi)有其他線程在等待鎖,這樣講Futex變量的值設(shè)為0就結(jié)束了焊唬;如果Futex變量的值2恋昼,說(shuō)明還有線程等待鎖,將Futex變量值設(shè)為0求晶,同時(shí)執(zhí)行FUTEX_WAKE()系統(tǒng)調(diào)用來(lái)喚醒等待的進(jìn)程焰雕。
對(duì)Futex變量操作時(shí),比較和賦值操作必須是原子的芳杏。