配套系列教学视频链接:
说明
系统:Android10.0
设备: FireFly RK3399 (ROC-RK3399-PC-PLUS)
前言
这个章节我们详细的来学习一下init.rc中各种语法, 深入了解init.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名字, 将会忽略和显示日志。
- Action可以在多个rc文件出现,比如on boot可以出现在init.rc , 也可以出现在/vendor/etc/init/hw/init.rk30board.rc中。
二, 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
三, 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脚本, 如运动传感器或其他外围设备所需的命令和服务。 |
四, 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;
}
特殊的几个命令:
bootchart | 启动或终止bootcharting, bootcharting是用于分析系统启动时间的。 格式: bootchart [start|stop] |
class_reset/class_restart class_start/class_stop | 复位/重启/启动/停止某一类的所有Service,语法: class_start <serviceclass> 例子: class_reset main |
copy | 拷贝, 和shell命令cp一样 例子: copy /proc/cmdline /dev/urandom |
domainname | 设置域名 例子: domainname localdomain |
enable | 将一个Service的标志从disable改成enable 语法: enable <servicename> 例子: on property:ro.boot.myfancyhardware=1 enable my_fancy_service_for_my_fancy_hardware |
exec | 启动一个只执行一次的临时Service, 语法:exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ] exec - system system -- /system/bin/vdc checkpoint markBootAttempt |
exec_start | 启动一个特定的Service, 语法 exec_start service_name 例子: exec_start apexd-bootstrap |
ifup | 启动网络接口, 语法: ifup <interface> |
insmod | 加载ko驱动,格式:insmod [-f] <path> [<options>] 例子: insmod /vendor/lib/modules/8822bs.ko |
setrlimit | 设置系统资源使用限制,linux下每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。 语法: setrlinit <resource> <cur> <max> <resource>有: cpu, fsize, stack, nice等 <cur>表示软限制,<max>表示硬限制, 值如果为unlimited表示最大值 |
start/stop/restart | 启动/停止/重启指定的服务 格式: start <servicename> |
symlink | 符号链接, 类似shell命令中的ln -s 格式: symlink <target> <sym_link> |
sysclktz | 设置当前系统时钟值, sysclktz 0就表示将系统时钟设置成GMT 0 |
trigger | 触发特定的action的触发器 例子: trigger early-fs |
wait_for_prop | 等待特定的属性变成特定的值 例子: wait_for_prop apexd.status ready |
wait | 在指定时间内等待特定文件的出现 格式: wait <file_path> [timeout] |
五, Service服务
Service表示一个开机自启动服务, 服务可以常驻型(不死型), 也可以是只执行一次, 可以选择开机自启动或者在特定的时候启动,语法:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
<name> service的名字,要保证唯一
<pathname> service对应的可执行程序路径
<argument> 启动service所带的参数
<option> 启动service时约束和附加选项,它影响着服务以什么方式和什么时候启动,
具体参考system/core/init/README.md
常见的option如下所示:
capabilities [ <capability>\* ] | 为启动的服务设置能力, 在执行特权操作时,如果进程的有效身份不是 root,就去检查是否具有该特权操作所对应的 capabilites,并以此决定是否可以进行该特权操作。比如要向进程发送信号(kill()),就得具有 capability CAP_KILL;如果设置系统时间,就得具有 capability CAP_SYS_TIME。 |
class <name> [ <name>\* ] | 为服务设置类别, 非常重要, 设置之后,系统就可以批量启动或停止一类服务。 |
console [<console>] | |
critical | 表示这是一个对设备至关重要的一个服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式,, 比如zygote服务就是这种。 |
disabled | 此服务不会自动启动,而是需要通过显式调用服务名来启动 |
file <path> <type> | 打开一个文件,并将文件的fd传递给启动的服务, 服务进程中可以通过android_get_control_file()拿到这个fd, <type>可以是"r", "w" or "rw" |
setenv<name><value> | 设置环境变量<name>为某个值<value> |
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ] | 创建一个名为/dev/socket/<name>的unix domain socket,然后将它的fd值传给启动它的进程,有效的<type>值包括dgram,stream和seqacket,而user和group的默认值是0,也可以设置进程对应的selinux安全上下文。 |
user<username> | 在启动服务前将用户组切换为<username>,默认情况下用户都是root |
group<groupname>[<groupname>]* | 在启动服务前将用户组切换为<groupname>,可以是多个用户组 |
oneshot | 只启动一次,当这个服务推出后,不会重启 |
onrestart | 当此服务重启时,需要执行某些命令 |
writepid <file> [ <file>\* ] | 当服务进行fork的时候, 将自进程的pid号写入到指定的文件中,用于cgroup/cpuset |
例子:
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
group logd system package_info readproc
capabilities SYSLOG AUDIT_CONTROL SETGID #允许使用 syslog() 系统调用
writepid /dev/cpuset/system-background/tasks
六,总结
init.rc是我们经常修改和开发的文件, 掌握它对我们开发定制有很大的帮助,也是做ROM开发必须要掌握的。