2. Binder基础概念
要理解 Binder 的性能与阻塞问题,必须先建立一套准确的“对象模型”。很多线上问题之所以难以定位,根源在于把 Binder 驱动、ServiceManager、Binder 实体、Binder 引用、代理对象混为一谈。本节从一次跨进程调用的完整链路出发,把这五个概念串成一条可追踪的路径,并解释“一次拷贝”到底省在哪里。
一、五个核心对象的分工
Binder 机制本质上是“内核中转的跨进程对象引用系统”。它由四类角色协作完成:
- Binder 驱动:位于内核态,是唯一的“中转站”。它负责在内核中维护 Binder 节点、引用、线程池与事务队列,并完成数据从发送方到接收方的搬运。所有跨进程调用最终都要经过它。
- ServiceManager:全局的“名字服务”,本身也是一个 Binder 服务(句柄为 0)。它维护“服务名 → Binder 引用”的映射,是客户端通过字符串名字找到远端服务的入口。
- Binder 实体(Binder Node):服务端进程内真实存在的对象,是“被引用的本体”。它在驱动中对应一个 binder_node,拥有唯一的节点标识。
- Binder 引用(Binder Ref):驱动中指向某个 Binder 实体的句柄记录。同一个实体在不同进程中可以有多个引用,每个引用在各自进程内表现为一个整数句柄(handle)。
- 代理对象(Proxy):客户端进程内的本地对象,持有句柄,负责把方法调用打包成 Parcel 事务并交给驱动。它“看起来像”远端对象,实际只是引用的用户态外壳。
二、一次跨进程调用的完整链路
下面这张图展示了从客户端调用代理对象,到服务端实体执行并返回的全过程,同时标出了数据拷贝发生的位置。
链路可以概括为四步:客户端代理把参数写入 Parcel 并调用 transact;驱动根据 handle 找到 binder_ref,再定位 binder_node,把事务放入目标进程队列;服务端 Binder 线程取出事务,调用实体的 onTransact 执行业务;结果沿原路返回并唤醒客户端线程。ServiceManager 只在“查找服务”阶段参与,把服务名解析成客户端可用的 handle。
三、一次拷贝原理:省掉的是哪一次
传统 IPC(如管道、Socket)通常需要两次数据拷贝:用户态发送缓冲区 → 内核缓冲区 → 接收方用户态缓冲区。Binder 通过 mmap 内存映射把接收进程的一块用户态虚拟地址与内核缓冲区映射到同一物理页,于是驱动只需把发送方数据拷贝一次到这块共享区域,接收方就能直接读取。
传统 IPC: 发送方用户态 --copy--> 内核缓冲区 --copy--> 接收方用户态
Binder: 发送方用户态 --copy--> 内核/接收方共享映射区(一次拷贝)
需要注意,这里的“一次拷贝”指的是大数据搬运次数,并不代表整个调用没有其他开销。句柄翻译、事务排队、线程唤醒、Parcel 序列化仍然存在,这些正是后续分析阻塞与延迟的重点。
四、常见误区与复盘清单
| 误区 | 正确理解 |
|---|---|
| handle 就是对象地址 | handle 只是进程内的引用编号,需经驱动翻译才能找到实体 |
| 代理对象就是远端对象 | 代理只是本地外壳,真正执行在服务端实体 |
| ServiceManager 参与每次调用 | 只在获取服务引用时参与,之后调用直接用 handle |
| 一次拷贝等于零开销 | 拷贝只发生一次,但排队、唤醒、序列化仍有成本 |
| 一个实体只有一个引用 | 同一实体可被多个进程引用,各自持有不同 handle |
掌握这套对象模型后,后续分析 Binder 线程池、事务缓冲区、同步阻塞与死锁时,就能准确判断问题发生在“引用翻译”“事务排队”还是“实体执行”阶段,而不是笼统地说“Binder 卡住了”。