2. Binder通信模型:Client/Server/ServiceManager架构与工作原理
2.1 三个角色一台戏
说到Binder,你得先搞明白它的基本盘——Client、Server、ServiceManager,加上藏在底层的Binder驱动。说白了,这就是一个「电话系统」:
- ServiceManager——好比总机电话本,上面记着所有服务对应的号码(句柄)。
- Server——服务提供方,先把自家服务登记到电话本上。
- Client——服务使用方,照着电话本打过去就能找到Server。
- Binder驱动——内核里的接线员,负责把通话内容从进程A搬到进程B。
我个人习惯先记住这个比喻,后面设计Mock方案时会反复用到。
| 角色 | 真实世界类比 | 核心职责 |
|---|---|---|
| Client | 打电话的人 | 通过句柄向Server发起IPC请求 |
| Server | 被呼叫的人 | 注册服务到SM,处理Client请求 |
| ServiceManager | 电话本总机 | 维护服务名→句柄的映射表 |
| Binder驱动 | 电话线+接线员 | 跨进程拷贝数据、管理Binder节点 |
注意:ServiceManager本身也是一个Binder服务,它的句柄固定为0。这意味着任何进程想查询服务,都得先跟句柄0通信。
2.2 服务注册与获取流程
我早期刚接触这部分时,总把注册和获取混在一起。其实流程非常清晰:
- Server调用
defaultServiceManager()->addService("hello", service),把服务名和Binder实体传给SM。 - SM内部维护一个表,存着「服务名 → Binder引用(句柄)」。
- Client调用
defaultServiceManager()->getService("hello"),SM返回一个Binder引用(句柄)。 - Client拿着这个句柄,调用
transact(),Binder驱动帮忙把请求数据送到Server进程。 - Server的线程池唤醒,处理请求,将结果原路返回。
嗯,这里有一个关键点——Client拿到的句柄在Client进程内是唯一的,但不同Client拿到同一个服务的句柄值可能不同。原因在于Binder驱动会按进程独立分配句柄。
// 伪代码展示调用链
// Server端
sp<IServiceManager> sm = defaultServiceManager();
sm->addService(String16("hello"), new MyService());
// Client端
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("hello"));
// 现在binder句柄就是通往Server的钥匙
binder->transact(CODE_HELLO, data, reply);
我曾经遇到过一个坑:在SystemServer启动早期调用了
getService(),发现返回空。后来才意识到ServiceManager还没启动完,或者服务还没注册上来。解决办法是使用waitForService()或自己实现重试逻辑。
2.3 体系结构图:一眼看清所有通信路径
2.4 核心数据结构:Binder实体与引用
你想想看,Binder驱动内部要管理几百个进程之间的通信,光靠句柄不够。驱动里维护了两张核心表:
- Binder实体(binder_node)——对应着一个具体的Binder服务对象,属于Server进程。
- Binder引用(binder_ref)——每个访问该服务的Client进程内,都有一个引用指向那个实体。
我记得有一次排查性能问题,发现某个服务创建了上千个引用,导致驱动内核内存暴涨。后来才意识到是Client频繁获取服务但没有释放引用(其实Binder有自动计数,但某些边角情况会泄漏)。
重要概念:
- 句柄(handle)是Binder引用在用户态的数字标识,Client通过它找到驱动里的ref。
- ref再指向Server进程中的node。
- node内部有指向Server服务对象的指针(在Server进程内)。
- 跨进程时,驱动会自动拷贝数据,并通过BC_TRANSACTION/BR_TRANSACTION协议交互。
2.5 避坑指南与实用技巧
我做Mock回放时经常遇到几个雷区,先列出来:
- ServiceManager的启动顺序:它必须在所有服务之前启动。Android源码里是init进程先fork出servicemanager进程,然后才启动Zygote。如果你自己开发系统服务,千万别依赖SM还没就绪就注册服务。
- 死亡通知:Server进程挂了,Client这边不会自动知道。你需要注册
linkToDeath()。不然你调用transact()的时候,Binder驱动返回DEAD_BINDER,你没处理的话,程序直接崩溃。 - 事务大小限制:Binder默认单次事务最大传输约1MB数据(具体看内核配置)。我曾经传输大图时踩过这个坑,后来用共享内存(ashmem)+Binder传fd的方式解决。
在做跨进程Mock回放时,最容易被忽略的是「Binder线程池」:Binder驱动会把请求派发给Server进程的binder线程。如果你在测试脚本里没有启动Binder线程池(
ProcessState::self()->startThreadPool()),那整个通信就卡死了。我这个坑至少踩过两回。
2.6 小结:记住这张图就够了
说白了,Binder通信就是「Client拿着句柄,通过驱动找Server」。ServiceManager是那个告诉你句柄是多少的中间人。后面课程讲Mock方案时,我们要么模拟ServiceManager的行为,要么直接拦截Binder驱动的transact调用——但无论哪种套路,都离不开今天讲的这个三角色架构。