说明
系统:Android9.0
前言
init.rc是我们经常修改和开发的文件, 掌握它对我们开发定制有很大的帮助,也是做ROM开发必须要掌握的。
我们详细的来学习一下init.rc中各种语法, 深入了解init.rc 各种细节。
- 熟悉init进程启动过程,并加载init.rc
init 进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动流程中一个关键的步骤,它有很多重要的职责,比如创建Zygote(孵化器)和属性服务等。它的生命周期贯穿整个Linux内核运行的始终。Android中所有其它的进程共同的鼻祖均为init进程。
Android系统启动流程:
如图可知,Kernel启动后,寻找init关键文件,并在用户空间启动init进程,该进程作为用户空间的第一个进程,并调用init进程中的main方法开始启动Zygote进程。

init用户进程到底发生了什么?
Kernel会启动init.cpp的main函数,重要的内容是加载linux的相关属性和加载init.rc文件。让我们狠扒一下到底发生了什么,在下面细节中会发现新的大陆:
- 启动一些安全Linux日志等服务。
- 创建和挂载启动所需的文件目录。
- 初始化和启动Linux属性服务。
- 解析init.rc配置文件,首先开启ServiceManager和MediaServer等关键进程。
- init进程fork启动Zygote服务进程。
Android P(9.0)的init入口函数由原先的init.cpp调整到了main.cpp。接下来就从main函数开始:
system/core/init/main.cpp

这个函数将调用到

- 加载init.rc,并解析
init.rc是一个非常重要的配置文件,它是由Android初始化语言(Android Init Language)编写的脚本,这种语言主要包含5种类型语句:Action、Command、Service、Option和Import。
- 基本语法规则
Init.rc中的语法都是Android自定义的, system/core/init/README.md有具体语法说明 ,整个init.rc 其实有以下几个部分组成:
|
Imports |
导入其他rc文件 |
|
Actions |
Commands |
|
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 ,也可以出现在init.unionman.rc中。
2)init.rc 示例

- Import介绍
早期的rc文件的内容都集中在/init.rc中, 大部分是service定义也是集中在这个文件中, 有可能会出现service对应的可执行程序没有编译进系统, 导致service执行失败, 现在是分散在不同分区中进行模块化,并且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");
常见的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 |
|
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
- 项目中可能遇到的问题:
- 开机默认启动服务了,却仍在开机完成后再启动服务。
示例:
on property:sys.boot_completed=1
start HNFirewall
service HNFirewall /system/bin/HN_Firewall.sh
class main
user root
oneshot
- 给system分区权限
# on early-boot
on boot
# According to cts testcase changes: fbd00778, we remove /proc/interrupts and /proc/stat read permission.
ifup eth0
chmod 755 /system/bin/configserver (#错误的命令)
- 给服务增加运行权限
on boot start init_unionman setprop wifi.direct.interface p2p-dev-wlan0 service init_unionman /system/bin/sh /system/bin/init_unionman.sh user root (使用 root 权限) group root (使用 root 权限) oneshot disabled seclabel u:r:init_unionman:s0
seclabel u:r:init_unionman:s0
这句的具体含义大概是设置init进程的安全上下文,不加这个会提示没有权限:service does not have a SELinux domain defined
添加开机启动服务程序关于Selinux权限问题说明:
当需要添加一个binder服务xxx程序,并且设置成开机自启动时,需要按照如下步骤操作:
第一步,我们可以在init.rc中添加了如下代码行:
service xxxx /system/bin/xxxx
class main
user root
group root
oneshot
seclabel u:r:xxxx:s0 #这句是为加selinux权限添加的,android5.1以后不加则无法启动该服务
编译img后烧到机器,发现服务xxx无法启动,kernel log中有如下提示(例如这里新加的服务程序是abcd的log):
init: Service abcd does not have a SELinux domain defined.
该提示说明没有定义SELinux domain,导致服务abcd无法自启动。
第二步,为了解决这个问题我们按如下方式修改并且添加sepolicy文件:
修改seplicy/file_contexts文件,添加以下内容:
/system/bin/xxx u:object_r:xxx_exec:s0
第三步,在sepolicy目录新增xxx.te文件,并在其中添加如下内容:
需要为新增的进程增加域、执行权限
type xxx, domain;
type xxx_exec, exec_type, file_type;
然后启用这个域init_daemon_domain(xxx)
例如我下面添加的一个sepolicy/abcd.te文件的例子:
type abcd, domain;
type abcd_exec, exec_type, file_type;
init_daemon_domain(abcd)
allow abcd init_tmpfs:file create_file_perms;
allow abcd self:capability { dac_override net_admin net_raw setgid setuid };
allow abcd device:dir { open read };
allow abcd shell:lnk_file { read };
allow abcd rootfs:lnk_file { getattr };
allow abcd socket_device:sock_file { write };
详细可参考seplicy/目录下的其他te文件。
或者根据logcat中打印出来的avc:denied log报错来添加,先把相关的avc报错全部收集起来:
在相应的te、文件中增加语句,语句格式为
allow sourcecontext targetcontext:class { 许可 };
例如出现下面的avc log,可以这样添加:
[ 4.009832] type=1400 audit(1577344637.716:12): avc: denied { read } for pid=263 comm=“lowmem_manage.s” path=“/system/bin/sh” dev=“mmcblk1p10”
ino=467 scontext=u:r:lowmem_manage:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=1
sourcecontext指的是"scontext=u:r:lowmem_manage:s0"中的lowmem_manage,targetcontext指的是"tcontext=u:object_r:rfs_system_file:s0"中的rfs_system_file,
class指的是"tclass=file"中的file,许可指的是"avc: denied { read }"中的read。
所以增加语句
allow lowmem_manage rfs_system_file:file { read };
其他avc报错可以类似添加即可。
PS:
其实有时候可以在init.xxxx.rc文件执行一些拷贝或者赋权限的操作,例如我这里:
chmod 0664 /sys/class/graphics/fb0/cabc
Android8以及以后的安卓系统, Google 启动了 Treble 计划,对 system 分区和 vendor 分区进行严格的分离,所以这样的自定义脚本我们最好不要放在 /system/bin/ 目录下。我们可以把它放在 /vendor/bin/ 目录下,否则如果放在system目录,会出现以下打印,尤其是执行循环1秒操作的脚本的时候打印就不停:
[ 365.808708] type=1401 audit(1663405174.780:2118): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 366.823168] type=1401 audit(1663405174.780:2118): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 366.823250] type=1401 audit(1663405175.793:2119): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 369.909818] type=1401 audit(1663405177.866:2124): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 369.909904] type=1401 audit(1663405178.880:2125): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 369.923998] type=1401 audit(1663405178.880:2125): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 369.924078] type=1401 audit(1663405178.893:2126): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 370.938655] type=1401 audit(1663405178.893:2126): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 370.938737] type=1401 audit(1663405179.910:2127): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 370.953006] type=1401 audit(1663405179.910:2127): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 370.953087] type=1401 audit(1663405179.923:2128): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 371.968127] type=1401 audit(1663405179.923:2128): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
[ 371.968209] type=1401 audit(1663405180.940:2129): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process
同时,从 Android 8.0开始,SELinux for Android 也进行了模块化,为了避免不必要的权限问题,我们使用 /vendor/bin/sh 作为脚本解释器。例如我这里:

当然,改了使用 /vendor/bin/sh 作为脚本解释器,对应的脚本文件也要放置到vendor/bin目录,对应平台的init.rc文件的路径名称也需要修改,参考如下:

- 总结
init.rc是我们经常修改和开发的文件, 掌握它对我们开发定制有很大的帮助,也是做ROM开发必须要掌握的。



554

被折叠的 条评论
为什么被折叠?



