一、从 ServiceManager 到 servicemanager.rc:一段进化的故事
聊到 Binder 机制,ServiceManager 是个绕不开的角色。我刚开始接触 Android 时,总觉得它就是个「电话总机」——谁要服务,找它查号就行。但后来深入源码才发现,这个「总机」本身也在不断进化。
今天我们就聊聊 ServiceManager 的演变史。说白了,就是从一段 C 代码,变成了一个 .rc 文件里的服务声明。这背后藏着 Android 系统架构的深刻变化。
1.1 早期的 ServiceManager:一个「裸奔」的守护进程
在 Android 早期版本(4.4 之前),ServiceManager 是怎么启动的?
嗯,它其实是在 init.rc 里被直接启动的。你翻翻旧代码,会看到类似这样的片段:
# 早期 init.rc 中的 ServiceManager 启动
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
这段配置看起来挺简单,对吧?但这里有个关键点:servicemanager 是作为核心服务启动的。它挂了,整个系统都得重启。
我记得有一次在项目中调试一个诡异的问题——系统启动到一半就卡住了。查了半天,发现是 servicemanager 进程被 kill 了,然后 zygote、media、surfaceflinger 全部跟着重启,形成了死循环。嗯,那时候我才真正理解「critical」这个标记的分量。
1.2 为什么需要进化?
早期的方案有什么问题?我总结了几点:
- 硬编码依赖:ServiceManager 的启动参数、权限、依赖关系都写死在 init.rc 里
- 缺乏灵活性:想改个参数?得改 init.rc,还得重新编译整个系统
- 安全隔离不足:ServiceManager 运行在 system 用户下,权限太大
- 调试困难:出了问题,你只能看 log,没有细粒度的控制手段
你想想看,一个系统里最核心的服务,居然用这么原始的方式管理,是不是有点说不过去?
1.3 servicemanager.rc 的诞生
从 Android 5.0(Lollipop)开始,Google 引入了新的服务管理机制。ServiceManager 的启动配置被独立出来,变成了 servicemanager.rc 文件。
这个文件长什么样?我贴一段典型的代码:
# servicemanager.rc - Android 5.0+ 版本
service servicemanager /system/bin/servicemanager
class core animation
user system
group system readproc
critical
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
onrestart restart hal
writepid /dev/cpuset/foreground/tasks
看到变化了吗?多了几个关键点:
- class core animation:加入了 animation 类,意味着它和动画服务有依赖关系
- group system readproc:增加了 readproc 权限组
- writepid:写 cgroup 控制组,做资源隔离
- 更多的 onrestart 依赖:drm、hal 等服务也被纳入重启链
我个人习惯把这种变化叫做「从硬编码到声明式」的转变。说白了,就是把服务的配置信息从代码里抽出来,放到一个独立的配置文件中。这样做的最大好处是——你可以不改代码,只改配置文件,就能调整服务的行为。
1.4 进化的核心:SELinux 策略的引入
这个进化过程中,最值得关注的是 SELinux 的引入。从 Android 5.0 开始,ServiceManager 的运行受到了 SELinux 策略的严格约束。
我们来看一个 SELinux 策略文件的片段:
# servicemanager.te - SELinux 策略
type servicemanager, domain;
type servicemanager_exec, exec_type, file_type;
init_daemon_domain(servicemanager)
# 允许 servicemanager 访问 binder 设备
allow servicemanager self:binder { transfer call };
allow servicemanager servicemanager:binder { transfer call };
# 允许 servicemanager 读取系统属性
allow servicemanager property_type:file read;
allow servicemanager system_prop:property_service set;
这段策略文件定义了:
- servicemanager 运行在独立的 domain 中
- 它只能访问特定的 binder 设备
- 它只能读取特定的系统属性
- 它不能随意访问文件系统
我曾经在项目中遇到过一个问题:某个第三方应用想注册一个系统服务,结果一直失败。查了半天,发现是 SELinux 策略限制了 servicemanager 的 binder 调用权限。嗯,从那以后,我每次修改服务注册逻辑,都会先检查 SELinux 策略。
1.5 架构对比:两张图看懂变化
为了让你更直观地理解这个进化,我画了两张架构对比图。
这张图清晰地展示了两个时代的差异。左边是「大锅饭」时代,所有配置混在一起;右边是「精细化管理」时代,每个服务都有自己的配置文件和安全策略。
1.6 关键变化总结
我把这个进化的关键变化整理成了一个表格:
| 维度 | 早期(Android 4.4-) | 现代(Android 5.0+) |
|---|---|---|
| 配置文件 | init.rc 硬编码 | 独立的 servicemanager.rc |
| 安全策略 | 无 SELinux 约束 | 严格的 SELinux 策略 |
| 权限控制 | system 用户,权限过大 | 精细化权限组(readproc 等) |
| 资源隔离 | 无 cgroup 控制 | writepid 到 cgroup |
| 依赖管理 | 简单的 onrestart 链 | 更完整的依赖关系(drm、hal 等) |
| 调试手段 | 仅 logcat | selinux 审计日志 + 细粒度控制 |
1.7 避坑指南:我踩过的几个坑
聊了这么多理论,分享几个实战中遇到的坑吧:
⚠️ 坑一:修改 servicemanager.rc 后不生效
我曾经修改了 servicemanager.rc 里的 group 配置,结果重启后完全不生效。查了半天才发现,原来是 SELinux 策略限制了 group 的变更。记住:修改 .rc 文件后,必须同步更新对应的 SELinux 策略文件。
⚠️ 坑二:onrestart 链导致的启动风暴
有一次我在调试一个系统服务,不小心把 servicemanager 的 onrestart 链写成了循环依赖。结果系统启动时,servicemanager 挂了 → zygote 重启 → media 重启 → surfaceflinger 重启 → 然后又导致 servicemanager 挂掉... 嗯,那场面,简直是灾难。建议:画清楚依赖图,避免循环依赖。
💡 小技巧:如何快速验证 SELinux 策略
我习惯用 adb shell dmesg | grep avc 查看 SELinux 审计日志。如果 servicemanager 启动失败,先看这个日志,90% 的问题都能定位到。
1.8 核心要点回顾
好了,这一章的内容就到这里。我帮你梳理一下核心要点:
- ServiceManager 的启动配置从 init.rc 独立为 servicemanager.rc,实现了配置与代码的分离
- SELinux 策略的引入,让 ServiceManager 的运行有了严格的安全边界
- 资源隔离和依赖管理更加精细化,cgroup、writepid 等机制被引入
- 调试手段更加丰富,SELinux 审计日志成为定位问题的利器
这个进化过程,说白了就是 Android 系统从「能用」到「安全、可控、易维护」的缩影。你想想看,一个简单的服务启动配置,背后竟然牵扯到安全策略、资源管理、依赖关系这么多东西。这就是系统工程师的日常——每一个细节都可能藏着坑。
下一章,我们会深入 ServiceManager 的源码,看看它到底是怎么注册和查询服务的。到时候你会发现,代码里的细节比配置文件的进化还要精彩。