从「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() 等纯本地用 永远不跨进程

底层时序大致是这样:

new IFoo.Stub()
   ↓ (JavaBBinder native 对象延迟创建)
首次 writeStrongBinder(stub) 跨进程
   ↓ libbinder 通过 ioctl(BINDER_WRITE_READ)
   ↓ 把 BBinder 指针交给驱动
Binder Driver 创建对应 binder_node
   ↓ 在驱动内分配 handle 给接收方
接收方进程拿到 handle → 包装成 BinderProxy

关键点:

  1. binder 驱动里的「注册」是懒注册——直到这个 Binder 对象第一次跨进程传递,驱动里才有它的 binder_node。光 new Stub() 不传出去,驱动完全不知道它存在。
  2. ServiceManager 是额外一层命名服务,只给「全局可寻址」的服务用(getSystemService 那一类)。普通 App 的 AIDL 服务不会没权限往里塞——addService 在普通进程会被 SELinux 拒绝,只有 system_server 等白名单进程能调。
  3. 普通 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 寻址
App ServiceManager Binder Driver system_server App ServiceManager Binder Driver system_server 启动期:注册 运行期:查找 包装成 BinderProxy 再包成 IActivityManager.Proxy addService("activity", AMS_stub) 为 AMS_stub 建 binder_node 在 ServiceManager 里登记 ("activity" → node) getService("activity") 查名字 返回 node 在 App 进程建 binder_ref 分配 handle (例如 17) 返回 handle=17

ServiceManager 自己也是个 Binder 服务,它的 handle 写死为 0——这是整个系统唯一不需要查找就能直达的入口。所有 Binder 寻址的起点都是它。

2.1.2 App 间 AIDL(bindService):通过 AMS 当中介

App 之间没有权限往 ServiceManager 注册,所以走 AMS 转交:

Server App Binder Driver AMS (system_server) Client App Server App Binder Driver AMS (system_server) Client App 这次返回本身就是一次跨进程 Stub 第一次经过 writeStrongBinder bindService(Intent, conn) 启动 Server 进程 调 onCreate / onBind onBind() 返回 IFoo.Stub 把这个 Binder 转交给 Client 在 Client 进程建 binder_ref 分配 handle ServiceConnection.onServiceConnected(IBinder) IFoo.Stub.asInterface(binder) 得到 IFoo.Proxy

注意:Stub 第一次跨进程传递时,驱动才给它创建 binder_node——之前一直只是个 Java 对象,驱动并不知道它存在。

2.2 一次方法调用的完整链路

假设 Client 调 iFoo.doSomething("hi"):

IFoo.Stub (Server 进程) libbinder (Server) Binder Driver libbinder (Client) IFoo.Proxy (Client 进程) Client 线程 IFoo.Stub (Server 进程) libbinder (Server) Binder Driver libbinder (Client) IFoo.Proxy (Client 进程) Client 线程 Client 线程进入 S 状态 (TASK_INTERRUPTIBLE) 等 reply doSomething("hi") Parcel.writeInterfaceToken writeString("hi") mRemote.transact(TRANSACTION_doSomething, data, reply, 0) ioctl(BINDER_WRITE_READ) BC_TRANSACTION + handle + parcel handle → binder_ref → binder_node 找到目标进程 一次拷贝:数据 → Server mmap 区 唤醒 Server 一个空闲 Binder 线程 投递 BR_TRANSACTION onTransact(code, data, reply, flags) switch(code) → doSomething(data.readString()) 写 reply Parcel ioctl 写 BC_REPLY 数据拷给 Client mmap 区 唤醒原 Client 线程 BR_REPLY transact() 返回,reply 已填好 reply.readXxx() 返回结果

几个关键细节:

  1. handle 是寻址唯一依据:Proxy 里持有的 BinderProxy 内部就是这个整数 handle。驱动收到 BC_TRANSACTION 时根据 (发起进程, handle) 查到 binder_node,再找到目标进程的 todo 队列。
  2. code 是路由方法:AIDL 编译时给每个方法生成 TRANSACTION_xxx = IBinder.FIRST_CALL_TRANSACTION + n,onTransact 里就是一个 switch 分发。
  3. 一次拷贝:Server 的接收 buffer 通过 mmap 映射到内核,Client 的 Parcel 数据直接从 Client 用户态拷到这块共享内存里,Server 用户态立即可读。整条链路只 1 次拷贝。
  4. 线程模型:Server 端有 Binder 线程池(默认上限 15,按需 spawn)。Client 调用是同步阻塞(除非 oneway),Client 线程在内核里 S 状态睡觉等 reply。
  5. Token 校验:writeInterfaceToken / enforceInterface 防止 handle 被错误调用——Client 写「我要调的是 IFoo」,Server 收到后必须匹配,否则抛 SecurityException

2.3 把两步串起来:从字符串名字到一次远程调用

Client 发起 bindService

AMS 启动 Server 获得 Stub

驱动建 binder_node 并分配 handle

Client 拿到 BinderProxy

调用 transact

驱动按 handle 路由到 Server 线程池

Stub.onTransact 按 code 分发

reply 原路返回 Client

一句话总结:

handle 是"地址",code 是"方法编号",Parcel 是"参数序列化",驱动负责按 handle 路由 + 一次拷贝唤醒目标线程池,ServiceManager / AMS 只是帮你"第一次拿到 handle"的中介。


三、Binder 驱动内部数据结构:binder_proc / binder_node / binder_ref

3.1 几个核心结构体

// 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 维护四棵红黑树

Client 进程 binder_proc

Server 进程 binder_proc

ref 指向 node

ref 指向 node

nodes 红黑树 key=BBinder ptr

refs_by_desc 红黑树 key=handle

refs_by_node 红黑树 key=node 指针

  • refs_by_desc:Client 端用 handle 反查 ref——这是最常走的路径
  • refs_by_node:Client 端给定 node 查是否已有 ref,避免重复创建
  • nodes:Server 端用 BBinder 指针查 node,避免重复创建

3.3 一次 transact 的真实查找链路

Client 调 transact(handle=5, ...) 时驱动内部:

Server binder_thread Server binder_proc binder_node binder_ref (handle=5) Client binder_proc Server binder_thread Server binder_proc binder_node binder_ref (handle=5) Client binder_proc ioctl 进入驱动 当前 task → 找到 binder_proc rb_search(refs_by_desc, key=5) 找到 binder_ref ref->>node node->>proc (找到目标 Server 进程) 从 threads 树挑空闲线程 或丢进 proc->>todo 数据拷到 Server mmap 区 唤醒线程

所以更准确的描述是:

Client 给一个 handle → 在自己binder_proc.refs_by_desc 红黑树里查 binder_refref->node 拿到 binder_nodenode->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 流程横跨用户态和内核态,容易混淆。下面这张图把边界划清楚:

Server 用户态 (libbinder + Stub) Binder Driver (内核态) Client 用户态 (libbinder) Server 用户态 (libbinder + Stub) Binder Driver (内核态) Client 用户态 (libbinder) ===== 以下全部在驱动里发生 ===== 同样在驱动里:reply 拷给 Client mmap 区 唤醒等待的 Client 线程 ioctl(fd, BINDER_WRITE_READ, cmd=BC_TRANSACTION, handle=5, data_ptr, data_size) 1) current → binder_proc (Client) 2) rb_search(refs_by_desc, key=5) → binder_ref 3) ref->>node → binder_node 4) node->>proc → Server binder_proc 5) copy_from_user 一次拷贝 Client 数据 → Server mmap 区 6) 挑空闲 binder_thread 或挂 proc->>todo 7) wake_up 目标线程 目标线程从 ioctl(BR_TRANSACTION) 返回 data_ptr 直接指向 mmap 区 BBinder::transact → onTransact (用户态分发) ioctl(BC_REPLY, reply 数据) ioctl(BR_REPLY) 返回

用户态 / 内核态分工:

阶段 在哪 谁做
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

关键点:

  1. 寻址全部在内核:binder_proc / binder_node / binder_ref 这些结构体和那四棵红黑树都是 drivers/android/binder.c 里的内核数据结构,用户态根本看不到、也碰不到。
  2. 用户态只知道 handle 这一个数字:libbinder 拿这个数字塞进 binder_transaction_data 然后 ioctl 进内核,剩下「handle 怎么映射到目标进程」全是驱动的事。
  3. 方法分发回到用户态:驱动只负责「把这包数据送到对面进程的某个线程的用户态 buffer」,至于 code 怎么解析、调哪个方法,那是 Server 用户态的 Stub.onTransact 干的。

邮政系统类比:

  • libbinder = 你贴邮票封信
  • Binder Driver = 邮局(拿门牌号查地址、跨城投递、唤醒收件人)
  • Stub.onTransact = 收件人拆信、分发到具体处理人

驱动做的就是中间那段「地址解析 + 物理投递」,绝不参与「信里写了啥」。


五、AMS 在 Binder 调用里到底起什么作用?

把整条链路按时间切两段就清楚了:

5.1 阶段一:建立连接(AMS 是中介)

只在 bindService / startService / startActivity 这种首次跨进程握手时,AMS 才参与:

Server App Binder Driver AMS (system_server) Client App Server App Binder Driver AMS (system_server) Client App 1. Client 找 AMS 牵线 2. AMS 启动 Server 进程并要 Binder 3. AMS 把 Binder 转交给 Client 4. 牵线完成,AMS 退场 transact 到 AMS (handle=固定的 通过 ServiceManager 拿到) bindService(Intent, conn) 通过 Zygote fork + bindApplication 调用 Service.onBind() onBind 返回 IFoo.Stub (跨进程时驱动建 binder_node) writeStrongBinder(stub) 给 Client 在 Client 进程建 binder_ref 分配 handle onServiceConnected(IBinder)

5.2 阶段二:之后的每一次方法调用(AMS 完全不在场)

一旦 Client 拿到了那个 IBinder(也就是拿到了 handle),后面 C → Binder Driver → S 直接走,根本不会经过 AMS:

Server App Binder Driver Client App Server App Binder Driver Client App AMS 不在这张图里! iFoo.doSomething("hi") ioctl(handle, BC_TRANSACTION) handle → ref → node → Server proc BR_TRANSACTION Stub.onTransact → 业务方法 BC_REPLY BR_REPLY

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 一定会卡


六、一图总结

Server 用户态

Binder Driver 内核态

Client 用户态

ioctl BC_TRANSACTION

BR_TRANSACTION

reply

BC_REPLY

唤醒

返回

返回

连接

连接

业务代码 iFoo.doSomething

IFoo.Proxy AIDL 生成

libbinder IPCThreadState

binder_ref refs_by_desc key=handle

binder_node node 指向 proc

Server binder_proc todo 队列

binder_thread 线程池

libbinder

IFoo.Stub onTransact 分发

业务实现

ServiceManager handle=0 只给系统服务

AMS 只在 bindService 时牵线

记忆口诀:

  1. AIDL 只是个代码生成器,不动驱动也不动 ServiceManager
  2. binder_node 是 Server 的身份证,binder_ref 是 Client 持有的「门票」,handle 是门票编号
  3. 寻址全在内核,用户态只知道 handle 这一个数
  4. ServiceManager 只服务系统级服务,handle 写死为 0
  5. AMS 只在首次牵线时出场,之后的 transact 直连不经过它
  6. Binder Driver 是运营商,不关心信里写了啥,只负责按地址投递
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐