【译】BINDER TRANSACTIONS IN THE BOWELS OF THE LINUX KERNEL
活頁夾是Android中主要的IPC / RPC(進程間通信)系統。?它允許應用程序彼此通信,并且它是Android環境中幾種重要機制的基礎。?例如,Android服務是建立在Binder之上的。?與Binder交換的消息稱為活頁夾事務?,它們可以傳輸簡單數據(例如整數),但也可以處理更復雜的結構,例如文件描述符,內存緩沖區或對象上的弱/強引用。?Internet上有很多有趣的Binder文檔,但是關于如何將消息從一個進程轉換到另一個進程的細節很少。?本文試圖描述Binder如何處理消息以及如何在不同進程之間執行復雜對象(文件描述符,指針)的轉換。?為此,將從用戶區到綁定程序內核進行綁定程序事務。
USERLAND中的活頁夾
在探索Binder內核模塊如何工作之前,讓我們看一下在調用Android Service的情況下如何在用戶區中準備事務。
Android服務概述
服務是在后臺運行并為其他應用程序提供功能的Android組件。?其中一些是Android框架的一部分,但已安裝的應用程序也可以公開其自身的功能。?當應用程序要公開新服務時,它首先注冊到“服務管理器”?(1)?,其中包含并更新所有正在運行的服務的列表。?稍后,客戶端向處理程序請求ServiceManager?(2)與該服務進行通信,并能夠調用公開的函數(3)?。
服務互動
從Android 8.0開始,存在三個不同的Binder域。?每個域都有其自己的服務管理器,并且可以通過/dev/的相應設備進行訪問。?下表描述了每個Binder域有一個設備:
綁定器域,摘自Android文檔網站
?
為了使用活頁夾系統,一個過程需要打開這些設備之一并執行一些初始化步驟,然后再發送或接收活頁夾交易。
準備活頁夾交易
Android框架在活頁夾設備頂部包含多個抽象層。?通常,當開發人員實施新服務時,他們會描述以高級語言公開的接口。?在框架應用程序的情況下,描述是使用AIDL語言編寫的,而由供應商開發的硬件服務則具有以HIDL語言編寫的接口描述。?這些描述被編譯到Java / C ++文件中,在其中使用Parcel組件對參數進行反序列化。?生成的代碼包含兩個類,一個Binder Proxy和一個Binder Stub?。?代理類用于請求遠程服務,而存根則用于接收傳入呼叫,如下圖所示。
?
黏合劑層
在最低級別,使用域對應的設備將應用程序連接到Binder內核模塊。?他們使用ioctl?syscall來發送和接收綁定程序消息。
序列化步驟使用Parcel類完成,該類提供了在Binder消息中讀取和寫入數據的功能。?有兩種不同的宗地類:
- /dev/binder和/dev/vndbinder域基于AIDL描述語言,并使用在frameworks/native/include/binder/Parcel.h定義的Parcel。?這個包裹允許發送基本類型和文件描述符?。?例如,以下代碼摘自命令SHELL_COMMAND_TRANSACTION的默認代理實現。?該命令準備并寫入遠程服務使用的標準輸入,輸出和錯誤流的文件描述符。
- /dev/hwbinder域使用在先前的基礎上在system/libhwbinder/include/hwbinder/Parcel.h實現的另一個Parcel。?這種Parcel實現允許發送數據緩沖區,例如C結構。?數據緩沖區可以嵌套,并包含指向其他結構的指針。?在以下示例中,結構hild_memory結構包含一個嵌入式結構(?hild_string?)和一個內存指針(?mHandle?):
這兩種Parcel能夠發送文件描述符和帶有內存地址的復雜數據結構。?因為這些元素包含特定于調用者進程的數據,所以Parcel組件將綁定對象寫入事務消息中。
活頁夾對象
除了簡單類型(字符串,整數等)之外,還可以發送綁定對象。?活頁夾對象是一種類型值為以下之一的結構:
//摘自:drivers / staging / Android / uapi / binder.h枚舉 {BINDER_TYPE_BINDER = B_PACK_CHARS( 's' , 'b' , '*' , B_TYPE_LARGE),BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS( 'w' , 'b' , '*' , B_TYPE_LARGE),BINDER_TYPE_HANDLE = B_PACK_CHARS( 's' , 'h' , '*' , B_TYPE_LARGE),BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS( 'w' , 'h' , '*' , B_TYPE_LARGE),BINDER_TYPE_FD = B_PACK_CHARS( 'f' , 'd' , '*' , B_TYPE_LARGE),BINDER_TYPE_FDA = B_PACK_CHARS( 'f' , 'd' , 'a' , B_TYPE_LARGE),BINDER_TYPE_PTR = B_PACK_CHARS( 'p' , 't' , '*' , B_TYPE_LARGE),};下面是一個類型為BINDER_TYPE_PTR的活頁夾對象的BINDER_TYPE_PTR?:
structinder_object_header {__u32 類型;};structinder_buffer_object {結構 binder_object_header hdr;__u32 標志;binding_uintptr_t 緩沖區;binding_size_t 長度;binding_size_t 父對象;活頁夾大小};hdr下方的屬性是特定于類型的。
不同的活頁夾對象可以描述如下:
- BINDER_TYPE_BINDER和BINDER_TYPE_WEAK_BINDER?:這些類型是對本地對象的強引用和弱引用。
- BINDER_TYPE_HANDLER和BINDER_TYPE_WEAK_HANDLE?:這些類型是對遠程對象的強引用和弱引用。
- BINDER_TYPE_FD?:此類型用于發送文件描述符號。?這通常用于發送ashmem共享內存以傳輸大量數據。?實際上,活頁夾交易消息被限制為1 MB。?但是,可以使用任何文件描述符類型(文件,套接字,標準輸入等)。
- BINDER_TYPE_FDA?:描述文件描述符數組的對象。
- BINDER_TYPE_PTR?:用于使用內存地址及其大小發送緩沖區的對象。
當Parcel類編寫緩沖區或文件描述符時,它將在數據緩沖區中添加活頁夾對象(圖上為藍色)。?活頁夾對象和簡單類型混合在數據緩沖區中。?每次寫入對象時,其相對位置都會插入到偏移緩沖區中(紫色)。
?
?
活頁夾消息緩沖區和偏移量
一旦data和offets緩沖區已滿,就準備將binder_transaction_data傳遞給內核。?我們可以注意到它包含上述指針,數據緩沖區和偏移量數組的大小。?字段handler用于設置目標過程,該過程是先前由服務管理器檢索的。?另一個有趣的屬性是code?,其中包含要執行的遠程服務的方法ID。
//文件:development / ndk / platforms / android-9 / include / linux / binder.hstructinder_transaction_data {工會 {size_t 句柄;無效 * ptr;} 目標;無效 * cookie;未簽名的 int 代碼;unsigned int 標志;pid_t sender_pid;uid_t sender_euid;size_t data_size;size_t offsets_size;工會 {結構 {const void * buffer;const void * 偏移量;} ptr;uint8_t buf [ 8 ];} 數據;};在調用ioctl之前,必須填充最后一個結構(?binder_write_read?)。?它包含讀寫命令緩沖區,并指向上一個緩沖區:
//文件:development / ndk / platforms / android-9 / include / linux / binder.hstructinder_write_read {簽名 長 write_size;簽名 長 write_consumed;無符號 長 write_buffer;簽名 長 read_size;簽名 長 read_consumed;無符號 長 read_buffer;};發送活頁夾事務所需的數據結構可以用下面的圖總結:
binding_write_read結構
我們可以注意到,?write_buffer并不直接指向binder_transaction_data結構。?它以命令標識符為前綴。?如果是交易,則值為BC_TRANSACTION_SG?。
請注意,除了BC_TRANSACTION_SG以外,?BC_TRANSACTION_SG存在許多命令,例如BC_ACQUIRE和BC_RELEASE以增加或減少強處理程序,或者在停止遠程服務時會注意到BC_REQUEST_DEATH_NOTIFICATION?。
現在所有人都準備好執行綁定程序事務,調用者需要使用命令BINDER_WRITE_READ調用ioctl?,內核模塊將處理該消息并轉換目標進程的所有綁定程序對象:強/弱處理程序,文件描述符和緩沖區。
在下一部分中,讓我們繼續在內核方面進行分析!
活頁夾內核模塊
現在,調用者進程已準備好其數據并執行了一個ioctl來發送事務。?所有活頁夾對象都將被翻譯,并且消息將被復制到目標內存中。
用于ioctl的命令由binder_ioctl_write_read函數處理,該函數執行數據參數的安全復制。
//文件:drivers / android / binder.c靜態 長 binder_ioctl ( 結構 文件 * filp, 無符號 int cmd, 無符號 長 arg){// [...]開關 (cmd) {情況 BINDER_WRITE_READ:ret = 活頁夾_ioctl_write_read(filp, cmd, arg, thread );如果 (ret)轉到 錯誤;休息 ; //文件:drivers / android / binder.c靜態 整數 binder_ioctl_write_read ( 結構 文件 * filp,unsigned int cmd, unsigned long arg,struct binder_thread * 線程 ){// [...]如果 (copy_from_user( & bwr, ubuf, sizeof (bwr))) {ret = -EFAULT;出去}// [...]如果 (bwr.write_size > 0 ) {ret = binder_thread_write(proc, thread ,bwr.write_buffer,bwr.write_size,& bwr.write_consumed);在寫事務的情況下,將調用函數binder_thread_write?,然后將與事務關聯的命令調度到相應的處理程序。
//文件:drivers / android / binder.c開關 (cmd) {情況 BC_INCREFS:情況 BC_ACQUIRE:情況 BC_RELEASE:情況 BC_DECREFS:// [...]情況 BC_TRANSACTION_SG:案例 BC_REPLY_SG: {struct binder_transaction_data_sg tr;如果 (copy_from_user( & tr, ptr, sizeof (tr)))返回 -EFAULT;ptr + = sizeof (tr);binding_transaction(proc, thread 和 tr.transaction_data,cmd == BC_REPLY_SG, tr.buffers_size);休息 ;}// [...]對于命令BC_TRANSACTION_SG?,在userland中準備的binder_transaction_data緩沖區由binder_transaction函數處理。
活頁夾交易
binder_transaction函數位于文件drivers/staging/Android/binder.c?。
這個重要的功能執行以下任務:在目標進程中(在活頁夾保留的內存中)分配一個緩沖區,驗證所有數據對象并執行轉換,在目標內存進程中復制數據和偏移緩沖區。
為了驗證活頁夾對象,內核查看包含所有對象相對位置的offsets緩沖區。?取決于對象類型,內核執行不同的轉換。
//文件:drivers / android / binder.c靜態 void binder_transaction ( struct binder_proc * proc,struct binder_thread * 線程 ,struct binder_transaction_data * tr, int 回復,binding_size_t extra_buffers_size){// [...]// bind_transaction函數中的對象驗證。// offp是指向偏移量緩沖區的指針for (; offp < off_end; offp ++ ) {struct binder_object_header * hdr;size_t object_size = 活頁夾驗證對象(t- > buffer, * offp);如果 (object_size == 0 || * offp < off_min) {bindingr_user_error( “%d:%d獲得了具有無效偏移量(%lld,最小%lld最大%lld)或對象的事務。 \ n ” ,proc- > pid, 線程 -> pid, (u64) * offp,(u64)off_min,(u64)t- > 緩沖區 -> data_size);return_error = BR_FAILED_REPLY;return_error_param = -EINVAL;return_error_line = __LINE__;轉到 err_bad_offset;}hdr = ( struct binder_object_header * )(t- > 緩沖區 -> 數據 + * offp);off_min = * offp + object_size;開關 (hdr- > type) {情況 BINDER_TYPE_BINDER:案例 BINDER_TYPE_WEAK_BINDER: {// [..]驗證和翻譯案例 BINDER_TYPE_HANDLE:案例 BINDER_TYPE_WEAK_HANDLE: {// [..]驗證和翻譯}案例 BINDER_TYPE_FD:{// [..]驗證和翻譯}案例 BINDER_TYPE_FDA:{// [..]驗證和翻譯}案例 BINDER_TYPE_PTR: {// [..]驗證和翻譯}弱/強粘結劑/處理機
活頁夾對象引用可以是指向本地對象的虛擬內存地址(活頁夾引用),也可以是標識另一個進程的遠程對象的處理程序(處理程序引用)。
當內核獲取對象引用(本地或遠程)時,它將更新內部表,該表包含每個進程的真實虛擬內存地址和處理程序(binder <=>處理程序)之間的映射。
有兩種翻譯:
- 將虛擬內存地址轉換為處理程序:?binder_translate_binder
- 將處理程序轉換為虛擬內存地址:?binder_translate_handle
Binder內核模塊保留共享對象的引用計數。?與新進程共享引用時,其計數器值將增加。?當不再使用參考時,將通知所有者并可以釋放它。
活頁夾->處理程序翻譯
//文件:drivers / android / binder.c靜態 int binding_translate_binder ( struct flat_binder_object * fp,struct binder_transaction * t,struct binder_thread * 線程 ){// [...]節點 = binder_get_node(proc,fp- > 粘合劑);if ( ! 節點) {節點 = binder_new_node(proc, fp);如果 ( ! 節點)返回 -ENOMEM;}如果 (fp- > cookie != 節點 -> cookie) {// [...]錯誤}// SELinux檢查如果 (security_binder_transfer_binder(proc- > tsk, target_proc- > tsk)) {// [...]錯誤}ret = binder_inc_ref_for_node(target_proc, 節點,fp- > hdr.type == BINDER_TYPE_BINDER,& 線程 -> todo 和& rdata);如果 (ret)完成如果 ( fp- > hdr.type == BINDER_TYPE_BINDER)fp- > hdr.type = BINDER_TYPE_HANDLE;其他fp- > hdr.type = BINDER_TYPE_WEAK_HANDLE;fp- > 粘結劑 = 0 ;fp- > handle = rdata.desc;fp- > cookie = 0 ;// [..]}該函數獲取與綁定器值(虛擬地址)對應的節點,或者如果不存在該節點,則創建一個新節點。?此節點在本地對象和遠程對象(?rdata.desc?)之間具有關聯。?在SELinux安全檢查之后,引用計數器將增加,并且綁定程序對象中的引用值將更改,并由引用處理程序替換。
處理程序->活頁夾翻譯
//文件:drivers / android / binder.c靜態 整數 binder_translate_handle ( 結構 flat_binder_object * fp,struct binder_transaction * t,struct binder_thread * 線程 ){// [...]節點 = binder_get_node_from_ref(proc,fp- > 句柄,fp- > hdr.type == BINDER_TYPE_HANDLE 和 src_rdata);if ( ! 節點) {// [...]錯誤}// SELinux安全檢查如果 (security_binder_transfer_binder(proc- > tsk, target_proc- > tsk)) {ret = -EPERM;完成}binding_node_lock(node);if ( node- > proc == target_proc) {如果 ( fp- > hdr.type == BINDER_TYPE_HANDLE)fp- > hdr.type = BINDER_TYPE_BINDER;其他fp- > hdr.type = BINDER_TYPE_WEAK_BINDER;fp- > 粘合劑 = 節點 -> ptr;fp- > cookie = 節點 -> cookie;// [...]binding_inc_node_nilocked(node,fp- > hdr.type == BINDER_TYPE_BINDER,0 , NULL);// [...]} 其他 {struct binder_ref_data dest_rdata;ret = binder_inc_ref_for_node(target_proc, 節點,fp- > hdr.type == BINDER_TYPE_HANDLE,NULL & dest_rdata);// [...]fp- > 粘結劑 = 0 ;fp- > handle = dest_rdata.desc;fp- > cookie = 0 ;}完成:binding_put_node(node);返回 ret}此翻譯功能與上一個功能非常相似。?但是,我們可以注意到,處理程序引用可以在不同的過程之間共享。?如果目標進程與節點匹配,則僅在綁定程序引用中轉換處理程序引用。
文件描述符
當聯編程序對象類型為BINDER_TYPE_FD或BINDER_TYPE_FDA時,內核需要檢查文件描述符是否正確(與打開的struct文件相關聯)并在目標進程中將其復制。?翻譯是由binder_translate_fd函數完成的。?詳情如下:
//文件:drivers / android / binder.c靜態 整數 bind_translate_fd ( 整數 fd,struct binder_transaction * t,struct binder_thread * 線程 ,struct binder_transaction * in_reply_to){// [...]// 1:檢查目標是否允許文件描述符如果 (in_reply_to)target_allows_fd = !! (in_reply_to- > 標志 和 TF_ACCEPT_FDS);其他target_allows_fd = t- > 緩沖區 -> target_node- > accept_fds;如果 ( ! target_allows_fd) {binder_user_error( “%d:%d使用fd獲得了%s,%d,但是目標不允許fds \ n ” ,proc- > pid, 線程 -> pid,in_reply_to ? “ reply” : “交易” ,fd);ret = -EPERM;轉到 err_fd_not_accepted;}// 2:獲取與文件描述符號相對應的文件結構文件 = fget(fd);如果 ( ! 文件) {binding_user_error( “%d:%d獲得了無效fd的交易,%d \ n ” ,proc- > pid, 線程 -> pid, fd);ret = -EBADF;轉到 err_fget;}// 3:SELinux檢查ret = security_binder_transfer_file(proc- > tsk, target_proc- > tsk, file);如果 (ret < 0 ) {ret = -EPERM;轉到 err_security;}// 4:在目標進程中獲取一個“免費”文件描述符號。target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);如果 (target_fd < 0 ) {ret = -ENOMEM;轉到 err_get_unused_fd;}// 5:將“ file”插入到具有target_fd文件描述符編號的目標進程中。task_fd_install(target_proc, target_fd, 文件);返回 target_fd;// [...]}經過一些驗證之后,對task_fd_install的最后一次調用將在目標進程中添加與調用方文件描述符關聯的文件。?在內部,它使用內核API函數__fd_install在進程fd數組中安裝文件指針。
緩沖對象
緩沖對象是最有趣的。?它們由硬件服務的Parcel類使用,并允許傳輸內存緩沖區。?緩沖區對象具有一種層次結構機制,可用于修補父對象的偏移量。?這對于發送包含指針的結構非常有用。?活頁夾緩沖區對象由以下結構定義:
//文件:include / uapi / linux / android / binder.hstructinder_buffer_object {結構 binder_object_header hdr;__u32 標志;binding_uintptr_t 緩沖區;binding_size_t 長度;binding_size_t 父對象;活頁夾大小};讓我們看一個例子:我們有以下代碼,我們想使用Binder發送hidl_string結構的實例。
struct hidl_string {//從C樣式的字符串復制。 nullptr將創建一個空字符串hidl_string( const char * );// ...私人的 :詳細信息 :: hidl_pointer < const char > mBuffer; //指向真正的char字符串的指針uint32_t mSize; //不包括結尾的'\ 0'。bool mOwnsBuffer; //如果為true,則mBuffer為可變字符*};hidl_string my_obj ( “我的演示字符串” );創建my_obj時,將執行堆分配以存儲給定的字符串,并設置屬性mBuffer?。?要將這個對象發送到另一個進程,需要兩個BINDER_TYPE_PTR對象:
- 第一個binder_buffer_offset?,其緩沖區字段指向my_obj結構
- 第二個指向堆中的字符串。?該對象必須是先前對象的子對象,并將parent_offset屬性設置為char * str在結構中的位置
下圖詳細說明了所需的兩個綁定程序對象的配置:
?
活頁夾消息緩沖區
當內核轉換這些對象時,它將修補子緩沖區中描述的偏移量,并將不同的緩沖區([object.buffer,object.buffer + object.length])復制到目標內存進程中。?在我們的例子中,對應于屬性mBuffer的偏移量是用指針修補的,該指針將字符串存儲在目標存儲過程中。
?
活頁夾消息緩沖區
為了解析my_obj數據,目標進程讀取第一個緩沖區以獲取hidl_struct?(3),而下一個緩沖區的預期大小為mSize以確保結構(?mSize?)中描述的大小與包含該大小的緩沖區的大小相同。字符串(4)?。
結論
Binder是一個復雜而強大的IPC / RPC系統,它可以使整個Android生態系統正常工作。?即使內核組件很舊,也很少有有關其工作原理的文檔。?此外,最近在Android內核(?https://lore.kernel.org/patchwork/patch/757477/?)中添加了有趣的對象類型BINDER_TYPE_FDA和BINDER_TYPE_PTR?。?這些新類型是Android 8.0中通過Treble項目引入的新HAL架構中的通信基礎(HIDL)。
參考文獻
-
喬納森·萊文的Andevon-Bidner
-
HIDL Android文檔
-
Android 9.0.0_r3源代碼
-
活頁夾源代碼
-
?
總結
以上是生活随笔為你收集整理的【译】BINDER TRANSACTIONS IN THE BOWELS OF THE LINUX KERNEL的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一次可靠的通信
- 下一篇: 《深入理解java虚拟机》第2章 Jav