Android开发Android开发经验谈Android开发

Android Binder 机制学习4

2021-05-26  本文已影响0人  __Y_Q

1. Kernel 层 Binder 驱动 相关结构体解释

注:无论是 Client 还是 Server 发给 Binder 驱动程序的都是 BC 开头的协议码, 由 Binder 驱动程序发出的都是 BR 开头的返回协议码.


2. Binder 设备的初始化过程总结

binder_init 方法总结如下

  1. 分配内存
  2. 调用 misc_register() 创建 binder 设备
  3. 放入到 binder_devices 链表

3. Binder 设备的打开过程总结

一个进程在使用 Binder 进程间通信机制之前, 首先要调用函数 open 打开设备文件 /dev/binder 来获得一个文件描述符, 然后才能通过这个文件描述符来和 Binder 驱动进行交互, 继而和其他进程执行 Binder 进程间通信.

binder_open 方法总结如下

  1. 创建 binder_proc 结构体 proc.
  2. 对 pro 进行初始化.设置其 PID, 优先级等.
  3. 将 pro 加入到全局列表 binder_proces
  4. 将 pro 保准在参数 filp 的成员变量 private_data 中.

参数 filp 指向一个打开文件结构体, 当进程调用函数 open 打开设备文件 /dev/binder 后, 内核就会返回一个文件描述符给进程, 而这个文件描述符与参数 filp 说指向的打开文件结构体是关联在一起的. 因此, 当进程后面以文件描述符为参数调用函数 mmap 或者 ioctl 来与 Binder 驱动程序交互时, 内核就会将该文件描述符相关的打开文件结构体传递为 Binder 驱动程序, 这时候 Binder 驱动程序就可以通过它的成员变量 private_data 来获得在函数 binder_open 中为进程创建的 binder_proc 结构体 proc.


4. Binder 设备的内存映射过程总结

当进程打开了设备文件 /dev/binder 之后, 还需要调用函数 mmap 把这个设备文件映射到进程的地址空间, 然后才可以使用 Binder 进程间通信机制. 设备文件 /dev/binder 对应的是一个虚拟的设备. 将它映射到进程的地址空间是为了为进程分配内核缓冲区.

mmap 方法总结如下
1.为内核空间地址分配大小.

Binder 驱动程序为进程分配的内核缓冲区有两个地址, 一个是用户空间地址, 由参数 vma 所指向的一个 vm_area_struct 结构体来描述. 另一个是内核空间地址 由变量 area 所指向的一个 vm_struct 结构体来描述. 进程通过用户空间地址来访问这块内核缓冲区内的内容, 而 Binder 驱动程序是通过内核空间地址来访问这块内核缓冲区的内容.

  1. 创建物理页面. 即分配内核缓冲区.一般为 4k 大小. 调用 binder_update_page_range() 方法分别映射到内核地址空间与用户地址空间.

当一个进程使用命令协议 BC_TRANSACTON 或者 BC_REPLY 来到与 Binder 驱动程序交互时, 它会从用户空间传递一个 binder_transaction_data 结构体给 Binder 驱动程序, 在这个结构体中有一个数据缓冲区与一个偏移数组缓冲区. 这两个缓冲区的内容就是需要拷贝到目标进程的内核缓冲区中的.


5. Framework 层 Binder 库

Android 系统在应用程序框架层中间将各种 Binder 驱动程序操作封装成了一个 Binder 库, 这样进程就可以方便的调用 Binder 库提供的接口来实现进程间通信.

BnInterface 与 BpInterface

在 Binder 库中, Service 组件和 Client 组件分别使用模板类 BnInterfaceBpInterface 来描述, 其中前者称为 Binder 本地对象, 后者称为 Binder 代理对象.

Binder 库中的 Binder 本地对象和 Binder 代理对象分别对应 Binder 驱动中的 Binder 实体对象与 Binder 引用对象.

无论是 BBinder 还是 BpBinder 类, 他们都是通过 IPCThreadState 类来和 Binder 驱动程序交互.

IPCThreadState

对于每一个 Binder 线程来说, 它的内部都有一个 IPCThreadState 对象,通过 IPCThreadState 类的 静态成员函数 self 来获取, 并且调用它的成员函数 transanct 来和 Binder 驱动程序交互.

IPCThreadState 类的成员函数 transanct 内部, 与 Binder 驱动程序的交互操作又是通过调用成员函数 talkWithDriver 来实现的, 它一方面负责向 Binder 驱动程序发送进程间通信请求, 另一方面又负责接收来自 Binder 驱动的进程间通信请求.

IPCThreadState 类有一个成员变量 mProcess, 指向一个 ProcessState 对象. 对于每一个使用了 Binder 进程间通信机制的进程来说, 它的内部都有一个 ProcessState 对象. 它负责打开 Binder 设备. 以及将设备文件 /dev/binder 映射到进程的地址空间. 由于 ProcessState 对象在进程范围内是唯一的, 因此 Binder 线程池中的每一个线程都可以通过它来和 Binder 驱动程序建立连接.

ProcessState

进程中的 ProcessState 对象可以通过 ProcessState 类的静态成员函数 self 来获取. 第一次调用 ProcessState 类的静态成员函数 self 时, Binder 库就会为进程创建一个 ProcessState 对象, 并且调用函数 open 来打开设备文件 /dev/binder, 接着又调用函数 mmap 将它映射到进程的地址空间. 即请求 Binder 前驱动为进程分配内核缓冲区. 映射完成后, 将得到的内核缓冲区的用户地址就保存在其成员变量 mVMStart 中.


binder_node, binder_ref, BBinder, BpBinder 之间的关系

Server 进程将一个 Binder 本地对象 BBinder 注册到 Service Manager 时, Binder 驱动就会为它创建一个 Binder 实体对象 binder_node, 接下来, 当 Client 通过 Service Manager 进程来查询一个 Binder 本地对象的代理对象接口时, Binder 驱动程序就会为它所对应的 Binder 实体对象 binder_node 创建一个 Binder 引用对象 binder_ref.

在 Client 进程与 Server 进程的一次通信过程中, 一共涉及到了四种类型对象, 分别是

它们的交互过程可划分为五个步骤

  1. 运行在 Client 进程中的 Binder 代理对象通过 Binder 驱动程序向运行在 Server 进程中的 Binder 本地对象发出一个进程间通信请求, Binder 驱动程序接着就根据 Client 进程传递过来的 Binder 代理对象的句柄值来找到对应的 Binder 引用对象.
  2. Binder 驱动程序功能键前面找到的 Binder 引用对象找到对应的 Binder 实体对象, 并且创建一个 binder_transacation 事务来描述本次进程间通信过程.
  3. Binder 驱动程序根据前面找到的 Binder 实体对象来找到运行在 Server 进程中的 Binder 本地对象, 将 Client 传来的数据交给它处理.
  4. Binder 本地对象处理完 Client 进程的通信请求之后, 就将结果返回给 Binder 驱动程序. Binder 驱动程序接着就找到前面所创建的一个事务.
  5. Binder 驱动程序根据前面找到的事务的相关属性来找到发出通信请求的 Client 进程, 并且通知 Client 进程将通信结果返回给对应的 Binder 代理对象处理.

从这五个过程可以看出, Binder 代理对象依赖于 Binder 引用对象. 而 Binder 引用对象又依赖于 Binder 实体对象. 最后 Binder 实体对象又依赖于 Binder 本地对象.

学习资料参考: 老罗的 Android 系统源代码情景分析

上一篇下一篇

猜你喜欢

热点阅读