紅茶一杯話Binder(傳輸機制篇_上)

紅茶一杯話Bindercss

(傳輸機制篇_上)html

 

侯 亮node

 

1 Binder是如何作到精確打擊的?

        咱們先問一個問題,binder機制究竟是如何從代理對象找到其對應的binder實體呢?難道它有某種制導裝置嗎?要回答這個問題,咱們只能靜下心來研究binder驅動的代碼。在本系列文檔的初始篇中,咱們曾經介紹過ProcessState,這個結構是屬於應用層次的東西,僅靠它固然沒法完成精確打擊。其實,在binder驅動層,還有個與之相對的結構,叫作binder_proc。爲了說明問題,我修改了初始篇中的示意圖,獲得下圖:linux

傳輸機制篇_上001

1.1 建立binder_proc

        當構造ProcessState並打開binder驅動之時,會調用到驅動層的binder_open()函數,而binder_proc就是在binder_open()函數中建立的。新建立的binder_proc會做爲一個節點,插入一個總鏈表(binder_procs)中。具體代碼可參考kernel/drivers/staging/android/Binder.c。android

        驅動層的binder_open()的代碼以下:cookie

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
 
    . . . . . .
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
   
    get_task_struct(current);
    proc->tsk = current;
    . . . . . .
    hlist_add_head(&proc->proc_node, &binder_procs);
    proc->pid = current->group_leader->pid;
    . . . . . .
    filp->private_data = proc;
    . . . . . .
}

注意,新建立的binder_proc會被記錄在參數filp的private_data域中,之後每次執行binder_ioctl(),都會從filp->private_data域從新讀取binder_proc的。less

        binder_procs總表的定義以下:async

static HLIST_HEAD(binder_procs);

咱們能夠在List.h中看到HLIST_HEAD的定義:函數

【kernel/include/linux/List.h】ui

#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }

因而binder_procs的定義至關於:

struct hlist_head binder_procs  = { .first = NULL };

隨着後續不斷向binder_procs表中添加節點,這個表會不斷加長,示意圖以下:

傳輸機制篇_上002

1.2 binder_proc中的4棵紅黑樹

         binder_proc裏含有不少重要內容,不過目前咱們只需關心其中的幾個域:

struct binder_proc
{
    struct hlist_node proc_node;
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    int pid;
    . . . . . .
    . . . . . .
};

注意其中的那4個rb_root域,「rb」的意思是「red black」,可見binder_proc裏搞出了4個紅黑樹。

傳輸機制篇_上003

其中,nodes樹用於記錄binder實體,refs_by_desc樹和refs_by_node樹則用於記錄binder代理。之因此會有兩個代理樹,是爲了便於快速查找,咱們暫時只關心其中之一就能夠了。threads樹用於記錄執行傳輸動做的線程信息。

        在一個進程中,有多少「被其餘進程進行跨進程調用的」binder實體,就會在該進程對應的nodes樹中生成多少個紅黑樹節點。另外一方面,一個進程要訪問多少其餘進程的binder實體,則必須在其refs_by_desc樹中擁有對應的引用節點。

        這4棵樹的節點類型是不一樣的,threads樹的節點類型爲binder_thread,nodes樹的節點類型爲binder_node,refs_by_desc樹和refs_by_node樹的節點類型相同,爲binder_ref。這些節點內部都會包含rb_node子結構,該結構專門負責鏈接節點的工做,和前文的hlist_node有點兒殊途同歸,這也是linux上一個經常使用的小技巧。咱們以nodes樹爲例,其示意圖以下:

傳輸機制篇_上004

         rb_node和rb_root的定義以下:

struct rb_node
{
    unsigned long  rb_parent_color;
#define RB_RED      0
#define RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */
 
struct rb_root
{
    struct rb_node *rb_node;
};

        binder_node的定義以下:

struct binder_node
{
    int debug_id;
    struct binder_work work;
    union {
        struct rb_node rb_node;
        struct hlist_node dead_node;
    };
    struct binder_proc *proc;
    struct hlist_head refs;
    int internal_strong_refs;
    int local_weak_refs;
    int local_strong_refs;
    void __user *ptr;       // 注意這個域!
    void __user *cookie;    // 注意這個域!
    unsigned has_strong_ref:1;
    unsigned pending_strong_ref:1;
    unsigned has_weak_ref:1;
    unsigned pending_weak_ref:1;
    unsigned has_async_transaction:1;
    unsigned accept_fds:1;
    unsigned min_priority:8;
    struct list_head async_todo;
};

        咱們前文已經說過,nodes樹是用於記錄binder實體的,因此nodes樹中的每一個binder_node節點,必須可以記錄下相應binder實體的信息。所以請你們注意binder_node的ptr域和cookie域。

        另外一方面,refs_by_desc樹和refs_by_node樹的每一個binder_ref節點則和上層的一個BpBinder對應,並且更重要的是,它必須具備和「目標binder實體的binder_node」進行關聯的信息。binder_ref的定義以下:

struct binder_ref
{
    int debug_id;
    struct rb_node rb_node_desc;
    struct rb_node rb_node_node;
    struct hlist_node node_entry;
    struct binder_proc *proc;
    struct binder_node *node;   // 注意這個node域
    uint32_t desc;
    int strong;
    int weak;
    struct binder_ref_death *death;
};

請注意那個node域,它負責和binder_node關聯。另外,binder_ref中有兩個類型爲rb_node的域:rb_node_desc域和rb_node_node域,它們分別用於鏈接refs_by_desc樹和refs_by_node。也就是說雖然binder_proc中有兩棵引用樹,但這兩棵樹用到的具體binder_ref節點實際上是複用的。

        你們應該還記得,在《初始篇》中我是這樣表達BpBinder和BBinder關係的:

傳輸機制篇_上005

如今,咱們有了binder_ref和binder_node知識,能夠再畫一張圖,來解釋BpBinder究竟是如何和BBinder聯繫上的:

傳輸機制篇_上006

        上圖只表示了從進程1向進程2發起跨進程傳輸的意思,其實反過來也是能夠的,即進程2也能夠經過本身的「引用樹」節點找到進程1的「實體樹」節點,並進行跨進程傳輸。你們能夠本身補充上圖。

        OK,如今咱們能夠更深刻地說明binder句柄的做用了,好比進程1的BpBinder在發起跨進程調用時,向binder驅動傳入了本身記錄的句柄值,binder驅動就會在「進程1對應的binder_proc結構」的引用樹中查找和句柄值相符的binder_ref節點,一旦找到binder_ref節點,就能夠經過該節點的node域找到對應的binder_node節點,這個目標binder_node固然是從屬於進程2的binder_proc啦,不過沒關係,由於binder_ref和binder_node都處於binder驅動的地址空間中,因此是能夠用指針直接指向的。目標binder_node節點的cookie域,記錄的實際上是進程2中BBinder的地址,binder驅動只需把這個值反映給應用層,應用層就能夠直接拿到BBinder了。這就是Binder完成精確打擊的大致過程。

 

2 BpBinder和IPCThreadState

        接下來咱們來談談Binder傳輸機制。

        在《初始篇》中,咱們已經提到了BpBinder和ProcessState。當時只是說BpBinder是代理端的核心,主要負責跨進程傳輸,而且不關心所傳輸的內容。而ProcessState則是進程狀態的記錄器,它裏面記錄着打開binder驅動後獲得的句柄值。由於咱們並無進一步展開來討論BpBinder和ProcessState,因此也就沒有進一步打通BpBinder和ProcessState之間的關係。如今,咱們試着補充一些內容。

        做爲代理端的核心,BpBinder總要經過某種方式和binder驅動打交道,纔可能完成跨進程傳遞語義的工做。既然binder驅動對應的句柄在ProcessState中記着,那麼如今就要看BpBinder如何和ProcessState聯繫了。此時,咱們須要提到IPCThreadState。

        從名字上看,IPCThreadState是「和跨進程通訊(IPC)相關的線程狀態」。那麼很顯然,一個具備多個線程的進程裏應該會有多個IPCThreadState對象了,只不過每一個線程只需一個IPCThreadState對象而已。這有點兒「局部單例」的意思。因此,在實際的代碼中,IPCThreadState對象是存放在線程的局部存儲區(TLS)裏的。

 

2.1 BpBinder的transact()動做

        每當咱們利用BpBinder的transact()函數發起一次跨進程事務時,其內部實際上是調用IPCThreadState對象的transact()。BpBinder的transact()代碼以下:

status_t BpBinder::transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive)
    {
        status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
 
    return DEAD_OBJECT;
}

        固然,進程中的一個BpBinder有可能被多個線程使用,因此發起傳輸的IPCThreadState對象可能並非同一個對象,但這沒有關係,由於這些IPCThreadState對象最終使用的是同一個ProcessState對象。

2.1.1 調用IPCThreadState的transact()

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    . . . . . .
        // 把data數據整理進內部的mOut包中
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    . . . . . .
   
    if ((flags & TF_ONE_WAY) == 0)
    {
        . . . . . .
        if (reply)
        {
            err = waitForResponse(reply);
        }
        else
        {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        . . . . . .
    }
    else
    {
        err = waitForResponse(NULL, NULL);
    }
   
    return err;
}

         IPCThreadState::transact()會先調用writeTransactionData()函數將data數據整理進內部的mOut包中,這個函數的代碼以下:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
                                              int32_t handle, uint32_t code,
                                              const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;
 
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;
   
    . . . . . .
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    . . . . . .
   
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
   
    return NO_ERROR;
}

        接着IPCThreadState::transact()會考慮本次發起的事務是否須要回覆。「不須要等待回覆的」事務,在其flag標誌中會含有TF_ONE_WAY,表示一去不回頭。而「須要等待回覆的」,則須要在傳遞時提供記錄回覆信息的Parcel對象,通常發起transact()的用戶會提供這個Parcel對象,若是不提供,transact()函數內部會臨時構造一個假的Parcel對象。

        上面代碼中,實際完成跨進程事務的是waitForResponse()函數,這個函數的命名不太好,但咱們也沒必要太在乎,反正Android中寫得很差的代碼多了去了,又不僅多這一處。waitForResponse()的代碼截選以下:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;
 
    while (1)
    {
        // talkWithDriver()內部會完成跨進程事務
        if ((err = talkWithDriver()) < NO_ERROR)
            break;
       
        // 事務的回覆信息被記錄在mIn中,因此須要進一步分析這個回覆
        . . . . . .
        cmd = mIn.readInt32();
        . . . . . .
        switch (cmd)
        {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
       
        case BR_DEAD_REPLY:
            err = DEAD_OBJECT;
            goto finish;
 
        case BR_FAILED_REPLY:
            err = FAILED_TRANSACTION;
            goto finish;
        . . . . . .
        . . . . . .
        default:
            // 注意這個executeCommand()噢,它會處理BR_TRANSACTION的。
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }
 
finish:
    . . . . . .
    return err;
}
 

2.1.2 talkWithDriver()

        waitForResponse()中是經過調用talkWithDriver()來和binder驅動打交道的,說到底會調用ioctl()函數。由於ioctl()函數在傳遞BINDER_WRITE_READ語義時,既會使用「輸入buffer」,也會使用「輸出buffer」,因此IPCThreadState專門搞了兩個Parcel類型的成員變量:mIn和mOut。總之就是,mOut中的內容發出去,發送後的回覆寫進mIn。

         talkWithDriver()的代碼截選以下:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    . . . . . .
    binder_write_read bwr;
   
    . . . . . .
    bwr.write_size = outAvail;
    bwr.write_buffer = (long unsigned int)mOut.data();
    . . . . . .
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (long unsigned int)mIn.data();
    . . . . . .
    . . . . . .
    do
    {
        . . . . . .
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        . . . . . .
    } while (err == -EINTR);
 
    . . . . . .
    . . . . . .
    return err;
}

看到了嗎?mIn和mOut的data會先整理進一個binder_write_read結構,而後再傳給ioctl()函數。而最關鍵的一句,固然就是那句ioctl()了。此時使用的文件描述符就是前文咱們說的ProcessState中記錄的mDriverFD,說明是向binder驅動傳遞語義。BINDER_WRITE_READ表示咱們但願讀寫一些數據。

        至此,應用程序經過BpBinder向遠端發起傳輸的過程就交代完了,數據傳到了binder驅動,一切就看binder驅動怎麼作了。至於驅動層又作了哪些動做,咱們留在下一篇文章再介紹。

 

如需轉載本文內容,請註明出處。

http://my.oschina.net/youranhongcha/blog/152233

謝謝。

相關文章
相關標籤/搜索