Binder机制2---Binder的数据结构以及Binder驱动

大纲

  1. Binder的数据结构

  2. Binder写操作命令字: BC_XXX

  3. Binder读操作命令字: BR_XXX

  4. Binder在传输数据中的表述:flat_binder_object

  5. Binder对象类型

  6. Binder实体在驱动中的表述:binder_node

  7. Binder引用在驱动中的表述:binder_ref

  8. Binder 进程、线程结构:binder_proc和binder_thread

  9. Binder收发数据包结构:binder_transaction_data

  10. Binder驱动

  11. binder_init

  12. binder_ioctl

  13. binder_open

  14. binder_release

  15. binder_flush

  16. binder_poll

  17. binder_mmap

2. Binder的数据结构

2.1 Binder在传输数据中的表述flat_binder_object

**
**

Binder可以在数据包的有效数据中越过进程边界从一个进程传递给另一个进程,这些传输中的Binder用 flat_binder_object结构来表示,如上所示

无论是Binder实体还是对实体的引用都从属于某个进程,所以该结构不能透明地在进程之间传输,必须有驱动的参与。例如当Server把 Binder实体传递给Client时,在发送数据中,flat_binder_object中的type是 BINDER_TYPE_BINDER,binder指向Server进程用户空间地址。如果透传给接收端将毫无用处,驱动必须对数据流中的这个 Binder做修改:将type该成BINDER_TYPE_HANDLE;为这个Binder在接收进程中创建位于内核中的引用并将引用号填入 handle中。对于发生数据流中引用类型的Binder也要做同样转换。经过处理后接收进程从数据流中取得的Binder引用才是有效的,才可以将其填 入数据包binder_transaction_data的target.handle域,向Binder实体发送请求。
这样做也是出于安全性考虑:应用程序不能随便猜测一个引用号填入target.handle中就可以向Server请求服务了,因为驱动并没有为你 在内核中创建该引用,必定会驱动被拒绝。唯有经过身份认证确认合法后,由‘权威机构’通过数据流授予你的Binder才能使用,因为这时驱动已经在内核中 为你建立了引用,交给你的引用号是合法的。

2.2 Binder对象的类型

2.5 Binder实体在驱动中的表述 binder_node

驱动中的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示

每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的 flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说 内存地址是唯一的,所以不会重复建设造成混乱。

2.6 Binder引用在驱动中的表述--引用描述结构:binder_ref

和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示

就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存放所有该进程正在使用的引用。但Binder的引用可以通过两个键值索引:

  • 对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体 在内核中 的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个 进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。

  • 引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号 将返回给 应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都保留给SMgr,其它值由驱动在创建引用时动态分配。向Binder 发送数据包时,应用程序通过将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的 Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信 息,实现数据包的路由。

2.7 Binder节点、进程、线程结构

binder_transaction:

  • 主要C/S即请求进程和服务进程的相关信息,方便进程间通信,以及信息的调用。该结构体包括了binder_work以及进程/线程结构体信息,以及binder状态结构体

binder_node:

  • binder的节点信息结构体,包括了binder_work, binder_proc.

binder_work:

  • 理解为binder驱动中,进程所要处理的工作项。

binder_proc:

  • 每打开一个binder驱动(系统允许多个进程打开binder驱动),都会有一个专门的binder_proc管理    当前进程的信息,包括:进程的ID、当前进程由mmap所映射出的buffer信息、以及当前进程所允许    的最大线程量。同时这个binder_proc会加入到系统的全局链表binder_procs中去,方便在不同进程之    间可以查找信息。

binder_thread:

  • 线程信息结构体。在进程下存在一个或多个线程,因此binder驱动使用binder_thread来管理对应的线    程信息,主要包括线程所属的binder_proc、当前状态looper以及一个binder_transaction结构的        transaction_stack.

3. Binder驱动

===================

3.1 binder_init

binder_init()初始化函数流程:

  1. 创建系统根节点:proc/binder

  2. 如果成功,创建”proc/binder/proc”,并把自己注册成Misc设备。 在Misc的Struct miscdevice binder_miscdev中:有.fops = &binder_fops。这个就对应了Binder驱动的6个函数调用方法:ioctl, poll, open, release,flush, mmap

  3. 创建目录:”/dev”,并创建如上图的5个文件节点

3.2 binder_ioctl

在该函数中,一共有7个命令,但只实现了5个。在用户空间通过ioctl函数调用相应底层驱动的命令,来实现相应的方法。

3.2.1 BINDER_WRITE_READ

这个io操作码有一个参数,形式为struct binder_write_read

BINDER_WRITE_READ命令流程:

  1. 检查命令的完整性

  2. 把数据从用户空间拷贝到”binder_write_read”的结构体中

  3. 判断这个结构体中的write_size和read_size是否大于0.

  4. 如果write_size大于0则调用binder_thread_write函数;如果read_size大于0则调用binder_thread_read函数。

  5. write_bufffer和read_buffer所指向的数据结构还指定了具体要执行的操作,write_bufffer和read_buffer所指向的结构体是struct binder_transaction_data

Binder收发数据包结构:binder_transaction_data

和写数据一样,其中最重要的消息是BR_TRANSACTION 或BR_REPLY,表明收到了一个格式为binder_transaction_data的请求数据包(BR_TRANSACTION)或返回数据包 (BR_REPLY)

Binder写操作命令字(code)

在 这些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令对,Binder数据通过这对命令发送给接收方。这对命令所承载的数 据包由结构体struct binder_transaction_data定义。Binder交互有同步和异步之分,利用binder_transaction_data中 flag域区分。如果flag域的TF_ONE_WAY位为1则为异步交互,即Client端发送完请求交互即结束, Server端不再返回BC_REPLY数据包;否则Server会返回BC_REPLY数据包,Client端必须等待接收完该数据包方才完成一次交 互。

**Binder读操作命令字(code)
**

**
**

和写数据一样,其中最重要的消息是BR_TRANSACTION 或BR_REPLY,表明收到了一个格式为binder_transaction_data的请求数据包(BR_TRANSACTION)或返回数据包 (BR_REPLY)

3.2.2 BINDER_SET_CONTEXT_MGR命令

作用是:将当前进程注册为SMgr。系统中同时只能存在一个SMgr。只要当前的SMgr没有调用close()关闭Binder驱动就不能有别的进程可以 成为SMgr。

流程:

  1. 检查binder_context_mgr_node的值是否为空,如果为空说明当前没有Context Manager的节点;

  2. 检查binder_context_mgr_uid是否存在,如果不存在,则设驱动中的全局变量binder_context_mgr_uid为当前进程的uid;如果存在,检查当前进程是否有执行命令的权限。

  3. 创建并初始化一个binder_node并赋值给全局变量binder_context_mgr_node。    

这个命令是将一个进程/线程设置为Context Manager。该命令一般在系统启动时初始化Binder驱动的过程中被调用

3.2.3 BINDER_SET_MAX_THREADS

该命令告知Binder驱动接收方(通常是Server端)线程池中最大的线程数。由于Client是并发向Server端发送请求 的,Server端必须开辟线程池为这些并发请求提供服务。告知驱动线程池的最大值是为了让驱动在线程达到该值时不要再命令接收端启动新的线程。

3.2.4 BINDER_THREAD_EXIT

通知Binder驱动当前线程退出了。Binder会为所有参与Binder通信的线程(包括Server线程池中的线程和Client发出请求的 线程)建立相应的数据结构。这些线程在退出时必须通知驱动释放相应的数据结构。

3.2.5 BINDER_VERSION

获得Binder驱动的版本号

3.3 binder_open

**
**

流程:

  1. 创建并分配空间,保存Binder的数据。增加当前线程或进程的引用计数,把值赋给binder_proc的tsk段。

  2. 初始化binder_proc队列;设置当前进程nice值为default_priority

  3. 并把创建的binder_proc对象添加到全局的binder_proc Hash table中。这样,进程间就可以互相访问彼此的binder_proc对象了。

  4. 把当前进程的组id赋值给proc->pid,把proc赋值给filp->private_data,最后保存filp。

3.4 binder_release

流程:

  1. 获得当前进程/线程的pid

  2. 调用remove_proc_entry()删除pid命名的只读文件。

  3. 调用binder_defer_work()释放binder_proc对象;此处使用workqueue来提高系统性能。

3.5 binder_flush

流程:

  1. 调用函数binder_defer_work

  2. 调用schedule_work()来执行相应操作;

  3. 调用binder_free_thread()释放thread信息。

此函数一般在将在关闭一个设备文件描述符复制时被调用

3.6 binder_poll

实现非阻塞IO模型函数流程:

  1. binder_get_thread(proc)获得当前进程信息。

  2. 如果是进程,调用proc_work();如果是线程,调用thread_work()

  3. 调用binder_has_x_work检测,来判断所要采用的等待方式

  4. 队列是否为空;

  5. 线程/进程的循环状态;

  6. 返回信息;

  7. 调用poll_wait实现poll操作。

3.7 binder_mmap

注意:mmap函数的第二个参数为VMA的结构体指针,VMA是用来管理进程地址空间中不同区域的数据结构,由内核来维持。最终的作用是把分配的空间加入到VMA区域中去。VMA在mmap函数中会被使用。

内存空间映射函数流程:

  1. 检查映射空间大小(分配空间不能大于4M)

  2. 检查flags(映射区域不能是可写区域)

  3. 检查内存是否被映射过(内存空间不能被重复映射)

  4. 申请虚拟空间   

  5. 分配页空间 

  6. 分配物理内存