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的一些语法。
定义在platform/system/core/init/README.md
.rc文件主要配置了两个东西,一个是action,一个是service,trigger和command是对action的补充,options是对service的补充。action加上trigger以及一些command,组成一个Section;service加上一些option,也组成一个Section ;.rc文件就是由一个个Section组成。.rc文件头部有一个import的语法,表示这些.rc也一并包含并解析,接下来我们重点讲下action和service.
action的格式如下:
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
以on开头,trigger是判断条件,command是具体执行一些操作,当满足trigger条件时,执行这些command。
trigger可以是一个字符串,如:
on early //表示当trigger early或QueueEventTrigger("early")调用时触发
也可以是属性,如:
on property:sys.boot_from_charger_mode=1//表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发
on property:sys.sysctl.tcp_def_init_rwnd=* // *表示任意值
条件可以是多个,用&&连接,如:
//表示当zygote-start触发并且ro.crypto.state属性值为unencrypted时触发
on zygote-start && property:ro.crypto.state=unencrypted
command就是一些具体的操作,如:
mkdir /dev/fscklogs 0770 root system //新建目录
class_stop charger //终止服务
trigger late-init //触发late-init
services的格式如下:
service [ ]*
以service开头,name是指定这个服务的名称,pathname表示这个服务的执行文件路径,argument表示执行文件带的参数,option表示这个服务的一些配置。
一个典型的例子;如:
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部门.
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 <value>
在Service启动时设置name-value的环境变量
socket
socket [ [ [ ] ] ]
创建一个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 <type>
打开一个文件,并将fd返回给这个Service. type 只能是 "r", "w" or "rw". 它对应的本地实现在libcutils的
android_get_control_file
user
user
在启动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 [ \* ]
在启动Service前将group改为第一个groupname,第一个groupname是必须有的,
默认值为root(或许默认值是无),第二个groupname可以不设置,用于追加组(通过setgroups).
capabilities
capabilities [ \* ]
在启动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:=' 或'property:=*' ,它会在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.
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命名的未启动的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
复制一个文件,与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,只有当执行结果返回,init进程才能继续执行. 这个跟exec相似,只是将一堆参数的设置改在在service中定义
export
export <value>
设置环境变量name-value. 这个环境变量将被所有已经启动的service继承
hostname
hostname <name>
设置主机名
ifup
ifup <interface>
开启指定的网络接口
insmod
insmod [-f] []
安装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 [ ]\* [--
mount
mount [ \* ] []
在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
调用 unlink(2)删除指定文件. 最好用exec -- rm ...代替,因为这样可以确保系统分区已经挂载好
rmdir
rmdir
调用 rmdir(2) 删除指定目录
setprop
setprop <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 <value>
等待name属性的值被设置为value,如果name的值一旦被设置为value,马上继续
write
write <content>
打开path下的文件,并用write(2)写入content内容. 如果文件不存在就会被创建,如果存在就会被覆盖掉
Imports
import关键字不是一个命令,但是如果有.rc文件包含它就会马上解析它里面的section,用法如下:
import
解析path下的.rc文件 ,括展当前文件的配置。如果path是个目录,这个目录下所有.rc文件都被解析,但是不会递归,
import被用于以下两个地方:
1.在初始化时解析init.rc文件
2.在mount_all时解析{system,vendor,odm}/etc/init/等目录下的.rc文件
后面的内容主要是一些跟调试init进程相关的东西,比如init.svc.可以查看service启动的状态,
ro.boottime.init记录一些关键的时间点,Bootcharting是一个图表化的性能监测工具等,由于与语法关系不大,就不作翻译了
明白了.rc文件的语法,我们再来看看init进程是如何解析.rc文件,将这些语法转化为实际执行的代码的
本文解释了Android启动文件init.rc的语法规则,下一篇将分析一下init进程是如何解析这个启动配置文件。未完待续。。。