From c781f727e729ce00664f03cf45f94c14ebdf2b7a Mon Sep 17 00:00:00 2001 From: Omooo <869759698@qq.com> Date: Wed, 13 May 2020 20:56:53 +0800 Subject: [PATCH] =?UTF-8?q?Create=20Binder=20=E9=A9=B1=E5=8A=A8=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Binder 驱动程序.md | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 blogs/Android/Framework/源代码情景分析/Binder 驱动程序.md diff --git a/blogs/Android/Framework/源代码情景分析/Binder 驱动程序.md b/blogs/Android/Framework/源代码情景分析/Binder 驱动程序.md new file mode 100644 index 0000000..14001f4 --- /dev/null +++ b/blogs/Android/Framework/源代码情景分析/Binder 驱动程序.md @@ -0,0 +1,172 @@ +--- +Binder 驱动程序 +--- + +#### 前言 + +Binder 驱动程序实现在内核空间中,它主要有 binder.h 和 binder.c 两个源文件组成。下面我们就开始介绍 Binder 驱动程序的基础知识,包括基础数据结构、初始化过程,以及设备文件 /dev/binder 的打开(open)、内存映射(mmap)和内核缓冲区管理等操作。 + +#### 基础数据结构 + +**struct binder_work** + +结构体 binder_work 用来描述待处理的工作项。 + +**struct binder_node** + +```c +struct binder_node { + struct binder_proc *proc; +} +``` + +结构体 binder_node 用来描述一个 Binder 实体对象。每一个 Service 组件在 Binder 驱动程序中都对应有一个 Binder 实体对象,用来描述它在内核中的状态。 + +成员变量 proc 指向一个 Binder 实体对象的宿主进程。在 Binder 驱动程序中,这些宿主进程通过一个 binder_proc 结构体来描述。宿主进程使用一个红黑树来维护它内部所有的 Binder 实体对象。 + +由于一个 Binder 实体对象可能会同时被多个 Client 组件引用,因此,Binder 驱动程序就使用结构体 binder_ref 来描述这些引用关系,并且将引用了同一个 Binder 实体对象的所有引用都保存在一个hash 列表中。这个 hash 列表通过 Binder 实体对象的成员变量 refs 来描述。 + +**struct binder_ref_death** + +结构体 binder_ref_death 用来描述一个 Service 组件的死亡接收通知。 + +**struct binder_ref** + +```c +struct binder_ref { + struct hlist_node node_entry; + struct binder_proc *proc; + struct binder_node *node; + uint32_t desc; +} +``` + +结构体 binder_ref 用来描述一个 Binder 引用对象,每一个 Client 组件在 Binder 驱动程序中都对应有一个 Binder 引用对象,用来描述它在内核中的状态。 + +成员变量 node 用来描述一个 Binder 引用对象所引用的 Binder 实体对象。前面在介绍结构体 binder_node 时提到,每一个 Binder 实体对象都有一个 hash 列表,用来保存那些引用了它的 Binder 引用对象,而这些 Binder 引用对象的成员变量 node_entry 正好是这个 hash 列表的节点。 + +成员变量 desc 是一个句柄值,或者称为描述符,它是用来描述一个 Binder 引用对象的。在 Client 进程的用户空间中,一个 Binder 引用对象是使用一个句柄值来描述的。因此,当 Client 进程的用户空间通过 Binder 驱动程序来访问一个 Service 组件时,它只需要指定一个句柄值,Binder 驱动程序就可以通过该句柄值找到对应的 Binder 引用对象,然后再根据该 Binder 引用对象的成员变量 node 找到对应的 Binder 实体对象,最后就可以通过该 Binder 实体对象找到要访问的 Service 组件。 + +**struct binder_buffer** + +```c +struct binder_buffer { + unsigned free :1; + unsigned allow_user_free : 1; + unsigned async_transaction : 1; + + struct binder_transaction *transaction; + struct binder_node *target_node; +} +``` + +结构体 binder_buffer 用来描述一个内核缓冲区,它是用来在进程间传输数据的。每一个使用 Binder 进程间通信机制的进程在 Binder 驱动程序中都有一个内核缓冲区列表,用来保存 Binder 驱动程序为它所分配的内核缓冲区。 + +成员变量 transaction 和 target_node 用来描述一个内核缓冲区正在交给哪一个事务以及哪一个 Binder 实体对象使用。Binder 驱动程序使用一个 binder_transaction 结构体来描述一个事务,每一个事务都关联有一个目标 Binder 实体对象。Binder 驱动程序将事务数据保存在一个内核缓冲区中,然后将它交给目标 Binder 实体对象处理;而目标 Binder 实体对象再将该内核缓冲区的内容交给相应的 Service 组件处理。Service 组件处理完成该事务之后,如果发现传递给它的内核缓冲区的成员变量 allow_user_free 的值为 1,那么该 Service 组件就会请求 Binder 驱动程序释放该内核缓冲区。 + +**struct binder_proc** + +```c +struct binder_proc { + struct rb_root threads; + int pid; + size_t buffer_size; + struct list_head todo; + wait_queue_head_t wait; +} +``` + +结构体 binder_proc 用来描述一个正在使用 Binder 进程间通信机制的进程。当一个进程调用函数 open 来打开设备文件 /dev/binder 时,Binder 驱动程序就会为它创建一个 binder_proc 结构体,并且将它保存在一个全局的 hash 列表中。 + +进程打开了设备文件 /dev/binder 之后,还必须调用函数 mmap 将它映射到进程的地址空间来,实际上是请求 Binder 驱动程序为它分配一块内核缓冲区,以便可以用来在进程间传输数据。Binder 驱动程序为进程分配的内核缓冲区的大小保存在成员变量 buffer_size 中。这些内核缓冲区有两个地址,其中一个是内核空间地址,另一个是用户空间地址。 + +前面提到,每一个使用了 Binder 进程间通信机制的进程都有一个 Binder 线程池,用来处理进程间通信请求,这个 Binder 线程池是由 Binder 驱动程序来维护的。结构体 binder_proc 的成员变量 threads 是一个红黑树的根节点,它以线程 ID 作为关键字来组织一个进程的 Binder 线程池。进程可以调用 ioctl 将一个线程注册到 Binder 驱动程序中,同时当进程没有足够的空闲线程来处理进程间通信请求时,Binder 驱动程序也可以主动要求进程注册更多的线程到 Binder 线程池中。 + +当进程接收到一个进程间通信请求时,Binder 驱动程序就将该请求封装成一个工具项,并且加入到进程的待处理工作项队列中,这个队列使用成员变量 todo 来描述。Binder 线程池中的空闲 Binder 线程会睡眠在由成员变量 wait 所描述的一个等待队列中,当它们的宿主进程的待处理工作项队列增加了新的工作项之后,Binder 驱动程序就会唤醒这些线程,以便它们可以去处理新的工作项。 + +**struct binder_thread** + +```c +struct binder_thread { + struct binder_proc *proc; + int pid; + int looper; +} +``` + +结构体 binder_thread 用来描述 Binder 线程池中的一个线程,其中,成员变量 proc 指向其宿主进程。 + +一个 Binder 线程的 ID 和状态是通过成员变量 pid 和 looper 来描述的。 + +一个线程注册到 Binder 驱动程序时,Binder 驱动程序就会为它创建一个 binder_thread 结构体,并且将它的状态初始化为 BINDER_LOOPER_STATE_NEED_RETURN,表示该线程需要马上返回到用户空间。由于一个线程在注册为 Binder 线程时可能还没有准备好去处理进程间通信请求,因此,最好返回到用户空间去做准备工作。 + +一个线程注册到 Binder 驱动程序之后,它接着就会通过 BC_REGISTER_LOOPER(Binder 驱动程序请求创建) 或者 BC_ENTER_LOOPER (应用程序主动注册)协议来通知 Binder 驱动程序,它可以处理进程间通信请求了。 + +**struct binder_transaction** + +```c +struct binder_transaction { + unsigned need_reply : 1; +} +``` + +结构体 binder_transaction 用来描述进程间通信过程,这个过程又称为一个事务。成员变量 need_reply 用来区分一个事务是同步的还是异步的。同步事务需要等待对方回复,这时候它的成员变量 need_reply 的值就会设置为 1;否则就设置为 0,表示这是一个异步事务,不需要等待回复。 + + + +以上介绍的结构体都是在 Binder 驱动程序内部使用的。前面提到,应用程序进程在打开了设备文件 /dev/binder 之后,需要通过 IO 控制函数 ioctl 来进一步与 Binder 驱动程序进行交互,因此,Binder 驱动程序就提供了一系列的 IO 控制命令来和应用程序进程通信。在这些 IO 控制命令中,最重要的便是 BINDER_WRITE_READ 命令了。 + +IO 控制命令 BINDER_WRITE_READ 后面所跟的参数是一个 binder_write_read 结构体,它的定义如下: + +```c +struct binder_write_read { + signed long write_size; + signed long write_consumed; + unsigned long write_buffer; + signed long read_size; + signed long read_consumed; + unsigned long read_buffer; +} +``` + +结构体 binder_write_read 用来描述进程间通信过程中所传输的数据。这些数据包括输入数据和输出数据,其中,成员变量 write_size、write_consumed 和 write_buffer 用来描述输入数据,即从用户空间传输到 Binder 驱动程序的数据;而成员变量 read_size、read_consumend 和 read_buffer 用来描述输出数据,即从 Binder 驱动程序返回给用户空间的数据,它也是进程间通信结果数据。 + +成员变量 write_buffer 指向一个用户空间缓冲区的地址,里面保存的内容即为要传输到 Binder 驱动程序的数据。缓冲区 write_buffer 的大小由成员变量 write_size 来制定,单位是字节。成员变量 write_consumed 用来描述 Binder 驱动程序从缓冲区 write_buffer 中处理了多少个字节的数据。 + +成员变量 read_buffer 也是指向一个用户空间缓冲区的地址,里面保存的内容即为 Binder 驱动程序返回给用户空间的进程间通信结果数据。缓冲区 read_buffer 的大小由成员变量 read_size 来指定,单位是字节。成员变量 read_consumed 用来描述用户空间应用程序从缓冲区 read_buffer 中处理了多少个字节的数据。 + +缓冲区 write_buffer 和 read_buffer 都是一个数组,数组的每一个元素都由一个通信协议代码及其通信数据组成。协议代码又分为两种类型,其中一种是在输入缓冲区 write_buffer 中使用的,称为命令协议代码;另一种是在输出缓冲区 read_buffer 中使用的,称为返回协议代码。命令协议代码通过 BinderDriverCommandProtocol 枚举值来定义,而返回协议代码通过 BinderDriverReturnProtocol 枚举值来定义。 + +命令协议码: + +命令协议代码 BC_TRANSACTION 和 BC_REPLY 后面跟的通信数据使用一个结构体 binder_transaction_data 来描述。当一个进程请求另外一个进程执行某一个操作时,源进程就使用命令协议代码 BC_TRANSACTION 来请求 Binder 驱动程序将通信数据传递到目标进程;当目标进程处理完成源进程所请求的操作之后,它就使用命令协议代码 BC_REPLY 来请求 Binder 驱动程序将结果数据传递给源进程。 + +返回协议码: + +返回协议代码 BR_TRANSACTION 和 BR_REPLY 后面跟的通信数据使用一个结构体 binder_transaction_data 来描述。当一个 Client 进程向一个 Servce 进程发出进程间通信请求时,Binder 驱动程序就会使用返回协议码 BR_TRANSACTION 通知该 Servcer 进程来处理该进程间通信请求;当 Server 进程处理完成该进程间通信请求之后,Binder 驱动程序就会使用返回协议码 BR_REPLY 将进程间通信请求结果数据返回给 Client。 + +返回协议代码 BR_TRANSACTION_COMPLETE 后面不需要指定通信数据。当 Binder 驱动程序接收到应用程序进程给它发送的一个命令协议代码 BC_TRANSACTION 或者 BC_REPLY 时,它就会使用返回协议码 BR_TRANSACTION_COMPLET来通知应用程序进程,该命令协议码已经被接收,正在分发给目标进程或者目标线程处理。 + +介绍完 Binder 驱动程序提供的命令协议码和返回协议码之后,接下来我们继续分析这些协议所使用的一个结构体 binder_transaction_data 的定义。 + +**struct binder_transaction_data** + +```c +struct binder_transaction_data { + union { + size_t handle; + void *ptr; + } target; + unsigned int code; + pit_t sender_pid; + uid_t sender_euid; +} +``` + +结构体 binder_transaction_data 用来描述进程间通信过程中所传输的数据。 + +成员变量 target 是一个联合体,用来描述一个目标 Binder 实体对象或者目标 Binder 引用对象。如果它描述的是一个目标 Binder 实体对象,那么它的成员变量 ptr 就指向与该 Binder 实体对象对应的一个 Service 组件内部的一个弱引用计数对象(weakref_impl)的地址;如果它描述的是一个目标 Binder 引用对象,那么它的成员变量 handle 就指向该 Binder 引用对象的句柄值。 + +成员变量 flags 是一个标志值,用来描述进程间通信行为特征,它的取值有 TF_ONE_WAY 和 TF_ACCEPT_FDS。如果成员变量 flags 的 TF_ONE_WAY 位被设置为 1,就表示这是一个异步的进程间通信过程;如果成员变量 flags 的 TF_ACCEPT_FDS 位被设置为 0,就表示源进程不允许目标进程返回的结果数据中包含有文件描述符; + +成员变量 sender_pid 和 sender_euid 表示发起进程间通信请求的进程的 PID 和 UID。这两个成员变量的值是由 Binder 驱动程序来填写的,因此,目标进程通过这两个成员变量就可以识别出源进程的身份,以便进行安全检查。 \ No newline at end of file