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};
[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之守护进程servicemanager
- Android笔记 - Binder之servicemanager代理对象
- android binder机制及其源码解析 之第二节重要函数讲解之常用数据结构(一)
- android binder机制及其源码解析之第二节 重要函数讲解之常用数据结构(一)
- android系统学习笔记——binder基础数据结构1
- Android Binder机制(二) Binder中的数据结构
- Android Binder机制(二) Binder中的数据结构
- android Binder机制2---Binder的数据结构以及Binder驱动
- 数据结构笔记之图(一)
- Android Binder 驱动分析 - 数据结构
- Android Binder通信数据结构介绍
- Android Binder 驱动分析 - 数据结构
- Android Binder 驱动分析 - 数据结构
- Android动画(1)--帧动画
- pip安装模块警告InsecurePlatformWarning: A true SSLContext object is not available.
- 问题:org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.mysql.jdbc.Dri
- 验证纯数字,以及数字位数
- LA 3644 (并查集)
- Android笔记 - Binder之数据结构(一)
- test
- 【 Codeforces 514E 】Darth Vader and Tree - DP 矩乘转移
- 国内的pythoner强烈建议使用豆瓣的pypi源
- JavaScript-获得当前时间
- pyhton序列化
- php数据结构算法示例
- Android内存溢出 (oom)实战
- 菜鸟猿大战Java之集合框架系列(四)