Understanding Android Source: Binder Driver

Binder驱动扮演的角色主要有:

  • 提供Binder通信的渠道;
  • 维护Binder对象的计数;
  • 转换传输中Binder实体对象和引用对象;
  • 管理数据缓冲区。

Initialization

通过device_initcall函数,来调用binder_init函数,进而完成Binder设备的初始化过程。
初始化过程中,函数misc_register和两个结构体file_operationsmiscdevice起到了重要作用。

为方便调试和跟踪问题,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结构体procproc将被插入到全局的红黑树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同步操作的功能。

  1. 首先调用get_vm_area分配一块地址空间,这块地址空间位于用户进程空间;
  2. 接着调用binder_update_page_range在内核里也分配相同页数大小的空间;
  3. 将二者的物理内存地址绑定在一起。

vm_area_structvm_struct都是用来描述一段连续的虚拟地址空间,(但它们对应的物理页面是可以不连续的)。前者用在用户地址空间(0-3G),后者用在内核地址空间(3G-4G)。关键的数据结构vm_operations_struct

  1. 调用alloc_page为内核地址空间分配一个物理页面;
  2. 调用map_vm_area将物理页面映射到内核地址空间;
  3. 调用vm_insert_page将物理页面映射到用户地址空间;
  4. 调用zap_page_range接触物理页面在用户地址空间的映射;
  5. 调用unmap_kernel_range解除物理页面在内核地址空间的映射;
  6. 调用__free_page释放该物理页面。

Buffer Manage

Binder驱动程序为进程维护了一个内核缓冲区池,其中的每一个内存都是用一个binder_buffer结构体来描述,并且保存在一个列表里。

当一个进程使用BC_TRANSCATIONBC_REPLY向另一个进程传递数据时,Binder驱动程序就需要将这些数据从用户空间拷贝到内核空间,然后再传递给目标进程。

此时,Binder驱动程序就会在目标进程的内存池中分配出一小块内存缓冲区来保存这些数据。进程会从用户空间传递来一个结构体binder_transcation_data,其中有一个数据缓冲区和一个偏移数组缓冲区,二者的内容就是要拷贝到目标进程的内核缓冲区的。

当一个进程处理完成Binder驱动程序给它发送的返回协议BR_TRANSCATIONBR_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_noderefs保存了引用对象,而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_descrefs_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进程间通信可以总结如下:

  1. 运行在Client进程的BpBinder发出进程间通信请求,Binder驱动程序根据Client进程传递来的BpBinder的句柄值来查找对应的binder_ref
  2. Binder驱动程序根据binder_ref找到对应的binder_node,并创建binder_transcation来描述该次进程间通信过程;
  3. Binder驱动程序根据binder_node找到运行在Server进程的BBinder,并将通信数据交给它处理;
  4. BBinder处理完成通信请求后,将结果返回给Binder驱动程序,Binder驱动程序接着就找到前面所创建的事务;
  5. Binder驱动程序根据事务找到发出通信请求的Client进程,将结果返回给BpBinder

Leave a comment

Your comment