你真的了解AIDL吗? 附:AIDL 与 Binder 交互全解析

从「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

关键点:

  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 寻址

sequenceDiagram participant SS as system_server participant K as Binder Driver participant SM as ServiceManager participant App as App Note over SS,SM: 启动期:注册 SS->>K: addService(&#34;activity&#34;, AMS_stub) K->>K: 为 AMS_stub 建 binder_node K->>SM: 在 ServiceManager 里登记<br/>(&#34;activity&#34; → node) Note over App,SM: 运行期:查找 App->>K: getService(&#34;activity&#34;) K->>SM: 查名字 SM-->>K: 返回 node K->>K: 在 App 进程建 binder_ref<br/>分配 handle (例如 17) K-->>App: 返回 handle=17 Note over App: 包装成 BinderProxy<br/>再包成 IActivityManager.Proxy

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

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

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

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

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

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

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

sequenceDiagram participant C as Client 线程 participant CP as IFoo.Proxy<br/>(Client 进程) participant CB as libbinder<br/>(Client) participant K as Binder Driver participant SB as libbinder<br/>(Server) participant ST as IFoo.Stub<br/>(Server 进程) C->>CP: doSomething(&#34;hi&#34;) CP->>CP: Parcel.writeInterfaceToken<br/>writeString(&#34;hi&#34;) CP->>CB: mRemote.transact(TRANSACTION_doSomething, data, reply, 0) CB->>K: ioctl(BINDER_WRITE_READ)<br/>BC_TRANSACTION + handle + parcel Note over C: Client 线程进入 S 状态<br/>(TASK_INTERRUPTIBLE) 等 reply K->>K: handle → binder_ref → binder_node<br/>找到目标进程 K->>K: 一次拷贝:数据 → Server mmap 区 K->>SB: 唤醒 Server 一个空闲 Binder 线程<br/>投递 BR_TRANSACTION SB->>ST: onTransact(code, data, reply, flags) ST->>ST: switch(code) → doSomething(data.readString()) ST-->>SB: 写 reply Parcel SB->>K: ioctl 写 BC_REPLY K->>K: 数据拷给 Client mmap 区<br/>唤醒原 Client 线程 K-->>CB: BR_REPLY CB-->>CP: transact() 返回,reply 已填好 CP-->>C: 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 把两步串起来:从字符串名字到一次远程调用

flowchart LR A[Client 发起 bindService] --> B[AMS 启动 Server 获得 Stub] B --> C[驱动建 binder_node 并分配 handle] C --> D[Client 拿到 BinderProxy] D --> E[调用 transact] E --> F[驱动按 handle 路由到 Server 线程池] F --> G[Stub.onTransact 按 code 分发] G --> H[reply 原路返回 Client]

一句话总结:

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

flowchart LR subgraph SP[Server 进程 binder_proc] SN[nodes 红黑树 key=BBinder ptr] end subgraph CP[Client 进程 binder_proc] CRD[refs_by_desc 红黑树 key=handle] CRN[refs_by_node 红黑树 key=node 指针] end CRD -->|ref 指向 node| SN CRN -->|ref 指向 node| SN
  • refs_by_desc:Client 端用 handle 反查 ref------这是最常走的路径
  • refs_by_node:Client 端给定 node 查是否已有 ref,避免重复创建
  • nodes:Server 端用 BBinder 指针查 node,避免重复创建

3.3 一次 transact 的真实查找链路

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

sequenceDiagram participant C as Client binder_proc participant R as binder_ref<br/>(handle=5) participant N as binder_node participant S as Server binder_proc participant T as Server binder_thread Note over C: ioctl 进入驱动<br/>当前 task → 找到 binder_proc C->>C: rb_search(refs_by_desc, key=5) C-->>R: 找到 binder_ref R->>N: ref->node N->>S: node->proc (找到目标 Server 进程) S->>T: 从 threads 树挑空闲线程<br/>或丢进 proc->todo T->>T: 数据拷到 Server mmap 区<br/>唤醒线程

所以更准确的描述是:

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 流程横跨用户态和内核态,容易混淆。下面这张图把边界划清楚:

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

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

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

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

sequenceDiagram participant C as Client App participant K as Binder Driver participant S as Server App Note over C,S: AMS 不在这张图里! C->>K: iFoo.doSomething(&#34;hi&#34;)<br/>ioctl(handle, BC_TRANSACTION) K->>K: handle → ref → node → Server proc K->>S: BR_TRANSACTION S->>S: Stub.onTransact → 业务方法 S->>K: BC_REPLY K-->>C: 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 一定会卡


六、一图总结

flowchart TB subgraph U1[Client 用户态] CA[业务代码 iFoo.doSomething] CP[IFoo.Proxy AIDL 生成] CL[libbinder IPCThreadState] end subgraph KN[Binder Driver 内核态] BR[binder_ref refs_by_desc key=handle] BN[binder_node node 指向 proc] BP[Server binder_proc todo 队列] BT[binder_thread 线程池] end subgraph U2[Server 用户态] SL[libbinder] SS[IFoo.Stub onTransact 分发] SI[业务实现] end SM[ServiceManager handle=0 只给系统服务] AMS[AMS 只在 bindService 时牵线] CA --> CP CP --> CL CL -->|ioctl BC_TRANSACTION| BR BR --> BN BN --> BP BP --> BT BT -->|BR_TRANSACTION| SL SL --> SS SS --> SI SI -->|reply| SL SL -->|BC_REPLY| BT BT -->|唤醒| CL CL -->|返回| CP CP -->|返回| CA SM -.连接.- BN AMS -.连接.- BN

记忆口诀:

  1. AIDL 只是个代码生成器,不动驱动也不动 ServiceManager
  2. binder_node 是 Server 的身份证,binder_ref 是 Client 持有的「门票」,handle 是门票编号
  3. 寻址全在内核,用户态只知道 handle 这一个数
  4. ServiceManager 只服务系统级服务,handle 写死为 0
  5. AMS 只在首次牵线时出场,之后的 transact 直连不经过它
  6. Binder Driver 是运营商,不关心信里写了啥,只负责按地址投递
相关推荐
dualven_in_csdn3 小时前
一键起飞调用示例
android·java·javascript
故渊at3 小时前
第十板块:Android 系统稳定性与调试 | 第二十五篇:Watchdog 与 ANR 的系统级监控
android·watchdog·系统稳定性·anr·超时监控
故渊at4 小时前
第十板块:Android 系统稳定性与调试 | 第二十六篇:Systrace 与 Perfetto 的系统级性能分析
android·perfetto·性能分析·systrace·系统稳定性
吕工-老船长19984 小时前
20260610----S905Y5(Android14)-----连接网络自动更新时间,时间设置为24小时
android
杉氧6 小时前
Kotlin 协程深度解析④:架构实战——在 MVVM/MVI 中的进阶应用
android·kotlin
Ab_stupid6 小时前
CTF-Android培训笔记
android·笔记
Ycocol6 小时前
AS同一个目录下的类导入导入其他类爆红无法跳转但是可以编译
android·ide·android studio
Meteors.7 小时前
安卓字节码插桩与埋点
android