Android笔记 - Binder之数据结构

Posted demonyan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android笔记 - Binder之数据结构相关的知识,希望对你有一定的参考价值。

有这样一句流传很广的话,程序等于数据结构加算法。我想这句话一样适用于 Binder 驱动程序。Binder 驱动程序的数据结构设计的十分精巧,Binder 通信机制就是建立在这些数据结构的基础上,因此了解它们对理解整个 Binder 通信机制很有帮助。

当然,也有人说程序等于 Google 加 GitHub。(●’◡’●)

在 Binder 驱动程序中,有两种类型的数据结构,第一种是 Binder 驱动内部使用的数据结构,定义在 common/drivers/staging/android/binder.c 文件中,第二种是 Binder 驱动以及用户空间都可以使用,定义在 common/drivers/staging/android/binder.h 文件中。本篇主要介绍 Binder 驱动内部使用的数据结构。

在 Binder 驱动内部的数据结构中,借用了一些 Linux 内核中广泛使用的数据结构和算法,其中包括双向循环链表(list_head),hash 表(hlist_head, hlist_node)以及红黑树(rb_root, rb_node),每种数据结构都有它适用的使用场景。

双向循环链表是 Linux 内核中通用算法之一,list_head 结构体中包含 next 指针和 prev 指针,分别用于指向链表中的前一个节点和后一个节点。使用时将 list_head 包含在宿主结构体中,查找时通过 list_head 指针就可以得到宿主结构体内容,Linux 内核提供了 list_entry 宏来完成这项工作。双向循环链表可以很方便地进行遍历,插入节点和删除节点,缺点是查询起来很慢。双向循环链表使用到的结构体定义如下所示:

struct list_head 
    struct list_head *next, *prev;
;

hash 表用于对元素进行快速访问,hlist_head 可以理解成 list_head 的变种,hlist_head 只有一个指针,比 list_head 省一半内存空间,它的出现主要是为了在内核中创建高效 hash 表。hlist_node 主要是为了解决碰撞冲突,在发生碰撞冲突时可以使用拉链法。hlist_node 在实现上有一个取巧的地方,就是把 pprev 定义为指向指针的指针,这样使得插入节点和删除节点实现起来更优雅,效率更高(StackOverflow上的解释)。hash 表使用到的结构体定义如下所示:

struct hlist_head 
    struct hlist_node *first;
;

struct hlist_node 
    struct hlist_node *next, **pprev;
;

红黑树也是 Linux 内核中通用算法之一,几乎所有涉及到频繁插入和查询的代码都会使用到它,内存管理,调度器,文件系统管理等都可以见到它的身影。红黑树是一种近似平衡二叉树,查找,插入和删除的时间复杂度都可以控制在 O(log n)。关于红黑树的更多内容可以查看 wiki,Linux 内核源码中的红黑树实现代码也相当精彩。红黑树使用到的结构体有 rb_node 和 rb_root,如下所示:

struct rb_node 
    unsigned long  __rb_parent_color;
    struct rb_node *rb_right;
    struct rb_node *rb_left;
 __attribute__((aligned(sizeof(long))));

struct rb_root 
    struct rb_node *rb_node;
;

Binder 驱动程序内部的数据结构是进程,线程,事务,实体对象以及引用对象在 Binder 驱动内部的抽象,主要包含以下内容:

1.1 struct binder_work

struct binder_work 
    struct list_head entry;                    [1]
    enum 
        BINDER_WORK_TRANSACTION = 1,
        BINDER_WORK_TRANSACTION_COMPLETE,
        BINDER_WORK_NODE,
        BINDER_WORK_DEAD_BINDER,
        BINDER_WORK_DEAD_BINDER_AND_CLEAR,
        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
     type;                                    [2]
;

结构体 binder_work 用于描述待处理的工作项,这些工作项可能属于某个进程,也可能属于一个进程中的某个线程。
[1] entry 用于将 binder_work 嵌入到宿主结构体中。
[2] type 用于描述工作项的类型。

1.2 struct binder_node

struct binder_node 
    int debug_id;
    struct binder_work work;                   [1]
    union 
        struct rb_node rb_node;                [2]
        struct hlist_node dead_node;           [3]
    ;
    struct binder_proc *proc;                  [4]
    struct hlist_head refs;                    [5]
    int internal_strong_refs;
    int local_weak_refs;
    int local_strong_refs;
    void __user *ptr;                          [6]
    void __user *cookie;                       [7]
    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;          [8]
    unsigned accept_fds:1;
    unsigned min_priority:8;
    struct list_head async_todo;               [9]
;

结构体 binder_node 用于描述一个 Binder 实体对象,每个 Service 组件(Binder 本地对象)在 Binder 驱动中都对应有一个 Binder 实体对象。
[1] work 用于描述 Binder 实体对象的待处理工作项。
[2] rb_node 用于表示 Binder 实体对象在宿主进程红黑树中的一个节点。这颗红黑树以 ptr 作为关键字。
[3] dead_node 用于表示死亡后的 Binder 实体对象在全局 hash 列表中的一个节点。
[4] proc 指向 Binder 实体对象的宿主进程。
[5] refs 指向一个 hash 表,该 hash 表保存了引用该 Binder 实体对象的所有 Binder 引用对象。
[6] ptr 指向 Service 组件(Binder 本地对象)内部的一个引用计数对象的地址,用于作为红黑树的关键字。
[7] cookie 指向 Service 组件(Binder 本地对象)的地址。
[8] has_async_transaction 用于描述 Binder 实体对象是否正在处理异步事务。
[9] async_todo 用于描述 Binder 实体对象的一个异步事务队列。

本文选择性忽略掉了用于维护实体对象生命周期的引用计数,调试信息,以及线程优先级等内容。

1.3 struct binder_ref

struct binder_ref 
    int debug_id;
    struct rb_node rb_node_desc;               [1]
    struct rb_node rb_node_node;               [2]
    struct hlist_node node_entry;              [3]
    struct binder_proc *proc;                  [4]
    struct binder_node *node;                  [5]
    uint32_t desc;                             [6]
    int strong;
    int weak;
    struct binder_ref_death *death;
;

结构体 binder_ref 用于描述一个 Binder 引用对象,每个 Client 组件在 Binder 驱动中都对应有一个 Binder 引用对象。
[1] rb_node_desc 用于表示 Binder 引用对象在宿主进程红黑树中的一个节点,这颗红黑树以 desc 作为关键字。
[2] rb_node_node 用于表示 Binder 引用对象在宿主进程红黑树中的一个节点,这颗红黑树以 node 地址值作为关键字。
[3] node_entry 用于表示 Binder 引用对象在 hash 列表中的一个节点,该 hash 列表是 Binder 实体对象用于保存它所引用的 Binder 引用对象。
[4] proc 指向一个 Binder 引用对象的宿主进程。
[5] node 指向一个 Binder 引用对象对应的 Binder 实体对象。
[6] desc 表示 Binder 引用对象的句柄值,Client 进程就是通过它来找到 Binder 驱动中的 Binder 引用对象。

1.4 struct binder_thread

struct binder_thread 
    struct binder_proc *proc;                  [1]            
    struct rb_node rb_node;                    [2]
    int pid;                                   [3]
    int looper;                                [4]
    struct binder_transaction *transaction_stack;    [5]
    struct list_head todo;                     [6]
    uint32_t return_error; 
    uint32_t return_error2; 
    wait_queue_head_t wait;                    [7]
    struct binder_stats stats;
;

结构体 binder_thread 用于描述进程的 Binder 线程池中的一个线程。
[1] proc 用于指向线程的宿主进程。
[2] rb_node 用于表示线程在宿主进程红黑树中的一个节点,这颗红黑树以 pid 作为关键字。
[3] pid 用于描述线程的 ID。
[4] 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_NEED_RETURN = 0x20
;
线程状态取值含义
BINDER_LOOPER_STATE_REGISTERED线程准备就绪(增加就绪线程数目),可以处理进程间通信请求
BINDER_LOOPER_STATE_ENTERED线程准备就绪,可以处理进程间通信请求
BINDER_LOOPER_STATE_EXITED线程处于退出状态
BINDER_LOOPER_STATE_INVALID线程处于异常状态
BINDER_LOOPER_STATE_WAITING线程处于空闲状态
BINDER_LOOPER_STATE_NEED_RETURN线程需要马上返回用户空间

[5] transaction_stack 用于指向一个 binder_transaction 堆栈。当 Binder 驱动程序决定将一个事务交给 Binder 线程处理时,就会将该事务封装好后添加到 binder_transaction 堆栈。
[6] todo 用于指向一个待处理项队列。如果 todo 为空,线程会进入睡眠状态,直到有新的待处理项加入队列。

transaction_stack 和 todo 功能不一样,前者相当于仓库,后者相当于生产线。

[7] wait 用于定义一个等待队列,实现线程的等待和唤醒。

1.5 struct binder_buffer

struct binder_buffer 
    struct list_head entry;                    [1]
    struct rb_node rb_node;                    [2]
    unsigned free:1;                           [3]
    unsigned allow_user_free:1;                [4]
    unsigned async_transaction:1;              [5]
    unsigned debug_id:29;

    struct binder_transaction *transaction;    [6]

    struct binder_node *target_node;           [7]
    size_t data_size;                          [8]
    size_t offsets_size;                       [9]
    uint8_t data[0];                           [10]
;

结构体 binder_buffer 用于描述一个内核缓存区,该内核缓存区用于在内核空间和用户空间之间传输数据。
[1] entry 用于将缓存区嵌入到内核缓存区链表中,该链表用于每个进程保存 Binder 驱动程序为其分配的内核缓存区。
[2] rb_node 用于表示内核缓存区在宿主进程红黑树中的一个节点。

如果 free 值为1,那么 rb_node 是宿主进程空闲内核缓存区红黑树中的一个节点,以缓存区大小作为关键字;否则 rb_node 是宿主进程正在使用内核缓存区红黑树中的一个节点,以缓存区首地址作为关键字。

[3] free 描述该内核缓存区是否空闲,值为1表示空闲,值为0表示正在使用。
[4] allow_user_free 如果值为1,那么 Service 组件处理完事务后,会请求 Binder 驱动程序释放该内核缓存区。
[5] async_transaction 值为1表示内核缓存区关联的是一个异步事务。
[6] transaction 用于描述内核缓存区由哪个事务在使用。
[7] target_node 用于描述内核缓存区由哪个 Binder 实体对象在使用。
[8] data_size 用于记录数据缓存区大小。
[9] offsets_size 用于记录偏移数组大小,它的大小也表示 Binder 对象的个数。
[10] data 指向一块大小可变的数据缓存区,用于保存真正的通信数据。

数据缓存区保存的通信数据分为两种类型:一种是普通数据,另一种是 Binder 对象。由于数据缓存区中的普通数据和 Binder 对象混合在一起保存,Binder 驱动程序需要额外的描述信息来找到里面的 Binder 对象,因此在数据缓存区后面有一个偏移数组,记录了每个 Binder 对象在数据缓存区中的偏移位置。如下图所示:

1.6 struct binder_proc

struct binder_proc 
    struct hlist_node proc_node;               [1]
    struct rb_root threads;                    [2]
    struct rb_root nodes;                      [3]
    struct rb_root refs_by_desc;               [4]
    struct rb_root refs_by_node;               [5]
    int pid;
    struct vm_area_struct *vma;                [6]
    struct task_struct *tsk;                   [7]
    struct files_struct *files;                [8]
    struct hlist_node deferred_work_node;      [9]
    int deferred_work;
    void *buffer;                              [10]
    ptrdiff_t user_buffer_offset;              [11]

    struct list_head buffers;                  [12]
    struct rb_root free_buffers;               [13]
    struct rb_root allocated_buffers;          [14]
    size_t free_async_space;

    struct page **pages;                       [15]
    size_t buffer_size;
    uint32_t buffer_free;
    struct list_head todo;                     [16]
    wait_queue_head_t wait;                    [17]
    struct binder_stats stats;
    struct list_head delivered_death;
    int max_threads;
    int requested_threads;
    int requested_threads_started;
    int ready_threads;
    long default_priority;
    struct dentry *debugfs_entry;
;

结构体 binder_proc 用于描述一个正在使用 Binder 进程间通信机制的进程,包含内存信息,线程信息,实体对象,引用对象,进程优先级等内容。当一个进程使用 open 系统调用来打开设备文件 /dev/binder 时,Binder 驱动就会为它创建一个 binder_proc 结构体。
[1] proc_node 用于表示进程在全局 hash 列表中的一个节点。
[2] threads 指向一颗红黑树的根节点,该红黑树中保存进程的 Binder 线程池。
[3] nodes 指向一颗红黑树的根节点,该红黑树中保存进程的 Binder 实体对象。
[4] refs_by_desc 指向一颗红黑树的根节点,该红黑树中保存进程的 Binder 引用对象。
[5] refs_by_node 指向一颗红黑树的根节点,该红黑树中保存进程的 Binder 引用对象。

refs_by_desc 和 refs_by_node 指向红黑树中的 Binder 引用对象都相同。但是为了方便查找,两者的关键字不一样,前者以 desc 作为关键字,后者以 node 地址值作为关键字。

[6] vma 用于保存应用程序使用的用户空间地址。
[7] tsk 指向任务控制块。
[8] files 指向进程打开文件表。
[9] deferred_work_node 用于表示进程中延时执行的工作项在 hash 列表中的一个节点,该 hash 列表用于保存 Binder 驱动中所有延时执行的工作项。
[10] buffer 用于保存 Binder 驱动使用的内核空间地址。
[11] user_buffer_offset 用于保存 buffer 和 vma 之间的一个固定差值。
[12] buffers 用于指向内核缓存区链表,该链表保存了进程所有的内核缓存区。
[13] free_buffers 指向一颗红黑树的根节点,该红黑树中保存所有空闲的内核缓存区。
[14] allocated_buffers 指向一颗红黑树的根节点,该红黑树中保存所有正在使用(已分配物理页面)的内核缓存区。
[15] pages 用于保存指向 Binder 驱动为进程分配物理页面的指针。
[16] todo 用于描述一个待处理工作项队列。
[17] wait 用于定义一个等待队列,实现进程的等待和唤醒。

binder_proc 结构体中涉及到 Linux 进程相关内容比如任务控制块,打开文件表,虚拟内存管理等,可以参考UnderStanding The Linux Kernel 3rd Edition相关章节。

binder_proc 结构体引用了之前描述的所有数据结构,它是如此复杂,以至于除了画图,想不到其他更好的表达方式。

1.7 struct binder_transaction

struct binder_transaction 
    int debug_id;
    struct binder_work work;                   [1]
    struct binder_thread *from;                [2]
    struct binder_transaction *from_parent;    [3]
    struct binder_proc *to_proc;               [4]
    struct binder_thread *to_thread;           [5]
    struct binder_transaction *to_parent;      [6]
    unsigned need_reply:1;

    struct binder_buffer *buffer;              [7]
    unsigned int    code;
    unsigned int    flags;
    long    priority;
    long    saved_priority;
    kuid_t  sender_euid;
;

结构体 binder_transaction 用于描述进程间通信时的一个事务。
[1] work 用于描述事务包含的待处理工作项。
[2] from 指向发起事务的线程。
[3] from_parent 指向事务所依赖的另一个事务。
[4] to_proc 指向负责处理事务的进程。
[5] to_thread 指向负责处理事务的线程。
[6] to_parent 用于描述下一个需要处理的事务。
[7] buffer 指向 Binder 驱动程序为事务分配的一块内核缓存区。

关于成员变量 from_parent 和 to_parent,老罗的《Android 系统源代码情景分析》中有一段不错的描述。假设线程A发起了一个事务 T1,需要由线程 B 来处理;线程 B 在处理事务 T1 时,又需要线程 C 先处理事务 T2;线程 C 在处理事务 T2 时,又需要线程 A 先处理事务 T3。这样,事务 T1 就依赖于事务 T2,而事务 T2 又依赖于事务 T3,它们的关系如下:
T2->from_parent = T1;
T3->from_parent = T2;
对于线程 A 来说,它需要处理的事务有两个,分别是 T1 和 T3,它首先要处理事务 T3,然后才能处理事务 T1,因此,事务 T1 和 T3 的关系如下:
T3->to_parent = T1;

至此,本篇关于 Binder 驱动内部使用到的数据结构已介绍完毕,关键是需要以 proc_node 结构体为核心,理清它们之间千丝万缕的联系。下篇继续介绍 Binder 驱动和用户空间都可以使用到的数据结构。

参考学习资料:
1. Android 系统源代码情景分析

以上是关于Android笔记 - Binder之数据结构的主要内容,如果未能解决你的问题,请参考以下文章

Android笔记 - Binder之数据结构

Android笔记 - Binder之基本概念

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android笔记 - Binder之处理注册Service组件请求

Android笔记 - Binder之处理注册Service组件请求