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 事务并交给驱动。它“看起来像”远端对象,实际只是引用的用户态外壳。
关键区分:Binder 实体是“本体”,Binder 引用是“指向本体的句柄记录”,代理对象是“客户端手里的遥控器”。三者不是同一个东西,但通过驱动被绑定在一起。

二、一次跨进程调用的完整链路

下面这张图展示了从客户端调用代理对象,到服务端实体执行并返回的全过程,同时标出了数据拷贝发生的位置。

客户端进程 代理对象 Proxy 持有 handle = 12 Parcel 数据 用户态缓冲区 调用 transact() 阻塞等待 reply Binder 驱动(内核态) binder_ref(handle=12) → 指向 binder_node binder_node(实体节点) 服务端进程的锚点 事务队列 + 线程池 等待服务端线程取走 一次拷贝发生在此处 服务端进程 Binder 实体 onTransact() 业务逻辑执行 返回结果 Parcel Binder 线程处理 写回 reply ① 事务 ② 投递 ③ 返回 ④ 唤醒 ServiceManager(handle=0) 名字 → 引用

链路可以概括为四步:客户端代理把参数写入 Parcel 并调用 transact;驱动根据 handle 找到 binder_ref,再定位 binder_node,把事务放入目标进程队列;服务端 Binder 线程取出事务,调用实体的 onTransact 执行业务;结果沿原路返回并唤醒客户端线程。ServiceManager 只在“查找服务”阶段参与,把服务名解析成客户端可用的 handle。

三、一次拷贝原理:省掉的是哪一次

传统 IPC(如管道、Socket)通常需要两次数据拷贝:用户态发送缓冲区 → 内核缓冲区 → 接收方用户态缓冲区。Binder 通过 mmap 内存映射把接收进程的一块用户态虚拟地址与内核缓冲区映射到同一物理页,于是驱动只需把发送方数据拷贝一次到这块共享区域,接收方就能直接读取。

传统 IPC:  发送方用户态 --copy--> 内核缓冲区 --copy--> 接收方用户态
Binder:    发送方用户态 --copy--> 内核/接收方共享映射区(一次拷贝)

需要注意,这里的“一次拷贝”指的是大数据搬运次数,并不代表整个调用没有其他开销。句柄翻译、事务排队、线程唤醒、Parcel 序列化仍然存在,这些正是后续分析阻塞与延迟的重点。

小数据(如几十字节的整型参数)走 Binder 时,拷贝开销本身很小,性能瓶颈往往出现在线程池耗尽、锁竞争或同步等待上,而不是拷贝次数。

四、常见误区与复盘清单

误区正确理解
handle 就是对象地址handle 只是进程内的引用编号,需经驱动翻译才能找到实体
代理对象就是远端对象代理只是本地外壳,真正执行在服务端实体
ServiceManager 参与每次调用只在获取服务引用时参与,之后调用直接用 handle
一次拷贝等于零开销拷贝只发生一次,但排队、唤醒、序列化仍有成本
一个实体只有一个引用同一实体可被多个进程引用,各自持有不同 handle
排障提示:当出现 “Binder transaction failed” 或调用长时间不返回时,先确认是 handle 失效(服务已死)、线程池耗尽,还是对端 onTransact 阻塞。三者现象相似,但根因完全不同。

掌握这套对象模型后,后续分析 Binder 线程池、事务缓冲区、同步阻塞与死锁时,就能准确判断问题发生在“引用翻译”“事务排队”还是“实体执行”阶段,而不是笼统地说“Binder 卡住了”。