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