尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Android Init进程全解析:启动流程、rc文件与属性服务

发布时间:2026/9/8 10:17:11

资讯中心
01
ARTICLE

Android Init进程全解析:启动流程、rc文件与属性服务

Android Init进程全解析:启动流程、rc文件与属性服务
1. Init 进程到底解决了什么问题做 Android 系统开发这些年如果要挑一个最容易被忽略却又绕不开的起点我会选 Init 进程。它不像 Binder、AMS 那样经常被挂在嘴边但只要是涉及“开机”“服务拉起”“属性读写”“异常重启”的问题追到最后都会回到它身上。Init 是内核启动完成后创建的第一个用户空间进程进程号为 1。它的特殊性在于内核把所有硬件资源、驱动、调度机制都准备好了但“用户空间的服务该怎么拉起按什么顺序拉拉挂了要不要重启”这些事内核不管。这就是 Init 存在的意义——它是用户空间的总调度员是那根把所有 Android 组件串起来的线。这篇内容会把 Init 的核心机制拆开讲清楚它的启动流程、rc 文件的解析逻辑、属性服务的工作原理以及它和 Zygote 之间那层关键的配合关系。无论你是刚入门的系统开发、做 BSP 的工程师还是想深入理解 Android Framework 启动链路的应用层开发者这篇都能帮你把“从内核态到用户态”这段路走明白。1.1 用户空间的第一进程先明确一个概念Linux 内核完成初始化后会主动去执行一个用户空间程序这个程序的 PID 固定为 1名为 Init。在标准 Linux 发行版里它通常是 systemd 或者 SysVinit在 Android 里它就是 AOSP 中的system/core/init。PID 为 1 意味着几件很特别的事它没有父进程内核直接派生的。它是所有孤儿进程的收养者。如果某个子进程的父进程死了内核会把该进程 reparent 到 PID 1由 Init 负责回收它的僵尸进程waitpid。如果它挂了内核会触发 panic系统直接重启。所以 Init 的代码风格一直是“宁可保守也不冒险”任何关键路径上的错误都会导致系统起不来或不停重启。这也是为什么做系统开发时遇到启动问题第一件事就是去查 Init 相关的日志。Android 的 Init 在 AOSP 里的源码路径是system/core/init/核心文件包括init.cpp主入口含 first stage 与 second stage 的启动逻辑。property_service.cpp属性服务的实现。service.cppservice 对象的解析与管理。action.cpp/parser.cpprc 文件解析与 Action 执行框架。signal_handler.cpp子进程信号处理与重启策略。从 Android 10 开始Init 被拆成了两个阶段first_stage_init和second_stage_init。前者跑在极其受限的环境里只做最基础的事情后者才进入真正完整的 Android 用户空间初始化流程。这个拆分后面会详细讲这里先有个整体印象。1.2 Init 的三大核心职责把 Init 的全部工作浓缩成一句话用可配置的方式按顺序拉起所有 Android 用户空间服务并持续监控它们的运行状态。展开来看主要是三块。第一块是解析并执行 rc 脚本。/init.rc是 Init 的核心配置文件里面用一套类 Shell 的语法定义了各类 Action 和 Service。Init 在启动时会读入这些文件按照触发条件Trigger的顺序执行动作Action并根据配置启动守护服务Service。Zygote、surfaceflinger、servicemanager 这些和你日常开发息息相关的重要进程严格来说都不是 Init 直接代码写的而是写在 rc 文件里被 Init 拉起来的。第二块是属性服务。Android 里有一种全局的键值对机制叫“系统属性”system property进程之间通过property_get/property_set来读写。Init 在启动早期就会把这套机制搭好把属性区域建在共享内存里通过 socket 接收来自各个进程的写请求并负责权限检查、值类型校验、持久化等。平时你敲的getprop、setprop底层就是在和 Init 的 property service 打交道。第三块是子进程的监控与重启。Init 是所有系统级 service 的直接父进程。当某个 service 进程异常退出时内核会向 Init 发送 SIGCHLD 信号Init 的信号处理模块会找出是哪个 service 死了然后根据它的配置决定是重新拉起、忽略还是触发系统重启。如果你见过一个服务挂了之后系统疯狂重启的现象那大概率就是 rc 里把该服务标记成了critical。这三块职责也不是孤立存在的——rc 解析解决“何时启动什么”属性服务解决“运行中的状态如何共享”信号处理解决“进程死后怎么办”。它们互相配合构成了 Android 用户空间运行的底座。2. Init 进程的启动流程全拆解很多讲 Init 的文章上来直接讲 rc 文件语法但我觉得那样会漏掉最精彩的部分Init 自己是怎么活过来的。从内核态切到用户态这一步是被很多工程师当成黑盒直接跳过的。2.1 从内核启动到 Init 的交接内核态到 Init 之间的交接路径大致是这样的start_kernel() - rest_init() - kernel_init() - run_init_process()kernel_init()在完成一系列内核层面的初始化后会去根目录查找可执行文件Android 里就是/init。它被直接替换成 Init 进程的代码也就是说 Init 不是被 fork 出来的普通进程而是以“替换内核 init 线程上下文”的方式诞生的这保证了它的 PID 必然是 1。Android 的/init在早期版本里是一个可执行的 ELF 文件现在的主流版本是通过ramdisk或者 vendor_boot 里的 ramdisk提供。设备厂商在制作 boot 分区时会把 init 可执行文件、init.rc、以及各种.rc文件塞进 ramdisk 里内核启动后先挂载 ramdisk再执行其中的 init。从 Android 10 开始init被拆成了两个阶段设计上是这么考虑的早期需要先把/system、/vendor这些分区 mount 上来这通常需要 device-mapper、文件系统驱动等能力但那时候文件系统还没准备好一些依赖还没就位所以第一阶段只干最基础的活等基础环境就绪再完整切换到第二阶段。举个例子——如果第一阶段就想去读/system/etc/xxx.conf那注定失败因为/system分区的 mount 是在第一阶段末尾或第二阶段才完成的。Android 把这套过程放进first_stage_init的实现里通过MountHandler、Devices这类组件去处理挂载、设备节点创建等事务。2.2 Init 主循环事件驱动的业务模型Init 启动完成后并不会“一次性把所有事情做完然后退出”它需要持续运行响应各种异步事件。它的主循环是一个基于 epoll 的事件驱动模型和你在应用层写的Looper思路类似但挂在它监听列表上的是一组“文件描述符”。这些被监听的 fd 主要来自几个方向属性服务的 socket 描述符。其他进程通过本地 socket 连接进来请求设置属性Init 需要及时读取并处理。子进程终止的信号。Linux 里子进程退出会产生 SIGCHLDInit 用 signalfd 把它转成可读事件加入 epoll方便在事件循环里统一处理。设备节点的 uevent。早期阶段需要处理冷插拔coldplug和热插拔hotplug事件创建、删除设备节点这些消息也会通过 netlink socket 接入主循环。各种显式注册的 epoll 源比如wait_for_prop命令中等待属性变化时的条件触发。主循环的大致逻辑是while (true) { epoll_wait(...) // 处理每个 fd 上的事件 // 1. 属性写入请求 - property_set // 2. 子进程退出 - 触发重启/记录日志 // 3. uevent - 创建/删除设备节点 // 4. 定时任务 - 执行周期动作 // 每次事件处理完后重新评估当前需要执行的 Action }每次事件处理完后Init 会重新检查当前的系统状态比如某些 property 是否满足条件如果条件满足就会被调度执行。比如你setprop sys.usb.config adb之后通过属性触发就能让adbd重新启动就是靠这套事件驱动的 Action 评估机制实现的。理解这个模型对排查问题很重要。很多人以为 Init 是线性执行的其实它是一个持续的、事件触发的循环。这也解释了为什么 rc 里某些on property:xxx1的触发块在系统运行过程中也能动态生效——它们不是只在开机阶段执行一次的。2.3 关键阶段的执行细节我挑几个最关键的阶段展开说这样后面看日志时不会一头雾水。First stage 的职责大概是这样它首先建立基本环境包括挂载/dev、/proc、/sys这些基础文件系统。初始化 SELinux加载第一阶段的安全策略。挂载/system、/vendor等逻辑分区。现在的 Android 设备基本都在用动态分区super所以这里会走FirstStageMount通过读取/system等分区信息结合 device-mapper 建立映射。创建设备节点。早期 Android 通过在/dev下预先放置节点或借助 uevent 实现现在主要依赖devtmpfs和 uevent 动态创建。设置/dev/__properties__目录为第二阶段启动做准备。Second stage 才是我们平时研究的重点。它会重新设置部分 SELinux 上下文保证自己以正确的域domain运行。解析并导入所有 rc 文件包括/init.rc和system、vendor、odm各分区中的附加 rc 文件。初始化 property service加载默认属性、以及各种*.prop文件。进入主事件循环。在实际板子上你可以在串口日志里看到类似这样的阶段标记[ 1.234567] init: init first stage started! [ 1.345678] init: [libfs_mgr] Created logical partition system [ 2.123456] init: init second stage started! [ 2.234567] init: Parsing file /init.rc当你在日志里看到 “init second stage started!”说明 Init 已经进入完整的用户空间初始化流程。后面的Parsing file /init.rc、starting service zygote之类的日志都属于这个阶段的产物。3. 核心机制一rc 文件解析与 Action 执行框架Init 的配置体系是 rc 文件。这个文件里的语法和 Shell 脚本有相似之处但它不是 Shell而是一套专门为 Init 设计的声明式语言。理解它能帮你搞清楚服务是怎么被组织、调度、重启的。3.1 rc 文件的语法结构rc 文件的基础单元是“行”以关键词开头后面跟参数用空格分隔。注释以#开头。常见的顶层关键词有四类on、service、import、option。一个 Action 的写法on early-init # 设置一些系统属性 setprop sys.usb.config none on property:ro.crypto.stateencrypted # 挂载 /data 分区前的处理on后面跟的是触发条件trigger可以是固定阶段名如early-init、init、late_init也可以是属性条件如property:sys.boot_completed1。当条件满足时下面缩进的所有命令会被依次执行。Service 定义长这样service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main priority -20 user root group root readproc socket zygote stream 660 root system onrestart restart zygoteservice关键字后面跟服务名、可执行文件路径和参数。下面的缩进行就是 Options——它们不是给 service 传的参数而是告诉 Init 如何管理这个服务。Import 语句则是用来引入其他 rc 文件的import /init.environ.rc import /system/etc/init/*.rc这里要特别注意不同厂商、不同分区system/vendor/odm下的 rc 文件都会被扫描和解析但导入顺序有讲究。/init.rc里通过 import 引入其他文件vendor 分区里通常也会有自己的一套 rc 文件用于拉起硬件相关的服务。3.2 Trigger 与 Action 的执行时机不懂 Trigger 的优先级你很难理解 Init 为什么会在某个时间点做某件事。我整理一下 Android 常见的执行阶段顺序Trigger大致时机典型用途early-init第二阶段开始后最早阶段创建基础目录、设置最低级的属性init基础环境就绪挂载 /dev 下的文件系统、创建关键目录early-fs文件系统准备阶段挂载不可信任的 /system 分区前的准备工作fs/system 挂载后检查、挂载各类文件系统post-fs文件系统挂载完成后设置磁盘相关属性和服务的准备工作post-fs-data/data 分区挂载并准备完成后加解密逻辑、启动 insmod 等zygote-start准备启动 Java 世界触发 zygote 服务的启动late_init其他主要服务启动完成各种收尾工作on块不一定只能挂一个固定阶段。你可以写on fs property:ro.crypto.stateencrypted表示既要到达fs阶段又要满足某个属性才会执行。这里是“且”的意思两个条件都满足才会触发。Property 型 Trigger 是理解“运行时动态触发”的关键。举个实际例子某个供应商的守护进程希望等 USB 连接状态变化后执行命令就可以写成on property:sys.usb.configadb start adbd当某个进程通过setprop sys.usb.config adb设置属性后Init 的事件循环会捕捉到属性变化重新评估所有依赖该属性的on块满足条件就执行命令。所以别再把on property:当成“只在开机时执行”的东西。3.3 service 的启动、重启与禁用Service 是 Init 里最核心的抽象。定义一个 service 后Init 会 fork 一个子进程去跑指定的可执行文件并持续监控它。如果子进程退出Init 会收到 SIGCHLD然后根据该 service 的配置决定怎么做。常见的 Options 及其含义Option作用注意事项class服务分组默认是 defaultmain/core 是常用分组disabled不随 class 自动启动需要显式start或ctl.startoneshot执行完就退出不算异常配合 disabled 常用于一次性初始化任务user以指定用户运行不要轻易给 root安全风险高group以指定组运行可附加多个组critical退出会导致系统重启慎用否则一个服务挂了就无限重启onrestart服务重启时额外执行命令常用于 zygote 挂掉时重启 surfaceflingersocket自动创建 socket 文件Android 服务间通信常用方式类class机制很实用。class main的服务会在系统启动的 main 阶段被统一拉起class core则会更早一点。通过class_start和class_stop命令可以一次性启动或停止整组服务。比如你在调试时想重启所有 main 类服务可以这样stop start这在 Init 的 rc 配置里对应的是class_start main和class_stop main。用adb shell stop adb shell start都能达到类似效果后者的本质就是通过属性控制 Init 执行 class 的启停。禁用服务也是一个常见操作。如果某个服务你不希望它自动启动可以直接在 rc 里给该 service 加上disabled再通过on property:xxxx按需启动。不过要注意修改 rc 文件后需要重新打包 boot/recovery 镜像或 vendor 镜像才能生效实机调试时一般先通过 adb 尝试setprop ctl.start service验证而不是反复刷机。4. 核心机制二属性服务与系统属性的读写如果你看过 Binder 的源码一定知道系统服务之间通信走的是 Binder但如果你问一个系统属性是怎么全局共享的答案反而是 Init 提供的属性服务。它是一套独立于 Binder 的机制简单但极其重要。4.1 属性服务的工作原理Android 属性property本质是一个全局的键值对表存储在共享内存里。任何进程都可以通过property_get()读取但写入则必须经过 Init 的 property service 审核。工作流程是这样的调用进程通过__system_property_set()发起写请求。这个请求会通过本地 socket 发送给 Init 的 property service。Init 校验请求方是否有权限写这个 key通过 SELinux 判断。校验通过后在共享内存的属性区域更新该 key 的值。如果 key 以persist.开头还会把值写入/data/property/下的持久化文件保证重启后不丢失。共享内存的设计让读操作非常快——不需要跨进程通信直接读共享内存的映射区域就行。这和一些你熟悉的“[进程间通过 mmap 共享数据]”思路完全一致只是 Android 把它做成了统一框架。补充一点property_get()是 libcutils 提供的 C APIJava 层对应的是android.os.SystemProperties.get()。Framework 里很多配置都通过属性传递比如ro.build.version.sdk、sys.boot_completed、dalvik.vm.heapsize等你随时可以通过getprop查看。4.2 属性权限与 SELinux 管控大多数系统属性写入失败的案例最后都指向同一个原因SELinux 拒绝了写请求。属性权限不是简单“能写/不能写”的开关而是通过 type 和 context 的组合来精确控制的。系统里有一份属性上下文映射文件路径通常是/system/etc/selinux/plat_property_contexts/vendor/etc/selinux/vendor_property_contexts文件内容大致长这样ro.build.version.sdk u:object_r:build_prop:s0 sys.usb.config u:object_r:usb_prop:s0 persist.sys.timezone u:object_r:timezone_prop:s0当一个进程尝试写某个属性时SELinux 会检查该进程的 domain 是否对这个属性 type 有set权限。比如shelldomain 能不能写sys.usb.config取决于 sepolicy 里是否有类似这样的规则allow shell usb_prop:property_service set;如果没有权限日志里会出现典型的avc: denied { set }记录。排查这类问题最简单的思路就是先getenforce看 SELinux 模式再在日志里搜avc: denied确认是哪个 source 对哪个 target 的什么权限被拒绝最后在对应策略文件里补充 allow 规则。调试阶段可以临时setenforce 0验证是否 SELinux 导致但绝不能把这个作为长期方案。还有一个常见坑属性的命名约定。ro.前缀代表只读read-only一旦设置之后就不能再修改只能重启生效persist.前缀代表持久化重启后仍保留net.前缀和网络相关sys.开头的通常由系统内部设置。你自定义属性时建议遵循这套前缀规范不要发明奇怪前缀否则可能触发 CTS 或 vendor 测试问题。4.3 实际调试中的属性操作技巧属性在开发和测试阶段是排查问题的一把利器。我整理几个实际工作中常用的场景。场景一查看当前系统各分区构建版本。adb shell getprop | grep ro.build场景二模拟某个系统状态触发 rc 逻辑。比如某些设备支持通过设置sys.usb.config切换 USB 功能adb shell setprop sys.usb.config adb场景三判断系统是否进入可交互状态。adb shell getprop sys.boot_completed值为 1 表示系统整体启动完成这对判断“系统是卡在 boot 阶段还是 Framework 阶段”非常有用。场景四用persist.属性保存自己的测试标记。比如写一个自定义属性用于判断某个模块是否需要输出 debug 日志adb shell setprop persist.vendor.debug.myapp 1应用层可以通过SystemProperties.getInt(persist.vendor.debug.myapp, 0)读取重启后依然有效。这在联调和复现问题上特别方便——不用反复改代码、编译、刷机。还有一个值得注意的细节属性值有长度限制PROP_VALUE_MAX是 92 字节。如果你试图写入超过 92 字节的字符串会被拒绝或截断。这个问题在设置一些 base64 编码、长路径之类的值时会遇到要记得先裁剪。5. 核心机制三Zygote 与 Init 的配合Zygote 是 Android Java 世界的起点但它的出生完全是由 Init 一手操办的。不了解 Init 和 Zygote 之间的关系你看系统启动日志时会少一半信息量。5.1 Zygote 的角色与启动基础Zygote 本身并不神秘它就是一个由app_process启动的 Java 进程。但它的特殊之处在于所有普通 Android 应用进程都是从 Zygote fork 出来的。这样做的好处是应用进程天然带有 Zygote 预加载好的类、资源和系统服务连接不用每个应用都重新加载一遍启动速度和内存占用都得到很大优化。在 rc 文件里Zygote 的定义大致是service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main priority -20 user root group root readproc socket zygote stream 660 root system onrestart restart zygote这里有两个细节值得展开。--start-system-server参数告诉 Zygote 启动完成后要立即 fork 出system_serversocket zygote stream 660 root system则是在/dev/socket/下创建名为zygote的 socket 文件权限是 660属主 root、属组 system。应用进程要 fork就是通过连接这个 socket 来发送请求的。另外注意onrestart restart zygote。这条规则的意思不是“如果 zygote 重启了就重启它自己”而是说当 zygote 这个 service 发生重启即进程被杀后重新拉起时Init 要额外执行restart zygote命令。这可能看起来有点绕实际含义是Zygote 如果异常退出Init 会依照 service 定义自动重新拉起它。但由于 system_server 是从 Zygote fork 出来的Zygote 重启后 system_server 肯定也处于异常状态所以需要顺带把和 system_server 强绑定的服务一起重启。一个比较经典的配置是这样的service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main ... onrestart restart zygote而像 surfaceflinger 这种属于 main class 的核心服务因 zygote 重启而被动重启的情况也多见。调试时如果在 logcat 里看到某个服务莫名其妙重启了不妨回头看看 rc 里的onrestart链。5.2 从 Init 视角看 Zygote 的启动时机Zygote 并不是 Init 启动最早的进程。拿主流的 Android 11 设备举例启动顺序大致是Init second stage 开始解析 rc。执行early-init、init、early-fs、fs、post-fs等阶段。挂载好/data等分区后进入post-fs-data。进入zygote-start阶段调用class_start main这一下会把 main class 的 service 都拉起来包括 zygote、surfaceflinger、servicemanager 等。也就是说Zygote 是 main class 服务中的“排头兵”但它的启动并不凌驾于所有 Init 阶段之上。它有依赖条件——比如/data分区必须已经可用某些/system下的库和 socket 目录也必须就绪。Zygote 启动后它会去加载 Java 层的核心类库、注册系统的 JNI 方法、预加载部分资源和主题然后进入 socket 监听状态。这时候你如果去连那个/dev/socket/zygote的 socket理论上就能请求 fork 新进程了。system_server 是所有系统服务AMS、PMS、WMS 等的宿主。它是 Zygote 启动早期就 fork 出来的子进程。从进程树上看system_server 的 PPID父进程 ID是 Zygote 的 PID而 Zygote 的 PPID 是 Init 的 PID。在设备上可以通过ps -A -o PID,PPID,NAME | grep -E zygote|system_server验证这一串父子关系。5.3 从 Init 到应用进程的完整链路这里顺便把整条链路串一下帮你建立整体观内核启动Init 成为用户空间第一个进程。Init 解析 rc进入 zygote-start 阶段通过class_start main拉起 Zygote。Zygote 启动后 fork 出 system_server。system_server 里的 ActivityManagerService(AMS) 启动后会向 Zygote 发起 socket 连接。用户点击 App 图标AMS 通过 socket 发消息给 Zygote让它 fork 一个新进程新进程就是我们的 App。这就是那条经典的“从硬件到应用”的链路。你会发现中间每一步都有一个清晰的“最终负责人”硬件归内核管服务的启动归 Init 管Java 世界的初始化归 Zygote 管App 进程的创建归 ZygoteAMS 管。在实际排查问题时这条链路的价值非常直接。如果开机后没有应用能启动先看 Zygote 是不是正常起来了如果 Zygote 起来了但 system_server 循环重启看它挂在哪个服务上如果 App 都正常但某个系统属性没生效回过来查 Init 的 property 日志。这种逐层排查的思维比死记硬背 API 有用得多。每个组件都有它自己的上游和下游搞清上下游关系很多疑难问题的边界就自然收缩了。6. 常见问题与排查技巧实录最后这部分写点实操。Init 相关的问题在开发、测试、售后阶段都会遇到我挑几个典型的把排查思路和命令写出来。6.1 开机卡死怎么看 Init 阶段日志开机卡死是系统开发里最让人头疼的问题之一。好在 Init 阶段的日志相对好拿关键是找对地方。如果你有串口那最好办。Init 在关键节点会打印日志比如[ 3.456789] init: starting service zygote... [ 3.456890] init: Sending signal 0 to service zygote (pid 1234) [ 3.456912] init: Service zygote restarting...如果没有串口可以通过last_kmsg或 pstore 拿内核日志。设备重启后进入 fastboot 或救援模式时通过/sys/fs/pstore/或adb shell cat /proc/last_kmsg查看。注意 Android 12 之后last_kmsg已经普遍被 pstore/ramoops 替代路径可能是/sys/fs/pstore/console-ramoops-0。拿到日志后第一件事是定位卡在哪个阶段。你可以这样做看是否有init second stage started!。没有的话问题在第一阶段通常是 mount 分区、SELinux 策略或者 initramfs 问题。看是否出现了Parsing file /init.rc。有解析日志但后面卡住重点查 rc 文件里某个 service 是否卡死了。看是否出现starting service zygote。如果 zygote 一直没起来多半是 Zygote 启动参数、SELinux 上下文或它依赖的库缺失。看sys.boot_completed是不是一直为 0。即使没有串口也可以用adb shell getprop来确认系统是否走到 Framework 启动阶段。有个细节要留意Init 自己的日志是写进/dev/kmsg的所以它和内核日志混在一起串口日志里看到的init: xxx就是它。logcat在早期阶段是起不来的所以不要在 Init 阶段指望 logcat靠串口和 kmsg 最可靠。6.2 rc 文件语法错误的排查rc 文件是文本配置写错了 Init 一般不会直接崩而是跳过或者报错。但“跳过”意味着服务没被拉起这种问题隐蔽性很强。常见的错误有漏了缩进。on块下的命令必须有缩进没有缩进会被当成一个新关键字。Service 参数写错路径。常见的像可执行文件路径不存在或者参数顺序不对。Option 拼写错误。比如把oneshot写成oneshotonInit 会直接报 unknown option。忘了 import。如果 rc 文件没有被正确 import里面定义的服务根本不会被解析。排查 rc 解析错误的方法是看 Init 的启动日志。Init 解析到出错的语句时会打印类似信息init: /init.rc: 123: invalid command star init: /system/etc/init/foo.rc: 45: parse error: unknown option wrong这里会给出文件路径和行号直接去看对应行就行。如果日志被冲掉了可以在编译时把 init 的 log level 调高或者把 rc 文件放在一个独立分区里反复修改、验证。另外一个很容易被忽略的问题rc 文件最后必须有一个换行符。如果文件末尾没有换行最后一个 token 可能解析失败。这在手动修改 rc 文件时特别常见建议养成“写完 rc 后习惯性 cat -A 看一眼”的习惯。6.3 属性设置失败与 SELinux 问题定位属性设置失败是系统联调里的高频问题。症状一般有两种设置后马上读还是旧值或者设置后其他进程读不到。第一步永远是区分“是权限被拒还是值被截断”。最快的验证方法adb shell setprop test.foo bar adb shell getprop test.foo如果返回空或者bar没生效先看当前getprop输出里是否有这个 key。如果完全没有那大概率是权限或者前缀问题。如果值存在但和你设置的不一致那可能是别的高优先级进程又改了一次或者触发了属性回调的联动修改。SELinux 拒绝的信息长这样avc: denied { set } for propertytest.foo scontextu:r:shell:s0 tcontextu:object_r:default_prop:s0 tclassproperty_service看到这种日志解决办法是调整 sepolicy让shell或相应的 domain对该属性 type 有 set 权限。具体做法是把属性加入对应的 property_contexts再在 sepolicy 中添加 allow 规则。还有一个经常踩的坑ro.开头的属性一旦设置就无法修改。如果你在代码里看到setprop ro.something 1别被误导——这条命令只会在进程启动早期执行一次之后你再怎么 set 都不会生效。如果想在运行期修改请使用persist.或sys.前缀。6.4 Init 自带的几个调试工具Init 里有一些不太常被提到但排查问题特别有用的命令和属性。wait_for_prop命令可以阻塞执行直到某个属性达到预期值。rc 里经常这样用on boot wait_for_prop sys.boot_completed 1 # 等 boot 完成后执行后续动作调试时如果你想让某个初始化步骤等一个异步事件可以临时加一行wait_for_prop不需要写代码。不过这命令只在 Init 的解析流程里有效不要在 adb shell 里直接敲。ctl.start、ctl.stop、ctl.restart是三个神奇的系统属性通过它们可以在运行时控制 service 的启停。比如你想重启 adbdadb shell setprop ctl.restart adbd这个操作的本质是让属性服务触发 Init 对该 service 执行重启逻辑。类似的setprop ctl.stop surfaceflinger会停掉 surfaceflinger。调试渲染和 HAL 相关服务时这三个属性极其好用。exec_start命令则用于主动运行一段 rc 中定义的 service通常配合oneshot使用。它和start的区别是exec_start会等待该命令执行完成再继续。这在制作带顺序依赖的启动动作时很有用。注意ctl.属性和exec_start往往受 SELinux 约束。不同厂商的开机脚本策略差异很大在别人代码里看到这类命令时先确认当前 domain 是否有对应的权限别想当然认为所有机器都能用。6.5 我个人的一点排障建议排查 Init 问题我个人的体会是先确立“当前系统到底走到了哪一步”再用二分法缩小范围。内核日志先看阶段标记阶段到了再看具体服务的启动状态服务启动失败再看它的依赖和 SELinux 日志。这三步走完绝大多数问题都能定位到具体文件或具体配置上。另外一个容易忽略的建议多熟悉你的平台厂商改过的 rc。AOSP 的 init.rc 是一回事高通的、MTK 的、展锐的都有自家改动。同样的class_start main在不同平台上实际拉起的服务个数和顺序很可能有差异。遇到问题时先看看厂商有没有在 rc 里挂额外的 service再去怀疑 AOSP 代码本身。Init 进程不像 Binder、HwBinder 那样有大量架构知识它的核心价值在于“把事情按正确顺序做对”。正因为它朴素反而值得每个做系统层开发的人仔细读一遍源码——不需要逐行读懂只要把init.cpp里主流程和service.cpp的管理逻辑过一遍以后再看到开机日志你就能在脑子里画出一条清晰的推进线了。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。