Friday, April 17, 2009

一千零一夜之 Android Binder

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

Binder 是 android 大量使用的 IPC 機制。當使用者在 launcher (home) 按了某個 App 時,這個動作會經由 ActivityManager 向 zygote 發出請求,並從 zygote fork 出新的 process 執行被選擇的 App。這個過程大致上有 4 個 process 參與:home、system server、zygote 和新產生的 appA。更仔細去看的話,

  1. system server 向 home 送出 touchscreen 事件

  2. home 向 system server 送出 start activity 事件

  3. system server 向 zygote 送出 appA 的啟動參數

  4. appA 向 system server 送出 attach 事件


除了第 3 項以外,其它跨 process 的溝通都是採用 binder。Zygote 是 android 上少數幾個聽 unix socket 的程式。驗證這個說法最簡單的方法是去看 process 的 memory maps 還有 opened fd,這個技巧在之前有用過,這邊暫且略過。

一個 IPC 機制的效能很大一部份取決於它資料交換的能力,從這點去切入是不錯的途徑。所有參與 binder 的 process 都會在一開始向 /dev/binder 裝置 mmap 一塊 read-only 的記憶體,大小在幾個 MB 左右。A process 傳送資料給 B process 的做法是,由 A 對 /dev/binder 做 ioctl 將資料先送給 binder driver。依資料大小,binder driver 動態配置足夠的 physical memory 存放來自 A 的資料,並在 B 一開始 mmap 得到的 address space 裡尋找一段足夠大且未使用的 address area。找到後,binder driver 會修改 B 的 page table,讓這段 address area 映射到剛剛動態配置的 physical memory。如此一來,B 可以直接存取 A 送過來的資料;等到 B 不需要這些資料時,再由 B 負責把這塊記憶體還回去給系統。這段實作可以參改 binder driver 的 binder_update_page_range 函數。

Process 對 binder 的操作只透過 ioctl 做 buffer 的讀寫。Buffer 的內容是一系列的 (command, argument) 組。每個 command 為 4 bytes,argument 的長度視 command 而定,可能是固定的,也可能是變動的。當 A 跟 B 交換資料時,通常會由一方送出 BC_TRANSACTION command,再由另一方回覆 BC_REPLY command。Transaction/reply 的 argument 除了一段 header 外,其餘的資料部份會使用上一段討論的方式做交換。資料的長度跟內容由 A、B 另行約定,可能是整數、浮點數或字串等。除了上述的基本資料型別外,還有三種用 struct flat_binder_object 表示的特殊型別:BINDER、HANDLE、FD。FD 型別讓 A、B 可以交換 file descriptor,也就是說,A 可以打開某個檔案,把 fd 傳給 B,讓 B 去讀寫。或者說,也是更重要的,surface manager 可以準備一塊 surface,把 surface 的 fd (一塊 ashmem 記憶體) 傳給一個 app,讓 app 可以在上面作畫。有了這層理解,我們對 android 可以有更自由的發揮,例如,可以不改一行 cairo 的程式碼,就利用 cairo 與 c++ 寫出一個會動的時鐘:



回到正題,我們知道 binder 還有 BINDER 與 HANDLE 這兩個特殊型別。它們的功用可以用 service port 跟 connection 做類比。就像 httpd 可以開 80 port 提供服務,client 可以跟它建立 connection 使用服務一樣,在 android 上頭,WindowManagerService 可能建立了一個 BINDER,提供了視窗操作的服務。其它程式可能會建立這個服務的 HANDLE。在 binder 之上,android 提供了 aidl 讓開發者可以把 remote service 當做 local object 操作,所以開發者可以直接呼叫 mWindowManager.setRotation 來旋轉螢幕,而幾乎可以不用知道 BINDER 跟 HANDLE 的存在。

當一個 process 收到 transaction 的時候,如果 binder driver 發現 process 沒有任何 thread 已經準備好等著要處理,等到下次有 thread 來處理時,它會收到 BR_SPAWN_LOOPER 的訊息,userspace 也會多產生一個 thread 專門對 /dev/binder 做 I/O,確保下次有 transaction 的時候,有比較高的機會是已經有 thread 準備好了。提個數字,在 android sdk 1.5 preview 的 emulator 剛開好機時,在我的機器上,Phone App 會有 11 個 thread,而其中有 6 個 thread 是跑在同一個迴圈裡等著對 /dev/binder 做 I/O。這項機制相信是為了縮短應用程式的反應時間。

Binder 再探討下去還有許多東西可以討論,例如 android native library 沒有 aidl 可以用,它如何建立操作 binder 的物件?或者 native code 裡面常常看到的 sp<>、wp<> 跟 binder 的 reference counting 關係為何?對 binder 的功能有初步認識後,再去理解這些實作會顯得直覺許多。

5 comments:

erin said...

you run a cairo program in Android? you serious? that is so cool..... XD
i thought we can only write java application in Android.... WOW!d

Michael said...

你好, 可否請你詳細說明一下, 如何在 Android 下跑 cairo 的時鐘 。
謝謝 !

olv said...

我會努力盡早生出來 :P

flying without wings said...

这个非常有趣, 能详细说一下么, 谢谢!

olv said...

請參考新發表的 Android Window System