Android笔记 - Binder之数据结构(一)

来源:互联网 发布:mac写日记的软件 编辑:程序博客网 时间:2024/06/04 19:22

有这样一句流传很广的话,程序等于数据结构加算法。我想这句话一样适用于 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 系统源代码情景分析

0 0
原创粉丝点击