Android跨進程通信IPC整體內(nèi)容如下
- 1、Android跨進程通信IPC之1——Linux基礎(chǔ)
- 2董虱、Android跨進程通信IPC之2——Bionic
- 3铐达、Android跨進程通信IPC之3——關(guān)于"JNI"的那些事
- 4殴泰、Android跨進程通信IPC之4——AndroidIPC基礎(chǔ)1
- 4、Android跨進程通信IPC之4——AndroidIPC基礎(chǔ)2
- 5摩梧、Android跨進程通信IPC之5——Binder的三大接口
- 6、Android跨進程通信IPC之6——Binder框架
- 7宣旱、Android跨進程通信IPC之7——Binder相關(guān)結(jié)構(gòu)體簡介
- 8仅父、Android跨進程通信IPC之8——Binder驅(qū)動
- 9、Android跨進程通信IPC之9——Binder之Framework層C++篇1
- 9浑吟、Android跨進程通信IPC之9——Binder之Framework層C++篇2
- 10笙纤、Android跨進程通信IPC之10——Binder之Framework層Java篇
- 11、Android跨進程通信IPC之11——AIDL
- 12组力、Android跨進程通信IPC之12——Binder補充
- 13省容、Android跨進程通信IPC之13——Binder總結(jié)
- 14、Android跨進程通信IPC之14——其他IPC方式
- 15燎字、Android跨進程通信IPC之15——感謝
一腥椒、為什么要學(xué)習(xí)Bionic
Bionic庫是Android的基礎(chǔ)庫之一,也是連接Android系統(tǒng)和Linux系統(tǒng)內(nèi)核的橋梁候衍,Bionic中包含了很多基本的功能模塊笼蛛,這些功能模塊基本上都是源于Linux,但是就像青出于藍而勝于藍蛉鹿,它和Linux還是有一些不一樣的的地方滨砍。同時,為了更好的服務(wù)Android,Bionic中也增加了一些新的模塊惋戏,由于本次的主題是Androdi的跨進程通信领追,所以了解Bionic對我們更好的學(xué)習(xí)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 當(dāng)前支持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> 表示當(dāng)前進程的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)時間习瑰、獲取當(dā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的屬性
- 標準輸入/輸出:提供格式化的輸入/輸出
- 字符串:提供字符串的移動秽荤、復(fù)制和比較等功能
- 寬字符:提供對寬字符的支持甜奄。
(二)、Libm庫
Libm 是數(shù)學(xué)函數(shù)庫窃款,提供了常見的數(shù)學(xué)函數(shù)和浮點運算功能课兄,但是Android浮點運算時通過軟件實現(xiàn)的,運行速度慢晨继,不建議頻繁使用烟阐。
(三)、libdl庫
libdl庫原本是用于動態(tài)庫的裝載紊扬。很多函數(shù)實現(xiàn)都是空殼蜒茄,應(yīng)用進程使用的一些函數(shù),實際上是在linker模塊中實現(xiàn)餐屎。
(四)檀葛、Libm庫
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希停,其中地址空間03GB分配給用戶進程使用,地址空間3GB4GB由內(nèi)核使用署隘,但是用戶進程并不是在啟動時就獲取了所有的0~3GB地址空間的訪問權(quán)利宠能,而是需要事先向內(nèi)核申請對模塊地址空間的讀寫權(quán)利。而且申請的只是地址空間而已磁餐,此時并沒有分配真是的物理地址违崇。只有當(dāng)進程訪問某個地址時,如果該地址對應(yīng)的物理頁面不存在诊霹,則由內(nèi)核產(chǎn)生缺頁中斷羞延,在中斷中才會分配物理內(nèi)存并建立頁表。如果用戶進程不需要某塊空間了脾还,可以通過內(nèi)核釋放掉它們伴箩,對應(yīng)的物理內(nèi)存也釋放掉。
但是由于缺頁中斷會導(dǎo)致運行緩慢鄙漏,如果頻繁的地由內(nèi)核來分配和釋放內(nèi)存將會降低整個體統(tǒng)的性能嗤谚,因此棺蛛,一般操作系統(tǒng)都會在用戶進程中提供地址空間的分配和回收機制。用戶進程中的內(nèi)存管理會預(yù)先向內(nèi)核申請一塊打的地址空間巩步,稱為堆旁赊。當(dāng)用戶進程需要分配內(nèi)存時,由內(nèi)存管理器從堆中尋找一塊空閑的內(nèi)存分配給用戶進程使用椅野。當(dāng)用戶進程釋放某塊內(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組成的鏈表進行管理响谓。
- 當(dāng)dlmalloc分配內(nèi)存時损合,會通過查找這些鏈表來快速找到一塊和要求的尺寸大小最匹配的空閑內(nèi)存塊(這樣做事為了盡量避免內(nèi)存碎片)。如果沒有合適大小的塊娘纷,則將一塊大的分成兩塊嫁审,一塊分配出去,另一塊根據(jù)大小再加入對應(yīng)的空閑鏈表中赖晶。
- 當(dā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ā)生在復(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()双揪,這么做會導(dǎo)致下次調(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)质礼,當(dāng)線程函數(shù)退出時或者調(diào)用pthread_exit()時都不會釋放線程所占用的系統(tǒng)資源旺聚。只有當(dā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)纲酗,當(dāng)前進程時間片用完,系統(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)掸绞、通過管道
更復(fù)雜的方法是:創(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項仅炊,當(dāng)然這塊區(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ù)并不檢查當(dāng)前是否還有線程正在使用這個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)上鎖了雀费。如果沒有上鎖就可以進入,否則就必須等待痊焊,進入后現(xiàn)將鎖鎖上坐儿,這樣別的線程就無法再進入了律胀,退出保護區(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是為了解決一些更復(fù)雜的同步問題而設(shè)計的〖揪欤考慮這樣的一種情況余蟹,A和B線程不但需要互斥訪問某個區(qū)域,而且線程A還必須等待線程B的運行結(jié)果子刮。如果僅使用互斥量進行保護威酒,在線程B先運行的的情況下沒有問題。但是如果線程A先運行挺峡,拿到互斥量的鎖葵孤,往下忘無法進行。
條件量就是解決這類問題的橱赠。在使用條件量的情況下尤仍,如果線程A先運行,得到鎖以后狭姨,可以使用條件量的等待函數(shù)解鎖并等待宰啦,這樣線程B得到了運行的機會。線程B運行完以后通過條件量的信號函數(shù)喚醒等待的線程A送挑,這樣線程A的條件也滿足了绑莺,程序就能繼續(xù)執(zhí)行力額暖眼。
(2)Condition函數(shù)
1?? 條件量在使用前需要先初始化惕耕,函數(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:條件量可以用于進程間線程同步挤安。
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ù)會先解鎖互斥量,因此丧鸯,使用前一定要確保mutex已經(jīng)上鎖蛤铜。鎖上后線程將掛起。pthread_cond_timedwait()用在希望線程等待一段時間的情況下丛肢,如果時間到了線程就會恢復(fù)運行围肥。
3?? 可以使用函數(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變量的操作必須是原子的,當(dāng)進程駛?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中預(yù)定義的值,但是在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)線程的掛起和喚醒姨谷。當(dāng)pshare的值為false時逗宁,執(zhí)行Futex系統(tǒng)調(diào)用的操作碼為
FUTEX_WAIT|FUTEX_PRIVATE_FLAG
內(nèi)核如何檢測到操作有FUTEX_PRIVATE_FLAG標記,能以更快的速度執(zhí)行七掛起和喚醒操作梦湘。
_futex_wait 和_futex_wake函數(shù)相當(dāng)于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()相當(dāng)于 _futex_wake()捌议,而 _futex_system4()相當(dāng)于 _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贫贝。
- 當(dāng)進程或線程嘗試持有鎖的時候秉犹,檢查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)暮现。
- 當(dāng)進程或線程釋放鎖的時候还绘,如果Futex變量的值為1,說明沒有其他線程在等待鎖栖袋,這樣講Futex變量的值設(shè)為0就結(jié)束了拍顷;如果Futex變量的值2,說明還有線程等待鎖塘幅,將Futex變量值設(shè)為0昔案,同時執(zhí)行FUTEX_WAKE()系統(tǒng)調(diào)用來喚醒等待的進程尿贫。
對Futex變量操作時,比較和賦值操作必須是原子的踏揣。