Binder机制学习总结

18 阅读8分钟

一、为什么 Android 非要自己搞个 Binder

Linux 原生 IPC(管道、消息队列、Socket、共享内存)在手机场景都不太合适:

IPC 方式问题
Socket两次拷贝,开销大,身份认证弱
共享内存性能最高但得自己管同步,编程复杂
消息队列/管道效率低,不适合高频调用

Android 是个"服务化"系统,AMS/WMS/IMS 全是跨进程调,要求高频低延迟 + 身份可信 + 编程友好,传统 IPC 凑不出这套,所以 Binder 自己上了。

Binder 的几个设计卖点:

  • 一次拷贝(mmap 共享,而不是传统的两次)
  • 驱动级身份校验,UID/PID 由驱动塞进事务,Client 没法伪造
  • 面向对象,跨进程像本地调用(IInterfacetransact()/onTransact()
  • 死亡通知 + 引用计数,Server 崩了 Client 能感知

二、四层架构(从上到下)

image.png

image.png

应用层(AIDL)    Proxy(Client) ──── Stub(Server)
                android.os.*              ↑
Java框架层      Binder / ServiceManager    │ JNI
                ──────────────────────────┼──
Native层        BpBinder ────────── BBinder│ libbinder
                IPCThreadState / ProcessState
                ──────────────────────────┼──
驱动层          binder.ko (mmap / 线程池 / 红黑树)
  • 应用层:AIDL 编译器生成 Proxy(Client 侧)和 Stub(Server 侧),开发者只碰这一层
  • Java 框架层android.os.BinderServiceManager,Proxy 把方法 id + 参数打成 Parcel,Stub 在 onTransact()里拆包分发
  • Native 层libbinderBpBinder(transact)/ BBinder(onTransact),每个进程的 ProcessState单例持有一个 Binder 设备 fd,IPCThreadState管单线程的读写循环
  • 驱动层/dev/binder,真正的"裁判",管内存映射、线程调度、binder_node/binder_ref 红黑树、UID/PID 校验

💡 ServiceManager 本身也是个 Binder Server,只是它的 handle 固定为 0——所有进程打开 binder 驱动后,先用 handle 0 跟它对话(注册/查服务),所以它相当于 Binder 世界的"DNS"。


三、一次完整 AIDL 调用的数据路径

Binder 应用篇

把上一轮 AIDL 的例子对到这条链上:

  proxy.foo()
    → Parcel.write()
    → BinderProxy.transact()
      → android_os_BinderProxy_transact(JNI)
        → BpBinder::transact()
          → IPCThreadState::transact()
            → ioctl(BINDER_WRITE_READ)   //与驱动交互
              └─ binder_ioctl() [kernel]
                  → binder_transaction()
                      → copy_from_user() ★
                      → wake up Server

Server App
  binder_thread_read()
    → BR_TRANSACTION
      → IPCThreadState::executeCommand()
        → BBinder::transact()
          → JavaBBinder::onTransact()
            → Stub.onTransact()
              → foo()
              → reply.write()

回包
  binder_transaction()
    → wake up Client
      → waitForResponse()
        → reply.read()
          → proxy.foo() 返回
  1. Client
  2. proxy.foo
  3. BinderProxy.transact---android_os_BinderProxy_transact(JNI);
  4. BpBinder::transact();
  5. IPCThreadState::transact();
  6. ioctl(BINDER_WRITE_READ) //与驱动交互
  7. binder_ioctl() [kernel]
  8. → binder_transaction()

关键节点:

  • Client 的 transact()默认是同步的——调出去之后当前线程就挂起等 reply(所以主线程调 Binder 有可能 ANR)
  • Server 的 onTransact()跑在 Binder 线程池里,不是主线程。
  • oneway 的区别:Client 调完 transact()不等 reply 直接返回,驱动侧事务标记为异步,Server 端走串行队列分发(这就是上轮说的"oneway 反而让 Server 串行"的根因)

四、驱动层两个核心机制

1. mmap:一次拷贝是怎么做到的

传统 IPC(Socket/管道):Client 用户态 → 内核态 → Server 用户态两次 copy_from_user/copy_to_user

Binder 的做法(以 Client → Server 单向为例):

  1. Server 进程在 open("/dev/binder")后调用 mmap在内核和 Server 用户空间之间映射同一块物理页(接收缓冲区)
  2. Client 调 ioctl(BINDER_WRITE_READ)发包,驱动用 copy_from_user()把 Client 用户态的 Parcel 拷进内核缓冲区​ —— 这是第 1 次拷贝
  3. 因为那块内核缓冲区跟 Server 用户空间是 mmap 共享的,Server 用户态直接就能读到,不需要再 copy_to_user —— 这一段是零拷贝
  4. Server 回包时反过来:Server 往共享区写结果 → 驱动 → Client 侧 copy_to_user()取回

所以严格说:单向 1 次拷贝(Client→驱动),往返 2 次,比传统 IPC 省 1 次

⚠️ 注意 Binder 不适合传超大块数据(一般单事务 ≤ 1MB,有些厂商还更小),因为 mmap 那块缓冲区是有限的。真要传大文件走 ashmem 或 fd 传递。

为什么存在多次 copy_from_user ?

传输控制协议部分存在2次拷贝,仅核心 Parcel data 是一次拷贝。

一次拷贝指的是单程——发送方用户→内核必拷一次,接收方 mmap 直读省一次。

真正建立内存映射的只有一次,其余的主要是地址引用的copy。

2. Binder 驱动在内核层(binder_proc)中的四棵红黑树

底层通信高度依赖 Binder 机制。为了高效管理海量的 Binder 实体和引用。

Binder 是面向对象的,Client 拿到的不是真的 Server 对象,而是引用

1.nodes(Binder实体树)

储存对象:binder_node:驱动里代表 Server 侧的 Binder 实体,如四大组件的具体实例,挂在 Server 进程的节点树里。

索引键:服务端在用户空间的内存地址(ptr)。

2.refs_by_desc(按句柄引用的客户端对象树)binder_ref:Client 侧拿到的是"引用",驱动维护 ref → node的红黑树映射,Client 手里的 handle其实就是 ref 的编号。方便客户端调用查找。

索引键handle(句柄号,类似文件描述符 FD)。

3.refs_by_node(按节点引用的客户端对象树)存储对象binder_ref索引键:Binder 实体对象指针。给 node,找所有引用它的 ref,比如server死了后,查找哪些客户端调用了它,并通知。

4.threads(进程内 Binder 线程树)

  • 存储对象binder_thread(处理 IPC 请求的线程池记录)。
  • 索引键:线程 ID(pid)。

跨进程传 Binder 对象时(比如 Service 的 onBind()返回 Binder),驱动会自动做 node/ref 转换——这才实现了"Binder 对象可以像参数一样传来传去"

这也是为什么 Binder 能当"虚拟总线"用:引用像指针,node 像对象,跨进程传引用就等价于跨进程传"指针"。

匿名binder

Android Binder框架实现之何为匿名/实名Binder_匿名binder-CSDN博客

注册到ServiceManager 的是实名binder;没有注册到ServiceManager 的是匿名binder。

匿名binder 必须借用实名 binder 来实现。

bindService 就是典型的匿名 binder .


五、线程模型

  1. 每个开了 Binder 的进程,驱动里都有一个 Binder 线程池
  2. DEFAULT_MAX_BINDER_THREADS = 15,加上 Binder 主线程 → 实际 16 个可并发处理事务
  3. 线程是 spawn-on-demand​ 的:初始几个,事务多了驱动通知 IPCThreadState自己 pthread_create新线程,触顶 15 就停。
  4. system_server 自己显式调到 31,因为扛的系统服务调用多。
  5. 事务在驱动里排队的条件是:16 个 Binder 线程全忙 + 新事务到来​ → Client 侧 transact()阻塞,直到有线程腾出来

AIDL 多进程并发不会自动阻塞,堵的永远是 Binder 线程池被打满 / stub 里自己加锁 / 等主线程


六、安全与死亡通知

  • UID/PID 由驱动在事务结构体 binder_transaction_data里塞好,Server 调 getCallingUid()拿到的是驱动给的,Client 改不了——这是 Binder 权限校验的根
  • 死亡通知(DeathRecipient) :Server 进程挂了,驱动会扫它所有的 binder_node,给每个还活着的引用方发 BR_DEAD_BINDER,Client 注册 linkToDeath()就能感知,常用来做 Service 重连

七、一句话串起来

AIDL 的 Proxy.transact()→ JNI → BpBinderioctlbinder.ko→ 驱动靠 mmap 共享区 + 一次 copy_from_user​ 把 Parcel 递到 Server 的 Binder 线程 → Stub.onTransact()分发 → 回包走反向 copy_to_user。整条链路里驱动是裁判(身份/引用/线程/死亡都归它管),mmap 是性能根,线程池 16 并发是上轮阻塞问题的答案。

PS:Binder相关类介绍:

IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。

IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)。

Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。


RefBase:指针基类 IBinder 继承RefBase,IBinder.h: 申明binder的必要方法; BBinder 继承IBinder;在Server进程中,BBinder为Binder本地对象提供了抽象的进程间接口. BpBinder 继承IBinder;Binder proxy ,在Client进程中; BBinder和BpBinder的功能并不是对称的,改成BnBinder可能更形象: Binder Native。 BnInterface 继承BBinder; 在Server进程中。 BpInterface 继承BpRefBase;BpRefBase继承RefBase,为Binder代理对象提供了进程间通信的接口

asInterface 完成的是Binder到Interface的转换,具体就是:BBinder->BnInterface;BpBinder->BpInterface。 而asBinder功能则相反,具体是:BnInterface->BBinder;BpInterface->BpBinder

Binder代理对象 类型为BpBinder,在用户空间创建,且执行在Client进程中.会被Client进程中的其他对象引用,另外会引用Binder驱动程序中的Binder引用对象. Binder引用对象 类型为binder_ref,在Binder驱动程序中创建,被Binder代理对象引用. Binder实体对象 类型为binder_node,在Binder驱动程序中创建,被Binder引用对象所引用 Binder本地对象 类型为BBinder,在用户空间中创建,且执行在Server进程中.会被Server进程中其他对象引用,还会被Binder实体对象引用.

ProcessState是负责打开Binder节点并做mmap映射,IPCThreadState是负责与Binder驱动进行具体的命令交互。 getService@ServiceManagerProxy-->transact@BinderProxy-->transact@BpBinder-->transact@IPCThreadState

Binder传输最大数据:普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 (1024*1024) - (4096 *2) :这个限制定义在ProcessState类中。