Binder通信是基於Service和Client的,所有需要IBinder通信的進程都必須創建一個IBinder接口。系統使用一個名爲ServiceManager的收穫進程管理着系統中的各個服務,它負責監聽是否有其他程序向其發送請求,如果有請求就響應,如果沒有,則繼續監聽等待。每個服務都要在ServiceManager中註冊,而請求服務的客戶端則向ServiceManager請求服務。在Android虛擬機啓動之前,系統會先啓動ServiceManager進程,ServiceManager會打開並通知Binder驅動程序自己將作爲系統的服務管理者,然後ServiceManager進入一個循環,等待處理來自其他進程的數據。Android Binder是一種在Android裏廣泛使用的一種遠程過程調用接口。從結構上來說Android Binder系統是一種服務器/客戶機模式,包括Binder Server、Binder Client和Android Binder驅動,實際的數據傳輸就是通過Android Binder驅動來完成的,這裏我們就來詳細的介紹Android Binder驅動程序。
通常來說,Binder是Android系統中的內部進程通訊(IPC)之一。在Android系統中共有三種IPC機制,分別是:
-標準Linux Kernel
IPC接口
-標準D-BUS接口
-Binder接口
儘管Google宣稱Binder具有更加簡潔、快速,消耗更小內存資源的優點,但並沒有證據表明D-BUS就很差。實際上D-BUS可能會更合適些,或許只是當時Google並沒有注意到它吧,或者Google不想使用GPL協議的D-BUS庫。我們不去探究具體的原因了,你只要清楚Android系統中支持了多個IPC接口,而且大部分程序使用的是我們並不熟悉的Binder接口。
Binder是OpenBinder的Google精簡實現,它包括一個Binder驅動程序、一個Binder服務器及Binder客戶端(?)。這裏我們只要介紹內核中的Binder驅動的實現。
對於Android Binder,它也可以稱爲是Android系統的一種RPC(遠程過程調用)機制,因爲Binder實現的功能就是在本地“執行”其他服務進程的功能的函數調用。不管是IPC也好,還是RPC也好,我們所要知道的就是Android Binder的功能是如何實現的。
Binder驅動原理
Binder驅動是作爲一個特殊字符型設備存在,設備節點爲/dev/binder,遵循Linux設備驅動模型。在驅動實現過程中,主要通過binder_ioctl函數與用戶空間的進程交換數據。BINDER_WRITE_READ用來讀寫數據,數據包中有個cmd用於區分不同的請求。binder_thread_write函數用於發送請求或返回結果,而binder_thread_read函數用於讀取結果。在binder_thread_write函數中調用binder_transaction函數來轉發請求並返回結果。當服務進程收到請求時,binder_transaction函數會通過對象的handle找到對象所在進程,如果handle爲0,就認爲請求的是ServiceManager進程。
Android 的Binder機制是基於OpenBinder來實現的,是一個OpenBinder的Linux實現。Android Binder的協議定義在binder.h頭文件中,Android的通訊就是基於這樣的一個協議的。
Android定義了五個(三大類)Binder類型,如下:
進程間傳輸的數據被稱爲Binder對象(Binder Object),它是一個flat_binder_object,定義如下:
其中,類型字段描述了Binder對象的類型,flags描述了傳輸方式,比如同步、異步等。
傳輸的數據是一個複用數據聯合體,對於BINDER類型,數據就是一個binder本地對象,如果是HANDLE類型,這數據就是一個遠程的handle對象。該如何理解本地binder對象和遠程handle對象呢?其實它們都指向同一個對象,不過是從不同的角度來說。舉例來說,假如A有個對象X,對於A來說,X就是一個本地的binder對象;如果B想訪問A的X對象,這對於B來說,X就是一個handle。因此,從根本上來說handle和binder都指向X。本地對象還可以帶有額外的數據,保存在cookie中。
Binder對象的傳遞是通過binder_transaction_data來實現的,即Binder對象實際是封裝在binder_transaction_data結構體中。
binder_transaction_data
這個數據結構纔是真正要傳輸的數據。它的定義如下:
結構體中的數據成員target是一個複合聯合體對象,請參考前面的關於binder本地對象及handle遠程對象的描述。code是一個命令,描述了請求Binder對象執行的操作。
Binder中有兩種索引,一是本地進程地址空間的一個地址,另一個是一個抽象的32位句柄(HANDLE),它們之間是互斥的:所有的進程本地對象的索引都是本地進程的一個地址(address, ptr, binder),所有的遠程進程的對象的索引都是一個句柄(handle)。對於發送者進程來說,索引就是一個遠端對象的一個句柄,當Binder對象數據被髮送到遠端接收進程時,遠端接受進程則會認爲索引是一個本地對象地址,因此從第三方的角度來說,儘管名稱不同,對於一次Binder調用,兩種索引指的是同一個對象,Binder驅動則負責兩種索引的映射,這樣才能把數據發送給正確的進程。
對於Android的Binder來說,對象的索引和映射是通過binder_node和binder_ref兩個核心數據結構來完成的,對於Binder本地對象,對象的Binder地址保存在binder_node->ptr裏,對於遠程對象,索引就保存在binder_ref->desc裏,每一個binder_node都有一個binder_ref對象與之相聯繫,他們就是是通過ptr和desc來做映射的,如下圖:
flat_binder_object就是進程間傳遞的Binder對象,每一個flat_binder_object對象內核都有一個唯一的binder_node對象,這個對象掛接在binder_proc的一顆二叉樹上。對於一個binder_node對象,內核也會有一個唯一的binder_ref對象,可以這麼理解,binder_ref的desc唯一的映射到binder_node的ptr和cookie上,同時也唯一的映射到了flat_binder_object的handler上。而binder_ref又按照node和desc兩種方式映射到binder_proc對象上,也就是可以通過binder_node對象或者desc兩種方式在binder_proc上查找到binder_ref或binder_node。所以,對於flat_binder_object對象來說,它的binder+cookie和handler指向了同一個binder_node對象上,即同一個binder對象。
BinderDriverCommandProtocol
Binder驅動的命令協議(BC_命令),定義了Binder驅動支持的命令格式及數據定義(協議)。不同的命令所帶有的數據是不同的。Binder的命令由binder_write_read數據結構描述,它是ioctl命令(BINDER_WRITE_READ)的參數。
Binder的BC的命令格式是:| CMD | Data|| CMD | Data|..
BinderDriverReturnProtocol
Binder驅動的響應(返回,BR_)協議,定義了Binder命令的數據返回格式。同Binder命令協議一樣,Binder驅動返回協議也是通過BINDER_WRITE_READ ioctl命令實現的,不同的是它是read操作。
Binder BR的命令格式是:| CMD | Data|| CMD | Data|..
驅動接口
Android Binder設備驅動接口函數是device_initcall(binder_init);
binder_init
初始化函數首先創建了一個內核工作隊列對象(workqueue),用於執行可以延期執行的工作任務:
掛在這個workqueue上的work是binder_deferred_work,定義如下。當內核需要執行work任務時,就通過workqueue來調度執行這個work了。
既然說到了binder_deferred_work,這裏有必要來進一步說明一下,binder_deferred_work是在函數binder_defer_work裏調度的:
deferred_work有三種類型,分別是BINDER_DEFERRED_PUT_FILES,BINDER_DEFERRED_FLUSH和BINDER_DEFERRED_RELEASE。它們都操作在binder_proc對象上。
初始化函數接着使用proc_mkdir創建了一個Binder的proc文件系統的根節點(binder_proc_dir_entry_root,/proc/binder),併爲binder創建了binder proc節點(binder_proc_dir_entry_proc,/proc/binder/proc),然後Binder驅動使用misc_register把自己註冊爲一個Misc設備(/dev/misc/binder)。最後,如果驅動成功的創建了/proc/binder根節點,就調用create_proc_read_entry創建只讀proc文件:/proc/binder/state,/proc/binder/stats,/proc/binder/transactions,/proc/binder/transaction_log,/proc/binder/failed_transaction_log。
用戶接口
驅動程序的一個主要同能就是向用戶空間的程序提供操作接口,這個接口是標準的,對於Android Binder驅動,包含的接口有:
-Proc接口(/proc/binder)
. /proc/binder/state
. /proc/binder/stats
. /proc/binder/transactions
. /proc/binder/transaction_log
./proc/binder/failed_transaction_log
. /proc/binder/proc/
-設備接口(/dev/binder)
. binder_open
. binder_release
. binder_flush
. binder_mmap
. binder_poll
. binder_ioctl
binder_open
通常來說,驅動程序的open函數是用戶調用驅動接口來使用驅動功能的第一個函數,稱爲入口函數。同其他驅動一樣,對於Android驅動,任何一個進程及其內的所有線程都可以打開一個binder設備。首先來看看Binder驅動是如何打開設備的。
首先,binder驅動分配內存以保存binder_proc數據結構。然後,binder填充binder_proc數據(初始化),增加當前線程/進程的引用計數並賦值給tsk
初始化binder_proc的隊列及默認優先級
增加BINDER_STAT_PROC的對象計數,並把創建的binder_proc對象添加到全局的binder_proc哈希列表中,這樣任何一個進程就都可以訪問到其他進程的binder_proc對象了。
把當前進程/線程的線程組的pid(pid指向線程id)賦值給proc的pid字段,可以理解爲一個進程id(thread_group指向線程組中的第一個線程的task_struct結構)。同時把binder_proc對象指針賦值給filp的private_data對象保存起來。
最後,在bindr proc目錄中創建只讀文件(/proc/binder/proc/$pid)用來輸出當前binder proc對象的狀態。這裏要注意的是,proc->pid字段,按字面理解它應該是保存當前進程/線程的id,但實際上它保存的是線程組的pid,也就是線程組中的第一個線程的pid(等於tgid,進程id)。這樣當一個進程或線程打開一個binder設備時,驅動就會在內核中爲其創建binder_proc結構來保存打開此設備的進程/線程信息。
binder_release
binder_release是一個驅動的出口函數,當進程退出(exit)時,進程需要顯示或隱式的調用release函數來關閉打開的文件。Release函數一般來清理進程的內核數據,釋放申請的內存。Binder驅動的release函數相對比較簡單,它刪除open是創建的binder proc文件,然後調度一個workqueue來釋放這個進程/線程的binder_proc對象(BINDER_DEFERRED_RELEASE)。這裏要提的一點就是使用workqueue(deffered)可以提高系統的響應速度和性能,因爲Android Binder的release及flush等操作是一個複雜費事的任務,而且也沒有必要在系統調用裏完成它,因此最好的方法是延遲執行這個費時的任務。其實在中斷處理函數中我們經常要把一些耗時的操作放到底半部中處理(bottom half),這是一樣的道理。當然真正的釋放工作是在binder_deferred_release函數裏完成的.
binder_flush
flush操作在關閉一個設備文件描述符拷貝時被調用,Binder的flush函數十分簡單,它只是簡單的調度一個workqueue執行BINDER_DEFERRED_FLUSH操作。flush操作比較簡單,內核只是喚醒所有睡眠在proc對象及其thread對象上的所有函數。
Binder設備對內存映射是有些限制的,比如binder設備最大能映射4M的內存區域;binder不能映射具有寫權限的內存區域。
不同於一般的設備驅動,大多的設備映射的設備內存是設備本身具有的,或者在驅動初始化時由vmalloc或kmalloc等內核內存函數分配的,Binder的設備內存是在mmap操作時分配的,分配的方法是先在內核虛擬映射表上獲取一個可以使用的區域,然後分配物理頁,並把物理頁映射到獲取的虛擬空間上。由於設備內存是在mmap操作中實現的,因此每個進程/線程只能做映射操作一次,其後的操作都會返回錯誤。
具體來說,binder_mmap首先檢查mmap調用是否合法,即是否滿足binder內存映射的條件,主要檢查映射內存的大小、flags,是否是第一次調用。
然後,binder驅動從系統申請可用的虛擬內存空間(注意不是物理內存空間),這是通過get_vm_area內核函數實現的:(get_vm_area是一個內核,功能是在內核中申請並保留一塊連續的內核虛擬內存空間區域)
然後根據請求映射的內存空間大小,分配binder核心數據結構binder_proc的pages成員,它主要用來保存指向申請的物理頁的指針。
一切都準備就緒了,現在開始分配物理內存(page)了。這是通過binder驅動的幫助函數binder_update_page_range來實現的。儘管名字上稱爲update page,但在這裏它是在分配物理頁並映射到剛纔保留的虛擬內存空間上。當然根據參數,它也可以釋放物理頁面。在這裏,Binder使用alloc_page分配頁面,使用map_vm_area爲分配的內存做映射關係,使用vm_insert_page把分配的物理頁插入到用戶vma區域。函數傳遞的參數很有意識,我們來看一下:
開始地址是proc->buffer很容易理解,但是也許你會奇怪爲什麼結束地址是proc->buffer+PAGE_SIZE?這是因爲,在這裏並沒有全部分配物理內存,其實只是分配了一個頁的物理內存(第一,函數是在分配物理頁,就是說物理內容的分配是以頁面爲單位的,因此所謂的開始地址和結束地址是用來計算需要分配多少物理頁面的,這是開始地址和結束地址的含義。第二,前面已經提到mmap的最大物理內存是4M,因此需要的最多的pages就是1K,一個指針是4個字節,因此最大的page指針buffer就是4K,也就是一個頁的大小。第三,不管申請mmap物理內存是多大,內核總是分配一個頁的指針數據,也就是每次都分配最多的物理頁。)下面來看看物理頁是如何分配的:
注:alloc_page, map_vm_area和vm_insert_page都是Linux內核中內存相關函數。
binder_buffer和proc->buffer的關係如下圖:
當然,這裏會使用到的核心數據結構binder_proc,用到的主要域是
buffer 記錄binder_proc的內核虛擬地址的首地址
buffer_size 記錄binder_proc的虛擬地址的大小
user_buffer_offset 記錄binder_proc的用戶地址偏移,即用戶進程vma地址與binder申請的vma地址的偏差
pages 記錄指向binder_proc物理頁(struct page)的指針(二維指針)
files 記錄進程的struct file_struct 結構
vma 記錄用戶進程的vma結構
binder_poll
poll函數是非阻塞型IO(select,poll調用)的內核驅動實現,所有支持非阻塞IO操作的設備驅動都需要實現poll函數。Binder的poll函數僅支持設備是否可非阻塞的讀(POLLIN),這裏有兩種等待任務,一種是一種是proc_work,另一種是thread_work。同其他驅動的poll實現一樣,這裏也是通過調用poll_wait函數來實現的,這裏就不多做敘述了。這裏需要明確提的一點就是這裏調用了下面的函數來取得當前進程/線程的thread對象: 特別要指出的,也是很重要的一點,就是這個函數實際上在爲服務進程的線程池創建對應的thread對象。後面還會詳細講解這個函數。首先介紹一下的是這個函數會查找當前進程/線程的thread對象,thread對象根據pid值保存在:struct rb_node **p = &proc->threads.rb_node;的紅黑樹中,如果沒有找到,線程就會創建一個thread對象並且根據pid的值把新創建的thread對象插入到紅黑樹中。
binder_ioctl
這個函數是Binder的最核心部分,Binder的功能就是通過ioctl命令來實現的。Binder的ioctl命令共有7個,定義在ioctl.h頭文件中:首先要說明的是BINDER_SET_IDLE_TIMEOUT 和 BINDER_SET_IDLE_PRIORITY在目前的Binder驅動中沒有實現。
這裏最重要的就是BINDER_WRITE_READ命令,它是Binder驅動核心的核心,Binder IPC機制主要是通過這個命令來實現的。下面我們首先來介紹簡單的用於設置Binder驅動參數的幾個ioctl命令,最後着重講述BINDER_WRITE_READ命令。
來看這個函數的功能,當應用程序一進入ioctl調用的時候,驅動就檢查是否有錯誤,如果有錯誤的話,應用程序將要睡眠到binder_user_error_wait的等待隊列上,直到沒有錯誤或是睡眠被信號中斷。
注:wait_event_interruptible是一個內核等待隊列函數,它是進程睡眠知道等待的條件爲真,或是被signal喚醒而返回-ERESTARTSYS錯誤。
如果沒有用戶錯誤,那麼驅動就調用函數binder_get_thread來取得或創建當前線程/進程的thread對象,關於這個函數馬上就會介紹到了。這裏要說明的是每一個線程/進程都有一個對應的thread對象。1. BINDER_SET_MAX_THREADS
這個ioctl命令用於設置進程的Biner對象所支持的最大線程數。設置的值保存在binder_proc結構的max_threads成員裏。
2. BINDER_SET_CONTEXT_MGR
在這裏會引入Binder的另一個核心數據binder_node。從功能上看,只有一個進程/線程能成功設置binder_context_mgr_node對象,這個進程被稱爲Context Manager(context_mgr)。當然,也只有創建binder_context_mgr_node對象的Binder上下文管理進程/線程纔有權限重新設置這個對象。進程的權限(cred->euid)保存在binder_context_mgr_uid對象裏。
從接口的角度來說,這是一個進程想要成爲一個Context Manager的唯一接口。一個Context Manager進程需要爲binder_proc創建一個binder_node類型的節點。節點是通過binder_new_node函數來創建的,我們在後面在詳細講解這個函數。節點創建成功後內核會初始化節點的部分數據(weak_ref和strong_ref)。
binder_proc成員node是binder_node的根節點,這是一棵紅黑樹(一種平衡二叉樹)。現在來看看binder_new_node函數,它首先根據規則找到第一個頁節點作爲新插入的節點的父節點(規則就是binder_node的指針對象,後面還會遇到)。
注:rb_link_node和rb_insert_color都是內核紅黑樹函數,rb_link_node是一個內聯函數,它把新節點插入到紅黑樹中的指定父節點下。rb_insert_color節把已經插入到紅黑樹中的節點調整並融合到紅黑樹中(參考根據紅黑樹規則)。
插入完成後,做最後的初始化工作,這裏着重說明兩點,一是把binder_proc對象指針保存在binder_node對象裏,二是初始化node對象的鏈表頭指針。
這裏還要說明一下的是,對於ContextManager對象來說,binder_node是binder_context_mgr_node,這個是全局變量;binder對象的索引(handler)固定爲0。要記住這一點,後面還會遇到的。
3. BINDER_THREAD_EXIT
通過調用binder_free_thread終止並釋放binder_thread對象及其binder_transaction事務。
4. BINDER_VERSION
讀取當前Binder驅動支持的協議版本號。
5. BINDER_WRITE_READ
前面提到,這個ioctl命令纔是Binder最核心的部分,Android Binder的IPC機制就是通過這個接口來實現的。我們在這裏來介紹binder_thread對象,其實在前面已經見到過了,但是因爲它與這個接口更加緊密,因此我們把它拿到這裏來介紹。
每一個進程的binder_proc對象都有一個binder_thread對象隊列(保存在proc->threads.rb_node節點隊列裏),每一個進程/線程的id(pid)就保存在線程自己的binder_thread結構的pid成員裏。Binder_thread對象是在binder_get_thread函數中創建的,ioctl函數在入口處會調用它來取得或創建binder_thread對象:
binder_thread對象保存在binder_proc對象的thread成員裏,同binder_node一樣,它是一棵紅黑樹。首先我們先來看一下binder_get_thread函數。這個函數還是比較簡單的,它遍歷thread樹找到同當前進程相關的binder_thread對象,判斷條件就是當前進程/線程的pid要等於thread對象裏記錄的pid,看下面的代碼:
如果找到了binder_thread對象,就直接返回該對象。如果沒有找到,就說明當前進程/線程的binder_thread對象還沒有創建,創建一個新的binder_thread節點並插入到紅黑樹中,返回這個新創建的binder_thread對象。當然,這裏還要對binder_thread對象最一個初始化工作
這裏要說明的是,進程/線程有兩個通過binder_get_thread創建進程/線程的binder_thread對象,一個是在調用binder_poll()的時候,另一個是在調用binder_ioctl的時候。
在介紹BINDER_WRITE_READ之前,我們先來看一下接口的參數,我們知道,每一個ioctl命令都可以有一個數據參數,BINDER_WRITE_READ的參數是一個binder_write_read類型的數據結構,它描述了可讀寫的數據,BINDER_WRITE_READ就是根據它的具體內容來做寫操作或是讀操作。
其中,x_size表示有多少個字節需要讀寫,x_consumed表示了已經被內核讀寫了多少數據,x_buffer是指向讀寫數據的指針。具體的讀寫操作是通過兩個核心函數來實現的,分別是binder_thread_write和binder_thread_read。當有寫數據的時候,binder_thread_write函數就會被調用來發送binder命令,當有讀數據的使用,binder_thread_read就會被調用來讀取binder命令。
binder_thread_write
Binder協議的命令(BC_和BR_)就是通過這個BINDER_WRITE_READ ioctl接口實現的,驅動也按照Binder命令的格式(命令+參數)來分類處理,下面我們具體來看一下這些命令的實現。
bwr.write_buffer裏保存的就是指向Binder協議命令的指針數據,首先,函數要得到這個數據的首尾地址,並且根據條件來處理所有可處理的Binder命令。
按照Binder命令協議,首先讀取Binder命令,由於buffer裏只是指向命令的指針,實際數據還保存在用戶空間,因此調用get_user函數從用戶空間讀取數據。取得命令後,先更新命令的狀態信息,即增加命令的使用計數用於統計。
然後根據讀取的命令按照Binder命令的協議分類處理。首先來看看這四個命令:
- BC_INCREFS
- BC_ACQUIRE
- BC_RELEASE
- BC_DECREFS
這四個命令是Binder的binder_ref對象的操作命令,用於增加或減少對象的引用計數,其命令格式是:
cmd | desc
binder_ref是Android Binder驅動的另一個核心數據結構,用於描述Binder節點對象的對象索引,對象索引由binder_ref->desc來描述。節點對象的binder_ref索引是通過函數binder_get_ref_for_node來創建的,我們先來看看這個函數。
同其他binder對象一樣,binder_ref也是保存在一個紅黑樹中的,函數首先在紅黑樹中查找是否已經爲節點創建ref索引了:
如果還沒有爲節點對象創建ref索引,就爲這個節點創建一個新的索引對象,並把binder_proc和binder_node對象賦值給索引對象,然後link到proc的refs_by_node紅黑樹中:
最後把新索引插入到節點對象的哈斯列表裏,並返回新創建的binder_ref對象。再來看我們的binder_thread_write函數,前面我們已經讀到了四個跟binder_ref相關的命令,取得這四個命令後,驅動進入這四個命令的處理分支。首先,按照協議,繼續讀取四個字節的target descriptor。target descriptor描述了命令作用的對象,即前面我們提到的binder_ref裏的desc。
得到target desc後,根據這個desc值查詢得到它所代表的binder_ref對象,這裏有兩個處理流程,一個是對於Context Manager節點,如果是Context Manager節點,並且是BC_INCREFS或者BC_ACQUIRE操作的話,需要用binder_get_ref_for_node函數,因爲有可能需要爲Context Manager節點創建新的索引對象。如果不是的話,就直接調用binder_get_ref取得binder_ref對象。
我們繼續來探討一下binder_inc_ref和binder_dec_ref兩個函數。這兩個函數在管理節點索引的像,如果節點索引被使用(INCREFS活ACQUIRE),就增加索引對象的引用計數(strong++或者weak++),當然,對於第一個strong或者weak索引對象,還會相應的增加索引映射的節點對象的使用計數(還有一些其他的操作)。同理如果節點對象被釋放(DECREFS和RELEASE),就減少索引對象的引用計數,如果strong和weak都減少到0了,就表示沒有程序在使用這個索引對象,就可以刪除索引了。
這裏有一個問題,就是在減少ref->weak的引用計數的時候,爲了保持函數邏輯的一致性,應該在ref->weak減少到0的時候調用binder_dec_node(ref->node, strong, 1),比如代碼中的紅色部分。當然不調用也沒關係,即使strong減少到0的時候也可以不調用這個函數,因爲在binder_delete_ref函數裏就有相關的處理,但是顯然調用跟能保持函數邏輯的一致性。
注:binder_inc_node函數會增加節點的internal_strong_refs,local_strong_refs或者local_weak_refs的使用計數,並且把node->work.entry添加到鏈表target_list裏;同理,binder_dec_node函數會減少internal_strong_refs,local_strong_refs或者local_weak_refs的使用計數,並刪除節點的work.entry鏈表,這裏不再詳細描述。
下面我們來看另外兩個命令,他們跟前面的四個命令是有關係的,
BC_INCREFS_DONE
BC_ACQUIRE_DONE
這兩個命令在INCREFS或者ACQUIRE使用完的時候會發送,命令格式是:CMD
| ptr | cookie
同前面的命令處理一樣,首先按照協議從用戶空間讀取node_ptr和cookie數據,在根據讀取的node->ptr查詢到對應的節點對象。這個節點對象就是之前進程/線程根據自己的ptr和cookie創建的。這裏不會創建節點對象,因此如果節點不存在或者cookie值不對,都直接返回錯誤。
然後根據命令,設置節點的pending_strong_ref或者pending_weak_ref爲零,並且調用函數binder_dec_node來減少節點的使用計數,這兩個命令就處理完成了。
這個命令是有關於Binder的buffer管理的,我們姑且現在這裏做簡單的介紹,詳細的內容會在後面單獨詳細的講解Android的Binder
Buffer管理機制。
BC_FREE_BUFFER
命令格式是:CMD | data_ptr (data_ptr指向在read操作中接收到的transaction
data)
同樣,這個命令首先從用戶空間讀取data_ptr數據,然後根據data_ptr數據,在binder_proc對象裏查找binder_buffer對象
首先藥判斷是否查找到buffer對象,或者buffer對象是否允許用戶釋放(buffer->allow_user_free)。滿足這些條件後,做釋放前的一些準備工作:將binder_buffer裏的binder_transaction的buffer及transaction對象都設置爲空,同時,根據是否有async_transaction操作,將has_async_transaction設置爲0,或者把async_todo.next移動到thread->todo的鏈表尾上。
最後調用函數釋放binder_buffer對象:
BC_TRANSACTION
BC_REPLY
命令格式是:CMD | binder_transaction_data
這兩個命令應該是Binder驅動裏最最核心的兩個命令了,Binder機制的數據傳遞過程就是在這兩個命令裏完成的。首先,同前面兩個命令一樣,根據Binder協議,驅動首先從用戶空間讀取命令的參數binder_transaction_data,然後調用binder_transaction函數,實際的數據傳輸就是在這個函數裏完成的。
下面我們來看binder_transaction這個函數,根據命令的不同,函數有兩個分支,一個是TRANSACTION處理,另一個是REPLY分支處理。這個函數的寫法實在不敢恭維,由於混合了兩種不同的分支處理,函數的易讀性比較差,也導致這個函數比較大,大概有400多行。其實完全可以根據不同的功能,把公共的代碼獨立爲一個或者幾個函數,而把不同的功能拆分爲幾個不同函數,這樣也許更好些。或者Google認爲也許作爲一個函數的話性能更好?
TRANSACTION處理流程
我們先來看Transaction命令的處理流程,這時,structbinder_transaction_data裏的數據含義是:
tr->target.handle ->遠程對象索引,即binder_ref裏的desc
下面的圖描述了binder_transaction_data裏數據組織結構。Binder驅動要從用戶空間讀取這些數據發送給目標對象。
驅動程序首先根據binder_transaction_data數據裏的目標/遠程對象索引(target.handle)查找到目標節點對象,進而得到目標進程(binder_proc)對象。前面提到過,如果handle是0,就表示目標節點是Context Manger的節點。
如果本次binder_transaction不是異步傳輸(flag不是TF_ONE_WAY),並且thread->transaction_stack(就是struct binder_transaction的鏈表)不是空,就說明目前正位於Binder傳輸的中間環節,需要根據from_parent查找到目標thread對象。在前面的操作中,thread->transaction_stack的to_thread被設置爲它自己,因此,如果不是的話就說明整個thread的transaction堆棧有問題。如果沒問題,就根據前面得到的target_proc回溯查找到target_thread。這裏有一個疑問,就是當查找到的時候,程序不是中斷查找,而是繼續回溯查找,從邏輯上看就是要找到最靠近堆棧根的那個對象,是不是傳輸堆棧中一個thread對象可以被放到傳輸堆棧中多次,是這樣麼?
這裏要解釋一下什麼是binder_transaction對象。可以這麼理解,binder_transaction_data是binder傳輸對象的外部表示,應用於應用程序的,而binder_transaction是binder傳輸對象的內部表示,應用於內核binder驅動本身。binder_transaction對象都位於binder_thread的傳輸棧上,其本身是一個多級鏈表結構,描述了傳輸來源和傳輸目標,也記錄了本次傳輸的信息,如binder_work、binder_buffer、binder命令等。
如果查找到target_thread,那麼他就是目標線程,否則,target_proc就是傳輸的目標。根據傳輸的目標設置本次binder傳輸的目標等待隊列(wait_queue)和本次binder_work需要掛載的列表(list),也就是target_wait和target_list:
回憶一下binder_poll函數,target_wait就是進程/線程在poll函數裏的等待隊列,也就是本次binder_transaction最後要喚醒的本次傳輸的目標進程/線程。到目前,target_node,target_thread,target_proc,target_wait和target_list都已經找到了。下面就該爲此次傳輸分配新的binder_transaction對象和binder_work對象了,並根據當前的信息填充內容。首先填充基本內容:sender_euid、to_proc、to_thread、code、flag、priority,如果不是異步傳輸,就把當前的binder_thread對象賦值給from,否者from就爲空,以便記錄此次binder_transaction對象的來源。
然後爲binder_transaction分配buffer,這是一個binder_buffer對象,由函數binder_alloc_buf來分配,就如上面的圖形所示,buffer的數據大小和offset數量都通過binder_transaction_data由用戶空間傳過來:
回憶一下在前面的binder_mmap操作中,已經爲proc->buffer分配了一個頁的物理內存,並且以binder_buffer的形式添加到proc->free_buffers樹中。binder_alloc_buf就是到proc->free_buffers樹中查找是否有可用的物理內存空間,如果有,就在這個binder_buffer對象上分配物理頁,並將這個binder_buffer從free_buffers上移到alloc_buffers上。當然如果binder_buffer對象的物理內存空間比實際需要的物理空間大的話,就將剩餘的內存移出來分配給新的binder_buffer對象並添加到free_buffers樹中供下次使用。如果成功的申請到了binder_buffer空間,就填充數據到binder_buffer上,並將用戶空間的data和offset數據拷貝到binder_buffer的數據裏。
第一,如果發送端是binder對象(BINDER或WEAK_BINDER),驅動就從本地proc裏查找binder_node節點,如果節點還沒有創建就創建一個新的節點對象
找到/創建了binder_node對象後,就根據這個node對象在目標進程(target_proc)裏查找活創建這個節點的索引對象(binder_ref,也可稱爲參考對象)。有了binder_ref索引對象後,就講索引ID(ref->desc)賦值給flat_binder_object,同時更改flat_binder_object的type爲HANDLE或者WEAK_HANDLE,並增加索引和節點的引用計數。
第二,如果發送端是索引對象(HANDLE或WEAK_HANDLE),驅動就根據flat_binder_object裏的handle從本地的proc裏讀取binder_ref對象:
如果找到binder_ref對象,並且相對的binder_node裏的proc對象就是target_proc對象,那麼這就是我們需要的索引對象了,爲什麼這麼說呢?這是因爲節點對象只有一個,但是每個節點在不同的進程上都可能會有一個索引對象,所以對於一個固定的節點對象,索引對象可以有多個,但是對於給定的進程(binder_proc),每個節點的索引對象是唯一的。類似魚前面的binder對象,根據找到的索引對象轉換flat_binder_object以指向對應的binder對象
第三,如果發送端是文件對象(BINDER_TYPE_FD),發送端要與接收端共享一個打開的文件,這個處理就簡單的多了。驅動先根據發送端的文件描述符(fd)得到struct file對象,在根據struct file對象爲目標進程分配新的文件描述符。
所以的binder_flat_object都處理好了,下面就準備喚醒目標進程/線程了,但在此之前,還要設置本次傳輸是否需要回應(REPLY):
下面就是喚醒目標進程/線程了:
以上就是BC_TRANSACTION的處理流程。BC_REPLY的處理流程跟BC_TRANSACTION類似,只不過REPLY是在傳輸同步傳輸時接收端反饋的數據,原來的接收端變成了發送端,原來的發送端變成了接收端,因此在獲取target_proc,target_thread,binder_transaction時不同而已,下面我們來看一下:
首先得到原來的binder_transaction對象:
然後從in_reply_to裏取得需要的數據:binder_transaction,target_thread。找到target_thread,也就找到了target_proc對象了。
第二點不同的是,在喚醒目標進程/線程之前,需要通過binder_pop_transaction函數來釋放原來的binder_transaction對象:
BC_REGISTER_LOOPER
BC_ENTER_LOOPER
BC_EXIT_LOOPER
命令格式是:CMD
這三個命令用來設置binder looper的狀態,binder looper一共有六種狀態,而這三個命令主要用來設置其中的三種,如果出現錯誤則將狀態設置爲INVALID。
下面的圖描述了不同命令時LOOPER狀態的變化。從圖中可以看出BINDER_LOOPER_STATE_REGISTERED和BINDER_LOOPER_STATE_ENTERED是互斥的。
BC_REQUEST_DEATH_NOTIFICATION
BC_CLEAR_DEATH_NOTIFICATION
命令格式: CMD | target_ptr | cookie
這兩個命令通知目標進程/線程執行Request或者Clear DEATH_NOTIFICATION命令。首先本地進程/線程需要通知目標進程/線程執行Request命令,只有進入裏Death狀態的目標進程/線程纔可以Clear DEATH_NOTIFICATION。
按照協議,驅動首先從用戶空間讀取目標索引對象的target_ptr和cookie值,再根據讀取的target_ptr值取得目標節點的binder_ref索引對象。
首先我們來看BC_REQUEST_DEATH_NOTIFICATION這個命令。一個binder目標對象只能執行一次death操作,對於一個已經進入了death狀態的目標對象來說,驅動認爲已經執行成功了直接返回。否則,驅動就要創建一個新的binder_ref_death對象,並將這個death對象賦值給ref索引對象,然後喚醒目標進程/線程執行命令
BC_CLEAR_DEATH_NOTIFICATION
相反,對於BC_CLEAR_DEATH_NOTIFICATION命令,目標對象必須已經執行了Request Death操作,即ref索引對象裏的death不能爲空,否者就沒有必要執行這個命令了。這個命令相對簡單,從ref對象裏取得death對象,並將ref->death置空,然後喚醒目標進程/線程執行命令。
BC_DEAD_BINDER_DONE
命令格式: CMD |cookie
這個命令也是關於binder_ref_death的,按理說應該與前兩個放在一起,在這裏單獨拿出來是因爲他們的參數不同。按照協議,首先從用戶空間讀取death的cookie數據,因爲cookie數據唯一的標識了binder_ref_death對象,因此可以根據cookie數據在proc對象上查找到對應的binder_ref_death對象。
找到death對象後,將death->work對象從proc或thread的todo列表上刪除。如果death的work.type被設置爲BINDER_WORK_DEAD_BINDER_AND_CLEAR了,就將它修改爲BINDER_WORK_CLEAR_DEATH_NOTIFICATION,並喚醒thread或proc的等待隊列處理它。
binder_thread_read
現在開始介紹binder_thread_read這個函數,前面提到在binder_ioctl的BINDER_WRITE_READ命令裏,如果參數裏的binder_write_read->read_size>0,那麼就會調用binder_thread_read讀取數據。簡單的說,binder_thread_read就是處理掛在進程或者線程上的todo list,也就是binder_work。
前處理
binder_thread_read也有一個comsumed參數,由於記錄本次處理過程中實際消耗了多少數據,顯然對於一次數據處理,第一次進入此函數時,*consumed的數據爲0,如果本次沒有處理完成,用戶程序還可以根據*consumed繼續處理。Android binder的實現,對於一次處理流程,我們可以稱之爲事務,事務起始的時候都返回一個沒有參數的BR_NOOP命令。
那麼對read函數來說是怎麼區分處理proc->todo還是thread->todo呢?很簡單,就是根據thread->transaction_stack和thread->todo,如果都是空就是proc->todo,否則就是thread->todo。
binder_thread_read有兩種模式,一種是阻塞型,一種是非阻塞型,對於非阻塞型讀操作,如果沒有數據(也就是位於prco->todo或thread->todo上的binder_work),binder_thread_read就直接返回EAGAIN錯誤。而對於阻塞型讀操作,程序就進入睡眠狀態,等待有可操作的binder_work。
這裏提一下thread->looper的狀態,需要注意的是,第一,在處理proc_work的時候,只有狀態必須是BINDER_LOOPER_STATE_REGISTERED或者是BINDER_LOOPER_STATE_ENTERED;第二,不管處理的是thread->todo還是proc->todo,如果沒有待處理的binder_work,那麼狀態被設置爲BINDER_LOOPER_STATE_WAITING,否則就清除BINDER_LOOPER_STATE_WAITING標識。
對於待處理的binder_work,分爲六種類型,分別是:
查詢binder_work
程序首先讀取binder_work對象,程序的實現上是優先處理thread->todo的,只有函數進入的時候設置了wait_for_proc_work標識,才處理proc->todo。當還沒有處理任何命令(僅僅發送了BR_NOOP命令給用戶空間),並且thread->looper沒有設置BINDER_LOOPER_STATE_NEED_RETURN時,程序講進入下一次的睡眠等待中,否則,程序不進入下一次睡眠而進入返回流程。
binder_work的處理
得到binder_work對象後,根據binder_work->type進入不同的處理過程:
1,如果是BINDER_WORK_TRANSACTION,則得到bnider_transaction對象
2,如果是BINDER_WORK_TRANSACTION_COMPLETE,則發送BR_TRANSACTION_COMPLETE命令到用戶空間通知程序TRANSACTION處理完成。並將work從鏈表上刪除,釋放爲work分配的內存
3,如果是BINDER_WORK_NODE,根據binder_work得到binder_node節點,然後根據節點的strond或者weak類型,增加/獲取、減少/釋放節點的索引,參數就是node->ptr和node->cookie數據。
4,如果是BINDER_WORK_DEAD_BINDER,BINDER_WORK_DEAD_BINDER_AND_CLEAR或者BINDER_WORK_CLEAR_DEATH_NOTIFICATION有關DEAD_BINDER的操作,我們在前面的biner_thread_write裏已經介紹了相關的BC命令。由於read函數相對簡單一些,不需要在詳細描述了,請參考下面的binder_ref_death的處理流程(包括binder_thread_write部分):
返回binder_transaction_data
如果是關於BINDER_WORK_TRANSACTION類型的數據處理,則需要像用戶空間返回binder_transaction_data數據。前面提到已經得到binder_transaction對象了,如果是BC_TRANSACTION命令發過來的數據,則從binder_transaction裏取得target_node數據,填充binder_transaction_data:
如果是BC_REPLY發過來的命令,則發回的BR命令就是BR_REPLY,並且target_prt和cookie都是空:
驅動繼續根據binder_transaction對象填充返回給用戶空間的binder_transaction_data對象數據,然後發送給用戶空間程序:
然後將處理完的binder_transaction從鏈表上移出去,並潤許用戶釋放裏面的buffer內存。
到這裏我們就詳細的介紹了Android的用戶接口實現,包括了最重要的binder IOCTL BINDER_WRITE_READ及其內部實現機制。從這裏我們就可以詳細的瞭解Android驅動程序是如果發送和接受數據的,從而實現Binder的RPC功能。