Binder機制學習
- Binder驅動
- Binder核心API
- Linux 使用兩級保護機制:0 級供系統內核使用,3 級供用戶程式使用,
- Linux 下的傳統 IPC 通信原理
- Linux 下的傳統 IPC 通信原理
- Binder通信程序
- ServiceManager行程啟動
- MMAP
Binder驅動
binder驅動在以misc設備進行注冊,作為虛擬字符設備,沒有直接操作硬體,只是對設備記憶體的處理,
主要是驅動設備的初始化(binder_init),打開 (binder_open),映射(binder_mmap),資料操作(binder_ioctl),

用戶態的程式呼叫Kernel層驅動是需要陷入內核態,進行系統呼叫(syscall),
比如打開Binder驅動方法的呼叫鏈為: open-> __open() -> binder_open(),
open()為用戶空間的方法,__open()便是系統呼叫中相應的處理方法,通過查找,對應呼叫到內核binder驅動的binder_open()方法,至于其他的從用戶態陷入內核態的流程也基本一致,
用戶空間呼叫open()方法,最侄訓呼叫binder驅動的binder_open()方法;mmap()/ioctl()方法也是同理,在BInder系列的后續文章從用戶態進入內核態,都依賴于系統呼叫程序,

Binder核心API
1.binder_init
該方法主要是為了注冊misc設備,debugfs_create_dir是指在debugfs檔案系統中創建一個目錄,回傳值是指向dentry的指標

注冊misc設備,miscdevice結構體,便是前面注冊misc設備時傳遞進去的引數

file_operations結構體,指定相應檔案操作的方法,可以看到在上面用戶空間的方法會最終呼叫到對應的方法,就是根據這個來定的

2.binder_open
創建binder_proc物件,并把當前行程等資訊保存到binder_proc物件,該物件管理IPC所需的各種資訊并擁有其他結構體的根結構體;再把binder_proc物件保存到檔案指標filp,以及把binder_proc加入到全域鏈表binder_procs,

Binder驅動中通過static HLIST_HEAD(binder_procs);,創建了全域的哈希鏈表binder_procs,用于保存所有的binder_proc佇列,每次新創建的binder_proc物件都會加入binder_procs鏈表中,

3.binder_mmap
主要功能:首先在內核虛擬地址空間,申請一塊與用戶虛擬記憶體相同大小的記憶體;然后再申請1個page大小的物理記憶體,再將同一塊物理記憶體分別映射到內核虛擬地址空間和用戶虛擬記憶體空間,從而實作了用戶空間的Buffer和內核空間的Buffer同步操作的功能,


總結:
binder_init:初始化字符設備;
binder_open:打開驅動設備,程序需要持有binder_main_lock同步鎖;
binder_mmap:申請記憶體空間,該程序需要持有binder_mmap_lock同步鎖;
binder_ioctl:執行相應的ioctl操作,該程序需要持有binder_main_lock同步鎖;
當處于binder_thread_read程序,read_buffer無資料則釋放同步鎖,并處于wait_event_freezable程序,等有資料到來則喚醒并嘗試持有同步鎖,
Linux 使用兩級保護機制:0 級供系統內核使用,3 級供用戶程式使用,
當一個任務(行程)執行系統呼叫而陷入內核代碼中執行時,稱行程處于內核運行態(內核態),此時處理器處于特權級最高的(0級)內核代碼中執行,當行程處于內核態時,執行的內核代碼會使用當前行程的內核堆疊,每個行程都有自己的內核堆疊,
當行程在執行用戶自己的代碼的時候,我們稱其處于用戶運行態(用戶態),此時處理器在特權級最低的(3級)用戶代碼中運行,
系統呼叫主要通過如下兩個函式來實作:
copy_from_user() //將資料從用戶空間拷貝到內核空間
copy_to_user() //將資料從內核空間拷貝到用戶空間
Linux 下的傳統 IPC 通信原理

這種傳統的 IPC 通信方式有兩個問題:
1.性能低下,一次資料傳遞需要經歷:記憶體快取區 --> 內核快取區 --> 記憶體快取區,需要 2 次資料拷貝;
2.接收資料的快取區由資料接收行程提供,但是接收行程并不知道需要多大的空間來存放將要傳遞過來的資料,因此只能開辟盡可能大的記憶體空間或者先呼叫 API 接收訊息頭來獲取訊息體的大小,這兩種做法不是浪費空間就是浪費時間,
跨行程通信是需要內核空間做支持的,傳統的 IPC 機制如管道、Socket 都是內核的一部分,因此通過內核支持來實作行程間通信自然是沒問題的,但是 Binder 并不是 Linux 系統內核的一部分,這個是 Linux 的動態內核可加載模塊(Loadable Kernel Module,LKM)的機制;
LKM:模塊是具有獨立功能的程式,它可以被單獨編譯,但是不能獨立運行,它在運行時被鏈接到內核作為內核的一部分運行,這樣,Android 系統就可以通過動態添加一個內核模塊運行在內核空間,用戶行程之間通過這個內核模塊作為橋梁來實作通信,
這個運行在內核空間,負責各個用戶行程通過 Binder 實作通信的內核模塊就叫 Binder 驅動(Binder Dirver),
Binder IPC 機制中涉及到的記憶體映射通過 mmap() 來實作,mmap() 是作業系統中一種記憶體映射的方法,
記憶體映射就是將用戶空間的一塊記憶體區域映射到內核空間,映射關系建立后,用戶對這塊記憶體區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間,
兩個空間各自的修改能直接反映在映射的記憶體區域,從而被對方空間及時感知,
Linux 下的傳統 IPC 通信原理
Binder IPC 正是基于記憶體映射(mmap)來實作的,但是 mmap() 通常是用在有物理介質的檔案系統上的,
比如行程中的用戶區域是不能直接和物理設備打交道的,如果想要把磁盤上的資料讀取到行程的用戶區域,需要兩次拷貝(磁盤–>內核空間→用戶空間);
通常在這種場景下 mmap() 就能發揮作用,通過在物理介質和用戶空間之間建立映射,減少資料的拷貝次數,用記憶體讀寫取代I/O讀寫,提高檔案讀取效率,
而 Binder 并不存在物理介質,因此 Binder 驅動使用 mmap() 并不是為了在物理介質和用戶空間之間建立映射,而是用來在內核空間創建資料接收的快取空間,
Binder IPC 通信程序通常是這樣:
1.首先 Binder 驅動在內核空間創建一個資料接收快取區;
2.接著在內核空間開辟一塊內核快取區,建立內核快取區和內核中資料接收快取區之間的映射關系,以及內核中資料接收快取區和接收行程用戶空間地址的映射關系;
3.發送方行程通過系統呼叫 copy_from_user() 將資料 copy 到內核中的內核快取區,由于內核快取區和接收行程的用戶空間存在記憶體映射,因此也就相當于把資料發送到了接收行程的用戶空間,這樣便完成了一次行程間的通信,

Binder 是基于 C/S 架構的,由一系列的組件組成,包括 Client、Server、ServiceManager、Binder 驅動,
其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間,
其中 Service Manager 和 Binder 驅動由系統提供,而 Client、Server 由應用程式來實作,
Client、Server 和 ServiceManager 均是通過系統呼叫 open、mmap 和 ioctl 來訪問設備檔案 /dev/binder,從而實作與 Binder 驅動的互動來間接的實作跨行程通信,

Binder IPC通信至少是兩個行程的互動:
client行程執行binder_thread_write,根據BC_XXX命令,生成相應的binder_work;
server行程執行binder_thread_read,根據binder_work.type型別,生成BR_XXX,發送到用戶空間處理,

Binder通信程序
1.首先,一個行程使用 BINDER_SET_CONTEXT_MGR 命令通過 Binder 驅動將自己注冊成為 ServiceManager
2.Server 通過驅動向 ServiceManager 中注冊 Binder(Server 中的 Binder 物體),表明可以對外提供服務,驅動為這個 Binder 創建位于內核中的物體節點以及 ServiceManager 對物體的參考,將名字以及新建的參考打包傳給 ServiceManager,ServiceManger 將其填入查找表,
3.Client 通過名字,在 Binder 驅動的幫助下從 ServiceManager 中獲取到對 Binder 物體的參考,通過這個參考就能實作和 Server 行程的通信,

Binder通信的代理模式
當 A 行程想要獲取 B 行程中的 object 時,驅動并不會真的把 object 回傳給 A,而是回傳了一個跟 object 看起來一模一樣的代理物件 objectProxy,這個 objectProxy 具有和 object 一摸一樣的方法,但是這些方法并沒有 B 行程中 object 物件那些方法的能力,這些方法只需要把把請求引數交給驅動即可,對于 A 行程來說和直接呼叫 object 中的方法是一樣的,
當 Binder 驅動接收到 A 行程的訊息后,發現這是個 objectProxy 就去查詢自己維護的表單,一查發現這是 B 行程 object 的代理物件,于是就會去通知 B 行程呼叫 object 的方法,并要求 B 行程把回傳結果發給自己,當驅動拿到 B 行程的回傳結果后就會轉發給 A 行程,一次通信就完成了,

ServiceManager行程啟動
1.ServiceManager成為Binder管理者
首先在android系統開始啟動時通過init.rc啟動了servicemanager(Native行程)可執行檔案行程,系統真正的ServieManager管理最終通過Native行程servicemanager來完成,接下來分析在main函式中做了那些事情:

1.binder_open(128*1024)通過打開/dev/binder設備節點回傳fd檔案描述符,通過mmap最終實作對binder驅動128K大小的記憶體映射
2.binder_become_context_manager(bs)通過ioctl往設備節點發送BINDER_SET_CONTEXT_MGR命令通知Binder驅動為servicemanager創建特殊不變的0句柄物體binder_node:binder_context_mgr_node

3.Binder_loop(bs,svcmgr_handler)通過iotcl發送BINDER_WRITE_READ命令并攜帶BC_ENTER_LOOPER請求命令告知Binder驅動創建了binder主執行緒并使binder驅動創建與之對應的binder_thread結構體存盤到binder_proc中,并通過for回圈進入回圈讀取binder驅動回傳資訊的回圈流程

ServiceManager(native行程)最終通過獲取binder驅動設備節點fd地址,與binder驅動記憶體映射(虛擬記憶體與物理記憶體映射),開啟binder主體執行緒回圈讀取binder驅動回傳的訊息而成為服務端;
成為服務端后能夠不斷接收來自客戶端(服務端 與客戶端在此統稱為客戶端)的binder請求通信,ServiceManager(native行程)完成其他服務的注冊并保存binder驅動創建的binder句柄值與服務名稱字串,保證客戶端通過字串獲取其他服務參考句柄;因而ServiceManager(native行程)為binder管理者(服務注冊與獲取的橋梁),
2.客戶端通過ServiceManager獲取服務
用戶行程需要和ServiceManager(native行程)行程通信,ServiceManager行程接收到請求后去回應
1.用戶行程第一步先實體化ServiceManager Binder Proxy代理

通過BinderInternal.getContextObject()呼叫獲取到句柄值為0的BpBinder native物件,最終通過javaObjectForIbinder()函式的jni轉換為BinderProxy類物件其實就是填充了這個類的mObject物件也就是ServiceManager Binder物件的參考封裝物件,所以SMN才能通過asInterface完成代理Proxy的實體化
MMAP

虛擬行程地址空間(vm_area_struct)和虛擬內核地址空間(vm_struct)都映射到同一塊物理記憶體空間,當Client端與Server端發送資料時,Client(作為資料發送端)先從自己的行程空間把IPC通信資料copy_from_user拷貝到內核空間,而Server端(作為資料接收端)與內核共享資料,不再需要拷貝資料,而是通過記憶體地址空間的偏移量,即可獲悉記憶體地址,整個程序只發生一次記憶體拷貝,一般地做法,需要Client端行程空間拷貝到內核空間,再由內核空間拷貝到Server行程空間,會發生兩次拷貝,
對于行程和內核虛擬地址映射到同一個物理記憶體的操作是發生在資料接收端,而資料發送端還是需要將用戶態的資料復制到內核態,到此,為何不直接讓發送端和接收端直接映射到同一個物理空間,那樣就連一次復制的操作都不需要了,0次復制操作那就與Linux標準內核的共享記憶體的IPC機制沒有區別了,對于共享記憶體雖然效率高,但是對于多行程的同步問題比較復雜,而管道/訊息佇列等IPC需要復制2兩次,效率較低,
如下是大致流程圖:

借鑒文章:
https://blog.csdn.net/universus/article/details/6211589
關于mmap:https://www.cnblogs.com/huxiao-tee/p/4660352.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/295055.html
標籤:其他
上一篇:還在做創業夢?醒醒!
