【我的AOSP第一课】Android Init 语言与 rc 文件

rc 文件是 AOSP 的重要组成部分,在学习 AOSP 时也要了解学习下 rc 文件

Android Init

Android Init 语言包含五大类语句

  1. 动作 (Actions)
  2. 命令 (Commands)
  3. 服务 (Services)
  4. 选项 (Options)
  5. 导入 (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/odmetc/init/ 目录内的所有文件。
  • 旧版设备:不具备First Stage Mount机制的设备逻辑:
    • /init.rc 导入 /init.${ro.hardware}.rc(厂商提供的主配置文件)
    • 在执行 mount_all 命令期间,Init 会加载 /{system,vendor,odm}/etc/init/ 目录下的所有文件。这些目录用于存放文件系统挂载后所需的动作和服务

目录设计意图

为了实现模块化,不同分区的目录承载不同的职责:

  1. /system/etc/init/ :用于核心系统项目,如 SurfaceFlingerlogcatdbootanim
  2. /vendor/etc/init/ :用于 SoC 厂商项目,如核心 SoC 功能所需的动作或守护进程。
  3. /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

定义了一个在以下三种情况下都会执行的动作:

  1. 初始启动阶段 ,如果此时属性 a=bc=d
  2. 当属性 c 已经等于 d,而属性 a 切换 到值 b 的任何时刻。
  3. 当属性 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> 必须是 dgramstreamseqpacket。用户和组默认为 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>

设置服务的关机行为。若未指定,服务在关机期间会被 SIGTERMSIGKILL 杀死。标记为 critical 的服务在关机超时前不会被杀死;如果关机开始时该服务未运行,则会启动它。

导入

可以像其他语言一样,将其他文件内的 rc 文件导入到本文件

import <路径>

解析一个 Init 配置文件,扩展当前配置。如果 <路径> 是一个目录,该目录下的每个文件都将被作为配置文件解析。导入操作不是递归的,嵌套的子目录不会被解析。

Init 仅在以下三个时间点导入 .rc 文件:

  1. 初始启动阶段 :导入 /init.rc 或由属性 ro.boot.init_rc 指定的脚本。
  2. 第一阶段挂载(First Stage Mount)后 :在导入 /init.rc 之后,立即导入 /{system,vendor,odm}/etc/init/
  3. 执行 mount_all 期间 :导入 /{system,vendor,odm}/etc/init/ 或在 mount_all 命令中指定路径的 .rc 文件。

由于历史遗留原因和向后兼容性,文件的导入顺序比较复杂,且并非严格保证。

保证命令执行顺序的唯一正确方法是:

  1. 将其放置在拥有更早执行触发器动作中。
  2. 将其放置在同一个文件 中拥有相同触发器的动作里,并确保它在文件前面。

尽管如此,第一阶段挂载设备的实际执行顺序通常如下:

  1. 解析 /init.rc,然后递归解析其内部包含的每个 import
  2. /system/etc/init/ 的内容按字母顺序排序并依次解析,每个文件解析后都会递归处理其内部的导入。
  3. /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

应用

  1. 可以在启动时挂载我们所需的目录
  2. 可以在启动时启动自定义的服务
  3. 可以在启动时设置自定义的系统属性
  4. 可以在启动时进行chmod
  5. 可以在启动时调整硬件状态
  6. 可以在启动时创建软硬链接
相关推荐
丐中丐9992 小时前
一个Binder通信中的多线程同步问题
android
诸神黄昏EX2 小时前
Android Qualcomm USB 专题系列【篇二:UsbGadget模式配置】
android
诸神黄昏EX2 小时前
Android Qualcomm USB 专题系列【总篇:USB HAL架构】
android·linux·网络
原神启动12 小时前
Ansible(三)—— 使用Ansible自动化部署LNMP环境
android·自动化·ansible
前端老白3 小时前
webview在微信小程序中,安卓加载失败,IOS正常加载
android·ios·微信小程序·webview
2501_937154933 小时前
适配中兴主流机型 纯净版刷机固件技术优势合集
android·源码·源代码管理·机顶盒
2501_915106323 小时前
用 HBuilder 上架 iOS 应用时如何管理Bundle ID、证书与描述文件
android·ios·小程序·https·uni-app·iphone·webview
TheNextByte13 小时前
如何通过OTG或不使用OTG将文件从Android传到U盘
android
2501_915909063 小时前
资源文件混淆在 iOS 应用安全中的实际价值
android·安全·ios·小程序·uni-app·iphone·webview