Android 初始化语言入门

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 执行的内置顺序如下:

  1. early-init:配置 cgroup 后,ueventd 冷启动完成前。
  2. init:冷启动完成后。
  3. charger:如果 ro.bootmode == "charger" 则触发。
  4. 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 会继续跟进去,直到所有相关文件都被读取并加载到内存中。

顺序:直接决定了动作执行的先后:

  1. 最优先:解析 /system/etc/init/hw/init.rc 及其内部直接 import 的文件。
  2. 系统级:按字母顺序解析 /system/etc/init/ 目录下的所有文件。
  3. 厂商/扩展级:依次解析 /system_ext/etc/init/、/vendor/etc/init/、/odm/etc/init/、/product/etc/init/。

4.6 属性 (Properties)

属性 (Properties) 是一种全局的状态管理机制。你可以把它想象成一个系统级别的"全局变量字典",所有的进程都可以读取,特定的进程可以修改。 属性是 键值对 (Key-Value Pairs)

  • 键 (Name) :通常采用小写字母和点分隔符,例如 ro.boot.hardwareinit.svc.adbd
  • 值 (Value) :字符串形式。
  • 语法引用 :在 .rc 文件中,使用 ${property.name} 来获取属性的值。

作用

  1. 作为触发器 (Property Triggers) 这是 .rc 文件最核心的用法。当某个属性被设置为特定值时,执行对应的 Actions。
    • 精确匹配:on property:ro.debuggable=1(当调试模式开启时执行)
    • 状态变化:on property:vendor.display.ready=*(只要这个属性发生了变化,无论变成什么值,都执行)。
    • 组合逻辑:on boot && property:a=b(只有在 boot 事件发生且属性 a 等于 b 时才执行)。
  2. 动态路径或参数拼接: 在解析 .rc 文件时,init 会将 ${} 替换为实际值。
  3. 服务控制: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 来创建节点。对于节点路径,它有三种默认行为:

  1. 块设备 创建为 /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>
  2. USB 设备 :如果 uevent 指定了 DEVNAME,则创建为 /dev/<uevent DEVNAME>;否则创建为 /dev/bus/usb/<bus_id>/<device_id>
  3. 所有其他设备 创建为 /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 才会真正开始收集数据。

使用步骤

  1. 开启采样:在设备上创建标记文件并重启

    bash 复制代码
    adb shell touch /data/bootchart/enabled
    adb reboot
  2. 数据收集:系统启动后,init 会将采样数据记录在 /data/bootchart/ 目录下。

  3. 导出数据:使用 Android 源码中的脚本(如 system/core/init/grab-bootchart.sh)将数据拉取到电脑上。

  4. 生成图表:使用 pybootchartgui 工具将原始数据处理成一张长图(通常是 PNG 或 SVG 格式)。

7 调试

发现错误、定位原因、解决问题;调试通常包含以下动作:

  • 观察状态:通过打印日志(Logcat 或 dmesg)查看服务是否启动、命令是否执行。
  • 断点挂起:让程序在某一行代码停住。文档中提到的 sigstop 就是干这个的------在服务运行前给它一个"定身法",让你能用调试器(如 GDB)进去看它的内存和寄存器。
  • 追踪流程:看系统执行的顺序。比如 on property:a=b 到底触发了没有?触发顺序对不对?
  • 环境模拟:手动修改属性(setprop)或手动启动服务(ctl.start),看系统在特定条件下的反应。
相关推荐
yangtuoni2 小时前
vscode调试C++ python相关配置
c++·vscode·python
思麟呀2 小时前
在Select的基础上学习poll
linux·网络·学习·tcp/ip
wuyoula2 小时前
尹之盾企业版网络验证
服务器·开发语言·javascript·c++·人工智能·ui·c#
喜欢吃燃面2 小时前
Linux 信号保存机制深度解析:从内核数据结构到进程状态管理
linux·运维·数据结构·学习
Carson带你学Android2 小时前
谁才是地表最强 Android Agent 大模型?Google官方测评来了!
android·openai
hi_ro_a2 小时前
C++ 手撕 STL 底层:红黑树封装 mymap/myset
数据结构·c++·算法
小卓(friendhan2005)2 小时前
基于Qt的音乐播放器项目
数据库·c++·qt
云边有个稻草人2 小时前
【Linux系统】第十节—【进程概念】环境变量 | 详解,包会!
linux·环境变量·命令行参数·环境变量的特性·获取linux环境变量的方法·环境变量path·通过代码获取linux环境变量
tankeven2 小时前
贪心算法(Greedy Algorithm)详解:从理论到C++实践
c++·算法