Binder是Android底層實現(xiàn)進程通訊的一種方式怀挠,由于它的調(diào)用過程比較復(fù)雜埃唯。本篇暫不涉及源碼撩匕,只是做一個原理上的講解,而具體的源碼調(diào)用墨叛,會在后面用一個系列的篇章來分析止毕。
Binder這種進程通訊是Android系統(tǒng)特有的,也就是說漠趁,進程間的通訊其實還有許多實現(xiàn)方式扁凛,通常稱為傳統(tǒng)的進程通訊方式。
傳統(tǒng)IPC方式:
- 管道(Pipe) :操作簡單闯传,但只能在父子進程間通訊谨朝,并且只能單向通訊。
- FiFO:解決了Pipe需要父子進程的問題甥绿,但長期存在于系統(tǒng)之中字币,使用不當容易出錯。
- 信號量(Signal):不適用數(shù)據(jù)量大的傳輸共缕。
- 消息隊列(Message):信息的復(fù)制需要消耗CPU的時間.不適用信息量大或操作頻繁的情形洗出。
- Socket:傳輸效率低,開銷大图谷,主要用在網(wǎng)絡(luò)通訊翩活。
- 共享內(nèi)存(Share Memory):共享內(nèi)存無須拷貝阱洪,可操作大數(shù)據(jù)量,但實現(xiàn)復(fù)雜菠镇。
傳統(tǒng)IPC原理:
對于 32 位系統(tǒng)冗荸,它的尋址空間(虛擬存儲空間)就是 2 的 32 次方,即 4GB利耍。分內(nèi)核空間和用戶空間:
- 內(nèi)核空間:最高的 1GB 字節(jié)供內(nèi)核使用蚌本,權(quán)限為0級;
- 用戶空間:低的 3GB 字節(jié)供各進程使用堂竟,權(quán)限為3級魂毁。
用戶空間是每個進程私有的,其他進程不能夠訪問出嘹。而用戶空間訪問內(nèi)核資源的資源席楚,無法直接訪問,需要借助系統(tǒng)調(diào)用來實現(xiàn)税稼。系統(tǒng)調(diào)用指的是烦秩,根據(jù)系統(tǒng)對外暴露的api接口,來訪問系統(tǒng)資源郎仆,換句話說只祠,系統(tǒng)封裝了一層實現(xiàn),如果用戶空間要訪問內(nèi)核資源扰肌,需要交給這一層實現(xiàn)抛寝,來幫我們訪問。
系統(tǒng)調(diào)用主要通過如下兩個函數(shù)來實現(xiàn):
//將數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間
copy_from_user()
//將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間
copy_to_user()
消息發(fā)送方曙旭,將要發(fā)送的數(shù)據(jù)存放在用戶空間的內(nèi)存緩存區(qū)中盗舰,接著通過系統(tǒng)調(diào)用進入內(nèi)核態(tài),內(nèi)核程序在內(nèi)核空間分配內(nèi)存桂躏,開辟一塊內(nèi)核緩存區(qū)钻趋。然后調(diào)用 copy_from_user() 函數(shù),將數(shù)據(jù)從用戶空間的內(nèi)存緩存區(qū)剂习,拷貝到內(nèi)核空間的內(nèi)核緩存區(qū)中蛮位。同樣的,接收方進程在接收數(shù)據(jù)時鳞绕,在用戶空間開辟一塊內(nèi)存緩存區(qū)失仁,接著內(nèi)核程序調(diào)用 copy_to_user() 函數(shù),將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收進程的用戶空間內(nèi)存緩存區(qū)们何。
可以看到萄焦,整個發(fā)送和接收的通訊過程,內(nèi)核需要做2次數(shù)據(jù)的拷貝垂蜗。這里的2次楷扬,指的是從用戶空間切換到內(nèi)核空間,和從內(nèi)核空間用戶空間贴见,每做一次切換烘苹,系統(tǒng)都需要比較大的開銷,因此片部,應(yīng)盡量減少切換的次數(shù)镣衡,這是第一個問題。
第二個問題是档悠,接收數(shù)據(jù)的緩存區(qū)由數(shù)據(jù)接收進程提供廊鸥,但是接收進程并不知道需要多大的空間,來存放將要傳遞過來的數(shù)據(jù)辖所,因此只能開辟盡可能大的內(nèi)存空間或者先調(diào)用 API 接收消息頭惰说,來獲取消息體的大小,這兩種做法不是浪費空間就是浪費時間缘回。
Binder原理
傳統(tǒng)的方式在用戶和內(nèi)核態(tài)間切換吆视,至少要拷貝2次數(shù)據(jù),共享內(nèi)存雖無需拷貝酥宴,但實現(xiàn)復(fù)雜啦吧。而Binder只要拷貝1次,性能僅次于共享內(nèi)存拙寡。
傳統(tǒng)的方式接收方無法獲得對方可靠的進程用戶ID/進程ID(UID/PID)授滓,從而無法鑒別對方身份。而android系統(tǒng)為每個安裝好的 APP 分配了自己的 UID肆糕,故而進程的 UID 是鑒別進程身份的可靠標志般堆。而且 Binder 既支持實名 Binder,又支持匿名 Binder擎宝,安全性高郁妈。
傳統(tǒng)的 IPC 方式如Pipe、Socket 都是內(nèi)核的一部分绍申,通過內(nèi)核支持來實現(xiàn)進程間通信噩咪,自然沒問題。但是 Binder 并不是 Linux 系統(tǒng)內(nèi)核的一部分极阅,因此胃碾,需要借助 Linux 的動態(tài)內(nèi)核可加載模塊(Loadable Kernel Module,LKM)的機制筋搏,動態(tài)添加一個內(nèi)核模塊運行在內(nèi)核空間中仆百,這個內(nèi)核模塊就是Binder 驅(qū)動(Binder Dirver)。
內(nèi)存映射
Binder Dirver是通過內(nèi)存映射奔脐,來進行進程通訊的俄周。系統(tǒng)對外提供了一個函數(shù)mmap()吁讨,來做內(nèi)存映射。它的原理是峦朗,將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間建丧。用戶對這塊內(nèi)存區(qū)域的修改,就可以直接反應(yīng)到內(nèi)核空間波势;同樣的翎朱,內(nèi)核空間對這塊內(nèi)存的修改,也能直接反應(yīng)到用戶空間尺铣。
但mmap通常用在有物理介質(zhì)的文件系統(tǒng)上拴曲。因為,進程中的用戶空間是不能直接訪問物理設(shè)備的凛忿,所以澈灼,通常情況下,我們對磁盤上一個文件進行I/O讀寫侄非,是系統(tǒng)先把磁盤的數(shù)據(jù)拷貝到內(nèi)核空間蕉汪,再從內(nèi)核空間把數(shù)據(jù)拷貝到用戶區(qū)域。這種情況下逞怨,就可以通過mmap來做映射者疤,用內(nèi)存讀寫取代I/O讀寫,剩去了數(shù)據(jù)拷貝叠赦,提高效率驹马。
int main(void){
//打開磁盤上的文件
int fd = open("test",O_RDWR|O_TRUNC|O_CREAT,0664);
//拓展10字節(jié)
ftruncate(fd,10);
//獲取文件長度
int len = lseek(fd,0,SEEK_END);
//映射地址
char *memp;
//內(nèi)存映射
memp = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
//映射失敗
if(memp==MAP_FAILED){
perror("mmap error");
exit(1);
}
//關(guān)閉文件
close(fd);
//將數(shù)據(jù)寫入內(nèi)存
strcpy(memp,"hello world\n");
//關(guān)閉內(nèi)存映射
int ret = munmap(memp,len);
if(ret==-1){
perror("mmap error");
exit(1);
}
return 0;
}
打開test文件:
hello world
如上代碼,我們將一個文件映射到內(nèi)存中除秀,然后往內(nèi)存中寫入一串數(shù)據(jù)糯累,數(shù)據(jù)將會同步寫入文件中。
上面的示例雖然使用了物理介質(zhì)(磁盤文件)來做映射册踩,但用mmap做內(nèi)存映射泳姐,物理介質(zhì)并不是必須的。也就是說暂吉,我們可以在內(nèi)核中創(chuàng)建一塊內(nèi)存空間胖秒,并將這塊內(nèi)存映射到接收進程的用戶空間(等同于磁盤文件),當我們往這塊內(nèi)存中寫數(shù)據(jù)時慕的,也就同步傳遞到接收進程的用戶空間中阎肝。大致的創(chuàng)建流程分三步:
- Binder 驅(qū)動先在內(nèi)核空間創(chuàng)建一個數(shù)據(jù)接收緩存區(qū);
- 接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū)肮街,將接收緩存區(qū)和接收進程用戶空間地址進行映射风题;
- 發(fā)送方進程通過系統(tǒng)調(diào)用 copy_from_user() 將數(shù)據(jù)拷貝到內(nèi)核中的內(nèi)核緩存區(qū),內(nèi)核緩存區(qū)會將數(shù)據(jù)寫到接收緩存區(qū)中,由于接收緩存區(qū)映射到了接收進程用戶空間地址沛硅,因此接收進程就同步收到了數(shù)據(jù)眼刃,整個過程只經(jīng)歷用戶到內(nèi)核的1次拷貝。