之前壹直對 Binder 理解不夠透徹,僅僅知道壹些皮毛,所以最近抽空深入理解壹下,並在這裏做個小結。
Binder 是 Android 系統中實現 IPC (進程間通信)的壹種機制。Binder 原意是“膠水、粘合劑”,所以可以想象它的用途就是像膠水壹樣把兩個進程緊緊“粘”在壹起,從而可以方便地實現 IPC 。
那麽為什麽會有進程通信呢?這是因為在 Linux 中進程之間是隔離的,也就是說 A 進程不知道有 B 進程的存在,相應的 B 進程也不知道 A 進程的存在。A 、B 兩進程的內存是不***享的,所以 A 進程的數據想要傳給 B 進程就需要用到 IPC 。
在這裏再科普壹下進程空間的知識點:進程空間可以分為用戶空間和內核空間。簡單的說,用戶空間是用戶程序運行的空間,而內核空間就是內核運行的空間了。因為像內核這麽底層、至關重要的東西肯定是不會簡單地讓用戶程序隨便調用的,所以需要把內核保護起來,就創造了內核空間,讓內核運行在內核空間中,這樣就不會被用戶空間隨便幹擾到了。兩個進程之間的用戶空間是不***享的,但是內核空間是***享的。
所以到這裏,有些同學會有個大膽的想法,兩個進程間的通信可以利用內核空間來實現啊,因為它們的內核空間是***享的,這樣數據不就傳過去了嘛。但是接著又來了壹個問題:為了保證安全性,用戶空間和內核空間也是隔離的。那麽如何把數據從發送方的用戶空間傳到內核空間呢?
針對這個問題提供了 系統調用 來解決,可以讓用戶程序調用內核資源。系統調用是用戶空間訪問內核空間的唯壹方式,保證了所有的資源訪問都是在內核的控制下進行的,避免了用戶程序對系統資源的越權訪問,提升了系統安全性和穩定性(這段話來自 《寫給 Android 應用工程師的 Binder 原理剖析》 )。我們平時的網絡、I/O操作其實都是通過系統調用在內核空間中運行的(也就是 內核態 )。
至此,關於 IPC 我們有了壹個大概的實現方案:A 進程的數據通過系統調用把數據傳輸到內核空間(即copy_from_user),內核空間再利用系統調用把數據傳輸到 B 進程(即 copy_to_user)。這也正是目前 Linux 中傳統 IPC 通信的實現原理,可以看到這其中會有兩次數據拷貝。
(圖片來自於 《寫給 Android 應用工程師的 Binder 原理剖析》 )
Linux 中的壹些 IPC 方式:
通過上面的講解我們可以知道,IPC 是需要內核空間來支持的。Linux 中的管道、socket 等都是在內核中的。但是在 Linux 系統裏面是沒有 Binder 的。那麽 Android 中是如何利用 Binder 來實現 IPC 的呢?
這就要講到 Linux 中的 動態內核可加載模塊 。動態內核可加載模塊是具有獨立功能的程序,它可以被單獨編譯,但是不能獨立運行。它在運行時被鏈接到內核作為內核的壹部分運行。這樣,Android 系統就可以通過動態添加壹個內核模塊運行在內核空間,用戶進程之間通過這個內核模塊作為橋梁來實現通信。(這段話來自 《寫給 Android 應用工程師的 Binder 原理剖析》 )在 Android 中,這個內核模塊也就是 Binder 驅動。
另外,Binder IPC 原理相比較上面傳統的 Linux IPC 而言,只需要壹次數據拷貝就可以完成了。那麽究竟是怎麽做到的呢?
其實 Binder 是借助於 mmap (內存映射)來實現的。mmap 用於文件或者其它對象映射進內存,通常是用在有物理介質的文件系統上的。mmap 簡單的來說就是可以把用戶空間的內存區域和內核空間的內存區域之間建立映射關系,這樣就減少了數據拷貝的次數,任何壹方的對內存區域的改動都將被反應給另壹方。
所以,Binder 的做法就是建立壹個虛擬設備(設備驅動是/dev/binder),然後在內核空間創建壹塊數據接收的緩存區,這個緩存區會和內存緩存區以及接收數據進程的用戶空間建立映射,這樣發送數據進程把數據發送到內存緩存區,該數據就會被間接映射到接收進程的用戶空間中,減少了壹次數據拷貝。具體可以看下圖理解
(圖片來自於 《寫給 Android 應用工程師的 Binder 原理剖析》 )
Binder 的優點
在整個 Binder 通信過程中,可以分為四個部分:
其中 Client 和 Server 是應用層實現的,而 Binder 驅動和 ServiceManager 是 Android 系統底層實現的。
具體流程如下:
(Binder通信過程示意圖來自於 《寫給 Android 應用工程師的 Binder 原理剖析》 )