Linux 4.16 Binder驅動學習筆記 ------數據結構

前言

本文主要對Binder驅動下的數據結構和設備的初始化過程進行簡要的分析,以理清數據結構對象之間的關係。基於Linux-4.15node

Binider進程的通訊機制以下圖所示:算法

圖片描述

Client、Service以及ServiceManager都運行在用戶空間中,而Binder Driver運行在內核空間中,其中ServiceManager和Binder驅動由系統負責提供,Clien和Services由應用程序實現。而Binder驅動與Service、Client、Servicemanager的交互是經過open、mmap、ioctl等接口實現通訊的。爲了更好地理解上層的Binder通訊,所以有必要對下層驅動進行簡單的分析。segmentfault

1. 數據結構

1.1 binder_work 處理的工做項

struct binder_work{
    //用於嵌入宿主,包括進程,線程
    struct list_head entry;
    //描述工做項類型
    enum {
        BINDER_WORK_TRANSACTION = 1,
        BINDER_WORK_TRANSACTION_COMPLETE,
        BINDER_WORK_RETURN_ERROR,
        BINDER_WORK_NODE,
        //下面三項爲死亡通知類型
        BINDER_WORK_DEAD_BINDER,
        BINDER_WORK_DEAD_BINDER_AND_CLEAR,
        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
    } type;
}
  • binder_work主要是爲了描述待處理的工做項,因爲工做項有可能屬於進程或線程,利用entry能夠將該結構體嵌入到宿主的結構當中,而list_head的數據結構是雙向鏈表的節點,而list_head自己是不帶有數據閾的,其經常使用的用法即嵌入到其餘數據結構如binder_work造成鏈表,雙向鏈表的結構如圖所示:

圖片描述

有關kernel雙向鏈表的實現,可參考Kernel list數據結構學習數組

  • type描述的是工做項的類型,能夠分爲cookie

    • BINDER_WORK_TRANSACTION
    • BINDER_WORK_TRANSACTION_COMPLETE
    • BINDER_WORK_TRANSACTION_NODE
    • BINDER_WORK_DEAD_BINDER
    • BINDER_WORK_DEAD_BINDER_AND_CLEAR
    • BINDER_WORK_CLEAR_DEATH_NOTIFICATION

1.2 binder_node 實體對象

binder_node用於描述Binder實體對象,每一個Service組件都在Binder驅動中對應一個binder_node實體,以描述其在內核的狀態。其數據結構以下:數據結構

struct binder_node {
    int debug_id;
    spinlock_t lock;
    struct binder_work work;//Binder實體須要處理的工做項
    union {
        struct rb_node rb_node; //紅黑樹節點,宿主進程有一棵紅黑樹維護全部Binder實體
        struct hlist_node dead_node;//hash節點,用於保存在全局hash列表中
    };
    struct binder_proc *proc; //用於指向Binder實體對象宿主進程
    struct hlist_head refs; //全部Client引用了該Binder實體都會保存在該hash變量中
    int internal_strong_refs;//強引用計數
    int local_weak_refs; //弱引用計數
    int local_strong_refs; //強引用計數
    int tmp_refs;
    binder_uintptr_t ptr;//指向Service組件地址
    binder_uintptr_t cookie;//指向Service組件內部的引用計數
    struct {
        /*
         * bitfield elements protected by
         * proc inner_lock
         */
        u8 has_strong_ref:1;
        u8 pending_strong_ref:1;
        u8 has_weak_ref:1;
        u8 pending_weak_ref:1;
    };
    struct {
        /*
         * invariant after initialization
         */
        u8 accept_fds:1;
        u8 min_priority;
    };
    bool has_async_transaction;//Binder實體是否正在處理一個異步事務
    struct list_head async_todo;//用於保存異步事務隊列
};
  • debug_id用於在debug中標誌Binder實體的ID身份
  • binder_node包含一個binder工做項
  • proc是指向Binder實體對象的宿主進程
  • 假如宿主並沒Dead,那麼宿主進程將會使用紅黑樹來維護其內部的Binder_node實體,而binder_node實體中將會有一個rb_node(紅黑樹)節點結構,做爲宿主紅黑樹中的一員。
  • 假如宿主已經Dead了,那麼將會將節點加入到全局Hash表中。

在這裏能夠稍微補充下關於紅黑樹的基本特性:異步

  • 每一個節點要麼是紅色要麼是黑的
  • 根節點是黑的
  • 每一個葉結點(葉結點即指樹尾端NIL指針或NULL結點)都是黑的
  • 若是一個結點是紅的,那麼它的兩個兒子都是黑的。
  • 對於任意結點而言,其到葉結點樹尾端NIL指針的每條路徑都包含相同數目的黑結點。
紅黑樹的優點在於可以以O(log2 n)的時間複雜度進行搜索、插入、刪除操做。此外,因爲它的設計,任何不平衡都會在三次旋轉以內解決。固然,還有一些更好的,但實現起來更復雜的數據結構可以作到一步旋轉以內達到平衡,但紅黑樹可以給咱們一個比較「便宜」的解決方案。紅黑樹的算法時間複雜度和AVL相同,但統計性能比AVL樹更高。

圖片描述

  • 值得稱奇的是因爲一個Binder實體的宿主程序只會alive或者dead,那麼其節點不是存在紅黑樹中就是存在哈希表中,所以設計者使用了union結構將空間使用率壓縮到了極致,在後面的數據結構也常常碰到。
  • 因爲一個binder實體可能會被多個client組件所引用,所以binder經過數據結構binder_ref來進行描述(後面會介紹),所以將全部引用了該binder實體對象的全部引用都放在了哈希表當中,即hlist_head refs
  • internal_strong_refs,local_strong_refs均描述binder實體對象的強引用數,local_weak_refs描述了其弱引用數。has_strong_ref,has_weak_ref,pending_strong_ref,pending_weak_ref則是相關的標誌位。其中又有以下規則:async

    • 當Binder實體對象請求Service組件執行某操做時,將會加強Service組件的強引用數以及弱引用數。此時has_strong_ref,has_weak_ref均置1。
    • 反之當Service組件完成Binder的請求操做時,將會減小其強引用計數以及弱引用計數。
    • 當增長或減小的過程當中,pending變量將置1,而當完成增長減小後,纔會置0。
  • ptr和cookie分別指向一個用戶空間地址,其中cookie指向Service組件的地址,ptr指向Service組件內部的一個引用計數對象地址。這兩個變量用以描述用戶空間的Service組件。
  • has_async_transaction描述是否正在處理異步事務。通常而言Binder驅動程序都將一個事務保存在線程的todo隊列中,代表線程將要處理該事務。而每一個事務和一個binder實體相關聯,表示該事務的處理目標對象。而經過binder_node是能夠找到對應的Service組件,那麼與該Binder對應的Service組件將會處理該事務。

圖片描述

注:異步的概念就是單向的進程間通訊請求,即不須要等待應答的進程間通訊請求。同步則須要等待應答纔會作下一步的動做。因爲不須要應答,Binder驅動認爲異步事務優先級比同步優先級要低。
  • min_priority表示Binder實體在處理來自Client的請求時,處理線程(Server進程中的線程)所應該具有的最小線程優先級
  • accept_fds描述Binder實體對象是否能夠接收包含有文件描述符的進程通訊數據。爲1時能夠接收。

1.3 binder_ref_death 死亡接收通知

struct binder_ref_death {
    /**
     * @work: worklist element for death notifications
     *        (protected by inner_lock of the proc that
     *        this ref belongs to)
     */
    struct binder_work work;
    binder_uintptr_t cookie;
};

binder_ref_death用來描述Binder的死亡接收通知。正常而言,當Service組件被其餘Client組件所引用時,是不能被銷燬的,可是存在Service之外崩潰的情景,此時,Client可以經過Binder驅動得知Service Dead的消息。所以client會將一個可以接收死亡通知的地址註冊在Binder驅動程序中。ide

  • work是Binder工做項,能夠取值爲有關dead的類型,即標誌一個具體的死亡通知類型。
  • cookie保存的是接收死亡通知對象的地址。
  • 當Binder驅動決定要向Client發送一個Service組件死亡通知時,將會把這個結構封裝成一個工做項,並依據場景設置work值,最後將工做項添加到Client的todo隊列中處理!

Client接收Binder驅動死亡通知情景:函數

  1. Binder驅動程序檢測到Service組件死亡,經過該死亡Service組件找到對應的Binder實體,而後經過該實體對象的成員變量refs找到全部Client進程,最後找到這些client進程註冊的全部死亡接收通知,即binder_ref_death結構體,並將這些結構體添加到Client進程的todo隊列中等待處理,死亡通知類型設置爲BINDER_WORK_DEAD_BINDER
  2. 當Client進程向Binder驅動註冊一個死亡接受通知時,若是Service組件已死亡,則Binder驅動馬上發送死亡通知給該Client進程,死亡通知類型設置爲BINDER_WORK_DEAD_BINDER

Client註銷Binder驅動死亡通知情景

  1. 若是Client進程註銷一死亡通知時,對應的Service組件未死,那麼Binder驅動會找到以前註冊的binder_ref_death結構體,並將類型work修改爲爲BINDER_WORK_CLEAR_DEATH_NOTIFICATION,而後將該結構體封裝成一個工做項添加到client進程的todo隊列等待處理。
  2. 若是Service組件已死,流程與上述相似,但類型爲BINDER_WORK_DEAD_BINDER_AND_CLEAR

1.4 binder_ref 引用對象

與Service組件在Binder驅動中存在一個Binder實體相似,Client在Binder驅動中也對應一個Binder引用對象以描述其在內核的狀態。一樣也是使用強弱計數來控制生命週期。

binder_ref_datadebug_id,desc,strong,weak等屬性封裝起來。

struct binder_ref_data {
    int debug_id;
    uint32_t desc;
    int strong;
    int weak;
};
  • debug_id標誌Binder引用對象,幫助Binder驅動程序進行調試。
  • desc(descriptor)是句柄值(描述符),它是用來專門描述引用對象的。在Client用戶空間中,Binder引用對象是使用句柄至進行描述的。 所以當Client需經過Binder驅動來訪問Service組件時,只須要經過描述符來尋找對應的Binder引用對象,再找到對應的Binder實體對象,從而找到相關的Service組件。
  • strong,weak是描述強弱引用計數,經過它們維護Binder引用對象的生命週期。
struct binder_ref {
    /* Lookups needed: */
    /*   node + proc => ref (transaction) */
    /*   desc + proc => ref (transaction, inc/dec ref) */
    /*   node => refs + procs (proc exit) */
    struct binder_ref_data data;
    struct rb_node rb_node_desc;//以desc爲紅黑樹節點
    struct rb_node rb_node_node;//以binder實體爲紅黑樹節點
    struct hlist_node node_entry;//哈希節點,放置在Binder實體哈希列表中
    struct binder_proc *proc;//binder引用對象的宿主
    struct binder_node *node;//指向binder實體
    struct binder_ref_death *death;
};
  • 在以binder_proc爲結構的宿主中(進程或者線程)會有兩棵紅黑樹(事實上宿主有多棵紅黑樹進行管理數據結構)保存Binder引用對象,而rb_node_descrb_node_node分別是紅黑樹中的節點,分別是以句柄值(desc)和對應的Binder實體對象的地址來做爲保存的關鍵字。
  • proc是Binder引用對象的宿主。
  • binder_node *node是引用指向的Binder實體對象。
  • binder實體中存在哈希表來保存引用對象(即保存引用binder實體的Client),那麼node_entry就是指放在哈希表裏的節點。
  • death指向的是死亡通知,當Client向Binder驅動註冊一個Service組件死亡通知時,驅動將會建立一個binder_ref_death結構體保存在death中。

圖片描述

1.5 binder_buffer內核緩衝區

binder_buffer用來描述內核緩衝區,它是用於進程通訊中傳輸數據的。每一個參與Binder進程間通訊的進程在Binder驅動進程中都有一個內核緩衝區列表,用於保存爲進程分配的內核緩衝區,其中binder_buffer中的entry就是鏈表的節點。

struct binder_buffer {
    struct list_head entry; /* free and allocated entries by address */ 
    struct rb_node rb_node; /* free entry by size or allocated entry */
                /* by address */
    unsigned free:1; //判斷該內核緩衝區是由空閒內核緩衝區紅黑樹仍是正在使用的空閒內核緩衝區紅黑樹
    unsigned allow_user_free:1; //置1時,Service組件會請求Binder驅動程序釋放內核緩衝區
    unsigned async_transaction:1; //關聯的事務是否爲異步
    unsigned free_in_progress:1;//判斷是否正在釋放內核緩衝區
    unsigned debug_id:28;

    struct binder_transaction *transaction;

    struct binder_node *target_node;//內核緩衝區正在使用的binde實體對象
    size_t data_size;
    size_t offsets_size;//數據緩衝區的偏移量,偏移數組保存Binde對象
    size_t extra_buffers_size;
    void *data;//指向大小可變的數據緩衝區
};
  • 與此同時,進程又維護着兩棵紅黑樹分別管理正在使用的內核緩衝區以及空閒的內核緩衝區。所以rb_node就是在指紅黑樹的節點,而區別是那棵樹是經過變量free來區別,假如free爲1,那麼是空閒內核緩衝區。
注:Binder驅動中常常看到unsigned 變量:n這樣的寫法,那是由於C中並無bool類型,C Programmer會利用unsigned(這裏的unsigned實際是unsigned int)並指定須要用的bits數目來實現,但令我疑惑的是雖然只指定使用其中的n bits,可是實際上仍是以unsigned int的大小來存儲數據的,但當struct程序是以 __attribute((packed))來規定數據規格來編譯時,那麼其數據結構所佔的大小就爲1
  • binder_transaction爲事務結構體,用以描述該內核緩衝區正在處理哪個事務,值得留意的是binder_transaction中也存在者指向內核緩衝區的指針,即它們的關係是雙向的。前面說到過事務與對應的Binder對象是相關聯的,這是由於binder_buffer中存在着指向binder實體的指針,那麼transaction經過找到內核緩衝區便可找到相關聯的binder實體。從而將內核緩衝區內容交給Service處理。
  • allow_user_free爲1時,則Service處理完事務後將會請求Binder驅動程序釋放內核緩衝區。
  • data、data_sizeoffset_size分別用來描述數據緩衝區,其中data指向的是實際的數據內容。數據緩衝區保存的數據分爲兩種類型,一種是普通數據,另外的是Binder對象,Binder驅動不關心數據的內容,可是要知道其中的Binder對象,從而根據它們來維護內核中實體對象以及引用對象的生命週期。
  • 數據緩衝區後有一個偏移數組保存着每個Binder對象的偏移位置,偏移數組大小保存在offset_size中。

1.6 binder_proc 進程間通訊進程

struct binder_proc {
    struct hlist_node proc_node;
    struct rb_root threads;//Binder線程池根節點,以線程ID做爲關鍵字
    struct rb_root nodes;//Binder實體對象紅黑樹根節點 
    struct rb_root refs_by_desc;//binder引用對象紅黑樹,以desc爲關鍵字
    struct rb_root refs_by_node;//binder引用對象紅黑樹,以binder_node地址爲關鍵字
    struct list_head waiting_threads;
    int pid;
    struct task_struct *tsk;
    struct files_struct *files;
    struct mutex files_lock;
    struct hlist_node deferred_work_node;//保存進程能夠延遲執行的工做項,爲哈希列表
    int deferred_work;
    bool is_dead;

    struct list_head todo;//待處理工做項,當進程接收到進程間通訊請求時,Binder驅動封裝工做項加入todo隊列
    wait_queue_head_t wait;//等待隊列
    struct binder_stats stats;//統計進程數據
    struct list_head delivered_death;
    int max_threads;
    int requested_threads;
    int requested_threads_started;
    int tmp_ref;
    long default_priority;
    struct dentry *debugfs_entry;
    struct binder_alloc alloc;
    struct binder_context *context;
    spinlock_t inner_lock;
    spinlock_t outer_lock;
};
  • binder_proc用來描繪使用Binder通訊機制進行通訊的進程,當進程調用函數open時打開/dev/binder時,Binder驅動將會爲其建立一個binder_proc結構體,並將其保存在一個全局的哈希表當中,其中proc_node即爲該進程在哈希表的節點。
struct binder_alloc {
    struct mutex mutex;
    struct vm_area_struct *vma;//內核緩衝區的用戶空間地址
    struct mm_struct *vma_vm_mm;
    void *buffer;//內核緩衝區的內核空間地址
    ptrdiff_t user_buffer_offset;//內核緩衝區中用戶空間地址與內核空間地址差值
    struct list_head buffers;//指向小塊內核緩衝區鏈表的頭部
    struct rb_root free_buffers;//空閒內核緩衝區紅黑樹根節點
    struct rb_root allocated_buffers;//已分配內核緩衝區紅黑樹根節點
    size_t free_async_space;//當前能夠保存異步事務數據的內核緩衝區大小
    struct binder_lru_page *pages;
    size_t buffer_size;//binde驅動爲進程分配的內核緩衝區大小
    uint32_t buffer_free;
    int pid;
};
  • 紅黑樹分別有以前提到的管理Binder引用對象的 refs_by_desc,refs_by_node,管理內核緩衝區的free_buffers,allocated_buffers,管理Binder實體對象的nodes。
  • 此外進程還有一個Binder線程池處理通訊請求,也是使用紅黑樹進行處理,而且紅黑樹的根節點正是threads。線程紅黑樹的關鍵字是ID,進程經過ioctl可以將線程註冊到Binder驅動程序中,當進程沒有足夠的線程處理時,Binder將會要求更多的線程註冊到線程池當中。
  • ready_threads表示的是空閒的線程數目。
  • 當進程打開了設備文件後,還會調用mmap進行內存映射,即將設備映射到進程空間中,其實是請求Binder爲其分配一塊內核緩衝區以便傳輸數據。而內核緩衝區有兩個地址,分別是內核空間地址,一個是用戶空間地址,內核空間地址是用buffer保存,用戶空間是用vma保存。它們之間的相差的值爲user_buffer_offset保存,所以只要知道其中一個地址即可以經過該相差值得出另一個空間的地址。另外因爲用戶空間的地址高於內核空間地址,因此計算user_buffer_offset的差值時,都是使用用戶空間地址減去內核空間地址。

clipboard.png

  • pid、tsk、files都是與進程相關的變量,即進程ID,任務控制塊以及打開文件結構體數組。
  • 用戶空間地址和內核空間地址都是虛擬地址,是線性地址,對應的物理地址是保存在類型爲struct page*的pages的數組當中,每個元素都指向一個物理頁面。在最初的時候只爲內核緩衝區分配看一個物理頁面,到後面時繼續分配。
進程收到進程間通訊的請求時,將會把請求封裝成工做項加入到進程的待處理工做項todo隊列中。線程池中的wait的等待隊列維護着空閒的Binder線程,當線程的宿主進程增長了新工做項後,Binder驅動就會喚醒這些線程以便它們可以處理新工做項。
  • Binder驅動還會將延遲工做的工做項保存在一個哈希表中,假如進程中有延遲執行的工做項,那麼deffered_work_node正是哈希表的節點,並使用deffered_work維護延遲工做項的類型。

1.7 binder_thread

struct binder_thread {
    struct binder_proc *proc;
    struct rb_node rb_node;
    struct list_head waiting_thread_node;
    int pid;
    int looper;              /* only modified by this thread */
    bool looper_need_return; /* can be written by other thread */
    struct binder_transaction *transaction_stack;
    struct list_head todo;
    struct binder_error return_error;
    struct binder_error reply_error;
    wait_queue_head_t wait;
    struct binder_stats stats;
    atomic_t tmp_ref;
    bool is_dead;
};
  • binder_thread用來描繪Binder線程池的一個線程。其中proc指向線程的宿主進程。
  • 因爲一個進程下有多個線程,rb_node正是進程維護的紅黑樹中的節點。
  • pid是線程的ID
  • looper是指線程的狀態,其狀態有如下幾種:
enum {
    BINDER_LOOPER_STATE_REGISTERED  = 0x01,
    BINDER_LOOPER_STATE_ENTERED     = 0x02,
    BINDER_LOOPER_STATE_EXITED      = 0x04,
    BINDER_LOOPER_STATE_INVALID     = 0x08,
    BINDER_LOOPER_STATE_WAITING     = 0x10,
    BINDER_LOOPER_STATE_POLL        = 0x20,
};

參考文獻: 《Android系統源代碼情景分析》

相關文章
相關標籤/搜索