源码详解Android 9.0(P) 系统启动流程目录:
源码详解Android 9.0(P)系统启动流程之init进程(第一阶段)
源码详解Android 9.0(P)系统启动流程之init进程(第二阶段)
源码详解Android 9.0(P)系统启动流程之init.rc语法规则
源码详解Android 9.0(P)系统启动流程之init进程(第三阶段)
源码详解Android 9.0(P)系统启动流程之核心服务和关键进程启动
源码详解Android 9.0(P)系统启动流程之Zygote进程
源码详解Android 9.0(P)系统启动流程之SystemServer
Android系统启动流程 init.rc语法规则
1. 概述
在前面的篇章源码详解Android 9.0§ 系统启动流程之init进程(第一阶段)和源码详解Android 9.0§ 系统启动流程之init进程(第二阶段)讲解了init经过前两个阶段后,已经建立了属性系统和SELinux系统,但是init进程还需要执行很多其他的操作,还要启动许多关键的系统服务,但是如果都是像属性系统和SELinux系统那样一行行代码去做,显得有点杂乱繁琐,而且不容易扩展,所以Android系统引入了init.rc。
init.rc是init进程启动的配置脚本,这个脚本是用一种叫Android Init Language(Android初始化语言)的语言写的,在7.0以前,init进程只解析根目录下的init.rc文件,但是随着版本的迭代,init.rc越来越臃肿,所以在7.0以后,init.rc一些业务被分拆到/system/etc/init,/vendor/etc/init,/odm/etc/init三个目录下,在本篇文章中,将先解释init.rc的一些语法。
2. Android Init Language语法
init.rc是一个可配置的初始文件,是由Android初始化语言编写(Android Init Language)编写的脚本,这里顺便扩展一些Android里面还有那些类似的Android独有的语言呢(譬如AIDL, HIDL等)?
init.rc文件主要包含如下五类声明:
- Action
- Command
- Service
- Options
- Import
init.rc的配置代码在system/core/rootdir/init.rc中,如果你够仔细的话,会发现在统计目录下还有许多的init.xxx.rc类似文件,这个后续会讲解为什么会存在这么多的init.xxx.rc文件。如下
init.rc文件是在init进程启动后执行的启动脚本,文件中记录着init进程需执行的操作,关于init.rc的相关介绍Android提供了一个官方的参考文档,在Android源码中的路径如下所示system/core/init/README.md中有详细介绍,不过是英文。
Android Init Language
---------------------
The Android Init Language consists of five broad classes of statements:
Actions, Commands, Services, Options, and Imports.
All of these are line-oriented, consisting of tokens separated by
whitespace. The c-style backslash escapes may be used to insert
whitespace into a token. Double quotes may also be used to prevent
whitespace from breaking text into multiple tokens. The backslash,
when it is the last character on a line, may be used for line-folding.
Lines which start with a `#` (leading whitespace allowed) are comments.
System properties can be expanded using the syntax
`${property.name}`. This also works in contexts where concatenation is
required, such as `import /init.recovery.${ro.hardware}.rc`.
Actions and Services implicitly declare a new section. All commands
or options belong to the section most recently declared. Commands
or options before the first section are ignored.
Services have unique names. If a second Service is defined
with the same name as an existing one, it is ignored and an error
message is logged.
.rc文件主要配置了两个东西,一个是action,一个是service,trigger和command是对action的补充,options是对service的补充。action加上trigger以及一些command,组成一个Section;service加上一些option,也组成一个Section ;.rc文件就是由一个个Section组成。.rc文件头部有一个import的语法,表示这些.rc也一并包含并解析。
下面让我们对rc文件中涉及到的五种类型的声明,一一讲解,包教包会。
2.1 Action
Action是以Section的形式出现的,每个Action Section可以含有若干的Command。Section只有起始标记,却没有明确的结束标记,也就是说,是用“后一个Section”的起始来结束"前一个Section",这里需要注意的一点是Action可以重复,但是最后会合并到一起。
上个实例来说明一下,其实rc里面的Action就是以"on"关键词开头的动作列表(action list):
on early-init #Action类型语句
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000 #Command语句
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
Action需要一个触发器(trigger)来触发它,这个会在解析init进程的代码里面看到,一旦满足了触发条件,这个Action就会被加到执行队列的末尾。
Action类型的语句格式是:
on <trigger> [&& <trigger>]* #设置触发器
<command> #动作触发之后要执行的命令
<command>
<command>
以on开头,trigger是判断条件,command是具体执行一些操作,当满足trigger条件时,执行这些command。
trigger触发的条件可以分为如下几种情况:
-
这里的trigger可以是字符串,如
on early-init #表示当trigger early-init或QueueEventTrigger("early-init")调用时触发
-
这里的trigger也可以是属性,如
on property:sys.boot_from_charger_mode=1 #表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发 class_stop charger trigger late-init on property:sys.init_log_level=* # *表示任意值触发 loglevel ${sys.init_log_level}
-
这里的trigger,条件可以是多个,用&&连接,如
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
-
command就是一些具体的操作,如:
mkdir /dev/fscklogs 0770 root system //新建目录 class_stop charger //终止服务 trigger late-init //触发late-init
2.2 service
Service也是以Section的形式出现的,其中每个Service Section可以包含有若干的Option。Section 只有起始标记,却没有明确的结束标记,也就是说,是用“后一个 Section”的起始来结束“前一个 Section”。这里有一点需要需要注意地是Service 不能出现重名。
上个实例来说明一下,其实rc里面的Service就是以“service”关键字开头的 服务列表(service list):
## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
Service表示一个服务程序,会通过 start command 执行。并根据 option 参数判断服务在退出时是否需要自动重启。
Service的语句结构如下:
service <name> <pathname> [ <argument> ]* #<service的名字><执行程序路径><传递参数>
<option> #option是service的修饰此,影响什么时候,如何启动service
<option>
<option>
...
从上面对Action和Service的解读中我们可以看出,这两个声明主要借助于系统环境变量或者Linux命令来在Android启动的不同阶段做一些工作,主要如下:
- 动作列表用于创建所需目录,以及为某些特定文件指定权限;
- 服务列表用来记录init进程需要启动的一些子进程,如上面代码所示,service关键字后的第一个字符串表示服务(子进程)的名称,第二个字符串表示服务的执行路径。
再来一个典型的例子;如:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
这个是配置在 /init.zygote64_32.rc文件中的service, 它就是我们常说的zygote进程的启动配置。zygote是进程名,可执行文件路径在/system/bin/app_process64,执行文件参数(就是可执行程序main函数里面的那个args)是
-Xzygote /system/bin –zygote –start-system-server –socket-name=zygote
后面的option是一些服务配置,比如:class main表示所属class是main,相当于一个归类,其他service也可以归为main,他们会被一起启动或终止,service有一个name,也有一个class,就像工作中,你有一个名字叫foxleezh,也可以说你属于android部门.
2.3 Options
Options是Services的参数配置,他们将影响Service如何运行以及运行时机。比如:
-
console
console [<console>] Service需要控制台. 第二个参数console的意思是可以设置你想要的控制台类型,默认控制台是/dev/console , /dev 这个前缀通常是被忽略的,比如你要设置控制台 /dev/tty0 ,那么只需要设置为console tty0
-
critical
critical 表示Service是严格模式. 如果这个Service在4分钟内退出超过4次,那么设备将重启进入recovery模式
-
disabled
disabled 表示Service不能以class的形式启动,只能以name的形式启动
-
setenv
setenv <name> <value> 在Service启动时设置name-value的环境变量
-
socket
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ] 创建一个unix域的socket,名字叫/dev/socket/name , 并将fd返回给Service. type 只能是 "dgram", "stream" or "seqpacket".User 和 group 默认值是 0. 'seclabel' 是这个socket的SELinux安全上下文,它的默认值是 service安全策略或者基于其可执行文件的安全上下文.它对应的本地实现在libcutils的android_get_control_socket
-
file
file <path> <type> 打开一个文件,并将fd返回给这个Service. type 只能是 "r", "w" or "rw". 它对应的本地实现在libcutils的 android_get_control_file
-
user
user <username> 在启动Service前将user改为username,默认启动时user为root(或许默认是无).在Android M版本,如果一个进程想拥有 Linux capabilities(相当于Android中的权限吧),也只能通过设置这个值. 以前,一个程序要想有Linux capabilities,必须先以root身份运行,然后再降级到所需的uid.现在已经有一套新的机制取而代之, 它通过fs_config允许厂商赋予特殊二进制文件Linux capabilities. 这套机制的说明文档在 http://source.android.com/devices/tech/config/filesystem.html.当使用这套新的机制时, 程序可以通过user参数选择自己所需的uid,而不需要以root权限运行. 在Android O版本,程序可以通过 capabilities参数直接申请所需的能力,参见下面的capabilities说明
-
group
group <groupname> [ <groupname>\* ] 在启动Service前将group改为第一个groupname,第一个groupname是必须有的, 默认值为root(或许默认值是无),第二个groupname可以不设置,用于追加组(通过setgroups).
-
capabilities
capabilities <capability> [ <capability>\* ] 在启动Service时将capabilities设置为capability. 'capability' 不能是"CAP_" prefix, like "NET_ADMIN" or "SETPCAP". 参考http://man7.org/linux/man-pages/man7/capabilities.7.html , 里面有capability的说明.
-
seclabel
seclabel <seclabel> 在启动Service前将seclabel设置为seclabel. 主要用于在rootfs上启动的service,比如ueventd, adbd. 在系统分区上运行的service有自己的SELinux安全策略,如果不设置,默认使用init的安全策略.
-
oneshot
oneshot 退出后不再重启
-
class
class <name> [ <name>\* ] 为Service指定class名字. 同一个class名字的Service会被一起启动或退出,默认值是"default",第二个name可以不设置, 用于service组.
-
animation
animation class
animation class 主要包含为开机动画或关机动画服务的service. 它们很早被启动,而且直到关机最后一步才退出.
它们不允许访问/data 目录,它们可以检查/data目录,但是不能打开/data目录,而且需要在/data不能用时也正常工作。
-
onrestart
onrestart 在Service重启时执行命令.
-
writepid
writepid <file> [ <file>\* ] 当Service调用fork时将子进程的pid写入到指定文件. 用于cgroup/cpuset的使用,当/dev/cpuset/下面没有文件 但ro.cpuset.default的值却不为空时,将pid的值写入到/dev/cpuset/cpuset_name/tasks文件中
-
priority
priority <priority> 设置进程优先级. 在-20~19之间,默认值是0,能过setpriority实现
-
namespace
namespace <pid|mnt> 当fork这个service时,设置pid或mnt标记
-
oom_score_adjust
oom_score_adjust <value> 设置子进程的 /proc/self/oom_score_adj 的值为 value,在 -1000 ~ 1000之间.
-
Triggers
Triggers Triggers 是个字符串,当一些事件发生满足该条件时,一些actions就会被执行 Triggers分为事件Trigger和属性Trigger 事件Trigger由trigger 命令或QueueEventTrigger方法触发.它的格式是个简单的字符串,比如'boot' 或 'late-init'. 属性Trigger是在属性被设置或发生改变时触发. 格式是'property:<name>=<value>'或'property:<name>=*',它会在init初始化设置属性的时候触发. 属性Trigger定义的Action可能有多种触发方式,但是事件Trigger定义的Action可能只有一种触发方式 比如: on boot && property:a=b 定义了action的触发条件是,boot Trigger触发,并且属性a的值等于b on property:a=b && property:c=d 这个定义有三种触发方式: 在初始化时,属性a=b,属性c=d. 在属性c=d的情况下,属性a被改为b. A在属性a=b的情况下,属性c被改为d.
2.4 Command
Command通常和Action关联在一起,一般表示一些具体的操作,通常是借助Linux命令完成相关的操作,比如:
-
bootchart
bootchart [start|stop] 启动或终止bootcharting. 这个出现在init.rc文件中,但是只有在/data/bootchart/enabled文件存在的时候才有效, 否则不能工作
-
chmod
chmod <octal-mode> <path> 修改文件读写权限
-
chown
chown <owner> <group> <path> 修改文件所有者或所属用户组
-
class_start
class_start <serviceclass> 启动所有以serviceclass命名的未启动的service(service有一个name,也有个class,这里的serviceclass就是 class,class_start和后面的start是两种启动方式,class_start是class形式启动,start是name形式启动)
-
class_stop
class_stop <serviceclass> 终止所有以serviceclass命名的正在运行的service
-
class_reset
class_reset <serviceclass> 终止所有以serviceclass命名的正在运行的service,但是不禁用它们. 它们可以稍后被class_start重启
-
copy
copy <src> <dst> 复制一个文件,与write相似,比较适合二进制或比较大的文件. 对于src,从链接文件、world-writable或group-writable复制是不允许的. 对于dst,如果目标文件不存在,则默认权限是0600,如果存在就覆盖掉
-
domainname
domainname <name> 设置域名
-
enable
enable <servicename> 将一个禁用的service设置为可用. 如果这个service在运行,那么就会重启. 一般用在bootloader时设置属性,然后启动一个service,比如 on property:ro.boot.myfancyhardware=1 enable my_fancy_service_for_my_fancy_hardware exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ] 新建子进程并运行一个带指定参数的命令. 这个命令指定了seclabel(安全策略),user(所有者),group(用户组). 直到这个命令运行完才可以运行其他命令,seclabel可以设置为 - 表示用默认值,argument表示属性值. 直到子进程新建完毕,init进程才继续执行.
-
exec_start
exec_start <service> 启动一个service,只有当执行结果返回,init进程才能继续执行. 这个跟exec相似,只是将一堆参数的设置改在在service中定义
-
export
export <name> <value> 设置环境变量name-value. 这个环境变量将被所有已经启动的service继承
-
hostname
hostname <name> 设置主机名
-
ifup
ifup <interface> 开启指定的网络接口
-
insmod
insmod [-f] <path> [<options>] 安装path下的模块,指定参数options. -f 表示强制安装,即便是当前Linux内核版本与之不匹配
-
load_all_props
load_all_props 加载/system, /vendor等目录下的属性,这个用在init.rc中
-
load_persist_props
load_persist_props 加载/data 下的持久化属性. 这个用在init.rc中
-
loglevel
loglevel <level> 设置日志输出等级,level表示等级
-
mkdir
mkdir <path> [mode] [owner] [group] 创建一个目录,path是路径,mode是读写权限,默认值是755,owner是所有者,默认值root,group是用户组, 默认值是root.如果该目录已存在,则覆盖他们的mode,owner等设置
-
mount_all
mount_all <fstab> [ <path> ]\* [--<option>] 当手动触发 "early" 和 "late"时,调用fs_mgr_mount_all 函数,指定fstab配置文件,并导入指定目录下的.rc文件 详情可以查看init.rc文件中的有关定义
-
mount
mount <type> <device> <dir> [ <flag>\* ] [<options>] 在dir目录下挂载一个名叫device的设备 _flag 包括 "ro", "rw", "remount", "noatime", ... options包括"barrier=1","noauto_da_alloc", "discard", ... 用逗号分开,比如barrier=1,noauto_da_alloc
-
restart
restart <service> 终止后重启一个service,如果这个service刚被重启就什么都不做,如果没有在运行,就启动
-
restorecon
restorecon <path> [ <path>\* ] 恢复指定目录下文件的安全上下文.第二个path是安全策略文件. 指定目录不需要必须存在,因为它只需要在init中正确标记
-
restorecon_recursive
restorecon_recursive <path> [ <path>\* ] 递归地恢复指定目录下的安全上下文,第二个path是安全策略文件位置
-
rm
rm <path> 调用 unlink(2)删除指定文件. 最好用exec -- rm ...代替,因为这样可以确保系统分区已经挂载好
-
rmdir
rmdir <path> 调用 rmdir(2) 删除指定目录
-
setprop
setprop <name> <value> 设置属性name-value
-
setrlimit
setrlimit <resource> <cur> <max> 指定一个进程的资源限制
-
start
start <service> 启动一个未运行的service
-
stop
stop <service> 终止一个正在运行的service
-
swapon_all
swapon_all <fstab> 调用 fs_mgr_swapon_all,指定fstab配置文件.
-
symlink
symlink <target> <path> 在path下创建一个指向target的链接
-
sysclktz
sysclktz <mins_west_of_gmt> 重置系统基准时间(如果是格林尼治标准时间则设置为0)
-
trigger
trigger <event> 触发事件event,由一个action触发到另一个action队列
-
umount
umount <path> 卸载指定path的文件系统
-
verity_load_state
verity_load_state 内部实现是加载dm-verity的状态
-
verity_update_state
verity_update_state <mount-point> 内部实现是设置dm-verity的状态,并且设置partition.mount-point.verified的属性. 用于adb重新挂载, 因为fs_mgr 不能直接设置它。
-
wait
wait <path> [ <timeout> ] 查看指定路径是否存在. 如果发现则返回,可以设置超时时间,默认值是5秒
-
wait_for_prop
wait_for_prop <name> <value> 等待name属性的值被设置为value,如果name的值一旦被设置为value,马上继续
-
write
write <path> <content> 打开path下的文件,并用write(2)写入content内容. 如果文件不存在就会被创建,如果存在就会被覆盖掉
2.5 Imports
import关键字不是一个命令,但是如果有.rc文件包含它就会马上解析它里面的section。
在前面我们讲过在system/core/rootdir文件目录下面还有其它类型的init.xxx.rc,那么这些rc文件是怎么加载的呢,这里就是import的功劳了。 import 则是导入其它 init…rc 用的,如 import /init.${ro.hardware}.rc。
用法如下:
import <path>
解析path下的.rc文件 ,括展当前文件的配置。如果path是个目录,这个目录下所有.rc文件都被解析,但是不会递归,
import被用于以下两个地方:
1.在初始化时解析init.rc文件
2.在mount_all时解析{system,vendor,odm}/etc/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
import /init.xiriservice.rc
/init.rc 是最主要的一个.rc文件,它由init进程在初始化时加载,主要负责系统初始化,它会导入 /init.${ro.hardware}.rc ,这个是系统级核心厂商提供的主要.rc文件
当执行 DoFirstStageMount语句时,init进程将加载所有在 /{system,vendor,odm}目录下的文件当然也包括对应目录下的rc文件,挂载好文件系统后,这些目录将会为Actions和Services服务。这个也是谷歌为了划分层次,针对不同的开发者不同开发阶段而做的优化。
上述三个目录用于扩展的init.rc功能分别如下:
/system/etc/init/
用于系统本身,比如SurfaceFlinger, MediaService, and logcatd./vendor/etc/init/
用于SoC(系统级核心厂商,如高通),为他们提供一些核心功能和服务/odm/etc/init/
用于设备制造商(odm定制厂商,如华为、小米),为他们的传感器或外围设备提供一些核心功能和服务
3. 小结
本文解释了Android启动文件init.rc的语法规则,下一篇将分析一下init进程是如何解析这个启动配置文件。