rc 文件是 AOSP 的重要组成部分,在学习 AOSP 时也要了解学习下 rc 文件
Android Init
Android Init 语言包含五大类语句
- 动作 (Actions)
- 命令 (Commands)
- 服务 (Services)
- 选项 (Options)
- 导入 (Imports)
所有这些语句都是以行为单位 ,由空格分隔 组成。可以使用反斜杠插入空格,或使用双引号防止空格将文本拆分。当反斜杠位于行尾时,可用于续行。以 # 开头的行是注释。
动作和服务会隐式声明一个新的段落。所有的命令或选项都归属于最近声明的段落。第一个段落之前出现的命令或选项将被忽略。
服务具有唯一的名称。如果定义了第二个与现有服务同名的服务,它将被忽略并记录一条错误信息。
可以使用
${property.name}语法使用系统属性。例如import /init.recovery.${ro.hardware}.rc。
Init.rc 文件
Init 语言使用以 .rc 为扩展名的纯文本文件。系统中通常存在多个此类文件,分布在不同的位置。
核心路径与加载机制
/init.rc:主要的配置文件,由 Init 可执行文件在启动初期首先加载,负责系统的初始设置First Stage Mount:支持First Stage Mount的设备,在加载完主/init.rc后,会立即加载/system、/vendor、/odm下etc/init/目录内的所有文件。- 旧版设备:不具备
First Stage Mount机制的设备逻辑:/init.rc导入/init.${ro.hardware}.rc(厂商提供的主配置文件)- 在执行
mount_all命令期间,Init 会加载/{system,vendor,odm}/etc/init/目录下的所有文件。这些目录用于存放文件系统挂载后所需的动作和服务
目录设计意图
为了实现模块化,不同分区的目录承载不同的职责:
/system/etc/init/:用于核心系统项目,如SurfaceFlinger、logcatd或bootanim。/vendor/etc/init/:用于 SoC 厂商项目,如核心 SoC 功能所需的动作或守护进程。/odm/etc/init/:用于设备制造商 (ODM) 项目,如运动传感器或其他外设所需的动作或守护进程。
所有位于系统、厂商或 ODM 分区中的服务,其条目都放置在对应分区的 /etc/init/ 目录下的 .rc 文件中。这种做法优于以往纯 init.rc。它确保 Init 只读取那些二进制文件存在于文件系统中的服务条目,同时能有效避免多服务合并时的代码冲突。
开发者可以使用
LOCAL_INIT_RC宏指定对应rc文件,以便编译时自动放置。
mount_all 的高级选项
在 mount_all 命令中,可以在路径后设置 --early 和 --late 选项:
--early:Init 将跳过带有latemount标志的挂载条目,并跳过触发文件系统加密状态事件。--late:Init 仅挂载带有latemount标志的条目,但跳过导入.rc文件。- 默认状态 :若不设置选项,
mount_all将处理指定 fstab 中的所有条目。
动作
动作的形式如下:
ini
on <触发器> [&& <触发器>]*
<命令>
<命令>
<命令>
动作被添加到队列并执行的顺序,取决于包含它们的文件的解析顺序 ,在单个文件内部则按先后顺序执行。
init.rc 中的示例
ini
on early-init
# ......
# do-something
on init
# .......
# do-something
# Healthd can trigger a full boot from charger mode by signaling this
# property when the power button is held.
on property:sys.boot_from_charger_mode=1
trigger late-init
# .......
# do-something
on load_persist_props_action
start logd
start logd-reinit
# .......
# do-something
# Indicate to fw loaders that the relevant mounts are up.
on firmware_mounts_complete
rm /dev/.booting
# Mount filesystems and start core system services.
on late-init
trigger early-fs
# Mount fstab in init.{$device}.rc by mount_all command. Optional parameter
# '--early' can be specified to skip entries with 'latemount'.
# /system and /vendor must be mounted by the end of the fs stage,
# while /data is optional.
trigger fs
trigger post-fs
# Mount fstab in init.{$device}.rc by mount_all with '--late' parameter
# to only mount entries with 'latemount'. This is needed if '--early' is
# specified in the previous mount_all command on the fs stage.
# With /system mounted and properties form /system + /factory available,
# some services can be started.
trigger late-fs
# Now we can mount /data. File encryption requires keymaster to decrypt
# /data, which in turn can only be loaded when system properties are present.
trigger post-fs-data
# Now we can start zygote for devices with file based encryption
trigger zygote-start
# Load persist properties and override properties (if enabled) from /data.
trigger load_persist_props_action
# Remove a file to wake up anything waiting for firmware.
trigger firmware_mounts_complete
trigger early-boot
trigger boot
on post-fs
# Load properties from
# /system/build.prop,
# /odm/build.prop,
# /vendor/build.prop and
# /factory/factory.prop
load_system_props
# start essential services
start logd
start servicemanager
start hwservicemanager
start vndservicemanager
# create the lost+found directories, so as to enforce our permissions
mkdir /cache/lost+found 0770 root root
# ......
# do-something
on late-fs
# ......
# do-something
on post-fs-data
# ......
# do-something
# It is recommended to put unnecessary data/ initialization from post-fs-data
# to start-zygote in device's init.rc to unblock zygote start.
on zygote-start && property:ro.crypto.state=unencrypted
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=unsupported
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
# ......
# do-something
on nonencrypted
class_start main
class_start late_start
on property:sys.init_log_level=*
loglevel ${sys.init_log_level}
on charger
class_start charger
on property:vold.decrypt=trigger_reset_main
class_reset main
on property:vold.decrypt=trigger_load_persist_props
load_persist_props
start logd
start logd-reinit
on property:vold.decrypt=trigger_post_fs_data
trigger post-fs-data
on property:vold.decrypt=trigger_restart_min_framework
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start main
on property:vold.decrypt=trigger_restart_framework
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start main
class_start late_start
on property:vold.decrypt=trigger_shutdown_framework
class_reset late_start
class_reset main
on property:sys.boot_completed=1
bootchart stop
# system server cannot write to /proc/sys files,
# and chown/chmod does not work for /proc/sys/ entries.
# So proxy writes through init.
on property:sys.sysctl.extra_free_kbytes=*
write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
# "tcp_default_init_rwnd" Is too long!
on property:sys.sysctl.tcp_def_init_rwnd=*
write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}
on property:security.perf_harden=0
write /proc/sys/kernel/perf_event_paranoid 1
on property:security.perf_harden=1
write /proc/sys/kernel/perf_event_paranoid 3
on property:ro.debuggable=1
# Give writes to anyone for the trace folder on debug builds.
# The folder is used to store method traces.
chmod 0773 /data/misc/trace
start console
触发器
触发器是用于匹配特定类型事件的字符串,用于引发"动作 (Action)"的发生。
触发器分为事件触发器 (Event triggers) 和 属性触发器 (Property triggers) 。
- 事件触发器 :由
trigger命令或 Init 可执行文件内部的QueueEventTrigger()函数触发。 - 属性触发器 :当指定的属性更改为特定值或更改为任何新值时触发。其形式分别为
property:<名称>=<值>和property:<名称>=*。
一个动作可以拥有多个属性触发器 ,但只能拥有一个事件触发器。
示例:
on boot && property:a=b
仅在
boot事件发生且 属性a等于b时才执行的动作。
on property:a=b && property:c=d
定义了一个在以下三种情况下都会执行的动作:
- 在初始启动阶段 ,如果此时属性
a=b且c=d。- 当属性
c已经等于d,而属性a切换 到值b的任何时刻。- 当属性
a已经等于b,而属性c切换 到值d的任何时刻。
命令
bootchart [start|stop]
启动或停止 bootchart(性能分析工具)。默认的
init.rc中包含此命令,但仅当/data/bootchart/enabled文件存在时才有效,否则该命令不执行任何操作。
chmod <八进制模式> <路径>
修改文件访问权限。
chown <所有者> <组> <路径>
修改文件的所有者和所属组。
class_start <服务类>
启动指定类别的所有服务(如果它们尚未运行)。
class_stop <服务类>
停止并禁用指定类别的所有服务(如果正在运行)。
class_reset <服务类>
停止指定类别的所有服务,但不禁用,稍后可以通过
class_start重新启动。
class_restart <服务类>
重启指定类别的所有服务。
copy <源路径> <目标路径>
复制文件。类似于
write,但适用于二进制或大数据量。不支持从软链接或全用户可写/组可写的文件复制。如果目标文件不存在,默认创建权限为 0600;如果已存在则会被截断。
domainname <名称>
设置域名。
enable <服务名>
将一个原本设置为
disabled的服务改为启用状态。如果该服务本该运行,则会立即启动。
exec [ <安全上下文> [ <用户> [ <组>* ] ] ] -- <命令> [ <参数>* ]
Fork 并执行带参数的命令。命令写在
--之后,以便区分可选的安全上下文、用户和补充组。在命令执行完成之前,Init 会暂停执行后续命令。参数中支持属性扩展。
exec_start <服务>
启动给定服务,并暂停执行 后续命令直到该服务返回。逻辑类似于
exec,但需要使用已有的服务。
export <名称> <值>
在全局环境变量中设置变量
<名称>的值为<值>。
hostname <名称>
设置主机名。
ifup <接口>
启动指定的网络接口(使其上线)。
insmod [-f] <路径> [<选项>]
安装指定路径的内核模块。
-f表示强制安装,即使内核版本不匹配。
load_all_props
从
/system、/vendor等目录加载属性。
load_persist_props
当
/data分区解密后加载持久化属性(Persistent Properties)。
loglevel <级别>
设置内核日志级别。
mkdir <路径> [模式] [所有者] [组]
创建目录。如果目录已存在,则会更新其模式、所有者和组。默认权限为 755。
mount_all <fstab> [ <路径> ]* [--<选项>]
根据 fstab 文件挂载所有分区,并导入指定路径下的
.rc文件。
mount <类型> <设备> <目录> [ <标志>* ] [<选项>]
尝试将设备挂载到指定目录。标志包括
ro,rw,remount等;选项以逗号分隔,如barrier=1。
restart <服务>
停止并重启一个正在运行的服务。如果服务正在重启中则不操作。
restorecon <路径> [ <路径>* ]
将指定路径的安全上下文恢复到
file_contexts配置中的定义。
restorecon_recursive <路径> [ <路径>* ]
递归地恢复指定目录树的安全上下文。
rm <路径>
删除指定路径的文件(调用
unlink)。
rmdir <路径>
删除指定路径的目录。
setprop <名称> <值>
设置系统属性。
setrlimit <资源> <当前值> <最大值>
设置资源的 rlimit(资源限制)。
start <服务>
启动一个服务(如果它尚未运行)。注意:这是异步的,Init 不会等待服务启动完成就继续执行后续命令。
注意: 这意味着即使你先
start了 A 服务再启动 B 服务,也不能保证 A 的通信通道在 B 尝试访问时已经就绪。
stop <服务>
停止一个正在运行的服务。
swapon_all <fstab>
对给定的 fstab 文件调用
fs_mgr_swapon_all。
symlink <目标> <路径>
在
<路径>处创建一个指向<目标>的符号链接。
sysclktz <格林威治偏移分钟数>
设置系统时区基准。
trigger <事件>
触发一个事件。用于从一个动作中将另一个动作排入执行队列。
umount <路径>
卸载该路径的文件系统。
verity_load_state / verity_update_state
用于加载和更新 dm-verity 状态的内部实现细节。
wait <路径> [ <超时时间> ]
轮询等待某个文件出现。默认超时时间为 5 秒。
wait_for_prop <名称> <value>
等待系统属性达到指定值。如果已满足条件则立即继续。
write <path> <content>
打开文件并写入字符串。如果文件不存在则创建,存在则截断。
服务
服务 是 Init 进程启动的程序,并且在它们退出时由 Init 重启(可选)。服务的定义形式如下:
xml
service <名称> <路径名> [ <参数> ]*
<选项>
<选项>
...
选项
选项是服务的修饰符,它们影响 Init 进程运行服务的方式和时机。
console [<console>]
该服务需要一个控制台。可选的第二个参数用于指定特定的控制台,而非默认值。默认的
/dev/console可以通过设置内核参数androidboot.console来更改。在所有情况下,都应省略开头的/dev/,例如/dev/tty0应写为console tty0。
critical
这是一个设备关键型 (device-critical) 服务。如果在 4 分钟内退出超过 4 次,设备将重启并进入恢复模式 (recovery mode)。
disabled
该服务不会自动启动,必须通过服务名称显式启动。
setenv <name> <value>
在启动的进程中,将环境变量
<name>设置为<value>。
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]
创建一个名为
/dev/socket/<name>的 Unix 域套接字,并将该文件描述符 (fd) 传递给启动的进程。<type>必须是dgram、stream或seqpacket。用户和组默认为 0。seclabel是该套接字的 SELinux 安全上下文,默认使用服务的安全上下文。
file <path> <type>
打开一个文件路径并将文件描述符传递给启动的进程。
<type>必须为r(读)、w(写) 或rw(读写)。
user <username>
在执行该服务前切换用户名为
<username>。目前默认为root(可能应该默认为nobody)。自 Android M 起,即使进程需要 Linux 能力 (capabilities),也应使用此选项。现在可以通过fs_config机制在文件系统层面为特定二进制文件分配能力。自 Android O 起,进程也可以直接在.rc文件中请求能力(见下文capabilities选项)。
group <groupname> [ <groupname>* ]
在执行该服务前切换组名为
<groupname>。第一个组名(必填)之后的其他组名用于设置进程的补充组(通过setgroups())。目前默认为root。
capabilities <capability> [ <capability>* ]
在执行该服务时设置 Linux 能力。
<capability>不应包含CAP_前缀,例如应写为NET_ADMIN而不是CAP_NET_ADMIN。
seclabel <seclabel>
在执行服务前切换到指定的 SELinux 安全上下文。主要用于从 rootfs 运行的服务(如 ueventd, adbd)。系统分区上的服务通常使用基于文件安全上下文定义的策略转换。
oneshot
服务退出时不要重启它。
class <name> [ <name>* ]
为服务指定服务类名称。同属一个类的所有服务可以一起启动或停止。如果未指定,服务默认属于
default类。
animation class
animation(动画)类应包含开机动画和关机动画所需的所有服务。由于这些服务启动极早且运行至关机最后阶段,因此不保证能够访问/data分区。这些服务可以检查/data下的文件,但不应保持文件打开状态。
onrestart
当服务重启时执行一条命令。
writepid <file> [ <file>* ]
当子进程 fork 时,将其 PID 写入指定文件。主要用于 cgroup/cpuset。
priority <priority>
服务的调度优先级。取值范围为 -20 到 19,默认为 0。通过
setpriority()设置。
namespace <pid|mnt>
在 fork 服务时进入新的 PID 或 mount 命名空间。
oom_score_adjust <value>
设置子进程的
/proc/self/oom_score_adj值,范围为 -1000 到 1000。
memcg.swappiness <value>
设置子进程的
memory.swappiness值(仅在挂载了 memcg 时有效)。
memcg.soft_limit_in_bytes <value>
设置子进程的
memory.soft_limit_in_bytes(内存软限制)。
memcg.limit_in_bytes <value>
设置子进程的
memory.limit_in_bytes(内存硬限制)。
shutdown <shutdown_behavior>
设置服务的关机行为。若未指定,服务在关机期间会被
SIGTERM和SIGKILL杀死。标记为critical的服务在关机超时前不会被杀死;如果关机开始时该服务未运行,则会启动它。
导入
可以像其他语言一样,将其他文件内的 rc 文件导入到本文件
import <路径>
解析一个 Init 配置文件,扩展当前配置。如果
<路径>是一个目录,该目录下的每个文件都将被作为配置文件解析。导入操作不是递归的,嵌套的子目录不会被解析。
Init 仅在以下三个时间点导入 .rc 文件:
- 初始启动阶段 :导入
/init.rc或由属性ro.boot.init_rc指定的脚本。 - 第一阶段挂载(First Stage Mount)后 :在导入
/init.rc之后,立即导入/{system,vendor,odm}/etc/init/。 - 执行
mount_all期间 :导入/{system,vendor,odm}/etc/init/或在mount_all命令中指定路径的.rc文件。
由于历史遗留原因和向后兼容性,文件的导入顺序比较复杂,且并非严格保证。
保证命令执行顺序的唯一正确方法是:
- 将其放置在拥有更早执行触发器 的动作中。
- 将其放置在同一个文件 中拥有相同触发器的动作里,并确保它在文件前面。
尽管如此,第一阶段挂载设备的实际执行顺序通常如下:
- 解析
/init.rc,然后递归解析其内部包含的每个import。 - 对
/system/etc/init/的内容按字母顺序排序并依次解析,每个文件解析后都会递归处理其内部的导入。 - 对
/vendor/etc/init重复步骤 2,随后对/odm/etc/init重复步骤 2。
init.rc示例
ini
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
应用
- 可以在启动时挂载我们所需的目录
- 可以在启动时启动自定义的服务
- 可以在启动时设置自定义的系统属性
- 可以在启动时进行chmod
- 可以在启动时调整硬件状态
- 可以在启动时创建软硬链接