1 简述
Android 初始化语言包含五大类语句:动作 (Actions)、命令 (Commands)、服务 (Services)、选项 (Options) 和 导入 (Imports)。
2 基础语法规则
- 面向行:所有语句都以行为单位,令牌(Tokens)由空格分隔。
- 转义与引号:使用反斜杠 \ 插入空格,或使用双引号 " 防止文本被切分。
- 折行:行尾使用 \ 可将下一行合并到当前行。
- 属性扩展:语法为 ${property.name},支持在字符串拼接中使用。
- 段落结构:Actions 和 Services 会隐式声明一个新段(Section)。所有命令或选项都归属于最近声明的那个段。
3 RC文件布局与存放
系统启动时会加载多个 .rc 文件:
- 核心文件:/system/etc/init/hw/init.rc(系统设置的主要负责人)。
- 分区目录:
- /system/etc/init/:系统核心组件(如 SurfaceFlinger, MediaService)。
- /vendor/etc/init/:SoC 供应商组件(如硬件驱动守护进程)。
- /odm/etc/init/:设备制造商组件(如传感器等外设)。
- APEX 模块:位于 /apex/*/etc/*.rc。支持版本化命名(如 init.32rc 表示在 SDK 32 及以上版本生效)。
4 语法概念
4.1 动作
动作是命名的命令序列。动作有一个触发器,用于确定何时执行该动作。当发生与动作触发器匹配的事件时,该动作将被添加到待执行队列的末尾(除非它已在队列中)。
队列中的每个动作按顺序出列,该动作中的每个命令按顺序执行。Init 在活动中的命令执行"之间"处理其他活动(设备创建/销毁、属性设置、进程重启)。
xml
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
4.2 服务
服务是 init 启动并在它们退出时(可选)重启的程序
xml
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
选项是服务的修饰符。它们影响 init 如何以及何时运行服务
- capabilities [ * ]:执行此服务时设置能力。
- class [ * ]:为服务指定类名。同类服务可一起启动/停止。
- console []:此服务需要控制台。
- critical:设备关键服务。崩溃多次将导致设备重启进入 bootloader
- disabled:不会随类自动启动,必须显式启动。
- file :打开文件路径并将其 fd 传递给进程。
- group [ * ]:执行前切换组。
- interface :关联服务提供的 AIDL 或 HIDL 接口。
- oneshot:服务退出时不重启。
- onrestart:服务重启时执行命令。
- override:覆盖先前的同名服务定义。
- seclabel :执行前切换 SELinux 上下文。
- user :执行前切换用户。
- enter_namespace : 进入位于 path 的 type 类型命名空间。
- ioprio : 设置 IO 优先级和类。
- keycodes [ * ]: 设置触发此服务的键值码。
- memcg.limit_in_bytes : 设置内存限制。
- namespace <pid|mnt>: 在 fork 服务时进入新的 PID 或 mount 命名空间。
- oom_score_adjust : 设置进程的 oom_score_adj 值。
- priority : 服务进程的调度优先级。
- reboot_on_failure : 如果进程无法启动或异常终止,则重启系统。
- restart_period : 非 oneshot 服务退出的重启间隔。
- rlimit : 对服务应用给定的 rlimit。
- setenv : 设置环境变量。
- shutdown <shutdown_behavior>: 设置关机行为。
- sigstop: 在调用 exec 之前向服务发送 SIGSTOP。
- socket ...: 创建 UNIX 域套接字。
- stdio_to_kmsg: 将 stdout 和 stderr 重定向到 /dev/kmsg_debug。
- task_profiles [ * ]: 为进程设置任务配置文件。
- timeout_period : 提供超时时间,超时后服务将被杀死。
- updatable: 标记服务稍后可被 APEX 覆盖。
4.3 触发器
触发器是可以用来匹配某些Kind事件并导致动作发生的字符串。 触发器分为事件触发器和属性触发器。
- 事件触发器:由 'trigger' 命令触发的简单字符串,如 'boot'。
- 属性触发器:当命名属性更改值时触发。形式为 property:= 或 property:=*。
触发序列 init 执行的内置顺序如下:
- early-init:配置 cgroup 后,ueventd 冷启动完成前。
- init:冷启动完成后。
- charger:如果 ro.bootmode == "charger" 则触发。
- late-init:非充电模式下的主触发器,它会引发出以下非内置序列:
- early-fs -> fs (挂载分区) -> post-fs
- late-fs -> post-fs-data (挂载 /data,设置加密)
- zygote-start -> early-boot -> boot
4.4 命令
bootchart [start|stop]: 开始/停止 bootcharting。chmod <octal-mode> <path>: 更改权限。chown <owner> <group> <path>: 更改所有者和组。class_start / class_stop / class_reset / class_restart: 类服务控制。copy <src> <dst>: 复制文件。domainname <name>: 设置域名。enable <servicename>: 启用禁用的服务。exec [ <seclabel> ... ] -- <command>: fork 并执行命令(同步)。exec_background: 异步执行命令。exec_start <service>: 启动给定服务并挂起 init 直到其返回。export <name> <value>: 设置全局环境变量。hostname <name>: 设置主机名。ifup <interface>: 启用网络接口。insmod [-f] <path> [<options>]: 安装模块。interface_start / interface_restart / interface_stop: 接口服务控制。load_exports <path>: 加载并导出文件中的变量。load_persist_props: 加载持久属性。loglevel <level>: 设置日志级别。mkdir <path> [mode] [owner] [group] ...: 创建目录。mount_all [ <fstab> ] [--<option>]: 挂载所有分区。mount <type> <device> <dir> ...: 尝试挂载设备。perform_apex_config: APEX 挂载后的任务。restart <service>: 重启服务。restorecon <path> [ <path>* ]: 恢复安全上下文。rm <path> / rmdir <path>: 删除文件或目录。readahead <file|dir> [--fully]: 预读取文件。setprop <name> <value>: 设置系统属性。setrlimit <resource> <cur> <max>: 设置资源限制。start / stop <service>: 启动/停止服务。swapon_all [ <fstab> ]: 启用所有 swap 分区。symlink <target> <path>: 创建符号链接。sysclktz <minutes>: 设置系统时区。trigger <event>: 触发一个事件。umount <path> / umount_all: 卸载文件系统。verity_update_state: 更新 dm-verity 状态。wait <path> [ <timeout> ]: 等待文件出现。wait_for_prop <name> <value>: 等待属性达到指定值。write <path> <content>: 写入文件内容。
4.5 导入 (Imports)
将多个分散的 .rc 配置文件组合成一个完整的启动逻辑。
核心功能
- 扩展配置:主配置文件 init.rc 非常庞大,通过导入功能,可以将硬件驱动、特定服务或厂商定制的逻辑拆分到独立的文件中。
- 条件加载:结合属性扩展语法,可以实现根据硬件型号动态加载配置。
规则:导入并不是一个普通的"命令",它是一个特殊的段(Section)。
- 解析时机:当 init 解析到 import 语句时,它不会立刻停下来去跑那个文件里的命令,而是先完成当前文件的解析,并把要导入的文件路径记录在一个待解析列表中。
- 递归处理:如果被导入的文件里还有 import 语句,init 会继续跟进去,直到所有相关文件都被读取并加载到内存中。
顺序:直接决定了动作执行的先后:
- 最优先:解析 /system/etc/init/hw/init.rc 及其内部直接 import 的文件。
- 系统级:按字母顺序解析 /system/etc/init/ 目录下的所有文件。
- 厂商/扩展级:依次解析 /system_ext/etc/init/、/vendor/etc/init/、/odm/etc/init/、/product/etc/init/。
4.6 属性 (Properties)
属性 (Properties) 是一种全局的状态管理机制。你可以把它想象成一个系统级别的"全局变量字典",所有的进程都可以读取,特定的进程可以修改。 属性是 键值对 (Key-Value Pairs) 。
- 键 (Name) :通常采用小写字母和点分隔符,例如
ro.boot.hardware或init.svc.adbd。 - 值 (Value) :字符串形式。
- 语法引用 :在
.rc文件中,使用${property.name}来获取属性的值。
作用
- 作为触发器 (Property Triggers) 这是 .rc 文件最核心的用法。当某个属性被设置为特定值时,执行对应的 Actions。
- 精确匹配:on property:ro.debuggable=1(当调试模式开启时执行)
- 状态变化:on property:vendor.display.ready=*(只要这个属性发生了变化,无论变成什么值,都执行)。
- 组合逻辑:on boot && property:a=b(只有在 boot 事件发生且属性 a 等于 b 时才执行)。
- 动态路径或参数拼接: 在解析 .rc 文件时,init 会将 ${} 替换为实际值。
- 服务控制:Init 会响应以 ctl. 开头的特殊属性。
状态
- init.svc.: 服务状态。
- dev.mnt.dev.<mount_point> 等: 挂载点相关的块设备名称。
4.7 引导时间
系统从按下电源键开始,到各个关键服务完成启动并进入可操作状态所经过的时间。
记录方式:将关键节点的启动时刻记录在**只读属性(Read-only Properties)**中。 这些属性的特点是:
- 格式:ro.boottime.<名称>
- 单位:纳秒(ns),但通常在通过 getprop 查看时会转换为人类可读的数值。
核心属性
- 阶段性时间
- ro.boottime.init:init 进程第一阶段开始的总时间
- ro.boottime.init.first_stage:完成第一阶段(挂载基础文件系统等)耗时。
- ro.boottime.init.selinux_setup:编译和加载 SELinux 策略所用的时间。
- 服务启动时间:ro.boottime.:记录某个特定服务(如 surfaceflinger 或 zygote)启动时的系统时间点。
5 ueventd
5.1 简述
ueventd 其实是 init 二进制文件的一个"分身"。在 Android 启动时,init 进程会通过第一个动作 on early-init 启动 ueventd。共用一套解析逻辑,ueventd 专注于硬件节点的管理。
Ueventd 有一个通用定制参数,即 ueventd 套接字的 rcvbuf_size 大小。通过 uevent_socket_rcvbuf_size 参数进行配置:uevent_socket_rcvbuf_size
eventd 监听内核 uevent 套接字,并根据收到的 add/remove uevents 在 /dev 中创建或删除节点。它默认使用 0600 模式以及 root 用户/组。它始终使用当前加载的 SEPolicy 中的 SELabel 来创建节点。对于节点路径,它有三种默认行为:
- 块设备 创建为
/dev/block/<basename uevent DEVPATH>。同时会为该节点创建软链接,路径包括/dev/block/<type>/<parent device>/<basename uevent DEVPATH>,/dev/block/<type>/<parent device>/by-name/<uevent PARTNAME>。如果设备是启动设备,还会创建/dev/block/by-name/<uevent PARTNAME>。 - USB 设备 :如果 uevent 指定了
DEVNAME,则创建为/dev/<uevent DEVNAME>;否则创建为/dev/bus/usb/<bus_id>/<device_id>。 - 所有其他设备 创建为
/dev/<basename uevent DEVPATH>。
5.2 核心职责
Android 系统中负责管理设备节点、设置权限和加载固件的关键守护进程。
- 管理 /dev 目录:监听内核的 uevent 消息,动态创建或删除设备节点。
- 设置权限:根据配置文件为 /dev 中的设备节点和 /sys 中的属性文件设置 mode(权限模式)、uid(所有者)和 gid(所属组)。
- 固件加载:响应内核的固件请求,从指定目录查找并加载固件到内核。
5.3 配置与语法
- 导入配置:通过 import 指令引入其他配置文件或目录下的配置。
- 设备权限配置:格式为 devname mode uid gid [options]。例如 /dev/null 0666 root root。
- Sysfs 属性配置:格式为 nodename attr mode uid gid [options]。例如 /sys/devices/system/cpu/cpu* cpufreq/scaling_max_freq 0664 system system。
- 子系统 (Subsystem) 自定义:可以改变特定子系统的设备命名规则(uevent_devname 或 uevent_devpath)和存放目录(dirname)。
5.3 关键机制
- 冷启动 (Coldboot):为了处理在 ueventd 启动前已存在的设备,它会遍历 /sys 并强制内核重新触发(regenerate)所有设备的 uevent。为了提速,这一过程支持多进程并行处理。
- 固件搜索路径:默认搜索路径包括 /etc/firmware/、/odm/firmware/、/vendor/firmware/ 和 /firmware/image/ 等。此外,也可以配置 external_firmware_handler 调用外部程序处理复杂的固件加载逻辑。
- 路径匹配规则:支持通配符 * 匹配。如果 * 在末尾或设置了 no_fnm_pathname,使用简单的 fnmatch;否则使用 FNM_PATHNAME 规则(不跨越 /)。
6 引导图 (Bootcharting)
是一个系统启动性能分析工具。通过在系统启动过程中,实时采样 CPU 使用率、磁盘 I/O、进程状态等数据,最后生成一张包含各种图表的"性能全景图"。
控制命令:bootchart [start|stop]。默认情况下命令是存在的,但是只有当/data/bootchart/enabled 文件存在时,init 才会真正开始收集数据。
使用步骤
-
开启采样:在设备上创建标记文件并重启
bashadb shell touch /data/bootchart/enabled adb reboot -
数据收集:系统启动后,init 会将采样数据记录在 /data/bootchart/ 目录下。
-
导出数据:使用 Android 源码中的脚本(如 system/core/init/grab-bootchart.sh)将数据拉取到电脑上。
-
生成图表:使用 pybootchartgui 工具将原始数据处理成一张长图(通常是 PNG 或 SVG 格式)。
7 调试
发现错误、定位原因、解决问题;调试通常包含以下动作:
- 观察状态:通过打印日志(Logcat 或 dmesg)查看服务是否启动、命令是否执行。
- 断点挂起:让程序在某一行代码停住。文档中提到的 sigstop 就是干这个的------在服务运行前给它一个"定身法",让你能用调试器(如 GDB)进去看它的内存和寄存器。
- 追踪流程:看系统执行的顺序。比如 on property:a=b 到底触发了没有?触发顺序对不对?
- 环境模拟:手动修改属性(setprop)或手动启动服务(ctl.start),看系统在特定条件下的反应。