一、为什么 Android 非要自己搞个 Binder
Linux 原生 IPC(管道、消息队列、Socket、共享内存)在手机场景都不太合适:
| IPC 方式 | 问题 |
|---|---|
| Socket | 两次拷贝,开销大,身份认证弱 |
| 共享内存 | 性能最高但得自己管同步,编程复杂 |
| 消息队列/管道 | 效率低,不适合高频调用 |
Android 是个"服务化"系统,AMS/WMS/IMS 全是跨进程调,要求高频低延迟 + 身份可信 + 编程友好,传统 IPC 凑不出这套,所以 Binder 自己上了。
Binder 的几个设计卖点:
- 一次拷贝(mmap 共享,而不是传统的两次)
- 驱动级身份校验,UID/PID 由驱动塞进事务,Client 没法伪造
- 面向对象 ,跨进程像本地调用(
IInterface→transact()/onTransact()) - 死亡通知 + 引用计数,Server 崩了 Client 能感知
二、四层架构(从上到下)


scss
应用层(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.Binder、ServiceManager,Proxy 把方法 id + 参数打成Parcel,Stub 在onTransact()里拆包分发 - Native 层 :
libbinder的BpBinder(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"。
三、驱动层两个核心机制
1. mmap:一次拷贝是怎么做到的
传统 IPC(Socket/管道):Client 用户态 → 内核态 → Server 用户态,两次 copy_from_user/copy_to_user。
Binder 的做法(以 Client → Server 单向为例):
- Server 进程在
open("/dev/binder")后调用mmap,在内核和 Server 用户空间之间映射同一块物理页(接收缓冲区) - Client 调
ioctl(BINDER_WRITE_READ)发包,驱动用copy_from_user()把 Client 用户态的 Parcel 拷进内核缓冲区 ------ 这是第 1 次拷贝 - 因为那块内核缓冲区跟 Server 用户空间是 mmap 共享的,Server 用户态直接就能读到 ,不需要再 copy_to_user ------ 这一段是零拷贝
- 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_node / binder_ref:怎么表示"远程对象"
Binder 是面向对象的,Client 拿到的不是真的 Server 对象,而是引用:
- binder_node:驱动里代表 Server 侧的 Binder 实体,挂在 Server 进程的节点树里
- binder_ref :Client 侧拿到的是"引用",驱动维护
ref → node的红黑树映射,Client 手里的handle其实就是 ref 的编号 - 跨进程传 Binder 对象时(比如 Service 的
onBind()返回 Binder),驱动会自动做 node/ref 转换------这才实现了"Binder 对象可以像参数一样传来传去"
这也是为什么 Binder 能当"虚拟总线"用:引用像指针,node 像对象,跨进程传引用就等价于跨进程传"指针"。
匿名binder
Android Binder框架实现之何为匿名/实名Binder_匿名binder-CSDN博客
注册到ServiceManager 的是实名binder;没有注册到ServiceManager 的是匿名binder。
匿名binder 必须借用实名 binder 来实现。
bindService 就是典型的匿名 binder .
四、一次完整 AIDL 调用的数据路径
把上一轮 AIDL 的例子对到这条链上:
arduino
Client App Server App
───────── ─────────
proxy.someMethod(a, b) stub.onTransact(code, data, reply, flags)
→ Parcel 打包 ← 拆包 dispatch
→ JNI → BpBinder.transact() → 执行业务
→ IPCThreadState::talkWithDriver → Parcel 写回
→ ioctl(BINDER_WRITE_READ) → 驱动通知 Client
└─→ 进入 binder.ko ◀─────────────┐
copy_from_user (1次) │
找到 target binder_node │
唤醒 Server 的 Binder 线程 │
│
Client 阻塞等 reply ◀─────────────────── 驱动把共享区数据交给 Server
copy_to_user (回包1次)
关键节点:
- Client 的
transact()默认是同步的------调出去之后当前线程就挂起等 reply(所以主线程调 Binder 有可能 ANR) - Server 的
onTransact()跑在 Binder 线程池里,不是主线程。 - oneway 的区别 :Client 调完
transact()不等 reply 直接返回,驱动侧事务标记为异步,Server 端走串行队列分发(这就是上轮说的"oneway 反而让 Server 串行"的根因)
五、线程模型
- 每个开了 Binder 的进程,驱动里都有一个 Binder 线程池
DEFAULT_MAX_BINDER_THREADS = 15,加上 Binder 主线程 → 实际 16 个可并发处理事务- 线程是 spawn-on-demand 的:初始几个,事务多了驱动通知
IPCThreadState自己pthread_create新线程,触顶 15 就停 - system_server 自己显式调到 31,因为扛的系统服务调用多
- 事务在驱动里排队的条件是: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 →BpBinder→ioctl进binder.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类中。