从「AIDL 服务端会不会被自动注册到 Binder 驱动」一路聊到「AMS 在 Binder 调用里到底起什么作用」的整理稿。
一、AIDL 服务端会被自动注册到 Binder 驱动 / ServiceManager 吗?
不会自动注册。 AIDL 只是个接口代码生成器 ------.aidl 文件编译后产出 Stub / Proxy 两个类,仅此而已。它不碰 Binder 驱动,也不碰 ServiceManager。
是否注册、注册到哪里,取决于你怎么用这个 Stub:
| 用法 | 是否在 binder 驱动注册 | 是否在 ServiceManager 注册 | 触发点 |
|---|---|---|---|
new IFoo.Stub() 当作普通对象返回 |
会(首次跨进程传递时) | 否 | 第一次被 writeStrongBinder() 序列化跨进程 |
Service.onBind() 返回给 bindService() 调用方 |
会 | 否 | AMS 把 binder 交给 client 时 |
ServiceManager.addService("foo", stub)(系统进程才能调) |
会 | 会 | 显式 addService |
Binder.allowBlocking() 等纯本地用 |
否 | 否 | 永远不跨进程 |
底层时序大致是这样:
scss
new IFoo.Stub()
↓ (JavaBBinder native 对象延迟创建)
首次 writeStrongBinder(stub) 跨进程
↓ libbinder 通过 ioctl(BINDER_WRITE_READ)
↓ 把 BBinder 指针交给驱动
Binder Driver 创建对应 binder_node
↓ 在驱动内分配 handle 给接收方
接收方进程拿到 handle → 包装成 BinderProxy
关键点:
- binder 驱动里的「注册」是懒注册 ------直到这个 Binder 对象第一次跨进程传递,驱动里才有它的
binder_node。光new Stub()不传出去,驱动完全不知道它存在。 - ServiceManager 是额外一层命名服务 ,只给「全局可寻址」的服务用(
getSystemService那一类)。普通 App 的 AIDL 服务不会 也没权限 往里塞------addService在普通进程会被 SELinux 拒绝,只有system_server等白名单进程能调。 - 普通 App 间通过
bindService共享 AIDL:AMS 充当中介,把 server 进程的 Stub binder 通过ServiceConnection.onServiceConnected交给 client,绕过 ServiceManager。
一句话:AIDL 只生成代码,binder_node 由驱动按需创建,ServiceManager 名字只有系统服务才会注册。
二、AIDL 服务端 / 客户端通过 Binder 交互的全过程
核心矛盾:Client 拿到的只是一个整数 handle ,怎么就能精准调到另一个进程里的某个对象?答案分两步------先建立寻址关系 ,再走调用流程。
2.1 Binder 怎么"找到"服务端:寻址的建立
Binder 驱动里维护两张关键表:
binder_node:每个 Server 端的 Stub 在驱动里有唯一一个 node(裸指针级别的全局身份)binder_ref:每个 Client 进程对某个 node 的「引用」,对应一个进程内的 handle 数字
handle 是进程私有的整数 ,类似 fd。同一个 Server,A 进程看到 handle=5,B 进程看到可能是 handle=12。驱动负责做 (进程, handle) → binder_node 的映射。
2.1.1 系统服务(如 AMS):通过 ServiceManager 寻址
ServiceManager 自己也是个 Binder 服务,它的 handle 写死为 0------这是整个系统唯一不需要查找就能直达的入口。所有 Binder 寻址的起点都是它。
2.1.2 App 间 AIDL(bindService):通过 AMS 当中介
App 之间没有权限往 ServiceManager 注册,所以走 AMS 转交:
注意:Stub 第一次跨进程传递时 ,驱动才给它创建 binder_node------之前一直只是个 Java 对象,驱动并不知道它存在。
2.2 一次方法调用的完整链路
假设 Client 调 iFoo.doSomething("hi"):
几个关键细节:
- handle 是寻址唯一依据 :
Proxy里持有的BinderProxy内部就是这个整数 handle。驱动收到BC_TRANSACTION时根据(发起进程, handle)查到binder_node,再找到目标进程的 todo 队列。 code是路由方法 :AIDL 编译时给每个方法生成TRANSACTION_xxx = IBinder.FIRST_CALL_TRANSACTION + n,onTransact里就是一个 switch 分发。- 一次拷贝 :Server 的接收 buffer 通过
mmap映射到内核,Client 的 Parcel 数据直接从 Client 用户态拷到这块共享内存里,Server 用户态立即可读。整条链路只 1 次拷贝。 - 线程模型 :Server 端有 Binder 线程池(默认上限 15,按需 spawn)。Client 调用是同步阻塞(除非
oneway),Client 线程在内核里S状态睡觉等 reply。 - Token 校验 :
writeInterfaceToken/enforceInterface防止 handle 被错误调用------Client 写「我要调的是 IFoo」,Server 收到后必须匹配,否则抛SecurityException。
2.3 把两步串起来:从字符串名字到一次远程调用
一句话总结:
handle 是"地址",code 是"方法编号",Parcel 是"参数序列化",驱动负责按 handle 路由 + 一次拷贝唤醒目标线程池,ServiceManager / AMS 只是帮你"第一次拿到 handle"的中介。
三、Binder 驱动内部数据结构:binder_proc / binder_node / binder_ref
3.1 几个核心结构体
c
// drivers/android/binder.c (简化)
struct binder_proc {
struct rb_root threads; // 本进程的 binder_thread,pid 为 key
struct rb_root nodes; // 本进程"提供"的 binder_node,ptr 为 key
struct rb_root refs_by_desc; // 本进程持有的 binder_ref,handle 为 key
struct rb_root refs_by_node; // 本进程持有的 binder_ref,node 指针为 key
struct list_head todo; // 待处理事务队列
...
};
struct binder_node {
struct binder_proc *proc; // 反向指针:这个 node 属于哪个 Server 进程
binder_uintptr_t ptr; // 用户态 BBinder 的指针(身份证)
...
};
struct binder_ref {
struct binder_proc *proc; // 反向指针:这个 ref 在哪个 Client 进程
struct binder_node *node; // 指向目标 node
uint32_t desc; // 进程私有 handle 数字(就是 Client 看到的 handle)
...
};
对应关系:
| 概念 | 结构体 | 作用域 | 数量 |
|---|---|---|---|
| 进程 | binder_proc |
每打开一次 /dev/binder 一个 |
每进程 1 |
| Server 端 Stub | binder_node |
全局唯一身份 | 每个 Stub 1 |
| Client 端引用 | binder_ref |
进程私有 handle | 每 (Client, Server) 1 |
| Binder 线程 | binder_thread |
进程内 | 线程池 N |
3.2 每个 binder_proc 维护四棵红黑树
refs_by_desc:Client 端用 handle 反查 ref------这是最常走的路径refs_by_node:Client 端给定 node 查是否已有 ref,避免重复创建nodes:Server 端用 BBinder 指针查 node,避免重复创建
3.3 一次 transact 的真实查找链路
Client 调 transact(handle=5, ...) 时驱动内部:
所以更准确的描述是:
Client 给一个 handle → 在自己 的
binder_proc.refs_by_desc红黑树里查binder_ref→ref->node拿到binder_node→node->proc拿到 Server 的binder_proc→ 把事务挂到 Server 的proc->todo或某个binder_thread->todo,并唤醒对应线程。
3.4 常见用词修正
| 误区说法 | 准确说法 |
|---|---|
| "binder_proc 在驱动红黑树以 node 作为 key" | 应该是 "binder_ref 在 Client 的 refs_by_desc 树里以 handle 为 key;refs_by_node 才以 node 指针为 key" |
| "binder 驱动根据 node 找到服务端" | node 本身就属于 Server 进程(node->proc 直接拿到),不需要"找",是 ref → node 这步起了"跨进程寻址"作用 |
四、用户态 / 内核态分工:这些事到底在哪发生?
整个 transact 流程横跨用户态和内核态,容易混淆。下面这张图把边界划清楚:
用户态 / 内核态分工:
| 阶段 | 在哪 | 谁做 |
|---|---|---|
Proxy.transact → Parcel 打包 |
用户态 | libbinder (IPCThreadState) |
ioctl(BINDER_WRITE_READ) |
用户 → 内核切换 | 系统调用 |
| handle → ref → node → proc 查找 | 内核态 | binder.c |
| 一次拷贝 + 唤醒目标线程 | 内核态 | binder.c (binder_transaction()) |
onTransact 分发到具体方法 |
用户态 | Stub.onTransact (你的 AIDL 代码) |
| 业务方法执行 | 用户态 | 你的实现 |
| reply 回写 | 内核态 | binder.c |
关键点:
- 寻址全部在内核 :
binder_proc / binder_node / binder_ref这些结构体和那四棵红黑树都是drivers/android/binder.c里的内核数据结构,用户态根本看不到、也碰不到。 - 用户态只知道 handle 这一个数字 :libbinder 拿这个数字塞进
binder_transaction_data然后ioctl进内核,剩下「handle 怎么映射到目标进程」全是驱动的事。 - 方法分发回到用户态 :驱动只负责「把这包数据送到对面进程的某个线程的用户态 buffer」,至于
code怎么解析、调哪个方法,那是 Server 用户态的Stub.onTransact干的。
邮政系统类比:
- libbinder = 你贴邮票封信
- Binder Driver = 邮局(拿门牌号查地址、跨城投递、唤醒收件人)
- Stub.onTransact = 收件人拆信、分发到具体处理人
驱动做的就是中间那段「地址解析 + 物理投递」,绝不参与「信里写了啥」。
五、AMS 在 Binder 调用里到底起什么作用?
把整条链路按时间切两段就清楚了:
5.1 阶段一:建立连接(AMS 是中介)
只在 bindService / startService / startActivity 这种首次跨进程握手时,AMS 才参与:
5.2 阶段二:之后的每一次方法调用(AMS 完全不在场)
一旦 Client 拿到了那个 IBinder(也就是拿到了 handle),后面 C → Binder Driver → S 直接走,根本不会经过 AMS:
5.3 AMS 角色一览
| 场景 | AMS 参与吗 | 它做什么 |
|---|---|---|
bindService 第一次连上 |
✅ | 启动 Server 进程 + 转交 IBinder |
| 启动 Activity / Service | ✅ | 调度、生命周期、Zygote fork |
| 系统服务(AMS/WMS/PMS)的调用 | 通过 ServiceManager 拿 handle | 之后就直连,不再过 ServiceManager |
拿到 IBinder 后调 iFoo.xxx() |
❌ | 完全不参与 |
拿到 IBinder 后调 linkToDeath |
❌ | 驱动直接处理 |
| Server 死了通知 Client | ❌ | Binder 驱动发 BR_DEAD_BINDER |
5.4 类比
- ServiceManager = 系统服务的电话簿(只给系统服务用)
- AMS = 婚介所,帮普通 App 牵线认识 Server
- Binder Driver = 电话运营商
牵线完了你们打电话(transact)走的是运营商,婚介所没事。
这也是为什么 AMS 卡死不会立刻让所有 App 间通信都挂掉 ,但新的 bindService / startActivity 一定会卡。
六、一图总结
记忆口诀:
- AIDL 只是个代码生成器,不动驱动也不动 ServiceManager
- binder_node 是 Server 的身份证,binder_ref 是 Client 持有的「门票」,handle 是门票编号
- 寻址全在内核,用户态只知道 handle 这一个数
- ServiceManager 只服务系统级服务,handle 写死为 0
- AMS 只在首次牵线时出场,之后的 transact 直连不经过它
- Binder Driver 是运营商,不关心信里写了啥,只负责按地址投递