在前面一篇文章Android系統匿名共用記憶體Ashmem(Anonymous Shared Memory)驅動程式原始碼分析中,我們系統地介紹了Android系統匿名共用記憶體的實現原理,其中著重介紹了它是如何輔助記憶體管理系統來有效地管理記憶體的,在再前面一篇文章Android系統匿名共用記憶體Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃中,我們還提到,Android系統匿名共用記憶體的另外一特點是通過Binder處理序間通訊機制來實現進程間共用的,本文中,將詳細介紹Android系統匿名共用記憶體是如何使用Binder處理序間通訊機制來實現進程間共用的。
由於Android系統匿名共用記憶體在進程間共用的原理涉及到Binder處理序間通訊機制的相關知識,所以希望讀者在繼續閱讀本文之前,最好對Android系統的Binder處理序間通訊機制有一定的瞭解,具體可以參考Android處理序間通訊(IPC)機制Binder簡要介紹和學習計劃這篇文章。
在Android系統匿名共用記憶體Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃這篇文章中,我們舉了一個例子來簡要地介紹了Android系統的匿名共用記憶體機制及其使用方法,在這篇文章中,我們繼續以這個執行個體來說明Android系統的匿名共用記憶體是如何使用Binder處理序間通訊機制來實現進程間共用的。為了方便描述,結合前面的Binder處理序間通訊機制知識,我們通過下面這個順序圖表來總結這個執行個體中的匿名共用記憶體檔案的檔案描述符在進程間傳輸的過程:
這裡, 我們需要關注的便是虛線框部分了,它在Binder驅動程式中實現了在兩個進程中共用同一個開啟檔案的方法。我們知道,在Linux系統中,檔案描述符其實就是一個整數。每一個進程在核心空間都有一個開啟檔案的數組,這個檔案描述符的整數值就是用來索引這個數組的,而且,這個檔案描述符只是在本進程內有效,也就是說,在不同的進程中,相同的檔案描述符的值,代表的可能是不同的開啟檔案。因此,在進程間傳輸檔案描述符時,不能簡要地把一個檔案描述符從一個進程傳給另外一個進程,中間必須做一過轉換,使得這個檔案描述在目標進程中是有效,並且它和源進程的檔案描述符所對應的開啟檔案是一致的,這樣才能保證共用。
在淺談Service Manager成為Android處理序間通訊(IPC)機制Binder守護進程之路一文中,我們介紹了用來傳輸的Binder對象的資料結構struct flat_binder_object,它定義在kernel/common/drivers/staging/android/binder.h 檔案中:
/* * This is the flattened representation of a Binder object for transfer * between processes. The 'offsets' supplied as part of a binder transaction * contains offsets into the data where these structures occur. The Binder * driver takes care of re-writing the structure type and data as it moves * between processes. */struct flat_binder_object {/* 8 bytes for large_flat_header. */unsigned longtype;unsigned longflags;/* 8 bytes of data. */union {void*binder;/* local object */signed longhandle;/* remote object */};/* extra data associated with local object */void*cookie;};
域type是一個枚舉類型,它的取值範圍是:
enum {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便是BINDER_TYPE_FD了,要傳輸的檔案描述符的值儲存在handle域中。
在Android系統處理序間通訊(IPC)機制Binder中的Server啟動過程原始碼分析一文中,我們詳細介紹了Binder對象在處理序間通訊傳輸的完整過程,這裡就不再詳述了,有興趣的讀都可以回過頭去參考一下。這裡,我們只關注檔案描述符類型的Binder對象在Binder驅動程式中的相關處理邏輯。
檔案描述符類型的Binder對象在Binder驅動程式中的相關處理邏輯實現在binder_transact函數,這個函數定義在kernel/common/drivers/staging/android/binder.c檔案中:
static voidbinder_transaction(struct binder_proc *proc, struct binder_thread *thread,struct binder_transaction_data *tr, int reply){struct binder_transaction *t;struct binder_work *tcomplete;size_t *offp, *off_end;struct binder_proc *target_proc;struct binder_thread *target_thread = NULL;struct binder_node *target_node = NULL;struct list_head *target_list;wait_queue_head_t *target_wait;struct binder_transaction *in_reply_to = NULL;struct binder_transaction_log_entry *e;uint32_t return_error;......offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));......off_end = (void *)offp + tr->offsets_size;for (; offp < off_end; offp++) {struct flat_binder_object *fp;......fp = (struct flat_binder_object *)(t->buffer->data + *offp);switch (fp->type) {......case BINDER_TYPE_FD: {int target_fd;struct file *file;if (reply) {if (!(in_reply_to->flags & TF_ACCEPT_FDS)) {binder_user_error("binder: %d:%d got reply with fd, %ld, but target does not allow fds\n",proc->pid, thread->pid, fp->handle);return_error = BR_FAILED_REPLY;goto err_fd_not_allowed;}} else if (!target_node->accept_fds) {binder_user_error("binder: %d:%d got transaction with fd, %ld, but target does not allow fds\n",proc->pid, thread->pid, fp->handle);return_error = BR_FAILED_REPLY;goto err_fd_not_allowed;}file = fget(fp->handle);if (file == NULL) {binder_user_error("binder: %d:%d got transaction with invalid fd, %ld\n",proc->pid, thread->pid, fp->handle);return_error = BR_FAILED_REPLY;goto err_fget_failed;}target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);if (target_fd < 0) {fput(file);return_error = BR_FAILED_REPLY;goto err_get_unused_fd_failed;}task_fd_install(target_proc, target_fd, file);if (binder_debug_mask & BINDER_DEBUG_TRANSACTION)printk(KERN_INFO " fd %ld -> %d\n", fp->handle, target_fd);/* TODO: fput? */fp->handle = target_fd;} break;......}}......}
這裡,我們先明確一下在Android系統匿名共用記憶體Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃這篇文章中所舉的例子擷取匿名共用記憶體檔案的檔案描述符的情境。匿名共用記憶體檔案是在Server進程建立的,Client通過IMemoryService.getFileDescriptor去擷取Server進程所建立的匿名共用記憶體檔案的檔案描述符,Server進程在返回這個檔案描述符的過程中進入到Binder驅動程式,即這裡的binder_transact函數。因此,這裡的當前執行binder_transact函數的進程是Server進程,即源進程是Server進程,而目標進程是Client進程,就是這裡的target_proc所表示的進程了。
函數binder_transaction處理檔案描述符類型的Binder對象就在中間的for迴圈裡面。
首先是獲得Binder對象,並儲存在本地變數fp中:
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
檔案描述符的值就儲存在fp->handle中,通過fget函數取回這個檔案描述符所對應的開啟檔案結構:
file = fget(fp->handle);
這裡的file是一個struct file指標,它表示一個開啟檔案結構。注間,在Linux系統中,開啟檔案結構struct file是可以在進程間共用的,它與檔案描述符不一樣。
接著在目標進程中獲得一個閒置檔案描述符:
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
現在,在目標進程中,開啟檔案結構有了,檔案描述符也有了,接下來就可以把這個檔案描述符和這個開啟檔案結構關聯起來就可以了:
task_fd_install(target_proc, target_fd, file);
由於這個Binder對象最終是要返回給目標進程的,所以還要修改fp->handle的值,它原來表示的是在源進程中的檔案描述符,現在要改成目標進程的檔案描述符:
fp->handle = target_fd;
這樣,對檔案描述符類型的Binder對象的處理就完成了。目標進程拿到這個檔案描述符後,就可以和源進程一起共用開啟檔案了。
至此,Android系統匿名共用記憶體利用Binder處理序間通訊機制來實現進程間共用的學習就結束了,整個Android系統匿名共用記憶體機制的學習也完成了,希望對讀者有所協助,重新學習Android系統匿名共用記憶體機制請回到Android系統匿名共用記憶體Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃一文。
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!