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 服务注册与获取流程

我早期刚接触这部分时,总把注册和获取混在一起。其实流程非常清晰:

  1. Server调用defaultServiceManager()->addService("hello", service),把服务名和Binder实体传给SM。
  2. SM内部维护一个表,存着「服务名 → Binder引用(句柄)」。
  3. Client调用defaultServiceManager()->getService("hello"),SM返回一个Binder引用(句柄)。
  4. Client拿着这个句柄,调用transact(),Binder驱动帮忙把请求数据送到Server进程。
  5. 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 体系结构图:一眼看清所有通信路径

Binder通信模型:注册与调用流程 用户空间 内核空间 Client 句柄=H Server 服务实体 ServiceManager 服务名→句柄表 Binder驱动 (与/dev/binder交互) ① addService ② getService ③ 返回句柄H ④ Binder驱动中转数据 关键说明 • Binder驱动维护了每个进程的Binder节点与句柄映射 • 句柄在进程内唯一,不同进程句柄值可能不同 • 所有IPC最终都通过ioctl发送给Binder驱动

2.4 核心数据结构:Binder实体与引用

你想想看,Binder驱动内部要管理几百个进程之间的通信,光靠句柄不够。驱动里维护了两张核心表:

  • Binder实体(binder_node)——对应着一个具体的Binder服务对象,属于Server进程。
  • Binder引用(binder_ref)——每个访问该服务的Client进程内,都有一个引用指向那个实体。

我记得有一次排查性能问题,发现某个服务创建了上千个引用,导致驱动内核内存暴涨。后来才意识到是Client频繁获取服务但没有释放引用(其实Binder有自动计数,但某些边角情况会泄漏)。

重要概念:
  1. 句柄(handle)是Binder引用在用户态的数字标识,Client通过它找到驱动里的ref。
  2. ref再指向Server进程中的node。
  3. node内部有指向Server服务对象的指针(在Server进程内)。
  4. 跨进程时,驱动会自动拷贝数据,并通过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调用——但无论哪种套路,都离不开今天讲的这个三角色架构。