Android10 Framework—Init进程-12.rc文件格式

237 阅读9分钟

十年鹅厂程序员,专注大前端、AI、个人成长

Android系列文章目录

rc文件说明

早期的rc文件的内容都集中在/init.rc中, 大部分的service定义也是集中在这个文件中, 同时有可能会出现service对应的可执行程序没有编译进系统, 导致service执行失败的情形, 现在各种rc文件是分散在不同分区中进行模块化,并且service对应代码和rc文件是一起编译的, 这样保证模块化的完整性。

  • /init.rc:最主要和最早被加载的rc文件, 一般都是做系统最初初始化的。
  • /init.${ro.hardware}.rc和/vendor/etc/init/hw/init.${ro.hardware}.rc:Soc对应的定制的初始化命令和服务。
  • /system/etc/init/:启动系统核心的服务,比如 SurfaceFlinger, MediaService, and logcatd。
  • /vendor/etc/init/:SOC厂商定制开机自启动的serviced对应的rc脚本,比如gnss,camera,sensor,drm等HAL相关服务
  • /odm/etc/init/:外设设备厂商定制自启动的serviced对应的rc脚本, 如运动传感器或其他外围设备所需的命令和服务。

rc文件内容说明

Init.rc 中的语法都是 Android 自定义的, system/core/init/README.md 有具体语法说明 ,整个 init.rc 其实有以 下几个部分组成:

关键字描述
Imports导入其他 rc 文件
Actions命令的集合, 形式为 on trigger, 其中 trigger 有两种, event 和 proptery。
Commands隶属于 Action 中的命令
Services后台服务, 一般都是 C/C++的守护进程,或者是 shell 脚本
Options隶属于 Service 中选项, 比如守护进程的用户 id, 组 id, 类别 , 是否执行一次, 创建额外套接字等。
其它规则:
  • #号作为注释。
  • 一行为单位, 以空格作为分隔符, 行尾可以用反斜杠''表示连接下一行。
  • 字符串中可以使用双引号(如“ ”)来表示空格。
  • ${property.name} 可以展开属性。
  • 以 import, on, service 开头的, 都表示一个 section(段落), 意味着一个关键词出现之后到出现第二个关键词 之前, 关键词后面的内容都属于一个 section。
  • Service 的关键词在所有的 rc 文件中都必须是唯一的, 如果出现第二个相同的 service 名字, 将会忽略和显 示日志。

init.rc部分内容示例:

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

# Cgroups are mounted right before early-init using list from /etc/cgroups.json

on early-init
    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0
    # Set the security context of /adb_keys if present. 
    restorecon /adb_keys

service ueventd /system/bin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

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
    # Give reads to anyone for the window trace folder on debug builds. 
    chmod 0775 /data/misc/wmtrace
    start console

Action

Action 其实就是一组命令的集合, 它有一个 trigger 触发器,当系统中出现与动作的触发器匹配的事件,这 个 Action 就会被加入到执行队列的尾部, 等到这个 Action 执行时, Action 中包含的所有命令将会按照先后顺序执行, 格式:

on <trigger> [&& <trigger>]*
    <command>
    <command>
    <command>
  • trigger 作为触发器,trigger 有两种, event 和 proptery。
  • command: 类似 shell 命令,但是并不是 shell 命令, 因为这些命令是在 init 进程中单独实现的, 而命令行我们执行的 shell 命令, 是在 toolbox 中实现的, 并且 rc 脚本中的 command 的数量有限。

Action 的示例:

on early-init
    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0
    # Set the security context of /adb_keys if present. 
    restorecon /adb_keys

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
    # Give reads to anyone for the window trace folder on debug builds. 
    chmod 0775 /data/misc/wmtrace
    start console

触发器在哪里触发呢: 一般在 init 的代码中,如:

system/core/init/init.cpp

am.QueueEventTrigger("early-init");
am.QueueEventTrigger("late-init");

或者在 rc 文件中, 如:

on late-init
    trigger early-fs
    trigger fs
on post-fs
    exec - system system -- /system/bin/vdc checkpoint markBootAttempt

常见的trigger:

on early-init #早期初始化
on boot #系统启动触发
on init #在初始化时触发
on late-init #在初始化晚期阶段触发
on charger #当充电时触发
on property:<key>=<value> #当属性值满足条件时触发
on post-fs #挂载文件系统
on post-fs-data #挂载 data

常见的 command: 大部分的命令都在 system/core/init/builtins.cpp 代码中实现:

// Builtin-function-map start
const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const Map builtin_functions = {
        {"bootchart",               {1,     1,    {false,  do_bootchart}}},
        {"chmod",                   {2,     2,    {true,   do_chmod}}},
        {"chown",                   {2,     3,    {true,   do_chown}}},
        {"class_reset",             {1,     1,    {false,  do_class_reset}}},
        {"class_reset_post_data",   {1,     1,    {false,  do_class_reset_post_data}}},
        {"class_restart",           {1,     1,    {false,  do_class_restart}}},
        {"class_start",             {1,     1,    {false,  do_class_start}}},
        {"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},
        {"class_stop",              {1,     1,    {false,  do_class_stop}}},
        {"copy",                    {2,     2,    {true,   do_copy}}},
        {"domainname",              {1,     1,    {true,   do_domainname}}},
        {"enable",                  {1,     1,    {false,  do_enable}}},
        {"exec",                    {1,     kMax, {false,  do_exec}}},
        {"exec_background",         {1,     kMax, {false,  do_exec_background}}},
        {"exec_start",              {1,     1,    {false,  do_exec_start}}},
        {"export",                  {2,     2,    {false,  do_export}}},
        {"hostname",                {1,     1,    {true,   do_hostname}}},
        {"ifup",                    {1,     1,    {true,   do_ifup}}},
        {"init_user0",              {0,     0,    {false,  do_init_user0}}},
        {"insmod",                  {1,     kMax, {true,   do_insmod}}},
        {"installkey",              {1,     1,    {false,  do_installkey}}},
        {"interface_restart",       {1,     1,    {false,  do_interface_restart}}},
        {"interface_start",         {1,     1,    {false,  do_interface_start}}},
        {"interface_stop",          {1,     1,    {false,  do_interface_stop}}},
        {"load_persist_props",      {0,     0,    {false,  do_load_persist_props}}},
        {"load_system_props",       {0,     0,    {false,  do_load_system_props}}},
        {"loglevel",                {1,     1,    {false,  do_loglevel}}},
        {"mark_post_data",          {0,     0,    {false,  do_mark_post_data}}},
        {"mkdir",                   {1,     4,    {true,   do_mkdir}}},
        // TODO: Do mount operations in vendor_init.
        // mount_all is currently too complex to run in vendor_init as it queues action triggers,
        // imports rc scripts, etc.  It should be simplified and run in vendor_init context.
        // mount and umount are run in the same context as mount_all for symmetry.
        {"mount_all",               {1,     kMax, {false,  do_mount_all}}},
        {"mount",                   {3,     kMax, {false,  do_mount}}},
        {"parse_apex_configs",      {0,     0,    {false,  do_parse_apex_configs}}},
        {"umount",                  {1,     1,    {false,  do_umount}}},
        {"umount_all",              {1,     1,    {false,  do_umount_all}}},
        {"readahead",               {1,     2,    {true,   do_readahead}}},
        {"restart",                 {1,     1,    {false,  do_restart}}},
        {"restorecon",              {1,     kMax, {true,   do_restorecon}}},
        {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},
        {"rm",                      {1,     1,    {true,   do_rm}}},
        {"rmdir",                   {1,     1,    {true,   do_rmdir}}},
        {"setprop",                 {2,     2,    {true,   do_setprop}}},
        {"setrlimit",               {3,     3,    {false,  do_setrlimit}}},
        {"start",                   {1,     1,    {false,  do_start}}},
        {"stop",                    {1,     1,    {false,  do_stop}}},
        {"swapon_all",              {1,     1,    {false,  do_swapon_all}}},
        {"enter_default_mount_ns",  {0,     0,    {false,  do_enter_default_mount_ns}}},
        {"symlink",                 {2,     2,    {true,   do_symlink}}},
        {"sysclktz",                {1,     1,    {false,  do_sysclktz}}},
        {"trigger",                 {1,     1,    {false,  do_trigger}}},
        {"verity_load_state",       {0,     0,    {false,  do_verity_load_state}}},
        {"verity_update_state",     {0,     0,    {false,  do_verity_update_state}}},
        {"wait",                    {1,     2,    {true,   do_wait}}},
        {"wait_for_prop",           {2,     2,    {false,  do_wait_for_prop}}},
        {"write",                   {2,     2,    {true,   do_write}}},
    };
    // clang-format on
    return builtin_functions;
}

常见命令有以下这些:

exec <path> [ <argument> ]*:运行指定路径下的程序,并传递参数
export <name> <value>:设置全局环境参数。此参数被设置后对全部进程都有效
ifup <interface>:使指定的网络接口"上线",相当激活指定的网络接口
import <filename>:导入一个额外的 rc 配置文件
hostname <name>:设置主机名
chdir <directory>:改变工作文件夹
chmod <octal-mode> <path>:设置指定文件的读取权限
chown <owner> <group> <path>:设置文件所有者和文件关联组
chroot <directory>:设置根文件夹
class_start <serviceclass>:启动指定类属的全部服务,假设服务已经启动,则不再反复启动
class_stop <serviceclass>:停止指定类属的全部服务
domainname <name>:设置域名
insmod <path>:安装模块到指定路径
mkdir <path> [mode] [owner] [group]:用指定参数创建一个文件夹
mount <type> <device> <dir> [ <mountoption> ]*:类似于linux的mount指令
setprop <name> <value>:设置属性及相应的值
setrlimit <resource> <cur> <max>:设置资源的rlimit
start <service>:假设指定的服务未启动,则启动它
stop <service>:假设指定的服务当前正在执行。则停止它
symlink <target> <path>:创建一个符号链接
sysclktz <mins_west_of_gmt>:设置系统基准时间
trigger <event>:触发另一个时间
write <path> <string> [ <string> ]*:往指定的文件写字符串

Service

Service 表示一个开机自启动服务, 服务可以常驻型(不死型), 也可以是只执行一次, 可以选择开机自启动或 者在特定的时候启动,语法:

service <name> <pathname> [ <argument> ]*
       <option>
       <option>
       ...
  • service的名字,要保证唯一
  • service对应的可执行程序路径
  • 启动service所带的参数
  • 启动service时约束和附加选项,它影响着服务以什么方式和什么时候启动,

服务的选项主要有:

  • class <name> [ <name>* ]:为服务指定 class 名字。 同一个 class 名字的服务会被一起启动或退出, 默认值是 default
  • console [<console>]:这个选项表明服务需要一个控制台。 第二个参数 console 的意思是可以设置你想要的控制台类型,默认控制台是 /dev/console, /dev 这个前缀通常是被省略的, 比如你要设置控制台 /dev/tty0, 那么只需要设置为console tty0 即可。
  • critical:表示服务是严格模式。 如果这个服务在4分钟内或者启动完成前退出超过4次,那么设备将重启进入 bootloader 模式
  • disabled:这个服务不会随着 class 一起启动。只能通过服务名来显式启动。比如 foobar 服务的 class 是 core, 且是 disabled 的,当执行 class_start core 时,foobar 服务是不会被启动的。 foobar 服务只能通过 start foobar 这种方法来启动。
  • file <path> <type> 根据文件路径 path 来打开文件,然后把文件描述符 fd 传递给服务进程。type 表示打工文件的方式,只有三种取值 r, w, rw。对于 native 程序来说,可以通过 libcutils 库提供的 android_get_control_file() 函数来获取传递过来的文件描述符。举个例子, logd.rc 部分内容如下:
service logd /system/bin/logd
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram+passcred 0222 logd logd
    file /proc/kmsg r
    file /dev/kmsg w
    user logd

其中通过 file /proc/kmsg r 以只读方式打开了设备文件 /proc/kmsg, 然后在代码中这么获取打开的文件, 见 sytem/core/logd/main.cpp main 函数:

static const char dev_kmsg[] = "/dev/kmsg";
fdDmesg = android_get_control_file(dev_kmsg);
if (fdDmesg < 0) {
    fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
}
  • group <groupname> [ <groupname>* ]:在启动 Service 前,将 Service 的用户组改为第一个groupname, 第一个 groupname 是必须有的, 第二个 groupname 可以不设置,用于追加组(通setgroups)。目前默认的用户组是 root 组。
  • oneshot:当服务退出的时候,不自动重启。适用于那些开机只运行一次的服务。
  • onrestart:在服务重启的时候执行一个命令
  • seclabel <seclabel>:在启动 Service 前设置指定的 seclabel,默认使用init的安全策略。 主要用于在 rootfs 上启动的 service,比如 ueventd, adbd。 在系统分区上运行的 service 有自己的 SELinux安全策略。
  • setenv <name> <value>:设置进程的环境变量
  • socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ] 创建一个 unix domain socket, 路径为 /dev/socket/name , 并将 fd 返回给 Service。 type 只能是 dgram, stream or seqpacket。user 和 group 默认值是 0。 seclabel 是这个 socket 的 SELinux security context, 它的默认值是 service 的 security context 或者基于其可执行文件的 security context。
  • user 在启动 Service 前修改进程的所属用户, 默认启动时 user 为 root

常见的option: 大部分都在system/core/init/service.cpp代码中实现:

const Service::OptionParserMap::Map& Service::OptionParserMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const Map option_parsers = {
        {"capabilities",
                        {0,     kMax, &Service::ParseCapabilities}},
        {"class",       {1,     kMax, &Service::ParseClass}},
        {"console",     {0,     1,    &Service::ParseConsole}},
        {"critical",    {0,     0,    &Service::ParseCritical}},
        {"disabled",    {0,     0,    &Service::ParseDisabled}},
        {"enter_namespace",
                        {2,     2,    &Service::ParseEnterNamespace}},
        {"file",        {2,     2,    &Service::ParseFile}},
        {"group",       {1,     NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}},
        {"interface",   {2,     2,    &Service::ParseInterface}},
        {"ioprio",      {2,     2,    &Service::ParseIoprio}},
        {"keycodes",    {1,     kMax, &Service::ParseKeycodes}},
        {"memcg.limit_in_bytes",
                        {1,     1,    &Service::ParseMemcgLimitInBytes}},
        {"memcg.limit_percent",
                        {1,     1,    &Service::ParseMemcgLimitPercent}},
        {"memcg.limit_property",
                        {1,     1,    &Service::ParseMemcgLimitProperty}},
        {"memcg.soft_limit_in_bytes",
                        {1,     1,    &Service::ParseMemcgSoftLimitInBytes}},
        {"memcg.swappiness",
                        {1,     1,    &Service::ParseMemcgSwappiness}},
        {"namespace",   {1,     2,    &Service::ParseNamespace}},
        {"oneshot",     {0,     0,    &Service::ParseOneshot}},
        {"onrestart",   {1,     kMax, &Service::ParseOnrestart}},
        {"oom_score_adjust",
                        {1,     1,    &Service::ParseOomScoreAdjust}},
        {"override",    {0,     0,    &Service::ParseOverride}},
        {"priority",    {1,     1,    &Service::ParsePriority}},
        {"restart_period",
                        {1,     1,    &Service::ParseRestartPeriod}},
        {"rlimit",      {3,     3,    &Service::ParseProcessRlimit}},
        {"seclabel",    {1,     1,    &Service::ParseSeclabel}},
        {"setenv",      {2,     2,    &Service::ParseSetenv}},
        {"shutdown",    {1,     1,    &Service::ParseShutdown}},
        {"sigstop",     {0,     0,    &Service::ParseSigstop}},
        {"socket",      {3,     6,    &Service::ParseSocket}},
        {"timeout_period",
                        {1,     1,    &Service::ParseTimeoutPeriod}},
        {"updatable",   {0,     0,    &Service::ParseUpdatable}},
        {"user",        {1,     1,    &Service::ParseUser}},
        {"writepid",    {1,     kMax, &Service::ParseWritepid}},
    };
    // clang-format on
    return option_parsers;
}