3. ServiceManager角色与位置

3.1 核心职责与定位

ServiceManager(简称SM)在Binder架构中扮演“服务域名服务器”的角色:

  • 服务注册中心:服务端进程向SM注册自身,告知服务名称与Binder句柄。
  • 服务查询代理:客户端进程通过服务名称向SM查询,获得目标服务的句柄。
  • 句柄(handle)分配器:为每个注册的服务分配唯一的整数句柄。
  • 系统启动锚点:init进程启动后第一个启动的Binder关键进程,是整个Binder通信的起点。
关键认知:ServiceManager本身也是一个Binder服务(特殊句柄0),但它的角色决定了它必须先于所有普通服务启动,且持有对/dev/binder的全局控制权。

3.2 系统启动阶段的初始化链路

Android系统启动时,ServiceManager的初始化按以下步骤执行:

  1. Init进程启动:解析init.rc,启动servicemanager进程。
  2. 打开Binder设备:servicemanager调用open("/dev/binder", O_RDWR),获得文件描述符。
  3. 映射传输缓冲区:mmap(BINDER_VM_SIZE)建立内核与用户态共享内存。
  4. 成为上下文管理器:调用ioctl(BINDER_SET_CONTEXT_MGR)将自己注册为Binder全局管理器。
  5. 进入循环:调用binder_loop()开始监听来自其他进程的请求。
启动阶段关键动作内核/用户态边界
init.rcservicemanager可执行入口用户态
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的完整流程:

  1. 服务进程启动后,调用defaultServiceManager()获取SM代理(句柄0)。
  2. 构造Parcel数据包,包含服务名称(字符串)和自身Binder引用。
  3. 通过Binder驱动将注册请求发送给SM。
  4. SM收到请求后,在内部哈希表中查找是否已存在同名服务:
    • 若已存在且允许覆盖?处理冲突策略见3.5。
    • 若不存在,分配新的句柄(从1递增),存储name→handle映射。
  5. 返回注册结果给服务进程。

3.4 服务查询流程(客户端视角)

客户端查询服务的原子步骤:

  1. 调用defaultServiceManager()获得SM代理。
  2. 调用getService(const String16& name),发送查询请求到句柄0。
  3. SM在哈希表中检查name是否注册:
    • 存在:返回对应句柄值。
    • 不存在:返回空句柄(0),客户端可设置死亡通知等待服务上线。
  4. 客户端获得句柄后,通过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到新平台时,可参考以下落地步骤:

  1. 实现binder_open()/binder_mmap()/binder_loop()基础逻辑。
  2. 在init脚本中确保SM优先启动。
  3. 编写内部注册表(哈希表+自旋锁保证并发安全)。
  4. 实现handle分配函数(从空闲池或递增)。
  5. 暴露标准接口:addService()、getService()、listServices()。

验收标准:

  • 启动后能通过`lshal`或`service list`看到注册的基础服务。
  • 客户端调用`getService("xxx")`返回非空句柄。
  • 服务终止后,同名服务重新注册能正常替换(或报唯一性冲突)。
  • 压力测试(1000次并发查询)无句柄重复或丢失。
ServiceManager初始化及服务注册/查询总体流程 系统启动(init) ServiceManager Binder Driver 服务端进程 客户端进程 服务注册表(哈希) 注册服务名+ref 查询服务名 返回句柄 说明: 1. SM进程通过ioctl(BINDER_SET_CONTEXT_MGR)成为Binder上下文管理器。 2. 所有普通服务注册与查询都经过SM中转,但数据传输直接在两端进行。

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`。句柄分配需考虑线程安全及句柄复用时机。