3. ServiceManager角色与位置
3.1 核心职责与定位
ServiceManager(简称SM)在Binder架构中扮演“服务域名服务器”的角色:
- 服务注册中心:服务端进程向SM注册自身,告知服务名称与Binder句柄。
- 服务查询代理:客户端进程通过服务名称向SM查询,获得目标服务的句柄。
- 句柄(handle)分配器:为每个注册的服务分配唯一的整数句柄。
- 系统启动锚点:init进程启动后第一个启动的Binder关键进程,是整个Binder通信的起点。
关键认知:ServiceManager本身也是一个Binder服务(特殊句柄0),但它的角色决定了它必须先于所有普通服务启动,且持有对/dev/binder的全局控制权。
3.2 系统启动阶段的初始化链路
Android系统启动时,ServiceManager的初始化按以下步骤执行:
- Init进程启动:解析init.rc,启动servicemanager进程。
- 打开Binder设备:servicemanager调用open("/dev/binder", O_RDWR),获得文件描述符。
- 映射传输缓冲区:mmap(BINDER_VM_SIZE)建立内核与用户态共享内存。
- 成为上下文管理器:调用ioctl(BINDER_SET_CONTEXT_MGR)将自己注册为Binder全局管理器。
- 进入循环:调用binder_loop()开始监听来自其他进程的请求。
| 启动阶段 | 关键动作 | 内核/用户态边界 |
|---|---|---|
| init.rc | servicemanager可执行入口 | 用户态 |
| binder_open | 打开/dev/binder,获取fd | 用户态→内核态 |
| binder_mmap | 映射物理内存,建立传输通道 | 用户态→内核态 |
| BINDER_SET_CONTEXT_MGR | 标记自身为ServiceManager | 用户态ioctl→内核态 |
| binder_loop | 无限读取Binder驱动消息 | 用户态 |
初始化验证方法:在Android系统启动后执行
service list可以查看当前已注册的服务列表,这是SM正常工作的重要验证手段。
3.3 服务注册流程(服务端视角)
服务进程(如media_server)注册到SM的完整流程:
- 服务进程启动后,调用
defaultServiceManager()获取SM代理(句柄0)。 - 构造
Parcel数据包,包含服务名称(字符串)和自身Binder引用。 - 通过Binder驱动将注册请求发送给SM。
- SM收到请求后,在内部哈希表中查找是否已存在同名服务:
- 若已存在且允许覆盖?处理冲突策略见3.5。
- 若不存在,分配新的句柄(从1递增),存储name→handle映射。
- 返回注册结果给服务进程。
3.4 服务查询流程(客户端视角)
客户端查询服务的原子步骤:
- 调用
defaultServiceManager()获得SM代理。 - 调用
getService(const String16& name),发送查询请求到句柄0。 - SM在哈希表中检查name是否注册:
- 存在:返回对应句柄值。
- 不存在:返回空句柄(0),客户端可设置死亡通知等待服务上线。
- 客户端获得句柄后,通过Binder驱动直接与目标服务通信(无需SM中转)。
性能风险:每次查询都需经过SM进程一次IPC,高频查询场景(如每次发送消息前都查询)会形成瓶颈。实际框架中客户端通常缓存查询到的句柄。
3.5 句柄分配策略与冲突处理
SM内部维护一个服务注册表,句柄分配遵从以下规则:
- 句柄0:保留给SM自身,不可用于普通服务。
- 句柄递增:从1开始,每注册一个新服务分配当前最大值+1。
- 服务名唯一性:相同服务名再次注册时,默认策略为覆盖旧服务(Android Q+ 可配置强制检查)。
- 句柄复用:服务注销后,句柄不会立即复用(避免 dangling handle),但SM会维护空闲列表供后续分配。
| 场景 | 句柄分配结果 | 风险点 |
|---|---|---|
| 首个服务注册 | handle=1 | 无 |
| 第二个服务注册 | handle=2 | 无 |
| 同名服务覆盖 | 句柄不变(保留原句柄但更新内部Binder引用) | 旧引用可能成为野指针 |
| 服务意外死亡 | 句柄标记为无效,但未立即释放 | 客户端查询到后使用会出错 |
3.6 数据流与控制流
- 数据流:服务名(字符串)+ Binder引用(flat_binder_object)→ SM内部哈希表存储。
- 控制流:用户态IPC请求 → Binder驱动解析 → SM的binder_loop分发 → 注册/查询处理 → 返回结果。
- 信任链路:客户端和服务端都信任SM返回的句柄是合法的。攻击风险:如果SM被篡改,可返回虚假句柄。
3.7 选型权衡
- 集中式 vs 分布式注册:Android选择集中式(SM),简化了服务发现逻辑,但带来单点故障。早期Android曾尝试用SELinux保护SM通信。
- 句柄为整数 vs 字符串:句柄整数在Binder驱动层面更高效,但需额外查询映射表;字符串可读性更好但开销大。Android采用整数句柄+字符串名称双层映射。
- 服务名冲突覆盖:默认覆盖策略减少了复杂度,但可能因重名引入隐蔽bug。新版本引入
ADD_SERVICE标志位可要求唯一性。
3.8 风险点与应对
| 风险 | 描述 | 应对措施 |
|---|---|---|
| SM单点故障 | SM进程crash导致所有服务无法注册/查询 | SM自身有SIGSEGV保护;系统设计为SM重启后重新注册所有服务 |
| 服务名劫持 | 恶意进程注册同名服务替代合法服务 | SELinux策略限制只有特定域可以向SM注册 |
| 句柄耗尽 | 频繁注册/注销导致句柄池用尽 | 句柄用int32,上限足够大;实际场景几乎不可能耗尽 |
| 查询延迟 | 高并发查询时SM性能下降 | 客户端缓存句柄;SM内部使用哈希表O(1)查询 |
3.9 落地步骤与验收标准
当你需要自定义ServiceManager或移植Binder到新平台时,可参考以下落地步骤:
- 实现binder_open()/binder_mmap()/binder_loop()基础逻辑。
- 在init脚本中确保SM优先启动。
- 编写内部注册表(哈希表+自旋锁保证并发安全)。
- 实现handle分配函数(从空闲池或递增)。
- 暴露标准接口:addService()、getService()、listServices()。
验收标准:
- 启动后能通过`lshal`或`service list`看到注册的基础服务。
- 客户端调用`getService("xxx")`返回非空句柄。
- 服务终止后,同名服务重新注册能正常替换(或报唯一性冲突)。
- 压力测试(1000次并发查询)无句柄重复或丢失。
3.10 关键接口示意
以下为ServiceManager核心API(伪代码级,用于理解流程):
// ServiceManager内部逻辑
status_t SM::addService(const String16& name, const sp<IBinder>& service, bool allowIsolated, int dumpsysFlags) {
AutoMutex _l(mLock);
// 检查是否已存在同名服务
Vector<ServiceEntry>::iterator it = findService(name);
if (it != mServices.end()) {
// 覆盖策略
it->binder = service;
return OK;
}
// 分配新句柄
int handle = getNextHandle();
ServiceEntry entry(name, service, handle);
mServices.push_back(entry);
return OK;
}
sp<IBinder> SM::getService(const String16& name) {
AutoMutex _l(mLock);
Vector<ServiceEntry>::iterator it = findService(name);
if (it != mServices.end()) {
return it->binder;
}
return NULL; // 未找到
}
实现注意:SM内部使用`Vector<ServiceEntry>`实际为性能可改用`std::unordered_map`。句柄分配需考虑线程安全及句柄复用时机。