1. Binder是干什么的?
簡單來說binder就是用于進程間通訊的踪区。但從不同角度對binder可以有不同的理解昆烁。引用《Android開發(fā)藝術探索》中的解釋:
- Binder是Android中一個實現(xiàn)了IBinder接口的類
- 是一種跨進程通訊的方式
- 是一種虛擬的物理設備,驅動為/dev/binder
- 在Android中是ServiceManager連接各種Manager極其Service的橋梁
- 是Android客戶端與服務端的媒介
估計在看完這些概念時缎岗,感覺是一臉懵静尼。不著急先繼續(xù)往下看。
在深入了解binder前传泊,我們有必要了解一些相關概念來循序漸進:
1.1 App為什么要多進程茅郎?
通常我們在日常開發(fā)中特定場景下會使用多進程,比如接入webview或渤、視頻音樂播放、大圖瀏覽奕扣、推送等功能時薪鹦,會開啟多進程。開啟多進程的方法很簡單惯豆,在對應的組件manifest文件節(jié)點下面指定process屬性即可棒卷。
為什么要開啟多進程另萤?
- 系統(tǒng)分配的dalvik虛擬機內(nèi)存空間有限,當使用過程中內(nèi)存超出時會oom。一個進程代表一個虛擬機锉矢,代表一塊內(nèi)存,開啟多進程相當于向系統(tǒng)多申請了內(nèi)存空間肤舞。
- 使用多進程可以實現(xiàn)風險隔離图仓。當一個進程掛掉了,不會影響另外一個進程揭厚。
ps:
查看系統(tǒng)給App分配的內(nèi)存空間使用命令:
getprop dalvik.vm.heapsize
1.2 了解到多進程的作用后却特,那么Linux進程間通訊的方式有哪些?
管道筛圆、信號量裂明、socket、共享內(nèi)存太援、binder等
那Android中為什么要使用binder來進行通訊闽晦?
2. Biner通訊特點
binder與共享內(nèi)存、socket對比
從表中可以看出
binder相比共享內(nèi)存提岔,在傳遞數(shù)據(jù)時會多一次數(shù)據(jù)拷貝仙蛉,而比傳統(tǒng)的通訊方式socket少一次數(shù)據(jù)拷貝。
那又什么是數(shù)據(jù)拷貝碱蒙?
說到這里就不得不先說下Android的內(nèi)存劃分了捅儒,在我們的Android系統(tǒng)中,內(nèi)存被操作系統(tǒng)劃分為兩塊:內(nèi)核空間和用戶空間:內(nèi)核空間是共享的,是內(nèi)核代碼運行的地方巧还,可以調(diào)用系統(tǒng)的一切資源鞭莽。用戶空間是每個進程獨有的,是用戶程序代碼運行的地方麸祷,從安全角度考慮澎怒,它不能直接調(diào)用系統(tǒng)的相關資源,只能通過系統(tǒng)接口(system call)來向內(nèi)核空間發(fā)出指令作出相關操作阶牍,比如訪問網(wǎng)絡喷面,讀寫文件等,都是通過內(nèi)核空間中轉了一次的走孽。兩者相互隔離惧辈,即當用戶的程序崩潰了,內(nèi)核也不會受影響磕瓷。
二者關系如下圖
例如當應用需要像系統(tǒng)中寫入一個文件時盒齿,它會分成以下幾步
- 調(diào)用wirte,告訴內(nèi)核需要寫入數(shù)據(jù)的開始地址與長度
- 內(nèi)核將數(shù)據(jù)拷貝到內(nèi)核緩存
- 由操作系統(tǒng)調(diào)用,將數(shù)據(jù)拷貝到磁盤困食,完成寫入
BInder方式為什么高效呢边翁?下面來對比下傳統(tǒng)IPC方式與Binder方式進程1需要向進程2寫入數(shù)據(jù)時的不同
傳統(tǒng)IPC方式
Binder方式
用binder來進行進程間通訊時,在從內(nèi)核空間拷貝數(shù)據(jù)到進程2這一步硕盹,會通過mmap()內(nèi)存映射關系符匾,直接與數(shù)據(jù)接收方共享同一塊物理內(nèi)存,從而減少一次數(shù)據(jù)拷貝瘩例。所以使用binder比傳統(tǒng)IPC方式更高效啊胶。那么有人會問,那寫入的時候也使用mmap不是更高效嗎垛贤?直接不用拷貝了创淡。其實這就是共享內(nèi)存。就是上面表中的0次拷貝南吮。但共享內(nèi)存由于安全關系琳彩,同步控制復雜,反而沒有binder高效部凑。
那什么是mmap呢露乏?
Linux通過將一個虛擬內(nèi)存區(qū)域與一個磁盤上的對象關聯(lián)起來,以初始化這個虛擬內(nèi)存區(qū)域的內(nèi)容涂邀,這個過程稱為內(nèi)存映射(memory mapping)
用戶空間與內(nèi)核空間上內(nèi)存概念都是邏輯地址瘟仿,而對應到真正的物理內(nèi)存地址,背后存在著地址一對一的映射比勉。通過mmap實現(xiàn)這樣的映射關系后劳较,就可以采用指針的方式讀寫操作某一段內(nèi)存驹止,而系統(tǒng)會自動回寫到對應的文件磁盤上。
前面只是大概的描述了下兩個進程間的通訊過程观蜗。我們知道Android 上層都是用 Java 寫的臊恋,但是 Binder 驅動是 C 實現(xiàn),他們之間數(shù)據(jù)具體是如何傳遞的呢墓捻?
3. Binder執(zhí)行流程
下面再給出一張binder的框架圖抖仅,和涉及到的類圖
binder框架圖
binder涉及到的類圖
簡單來說就是Fromwork(java)層與native(c)層通過JNI搭建一個橋梁,注冊各種方法砖第,然后到native層又通過系統(tǒng)提供的接口(system call)與內(nèi)核層通訊撤卢,來進行一些操作,比如注冊服務與獲取服務等梧兼。
下面再從源碼的角度來看看放吩,這一步一步是怎么實現(xiàn)的
4. Binder的JNI方法注冊
4.1 Android系統(tǒng)開機啟動啟動流程
首先來回顧下Android系統(tǒng)的開機啟動流程
① 電源啟動
② 引導芯片代碼加載引導程序BootLoader到RAM中去執(zhí)行。BootLoader是Android操作系統(tǒng)開始運行前的一個小程序羽杰,它負責把操作OS系統(tǒng)拉起來并運行渡紫。
③ Linux內(nèi)核系統(tǒng)啟動后開始系統(tǒng)設置,并在系統(tǒng)文件中找到init.rc文件忽洛,啟動init進程
④ init進程啟動后,會做一些初始化及啟動屬性服務的工作环肘,并且啟動zygote進程
⑤ zygote進程啟動后會創(chuàng)建java虛擬機并為虛擬機注冊jni方法欲虚,創(chuàng)建服務端socket,啟動SystemServer進程
⑥ SystemServer會啟動Binder線程池及SystemServiceManager,并啟動各種Service(AMS、WMS等)
⑦ AMS會啟動Launcher,Launcher啟動后會把已安裝的應用圖標顯示到手機桌面上
4.2 zygote進程啟動注冊jni
在上述的第四步中悔雹,init進程通過解析init.zygote.rc文件复哆,創(chuàng)建zygote進程。zygote進程所對應的可執(zhí)行程序是app_process,所對應的源文件是app_main.cpp腌零。該文件位于Android系統(tǒng)源碼的/system/core/rootdir/init.zygote32.rc目錄下
4.3 源碼跟蹤
framworks/base/cmds/app_process/app_main.cpp
//執(zhí)行main方法
//將zygote標志位置為true
//運行AndroidRuntime.cpp的start方法
framworks/base/core/jni/AndroidRuntime.cpp
//執(zhí)行start方法
//執(zhí)行startReg()
//注冊jni方法
//循環(huán)注冊jni方法
//里面包含binder
framworks/base/core/jni/android_util_Binder.cpp
//具體執(zhí)行注冊
以第一個為例梯找,下面兩個方法注冊都差不多就不再一 一分析。
//這里可以理解為相當于做個一個反射益涧,將java層的方法一一對應起來锈锤,建立關聯(lián)。
通過findClassOrDie方法闲询,查找文件kBinderPathName="android/os/Binder"返回對應的class對象久免;通過gBinderOffsets結構體,保存Java層Binder類的信息扭弧,為JNI層訪問Java層提供通道阎姥;通過RegisterMethodsOrDie為gBinderMethods數(shù)組完成映射關系,從而為Java層訪問JNI層提供通道
經(jīng)過上面的源碼流程鸽捻,把java層到native層JNI注冊的流程摸清楚了呼巴,下面再介紹native層到內(nèi)核層systemCall是怎么進行的
5. Binder的驅動啟動
5.1 示意圖
在Linux中泽腮,一切皆文件。對binder的驅動初始化也是從binder.c 開始的衣赶。主要涉及到binder_init()诊赊、binder_open()、binder_mmap()屑埋、binder_ioctl()這幾個函數(shù)豪筝。
5.2 源碼追蹤
#kernel/drivers/staging/android/binder.c
注意該文件不在Android源碼里面,位于Linux源碼里面摘能。與Android源碼framwork文件夾里那個不是一回事续崖。
在binder_init函數(shù)中調(diào)用了init_binder_device(const char *name)函數(shù),在這里主要干了三件事
- 為binder設備開辟內(nèi)存空間
- 初始化驅動設備
- 將hlist節(jié)點添加到binder_devices為表頭的設備鏈表
其中在第二步初始化時拿到了binder_fops結構體指針团搞,在后續(xù)過程中严望,會調(diào)用到binder_fops.mmop、binder_fops.open逻恐、binder_ioctl
先來看binder_open函數(shù)中
在這里主要干了四件事:
- 分配內(nèi)存像吻,創(chuàng)建binder_proc
- 初始化binder_proc(可以理解為把java層的binder相關信息保存到proc)
- 將proc托管到file中
- 把proc添加到鏈表中procs保管起來
再來看binder_mmap 函數(shù)
android6.0已經(jīng)把binder_buffer有關的操作和binder.c分開了,實現(xiàn)在binder_alloc.c文件里面复隆,這里不再詳述拨匆。
感興趣可查看
https://github.com/torvalds/linux/blob/master/drivers/android/binder_alloc.c
在這里主要干了三件事
- 通過用戶空間的虛擬內(nèi)存大小分配一塊內(nèi)核的虛擬內(nèi)存,二者大小相等挽拂,且不超過4M
- 分配一塊物理內(nèi)存--4KB (先分配1頁one page)等需要用的時候再擴大惭每,免費內(nèi)存浪費
- 計算到的用戶空間和內(nèi)核空間地址偏移量,并把這塊物理內(nèi)存分別映射到用戶空間的虛擬內(nèi)存和內(nèi)核的虛擬內(nèi)存
注意mmap所映射的內(nèi)容大小最大為4M亏栈,是由sz_4M屬性決定的台腥。
緊接著來到binder_ioctl函數(shù)中
它會根據(jù)傳遞過來的命令做出相應的操作,比如讀寫操作
找了這么就終于來到我們前面提到的copy_from_user()和copy_to_user()中了绒北,這兩個函數(shù)分別把用戶空間數(shù)據(jù)ubuf拷貝到內(nèi)核空間數(shù)據(jù)bwr及反之黎侈。
注意這里拷貝的是數(shù)據(jù)頭,不是有效數(shù)據(jù)
5.4 常見面試題
順便提一句闷游,在面試的時候經(jīng)常會被問到intent最大一次能攜帶多少數(shù)據(jù)峻汉?答案:1M-8k(8k是兩個pagesize,一個pagesize是申請物理內(nèi)存的最小單元)
原因是如果一個進程使用ProcessState這個類來初始化Binder服務脐往,這個進程的Binder內(nèi)核內(nèi)存上限就是BINDER_VM_SIZE俱济,也就是1MB-8KB。
frameworks/native/libs/binder/ProcessState.cpp
初始化在zygote進程初始化binder服務時調(diào)用的