Android Binder 机制学习4

作者: __Y_Q | 来源:发表于2021-05-26 18:17 被阅读0次

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

    • binder_work
      结构体 binder_work 用来描述待处理的工作项, 这些工作项有可能属于一个进程, 也有可能属于某一个进程中的某一个线程.

    • binder_node
      结构体 binder_node 用来描述一个 Binder 实体对象. 每一个 Service 组件在 Binder 驱动程序中都对应一个 Binder 实体对象, 用来描述它在内核中的状态.

    • binder_ref
      由于一个 Binder 实体对象可能会被多个 Client 组件引用, 因此 Binder 驱动程序就使用结构体 bindr_ref 来描述这些引用关系. 并且将引用了同一个 Binder 实体对象的所有引用都保存在一个 hash 列表中. 这个 hash 列表通过 Binder 实体对象 binder_node 的成员变量 refs 来描述. Binder 驱动程序可以通过这个成员变量就可以知道有哪些 Client 组件引用了同一个 Binder 实体对象.

    • binder_proc
      结构体 binder_proc 用来描述一个正在使用 Binder 进程间通信机制的进程. 当一个进程调用函数 open(/dev/binder) 时, Binder 驱动程序就会为它创建一个 binder_proc 结构体. 并将它保存在一个全局的 hash 列表 binder_proces 中.

    • binder_thread
      结构体 binder_thread 用来描述 Binder 线程池中的一个线程. 当一个线程注册到 Binder 驱动程序时, Binder 驱动程序就会为它创建一个 binder_thread 结构体. 并且将它的状态初始化为 BINDER_LOOPER_STATE_NEED_RETURN 表示该线程需要马上返回到用户空间. 由于一个线程在注册为 Binder 线程时可能还没有准备好去处理进程间通信请求, 因此, 最好返回到用户空间去做准备工作.
      其中成员变量 struct binder_proc *proc 指向其宿主进程. 一个 Binder 线程的 ID 和状态是通过成员变量 int pidint looper 来描述的.
      (每一个使用了 Binder 进程间通信机制的进程都有一个 Binder 线程池, 用来处理进程间通信请求, 这个 Binder 线程池是由 Binder 驱动程序来维护的.)

    • binder_transaction
      结构体 binder_transaction 是用来描述进程间通信过程, 这个过程也被称为一个事务.
      成员变量 unsigned need_reply 用来区分是同步还是异步.0 为 异步.
      成员变量 struct binder_thread *from 指向发起事务的线程.
      成员变量 struct binder_proc *to_procstruct binder_thread *to_thread 分别指向负责处理该事务的进程和线程. 又被称为目标进程和目标线程.

    • binder_buffer
      结构体 binder_buffer 用来描述一个内核缓冲区, 它是用来在进程间传输数据的. 每一个使用 Binder 进程间通信机制的进程在 Binder 驱动中都有一个内核缓冲区列表, 用来保存 Binder 驱动程序为它分配的内核缓冲区.
      成员变量 struct binder_transaction *transactionstruct binder_node *target_node 用来描述一个内核缓冲区正在交给哪一个事务以及哪一个 Binder 实体对象使用.
      Binder 驱动程序将事务数据保存在一个内核缓冲区中. 然后将它交给目标 Binder 实体对象处理. 而目标 Binder 实体对象再将该内核缓冲区的内容交给相应的 Service 组件处理.
      成员变量 data 指向一块大小可变的数据缓冲区. 它是真正用来保存通信数据的.

    • binder_transaction_data
      结构体 binder_transaction_data 用来描述进程间通信过程中所传输的数据.
      成员变量 target 是一个联合体, 用来描述一个目标 Binder 实体对象或者目标 Binder 引用对象. 那么它的成员变量 ptr 就指向与该 Binder 实体对象对应的一个 Service 组件内部的一个弱引用计数对象的地址. 如果它描述的是一个目标 Binder 引用对象, 那么它的成员变量 handle 就指向该 Binder 引用对象的句柄值.

    • binder_write_read
      应用程序进程打开了设备文件 /dev/binder 之后, 需要通过 IO 控制函数 ioctl 来进一步与 Binder 驱动程序进行交互, 因此 Binder 驱动程序就提供了一系列的 IO 控制命令来和应用程序进程通信. 而在这些控制命令中, 最重要的就是 BINDER_WRITE_READ 命令了. 这个命令后面说跟的参数就是一个 binder_write_read 结构体.
      结构体 binder_write_read 用来描述进程间通信过程中所传输的数据. 这些数据包括输入数据和输出数据.
      成员变量 write_size, write_consumed, write_buffer 用来描述输入数据, 即从用户空间传到 Binder 驱动程序的数据.
      成员变量 read_size, read_consumed, read_buffer 用来描述输出数据, 即从 Binder 驱动程序返回给用户空间的数据, 也是进程间通信的结果数据.
      其中 write_buffer 与 read_buffer 都是一个数组, 数组的每一个元素都由一个通信系协议代码及通信数据组成, 协议码又分为两种类型, 其中一种是输入缓冲区中 write_buffer 中使用的, 成为命令协议码, 另一种是在输出缓冲区 read_buffer 中使用的, 成为返回协议码.

    • 命令协议码 BinderDriverCommandProtocol
      其中 BC_TRANSACTION, BC_REPLY 后面跟的通信数据使用一个结构体 binder_transaction_data 来描述.
      当一个进程请求另外一个进程执行某一个操作时, 源进程就使用命令协议代码 BC_TRANSACTION 来请求 Binder 驱动程序将通信数据传递到目标进程, 当目标进程处理完源进程所请求的操作之后, 就使用命令协议代码 BC_REPLY 来请求 Binder 驱动程序将结果数据传递给源进程.
      当一个线程将自己注册到 Binder 驱动程序之后, 它接着就会使用命令协议码 BC_ENTER_LOOPER 来通知 Binder 驱动程序, 它已经准备就绪处理进程间通信请求了,

    • 返回协议码 BinderDriverReturnProtocol
      返回协议代码 BR_TRANSACTIONBR_REPLY 后面跟的通信数据使用的也是用结构体 binder_transaction_data 来描述.
      当一个 Client 进程向一个 Service 进程发出进程间通信请求时, Binder 驱动程序就会使用返回协议码 BR_TRANSACTION, 通知该 Server 进程来处理该进程间通信请求. 当 Server 进程处理完该进程间通信请求之后, Binder 驱动程序就会使用返回协议代码 BR_REPLY 将进程间通信请求结果数据返回给 Client 进程.
      当 Binder 驱动程序接收到应用程序进程给它发送的一个命令协议码 BC_TRANSACTION 或者 BC_REPLY 时, 它就会使用返回协议代码 BR_TRANSACTION_COMPLETE 来通知应用程序进程, 该命令协议代码已经被接收, 正在分发给目标进程或者线程处理.

    注:无论是 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 引用对象.

    • BnInterface
      模板类 BnInterface 继承了 BBinder 类, BBinder 类为 Binder 本地对象提供了抽象的进程间通信接口. BBinder 类有两个重要的成员函数 transactonTransact. 当一个 Binder 代理对象通过 Binder 驱动程序向一个 Binder 本地对象发出一个进程间通信请求时, Binder 驱动程序就会调用该 Binder 本地对象的成员函数 transact 来处理该请求
      成员函数 onTransact 是由 BBinder 的子类, 即 Binder 本地对象类来实现的. 它负责分发与业务相关的进程间通信请求.

    • BpInterface
      模板类 BpInterface 继承了 BpRefBase 类, 后者为 Binder 代理对象提供了抽象的进程间通信接口.
      BpRefBase 类有一个重要的成员变量 mRemote, 它指向一个 BpBinder 对象, 可以通过成员函数 remote 来获取.
      BpBinder 类实现了 BpRefBase 的进程间通信接口. 其中 BpBinder 类的成员变量 mHandle是一个整数, 表示一个 Client 组件的句柄值.

      每一个 Client 组件在Binder 驱动程序中都对应有一个 Binder 引用对象, 而每一个 Binder 引用对象都有一个句柄值, Client 组件就是通过这个句柄值来和 Binder 驱动程序中的 Binder 引用对象建立对应关系的.

      BpBinder 类中也有成员函数 transact, 用来向运行在 Server 进程中的 Service 组件发送进程间通信请求. 它会将成员变量 mHandle 以及进程间通讯数据发送给 Binder 驱动程序. 这样 Binder 驱动程序就能根据这个句柄值来找到对用的 Binder 引用对象. 继而找到对应的 Binder 实体对象. 最后就可以将进程间通讯数据发送给对应的 Service 组件了.

    无论是 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 之间的关系

    • binder_node
      binder_node 在最开始说过, 是一个结构体, 对应着一个 Binder 实体对象. 是在 Binder 驱动程序中创建的. 每一个 Service 组件在 Binder 驱动程序中都对应一个 Binder 实体对象,

    • binder_ref
      binder_ref 可以理解为 Binder 引用对象. 同样也是在 Binder 驱动程序中创建并且被用户空间的代理对象所引用.
      当 Client 进程第一次引用一个 Binder 实体对象时, Binder 驱动程序就会在内部为它创建一个 Binder 引用对象. Binder 引用对象是运行在内核空间的, 引用了它的 Binder 代理对象运行在用户空间.

    • BBinder
      BBinder 可以理解为 Binder 本地对象, 在用户空间创建. 并且运行在 Server 进程中. Binder 本地对象一方面会被运行在 Server 进程中的其他对象引用, 另一方面也会被 Binder 驱动程序中的 Binder 实体对象引用 (binder_node).

    • BpBinder
      BpBinder 可以理解为 Binder 代理对象. 它是在用户空间中创建的, 并且运行在 Client 进程中. 与 Binder 本地对象类似, Binder 代理对象一方面会被运行在 Client 进程中的其他对象引用, 另一方面它也会引用 Binder 驱动程序中的 Binder 引用对象 (binder_ref). 每一个 Binder 代理对象都是通过一个句柄值来和一个 Binder 引用对象关联.

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

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

    • 位于 Binder 驱动程序中的 Binder 实体对象 binder_node
    • 位于 Binder 驱动程序中的 Binder 引用对象 binder_ref
    • 位于 Binder 库中的 Binder 本地对象 BBinder
    • 位于 Binder 库中的 Binder 代理对象 BpBinder

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

    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 系统源代码情景分析

    相关文章

      网友评论

        本文标题:Android Binder 机制学习4

        本文链接:https://www.haomeiwen.com/subject/zxbnuhtx.html