Understanding Android Source: Binder Driver
Binder驱动扮演的角色主要有:
- 提供Binder通信的渠道;
- 维护Binder对象的计数;
- 转换传输中Binder实体对象和引用对象;
- 管理数据缓冲区。
Initialization
通过device_initcall
函数,来调用binder_init
函数,进而完成Binder设备的初始化过程。
初始化过程中,函数misc_register
和两个结构体file_operations
、miscdevice
起到了重要作用。
为方便调试和跟踪问题,Binder驱动程序在/sys/kernel/debug下有一个binder的文件夹,其中有五个文件和一个文件夹。
- state
- stats
- transcations
- transcation_log
- failed_transcation_log
- proc
proc 是一个文件夹,每一个使用Binder进程间通信机制的进程都在其中建立一个文件,文件名就是进程ID。
通过它,可以获取Binder线程池、Binder实体对象、Binder引用对象,以及内核缓冲区等信息。
Open
在类ProcessState
实例化过程中,在其构造函数里会调用函数open
,来打开设备文件/dev/binder。此时,驱动程序中binder_open
函数会被调用进程间通信。
在执行过程中,其为当前进程创建并初始化binder_proc
结构体proc
,proc
将被插入到全局的红黑树binder_procs
中,换言之,binder_procs
里保存了所有打开/dev/binder的信息。
同时,变量proc
被赋值给file
结构体的private_data
,调用驱动的其他操作时,可以从file
结构体里取出来,用来代表当前进程的binder_proc
结构体。
Mapping
在上面一节里,类ProcessState
构造函数在调用函数open
之后,会继续调用mmap
函数进行内存映射时。此时,驱动程序中binder_mmap
函数会被调用。
一个使用了Binder进程间通信机制的进程,只有将设备文件/dev/binder映射到自己的地址空间以后,Binder驱动程序才能够为它分配内核缓冲区,以便用来传输进程间通信数据。
Binder驱动程序最多可以为进程分配4M内核缓冲区,这些内核缓冲区在用户空间只可以读,不可以写,不可以拷贝,也禁止设置会执行写操作标志位的。但是在ProcessState
里可以看到,其只申请了不到1MB的缓冲区。同时,内核缓冲区有两个地址,其中一个是用户空间地址,另外一个是内核空间地址,二者是简单的线性对应关系。
这些内核缓冲区即为一系列物理页面,它们分别被映射到进程的用户地址空间和内核地址空间。在Linux内核中,一个物理页面的大小是PAGE_SIZE,其是一个宏,一般定义为4K。
首先在内核虚拟地址空间,申请一块与用户虚拟内存相同大小的内存,然后再申请1个page大小的物理内存,再将同一块物理内存分别映射到内核虚拟地址空间和用户虚拟内存空间,从而实现了用户空间的Buffer和内核空间的Buffer同步操作的功能。
- 首先调用
get_vm_area
分配一块地址空间,这块地址空间位于用户进程空间; - 接着调用
binder_update_page_range
在内核里也分配相同页数大小的空间; - 将二者的物理内存地址绑定在一起。
vm_area_struct
和vm_struct
都是用来描述一段连续的虚拟地址空间,(但它们对应的物理页面是可以不连续的)。前者用在用户地址空间(0-3G),后者用在内核地址空间(3G-4G)。关键的数据结构vm_operations_struct
- 调用
alloc_page
为内核地址空间分配一个物理页面; - 调用
map_vm_area
将物理页面映射到内核地址空间; - 调用
vm_insert_page
将物理页面映射到用户地址空间; - 调用
zap_page_range
接触物理页面在用户地址空间的映射; - 调用
unmap_kernel_range
解除物理页面在内核地址空间的映射; - 调用
__free_page
释放该物理页面。
Buffer Manage
Binder驱动程序为进程维护了一个内核缓冲区池,其中的每一个内存都是用一个binder_buffer
结构体来描述,并且保存在一个列表里。
当一个进程使用BC_TRANSCATION
或BC_REPLY
向另一个进程传递数据时,Binder驱动程序就需要将这些数据从用户空间拷贝到内核空间,然后再传递给目标进程。
此时,Binder驱动程序就会在目标进程的内存池中分配出一小块内存缓冲区来保存这些数据。进程会从用户空间传递来一个结构体binder_transcation_data
,其中有一个数据缓冲区和一个偏移数组缓冲区,二者的内容就是要拷贝到目标进程的内核缓冲区的。
当一个进程处理完成Binder驱动程序给它发送的返回协议BR_TRANSCATION
或BR_REPLY
之后,就会使用命令协议BC_FREE_BUFFER
来通知Binder驱动程序释放相应的内存缓冲区。
内核缓冲区的分配操作由函数binder_alloc_buf
实现的,其释放操作是由函数binder_free_buf
实现的。
当发生Binder调用时,数据会先从发送进程复制到内核空间,驱动会在接收进程的缓冲区中寻找一块合适大小的空间来存放数据,接收进程的用户空间缓冲区和内核空间缓冲区共享,因此接收进程无需再拷贝数据到用户空间。
binder.c
binder_node
用来描述Binder实体对象,对应Service组件。
proc
- 指向宿主进程,这些宿主进程都是通过
binder_proc
来描述。 rb_node
- 宿主进程使用一个红黑树来维护它内部所有的Binder实体对象,而每一个Binder实体对象的成员变量
rb_node
正好是这个红黑树的一个节点。 dead_node
- 如果宿主进程已经死亡,那么这个Binder实体对象就会通过成员变量
dead_node
保存在一个全局的列表中。 refs
- 由于一个Binder实体对象可能会同时被多个Client组件引用,因此,Binder驱动程序就用结构体
binder_ref
来描述这些应用关系,并且将引用了同一个Binder实体对象的所有引用都保存在一个hash列表中。通过它即可知道哪些Client组件引用了该实体对象。 cookie
- 指向Service组件的地址
ptr
- 指向Service组件内部的一个引用计数对象(weakref_impl)的地址。
binder_ref
用来描述Binder引用对象,对应Client组件。BpHandler
中的mHandler
的值就是它在引用表中的位置。
node
- 用来描述Binder引用对象所应用的Binder实体对象。
node_entry
- 结构体
binder_node
的refs
保存了引用对象,而node_entry
正好是这个列表的节点。 desc
- 一个句柄值或描述符,描述一个Binder引用对象,在Client进程的用户空间中,一个Binder引用对象使用一个句柄值来描述,因此,Client进程通过一个句柄值,Binder驱动程序依据它找到对应的Binder引用对象。该句柄值在进程范围内是唯一的。
proc
- 指向Binder引用对象的宿主进程。
rb_node_desc
- 宿主进程使用红黑树来保存内部所有的Binder引用对象,
rb_node_desc
是句柄值作为关键字来存储。 rb_node_node
- 同上,但是是以Binder实体对象的地址来作为关键字进行存储。
death
- 当Client进程向Binder驱动程序注册一个它所引用的Service组件的死亡接收通知时,Binder驱动程序就会创建一个
binder_ref_death
结构体,并保存在对应的Binder饮用对象的death
成员变量里。
binder_proc
用来描述一个正在使用Binder进程间通信机制的进程。
proc_node
- 当一个进程调用函数
open
来打开设备文件/dev/binder时,Binder驱动程序就会为它创建一个binder_porc
结构体,并把它保存在一个全局的hash列表中,proc_node
就是这个列表的节点。 nodes
refs_by_desc
refs_by_node
- 一个进程内部包含一系列的Binder实体对象和Binder引用对象,进程使用三个红黑树来组织它们。成员变量
nodes
所描述的红黑树是用来组织Binder实体对象的,它是以binder_node
的成员变量ptr
作为关键字的。成员变量refs_by_desc
和refs_by_node
都是用来组织Binder引用对象的,前者是以binder_ref
的成员变量desc
作为关键字的,后者是以binder_ref
的成员变量node
作为关键字的。 buffer_size
- Binder驱动程序为进程分配的内核缓冲区的大小。
buffer_free
- 空闲内核缓冲区的大小。
buffer
- 内核缓冲区的内核空间地址。
wma
- 内核缓冲区的用户空间地址。
user_buffer_offset
- 内核缓冲区的内核空间地址与用户空间地址的差值。
buffers
- 小块的内核缓冲区
binder_buffer
,保存在列表中,按照地址从小到大进行排列,buffers
指向这个列表的头部。 free_buffers
- 还没有分配物理页面的内核缓冲区
binder_buffer
allocated_buffers
- 已经分配了物理页面的内核缓冲区
binder_buffer
threads
- 每一个使用了Binder进程间通信机制的进程都有一个Binder线程池,其由Binder驱动程序管理。
threads
是一个红黑树的根节点,它是以线程ID作为关键字来组织一个进程的Binder线程池。 wait
- Binder线程池中的空闲Binder线程会睡眠在由成员变量
wait
所描述的一个等待队列里,当它们的宿主进程的待处理工作项队列增加了新的工作项,Binder驱动程序就会唤醒这些线程,由它们去处理新的工作项。 todo
- 当进程接收到一个进程间通信请求时,Binder驱动程序就会将该请求封装为一个工作项,并加入到进程的待处理工作项队列中,这个队列就是
todo
所描述的。
binder_ref_death
描述一个Service组件的死亡接收通知。
binder_buffer
描述内核缓冲区,用来在进程间传输数据。驱动通过mmap
的方式创建了一块大的缓存区,每次Binder传输数据,会在缓存区分配一个binder_buffer
的结构来保存数据。
binder_work
描述待处理的工作项。
binder_thread
描述Binder线程池里的一个线程。
binder_transaction
描述进程间通信过程,这个过程又称为是一个事务。
binder.h
binder_write_read
描述进程间通信过程中所传输的数据。
binder_ptr_cookie
描述一个Binder实体对象或一个Service组件的死亡接收通知。
flat_binder_object
描述一个Binder实体对象和一个Binder引用对象外,还可以用来描述一个文件描述符,通过成员变量type进行区分。
Summary
Binder进程间通信可以总结如下:
- 运行在Client进程的
BpBinder
发出进程间通信请求,Binder驱动程序根据Client进程传递来的BpBinder
的句柄值来查找对应的binder_ref
; - Binder驱动程序根据
binder_ref
找到对应的binder_node
,并创建binder_transcation
来描述该次进程间通信过程; - Binder驱动程序根据
binder_node
找到运行在Server进程的BBinder
,并将通信数据交给它处理; BBinder
处理完成通信请求后,将结果返回给Binder驱动程序,Binder驱动程序接着就找到前面所创建的事务;- Binder驱动程序根据事务找到发出通信请求的Client进程,将结果返回给
BpBinder
。