1. 程式人生 > >深入分析Android Binder 驅動

深入分析Android Binder 驅動

Binder通訊是基於Service和Client的,所有需要IBinder通訊的程序都必須建立一個IBinder介面。系統使用一個名為ServiceManager的收穫程序管理著系統中的各個服務,它負責監聽是否有其他程式向其傳送請求,如果有請求就響應,如果沒有,則繼續監聽等待。每個服務都要在ServiceManager中註冊,而請求服務的客戶端則向ServiceManager請求服務。在Android虛擬機器啟動之前,系統會先啟動ServiceManager程序,ServiceManager會開啟並通知Binder驅動程式自己將作為系統的服務管理者,然後ServiceManager進入一個迴圈,等待處理來自其他程序的資料。

Android Binder是一種在Android裡廣泛使用的一種遠端過程呼叫介面。從結構上來說Android Binder系統是一種伺服器/客戶機模式,包括Binder ServerBinder ClientAndroid Binder驅動,實際的資料傳輸就是通過Android Binder驅動來完成的,這裡我們就來詳細的介紹Android Binder驅動程式。

通常來說,BinderAndroid系統中的內部程序通訊(IPC)之一。在Android系統中共有三種IPC機制,分別是:

-標準Linux Kernel IPC介面-標準D-BUS介面Binder介面

儘管Google

宣稱Binder具有更加簡潔、快速,消耗更小記憶體資源的優點,但並沒有證據表明D-BUS就很差。實際上D-BUS可能會更合適些,或許只是當時Google並沒有注意到它吧,或者Google不想使用GPL協議的D-BUS庫。我們不去探究具體的原因了,你只要清楚Android系統中支援了多個IPC介面,而且大部分程式使用的是我們並不熟悉的Binder介面。

BinderOpenBinderGoogle精簡實現,它包括一個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協議

Android Binder機制是基於OpenBinder來實現的,是一個OpenBinderLinux實現。Android Binder的協議定義在binder.h標頭檔案中,Android的通訊就是基於這樣的一個協議的。

Binder Type

Android定義了五個(三大類)Binder型別,如下:

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 Object

程序間傳輸的資料被稱為Binder物件(Binder Object),它是一個flat_binder_object,定義如下:

struct flat_binder_object {
    /* 8 bytes for large_flat_header. */
    unsigned long        type;
    unsigned long        flags;
    /* 8 bytes of data. */
    union {
        void        *binder;    /* local object */
        signed long    handle;        /* remote object */
    };
    /* extra data associated with local object */
    void            *cookie;
};

其中,型別欄位描述了Binder物件的型別,flags描述了傳輸方式,比如同步、非同步等。

enum transaction_flags {
    TF_ONE_WAY    = 0x01,    /* this is a one-way call: async, no return */
    TF_ROOT_OBJECT    = 0x04,    /* contents are the component's root object */
    TF_STATUS_CODE    = 0x08,    /* contents are a 32-bit status code */
    TF_ACCEPT_FDS    = 0x10,    /* allow replies with file descriptors */
};

傳輸的資料是一個複用資料聯合體,對於BINDER型別,資料就是一個binder本地物件,如果是HANDLE型別,這資料就是一個遠端的handle物件。該如何理解本地binder物件和遠端handle物件呢?其實它們都指向同一個物件,不過是從不同的角度來說。舉例來說,假如A有個物件X,對於A來說,X就是一個本地的binder物件;如果B想訪問AX物件,這對於B來說,X就是一個handle。因此,從根本上來說handlebinder都指向X。本地物件還可以帶有額外的資料,儲存在cookie中。

Binder物件的傳遞是通過binder_transaction_data來實現的,即Binder物件實際是封裝在binder_transaction_data結構體中。

binder_transaction_data

這個資料結構才是真正要傳輸的資料。它的定義如下:

struct binder_transaction_data {
    /* The first two are only used for bcTRANSACTION and brTRANSACTION,
     * identifying the target and contents of the transaction.
     */
    union {
        size_t    handle;    /* target descriptor of command transaction */
        void    *ptr;    /* target descriptor of return transaction */
    } target;
    void        *cookie;    /* target object cookie */
    unsigned int    code;        /* transaction command */
    /* General information about the transaction. */
    unsigned int    flags;
    pid_t        sender_pid;
    uid_t        sender_euid;
    size_t        data_size;    /* number of bytes of data */
    size_t        offsets_size;    /* number of bytes of offsets */
    /* If this transaction is inline, the data immediately
     * follows here; otherwise, it ends with a pointer to
     * the data buffer.
     */
    union {
        struct {
            /* transaction data */
            const void    *buffer;
            /* offsets from buffer to flat_binder_object structs */
            const void    *offsets;
        } ptr;
        uint8_t    buf[8];
    } data;
};

結構體中的資料成員target是一個複合聯合體物件,請參考前面的關於binder本地物件及handle遠端物件的描述。code是一個命令,描述了請求Binder物件執行的操作。

物件的索引和對映

Binder中有兩種索引,一是本地程序地址空間的一個地址,另一個是一個抽象的32位控制代碼(HANDLE),它們之間是互斥的:所有的程序本地物件的索引都是本地程序的一個地址(address, ptr, binder),所有的遠端程序的物件的索引都是一個控制代碼(handle)。對於傳送者程序來說,索引就是一個遠端物件的一個控制代碼,當Binder物件資料被髮送到遠端接收程序時,遠端接受程序則會認為索引是一個本地物件地址,因此從第三方的角度來說,儘管名稱不同,對於一次Binder呼叫,兩種索引指的是同一個物件,Binder驅動則負責兩種索引的對映,這樣才能把資料傳送給正確的程序。

對於AndroidBinder來說,物件的索引和對映是通過binder_nodebinder_ref兩個核心資料結構來完成的,對於Binder本地物件,物件的Binder地址儲存在binder_node->ptr裡,對於遠端物件,索引就儲存在binder_ref->desc裡,每一個binder_node都有一個binder_ref物件與之相聯絡,他們就是是通過ptrdesc來做對映的,如下圖:


flat_binder_object就是程序間傳遞的Binder物件,每一個flat_binder_object物件核心都有一個唯一的binder_node物件,這個物件掛接在binder_proc的一顆二叉樹上。對於一個binder_node物件,核心也會有一個唯一的binder_ref物件,可以這麼理解,binder_refdesc唯一的對映到binder_nodeptrcookie上,同時也唯一的對映到了flat_binder_objecthandler上。而binder_ref又按照nodedesc兩種方式對映到binder_proc物件上,也就是可以通過binder_node物件或者desc兩種方式在binder_proc上查詢到binder_refbinder_node。所以,對於flat_binder_object物件來說,它的binder+cookiehandler指向了同一個binder_node物件上,即同一個binder物件。

BinderDriverCommandProtocol

Binder驅動的命令協議(BC_命令),定義了Binder驅動支援的命令格式及資料定義(協議)。不同的命令所帶有的資料是不同的。Binder的命令由binder_write_read資料結構描述,它是ioctl命令(BINDER_WRITE_READ)的引數。

struct binder_write_read {
    signed long    write_size;    /* bytes to write */
    signed long    write_consumed;    /* bytes consumed by driver */
    unsigned long    write_buffer;
    signed long    read_size;    /* bytes to read */
    signed long    read_consumed;    /* bytes consumed by driver */
    unsigned long    read_buffer;
};
對於寫操作,write_buffer包含了一系列請求執行緒執行的Binder命令;對於讀(返回)操作,read_buffer包含了一系列執行緒執行後填充的返回值。Binder命令(BC_)用於BINDER_WRITE_READ的write操作。
Binder的BC的命令格式是:| CMD | Data|| CMD | Data|..
enum BinderDriverCommandProtocol {
	BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data),
	BC_REPLY = _IOW('c', 1, struct binder_transaction_data),
	/*
	 * binder_transaction_data: the sent command.
	 */

	BC_ACQUIRE_RESULT = _IOW('c', 2, int),
	/*
	 * not currently supported
	 * int:  0 if the last BR_ATTEMPT_ACQUIRE was not successful.
	 * Else you have acquired a primary reference on the object.
	 */

	BC_FREE_BUFFER = _IOW('c', 3, int),
	/*
	 * void *: ptr to transaction data received on a read
	 */

	BC_INCREFS = _IOW('c', 4, int),
	BC_ACQUIRE = _IOW('c', 5, int),
	BC_RELEASE = _IOW('c', 6, int),
	BC_DECREFS = _IOW('c', 7, int),
	/*
	 * int:	descriptor
	 */

	BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie),
	BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie),
	/*
	 * void *: ptr to binder
	 * void *: cookie for binder
	 */

	BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc),
	/*
	 * not currently supported
	 * int: priority
	 * int: descriptor
	 */

	BC_REGISTER_LOOPER = _IO('c', 11),
	/*
	 * No parameters.
	 * Register a spawned looper thread with the device.
	 */

	BC_ENTER_LOOPER = _IO('c', 12),
	BC_EXIT_LOOPER = _IO('c', 13),
	/*
	 * No parameters.
	 * These two commands are sent as an application-level thread
	 * enters and exits the binder loop, respectively.  They are
	 * used so the binder can have an accurate count of the number
	 * of looping threads it has available.
	 */

	BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_ptr_cookie),
	/*
	 * void *: ptr to binder
	 * void *: cookie
	 */

	BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_ptr_cookie),
	/*
	 * void *: ptr to binder
	 * void *: cookie
	 */

	BC_DEAD_BINDER_DONE = _IOW('c', 16, void *),
	/*
	 * void *: cookie
	 */
};

BinderDriverReturnProtocol

Binder驅動的響應(返回,BR_)協議,定義了Binder命令的資料返回格式。同Binder命令協議一樣,Binder驅動返回協議也是通過BINDER_WRITE_READ ioctl命令實現的,不同的是它是read操作。

Binder BR的命令格式是:| CMD | Data|| CMD | Data|..

enum BinderDriverReturnProtocol {
	BR_ERROR = _IOR('r', 0, int),
	/*
	 * int: error code
	 */

	BR_OK = _IO('r', 1),
	/* No parameters! */

	BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data),
	BR_REPLY = _IOR('r', 3, struct binder_transaction_data),
	/*
	 * binder_transaction_data: the received command.
	 */

	BR_ACQUIRE_RESULT = _IOR('r', 4, int),
	/*
	 * not currently supported
	 * int: 0 if the last bcATTEMPT_ACQUIRE was not successful.
	 * Else the remote object has acquired a primary reference.
	 */

	BR_DEAD_REPLY = _IO('r', 5),
	/*
	 * The target of the last transaction (either a bcTRANSACTION or
	 * a bcATTEMPT_ACQUIRE) is no longer with us.  No parameters.
	 */

	BR_TRANSACTION_COMPLETE = _IO('r', 6),
	/*
	 * No parameters... always refers to the last transaction requested
	 * (including replies).  Note that this will be sent even for
	 * asynchronous transactions.
	 */

	BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie),
	BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie),
	BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie),
	BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie),
	/*
	 * void *:	ptr to binder
	 * void *: cookie for binder
	 */

	BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie),
	/*
	 * not currently supported
	 * int:	priority
	 * void *: ptr to binder
	 * void *: cookie for binder
	 */

	BR_NOOP = _IO('r', 12),
	/*
	 * No parameters.  Do nothing and examine the next command.  It exists
	 * primarily so that we can replace it with a BR_SPAWN_LOOPER command.
	 */

	BR_SPAWN_LOOPER = _IO('r', 13),
	/*
	 * No parameters.  The driver has determined that a process has no
	 * threads waiting to service incomming transactions.  When a process
	 * receives this command, it must spawn a new service thread and
	 * register it via bcENTER_LOOPER.
	 */

	BR_FINISHED = _IO('r', 14),
	/*
	 * not currently supported
	 * stop threadpool thread
	 */

	BR_DEAD_BINDER = _IOR('r', 15, void *),
	/*
	 * void *: cookie
	 */
	BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, void *),
	/*
	 * void *: cookie
	 */

	BR_FAILED_REPLY = _IO('r', 17),
	/*
	 * The the last transaction (either a bcTRANSACTION or
	 * a bcATTEMPT_ACQUIRE) failed (e.g. out of memory).  No parameters.
	 */
};

驅動介面

Android Binder裝置驅動介面函式是device_initcall(binder_init);

我們知道一般來說裝置驅動的介面函式是module_initmodule_exit,這麼做是為了同時相容支援靜態編譯的驅動模組(buildin)和動態編譯的驅動模組(module)。但是AndroidBinder驅動顯然不想支援動態編譯的驅動,如果你需要將Binder驅動修改為動態的核心模組,可以直接將device_initcall修改為module_init,但不要忘了增加module_exit的驅動解除安裝介面函式。

binder_init

初始化函式首先建立了一個核心工作佇列物件(workqueue),用於執行可以延期執行的工作任務:

static struct workqueue_struct *binder_deferred_workqueue;
binder_deferred_workqueue = create_singlethread_workqueue("binder");

掛在這個workqueue上的workbinder_deferred_work,定義如下。當核心需要執行work任務時,就通過workqueue來排程執行這個work了。

static DECLARE_WORK(binder_deferred_work, binder_deferred_func);
queue_work(binder_deferred_workqueue, &binder_deferred_work);

既然說到了binder_deferred_work,這裡有必要來進一步說明一下,binder_deferred_work是在函式binder_defer_work裡排程的:

static void binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer)
{
    mutex_lock(&binder_deferred_lock);
    proc->deferred_work |= defer;
    if (hlist_unhashed(&proc->deferred_work_node)) {
        hlist_add_head(&proc->deferred_work_node,
                &binder_deferred_list);
        queue_work(binder_deferred_workqueue, &binder_deferred_work);
    }
    mutex_unlock(&binder_deferred_lock);
}

deferred_work有三種類型,分別是BINDER_DEFERRED_PUT_FILESBINDER_DEFERRED_FLUSHBINDER_DEFERRED_RELEASE。它們都操作在binder_proc物件上。

enum binder_deferred_state {
    BINDER_DEFERRED_PUT_FILES    = 0x01,
    BINDER_DEFERRED_FLUSH        = 0x02,
    BINDER_DEFERRED_RELEASE      = 0x04,
};

初始化函式接著使用proc_mkdir建立了一個Binderproc檔案系統的根節點(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_init)中初始化的

binder_open

通常來說,驅動程式的open函式是使用者呼叫驅動介面來使用驅動功能的第一個函式,稱為入口函式。同其他驅動一樣,對於Android驅動,任何一個程序及其內的所有執行緒都可以開啟一個binder裝置。首先來看看Binder驅動是如何開啟裝置的。

首先,binder驅動分配記憶體以儲存binder_proc資料結構。然後,binder填充binder_proc資料(初始化),增加當前執行緒/程序的引用計數並賦值給tsk

get_task_struct(current);
proc->tsk = current;

初始化binder_proc的佇列及預設優先順序

INIT_LIST_HEAD(&proc->todo);
init_waitqueue_head(&proc->wait);
proc->default_priority = task_nice(current);

增加BINDER_STAT_PROC的物件計數,並把建立的binder_proc物件新增到全域性的binder_proc雜湊列表中,這樣任何一個程序就都可以訪問到其他程序的binder_proc物件了。

binder_stats.obj_created[BINDER_STAT_PROC]++;
hlist_add_head(&proc->proc_node, &binder_procs);

把當前程序/執行緒的執行緒組的pidpid指向執行緒id)賦值給procpid欄位,可以理解為一個程序idthread_group指向執行緒組中的第一個執行緒的task_struct結構)。同時把binder_proc物件指標賦值給filpprivate_data物件儲存起來。

proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
filp->private_data = proc;

最後,在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)。這裡要提的一點就是使用workqueuedeffered)可以提高系統的響應速度和效能,因為Android Binderreleaseflush等操作是一個複雜費事的任務,而且也沒有必要在系統呼叫裡完成它,因此最好的方法是延遲執行這個費時的任務。其實在中斷處理函式中我們經常要把一些耗時的操作放到底半部中處理(bottom half),這是一樣的道理。當然真正的釋放工作是在binder_deferred_release函式裡完成的.

binder_flush

flush操作在關閉一個裝置檔案描述符拷貝時被呼叫,Binderflush函式十分簡單,它只是簡單的排程一個workqueue執行BINDER_DEFERRED_FLUSH操作。flush操作比較簡單,核心只是喚醒所有睡眠在proc物件及其thread物件上的所有函式。

binder_mmap

mmap(memory map)用於把裝置記憶體對映到使用者程序地址空間中,這樣就可以像操作使用者記憶體那樣操作裝置記憶體。(還有一種說法,mmap用於把使用者程序地址空間對映到裝置記憶體上,儘管說法不同,但是說的是同一個事情)
Binder裝置對記憶體對映是有些限制的,比如binder裝置最大能對映4M的記憶體區域;binder不能對映具有寫許可權的記憶體區域。
不同於一般的裝置驅動,大多的裝置對映的裝置記憶體是裝置本身具有的,或者在驅動初始化時由vmalloc或kmalloc等核心記憶體函式分配的,Binder的裝置記憶體是在mmap操作時分配的,分配的方法是先在核心虛擬對映表上獲取一個可以使用的區域,然後分配物理頁,並把物理頁對映到獲取的虛擬空間上。由於裝置記憶體是在mmap操作中實現的,因此每個程序/執行緒只能做對映操作一次,其後的操作都會返回錯誤。
具體來說,binder_mmap首先檢查mmap呼叫是否合法,即是否滿足binder記憶體對映的條件,主要檢查對映記憶體的大小、flags,是否是第一次呼叫。
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
…
}
vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
if (proc->buffer) {
…
}
然後,binder驅動從系統申請可用的虛擬記憶體空間(注意不是實體記憶體空間),這是通過get_vm_area核心函式實現的:(get_vm_area是一個核心,功能是在核心中申請並保留一塊連續的核心虛擬記憶體空間區域)
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
proc->buffer = area->addr;
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
然後根據請求對映的記憶體空間大小,分配binder核心資料結構binder_proc的pages成員,它主要用來儲存指向申請的物理頁的指標。
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
proc->buffer_size = vma->vm_end - vma->vm_start;
一切都準備就緒了,現在開始分配實體記憶體(page)了。這是通過binder驅動的幫助函式binder_update_page_range來實現的。儘管名字上稱為update page,但在這裡它是在分配物理頁並對映到剛才保留的虛擬記憶體空間上。當然根據引數,它也可以釋放物理頁面。在這裡,Binder使用alloc_page分配頁面,使用map_vm_area為分配的記憶體做對映關係,使用vm_insert_page把分配的物理頁插入到使用者vma區域。函式傳遞的引數很有意識,我們來看一下:
binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma);
開始地址是proc->buffer很容易理解,但是也許你會奇怪為什麼結束地址是proc->buffer+PAGE_SIZE?這是因為,在這裡並沒有全部分配實體記憶體,其實只是分配了一個頁的實體記憶體(第一,函式是在分配物理頁,就是說物理內容的分配是以頁面為單位的,因此所謂的開始地址和結束地址是用來計算需要分配多少物理頁面的,這是開始地址和結束地址的含義。第二,前面已經提到mmap的最大實體記憶體是4M,因此需要的最多的pages就是1K,一個指標是4個位元組,因此最大的page指標buffer就是4K,也就是一個頁的大小。第三,不管申請mmap實體記憶體是多大,核心總是分配一個頁的指標資料,也就是每次都分配最多的物理頁。)下面來看看物理頁是如何分配的:
*page = alloc_page(GFP_KERNEL | __GFP_ZERO);
tmp_area.addr = page_addr;
tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
page_array_ptr = page;
ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
ret = vm_insert_page(vma, user_page_addr, page[0]);

注:alloc_page, map_vm_areavm_insert_page都是Linux核心中記憶體相關函式。

物理頁分配完後,這個實體記憶體就交給binder_buffer來管理了,剛剛申請的實體記憶體以binder_buffer的方式放到proc->buffers連結串列裡。
struct binder_buffer *buffer;
buffer = proc->buffer;
INIT_LIST_HEAD(&proc->buffers);
list_add(&buffer->entry, &proc->buffers);
buffer->free = 1;
binder_insert_free_buffer(proc, buffer);
proc->free_async_space = proc->buffer_size / 2;
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函式來實現的,這裡就不多做敘述了。
poll_wait(filp, &thread->wait, wait);
這裡需要明確提的一點就是這裡呼叫了下面的函式來取得當前程序/執行緒的thread物件:
thread = binder_get_thread(proc);
特別要指出的,也是很重要的一點,就是這個函式實際上在為服務程序的執行緒池建立對應的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標頭檔案中:
#define BINDER_WRITE_READ               _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT         _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS          _IOW('b', 5, size_t)
#define BINDER_SET_IDLE_PRIORITY        _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR          _IOW('b', 7, int)
#define BINDER_THREAD_EXIT              _IOW('b', 8, int)
#define BINDER_VERSION                  _IOWR('b', 9, struct binder_version)
首先要說明的是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的等待佇列上,直到沒有錯誤或是睡眠被訊號中斷。
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
    return ret;

注: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 Managercontext_mgr)。當然,也只有建立binder_context_mgr_node物件的Binder上下文管理程序/執行緒才有許可權重新設定這個物件。程序的許可權(cred->euid)儲存在binder_context_mgr_uid物件裡。

binder_context_mgr_uid = current->cred->euid;

從介面的角度來說,這是一個程序想要成為一個Context Manager的唯一介面。一個Context Manager程序需要為binder_proc建立一個binder_node型別的節點。節點是通過binder_new_node函式來建立的,我們在後面在詳細講解這個函式。節點建立成功後核心會初始化節點的部分資料(weak_refstrong_ref)。

binder_context_mgr_node->local_weak_refs++;
binder_context_mgr_node->local_strong_refs++;
binder_context_mgr_node->has_strong_ref = 1;
binder_context_mgr_node->has_weak_ref = 1;

binder_proc成員nodebinder_node的根節點,這是一棵紅黑樹(一種平衡二叉樹)。現在來看看binder_new_node函式,它首先根據規則找到第一個頁節點作為新插入的節點的父節點(規則就是binder_node的指標物件,後面還會遇到)。

while (*p) {
    parent = *p;
    node = rb_entry(parent, struct binder_node, rb_node);
    if (ptr < node->ptr)
        p = &(*p)->rb_left;
    else if (ptr > node->ptr)
        p = &(*p)->rb_right;
    else
        return NULL;
}
找到節點了,那麼呼叫核心函式建立並插入節點吧
node = kzalloc(sizeof(*node), GFP_KERNEL);
binder_stats.obj_created[BINDER_STAT_NODE]++;
rb_link_node(&node->rb_node, parent, p);
rb_insert_color(&node->rb_node, &proc->nodes);
注:rb_link_node和rb_insert_color都是核心紅黑樹函式,rb_link_node是一個行內函數,它把新節點插入到紅黑樹中的指定父節點下。rb_insert_color節把已經插入到紅黑樹中的節點調整並融合到紅黑樹中(參考根據紅黑樹規則)。
插入完成後,做最後的初始化工作,這裡著重說明兩點,一是把binder_proc物件指標儲存在binder_node物件裡,二是初始化node物件的連結串列頭指標。
node->proc = proc;
node->ptr = ptr;
node->cookie = cookie;
node->work.type = BINDER_WORK_NODE;
INIT_LIST_HEAD(&node->work.entry);
INIT_LIST_HEAD(&node->async_todo);

這裡還要說明一下的是,對於ContextManager物件來說,binder_nodebinder_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 BinderIPC機制就是通過這個介面來實現的。我們在這裡來介紹binder_thread物件,其實在前面已經見到過了,但是因為它與這個介面更加緊密,因此我們把它拿到這裡來介紹。

每一個程序的binder_proc物件都有一個binder_thread物件佇列(儲存在proc->threads.rb_node節點佇列裡),每一個程序/執行緒的id(pid)就儲存線上程自己的binder_thread結構的pid成員裡。Binder_thread物件是在binder_get_thread函式中建立的,ioctl函式在入口處會呼叫它來取得或建立binder_thread物件:

thread = binder_get_thread(proc);

binder_thread物件儲存在binder_proc物件的thread成員裡,同binder_node一樣,它是一棵紅黑樹。首先我們先來看一下binder_get_thread函式。這個函式還是比較簡單的,它遍歷thread樹找到同當前程序相關的binder_thread物件,判斷條件就是當前程序/執行緒的pid要等於thread物件裡記錄的pid,看下面的程式碼:

while (*p) {
    parent = *p;
    thread = rb_entry(parent, struct binder_thread, rb_node);
    if (current->pid < thread->pid)
        p = &(*p)->rb_left;
    else if (current->pid > thread->pid)
        p = &(*p)->rb_right;
    else
        break;
}

如果找到了binder_thread物件,就直接返回該物件。如果沒有找到,就說明當前程序/執行緒的binder_thread物件還沒有建立,建立一個新的binder_thread節點並插入到紅黑樹中,返回這個新建立的binder_thread物件。當然,這裡還要對binder_thread物件最一個初始化工作

thread = kzalloc(sizeof(*thread), GFP_KERNEL);
binder_stats.obj_created[BINDER_STAT_THREAD]++;
thread->proc = proc;
thread->pid = current->pid;
init_waitqueue_head(&thread->wait);
INIT_LIST_HEAD(&thread->todo);
rb_link_node(&thread->rb_node, parent, p);
rb_insert_color(&thread->rb_node, &proc->threads);
thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
thread->return_error = BR_OK;
thread->return_error2 = BR_OK;

這裡要說明的是,程序/執行緒有兩個通過binder_get_thread建立程序/執行緒的binder_thread物件,一個是在呼叫