你真的了解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() 等纯本地用永远不跨进程

底层时序大致是这样:

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 寻址
AppServiceManagerBinder Driversystem_serverAppServiceManagerBinder Driversystem_server启动期:注册运行期:查找包装成 BinderProxy再包成 IActivityManager.ProxyaddService("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 AppBinder DriverAMS(system_server)Client AppServer AppBinder DriverAMS(system_server)Client App这次返回本身就是一次跨进程Stub 第一次经过 writeStrongBinderbindService(Intent, conn)启动 Server 进程调 onCreate / onBindonBind() 返回 IFoo.Stub把这个 Binder 转交给 Client在 Client 进程建 binder_ref分配 handleServiceConnection.onServiceConnected(IBinder)IFoo.Stub.asInterface(binder)得到 IFoo.Proxy

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

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

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

IFoo.Stub(Server 进程)libbinder(Server)Binder Driverlibbinder(Client)IFoo.Proxy(Client 进程)Client 线程IFoo.Stub(Server 进程)libbinder(Server)Binder Driverlibbinder(Client)IFoo.Proxy(Client 进程)Client 线程Client 线程进入 S 状态(TASK_INTERRUPTIBLE) 等 replydoSomething("hi")Parcel.writeInterfaceTokenwriteString("hi")mRemote.transact(TRANSACTION_doSomething, data, reply, 0)ioctl(BINDER_WRITE_READ)BC_TRANSACTION + handle + parcelhandle → binder_ref → binder_node找到目标进程一次拷贝:数据 → Server mmap 区唤醒 Server 一个空闲 Binder 线程投递 BR_TRANSACTIONonTransact(code, data, reply, flags)switch(code) → doSomething(data.readString())写 reply Parcelioctl 写 BC_REPLY数据拷给 Client mmap 区唤醒原 Client 线程BR_REPLYtransact() 返回,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 端 Stubbinder_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_threadServer binder_procbinder_nodebinder_ref(handle=5)Client binder_procServer binder_threadServer binder_procbinder_nodebinder_ref(handle=5)Client binder_procioctl 进入驱动当前 task → 找到 binder_procrb_search(refs_by_desc, key=5)找到 binder_refref->>nodenode->>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_ref3) ref->>node → binder_node4) node->>proc → Server binder_proc5) copy_from_user 一次拷贝Client 数据 → Server mmap 区6) 挑空闲 binder_thread 或挂 proc->>todo7) 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 AppBinder DriverAMS (system_server)Client AppServer AppBinder DriverAMS (system_server)Client App1. Client 找 AMS 牵线2. AMS 启动 Server 进程并要 Binder3. AMS 把 Binder 转交给 Client4. 牵线完成,AMS 退场transact 到 AMS (handle=固定的通过 ServiceManager 拿到)bindService(Intent, conn)通过 Zygote fork + bindApplication调用 Service.onBind()onBind 返回 IFoo.Stub(跨进程时驱动建 binder_node)writeStrongBinder(stub) 给 Client在 Client 进程建 binder_ref分配 handleonServiceConnected(IBinder)

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

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

Server AppBinder DriverClient AppServer AppBinder DriverClient AppAMS 不在这张图里!iFoo.doSomething("hi")ioctl(handle, BC_TRANSACTION)handle → ref → node → Server procBR_TRANSACTIONStub.onTransact → 业务方法BC_REPLYBR_REPLY

5.3 AMS 角色一览

场景AMS 参与吗它做什么
bindService 第一次连上启动 Server 进程 + 转交 IBinder
启动 Activity / Service调度、生命周期、Zygote fork
系统服务(AMS/WMS/PMS)的调用通过 ServiceManager 拿 handle之后就直连,不再过 ServiceManager
拿到 IBinder 后调 iFoo.xxx()完全不参与
拿到 IBinder 后调 linkToDeath驱动直接处理
Server 死了通知 ClientBinder 驱动发 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 是运营商,不关心信里写了啥,只负责按地址投递
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值