源码详解Android 9.0(P) 系统启动流程之init.rc语法规则

源码详解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


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如何运行以及运行时机。比如:

  1. console

    console [<console>]
    
    Service需要控制台. 第二个参数console的意思是可以设置你想要的控制台类型,默认控制台是/dev/console ,
    /dev 这个前缀通常是被忽略的,比如你要设置控制台 /dev/tty0 ,那么只需要设置为console tty0
    
  2. critical

    critical
    
    表示Service是严格模式. 如果这个Service在4分钟内退出超过4次,那么设备将重启进入recovery模式
    
  3. disabled

    disabled
    
    表示Service不能以class的形式启动,只能以name的形式启动
    
  4. setenv

    setenv <name> <value>
    
    在Service启动时设置name-value的环境变量
    
  5. 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
    
  6. file

    file <path> <type>
    
    打开一个文件,并将fd返回给这个Service. type 只能是 "r", "w" or "rw". 它对应的本地实现在libcutils的
    android_get_control_file 
    
  7. 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说明
    
  8. group

    group <groupname> [ <groupname>\* ]
    
    在启动Service前将group改为第一个groupname,第一个groupname是必须有的,
    默认值为root(或许默认值是无),第二个groupname可以不设置,用于追加组(通过setgroups).
    
  9. 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的说明.
    
  10. seclabel

    seclabel <seclabel>
    
    在启动Service前将seclabel设置为seclabel. 主要用于在rootfs上启动的service,比如ueventd, adbd.
    在系统分区上运行的service有自己的SELinux安全策略,如果不设置,默认使用init的安全策略.
    
  11. oneshot

    oneshot
    
    退出后不再重启
    
  12. class

    class <name> [ <name>\* ]
    
    为Service指定class名字. 同一个class名字的Service会被一起启动或退出,默认值是"default",第二个name可以不设置,
    用于service组.
    
  13. animation

animation class

animation class 主要包含为开机动画或关机动画服务的service. 它们很早被启动,而且直到关机最后一步才退出.
它们不允许访问/data 目录,它们可以检查/data目录,但是不能打开/data目录,而且需要在/data不能用时也正常工作。
  1. onrestart

    onrestart 
    
    在Service重启时执行命令.
    
  2. writepid

    writepid <file> [ <file>\* ]
    
    当Service调用fork时将子进程的pid写入到指定文件. 用于cgroup/cpuset的使用,当/dev/cpuset/下面没有文件
    但ro.cpuset.default的值却不为空时,将pid的值写入到/dev/cpuset/cpuset_name/tasks文件中
    
  3. priority

    priority <priority>
    
    设置进程优先级. 在-20~19之间,默认值是0,能过setpriority实现
    
  4. namespace

    namespace <pid|mnt>
    
    当fork这个service时,设置pid或mnt标记
    
  5. oom_score_adjust

    oom_score_adjust <value>
    
    设置子进程的 /proc/self/oom_score_adj 的值为 value,在 -1000 ~ 1000之间.
    
  6. 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命令完成相关的操作,比如:

  1. bootchart

    bootchart [start|stop]
    
    启动或终止bootcharting. 这个出现在init.rc文件中,但是只有在/data/bootchart/enabled文件存在的时候才有效,
    否则不能工作
    
  2. chmod

    chmod <octal-mode> <path>
    
    修改文件读写权限
    
  3. chown

    chown <owner> <group> <path>
    
    修改文件所有者或所属用户组
    
  4. class_start

    class_start <serviceclass>
    
    启动所有以serviceclass命名的未启动的service(service有一个name,也有个class,这里的serviceclass就是
    class,class_start和后面的start是两种启动方式,class_start是class形式启动,start是name形式启动)
    
  5. class_stop

    class_stop <serviceclass> 
    
    终止所有以serviceclass命名的正在运行的service
    
  6. class_reset

    class_reset <serviceclass>
    
    终止所有以serviceclass命名的正在运行的service,但是不禁用它们. 它们可以稍后被class_start重启
    
  7. copy

    copy <src> <dst>
    
    复制一个文件,与write相似,比较适合二进制或比较大的文件.
    
    对于src,从链接文件、world-writable或group-writable复制是不允许的.
    
    对于dst,如果目标文件不存在,则默认权限是0600,如果存在就覆盖掉
    
  8. domainname

    domainname <name>
    
    设置域名
    
  9. 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进程才继续执行.
    
  10. exec_start

    exec_start <service>
    
    启动一个service,只有当执行结果返回,init进程才能继续执行. 这个跟exec相似,只是将一堆参数的设置改在在service中定义
    
  11. export

    export <name> <value>
    
    设置环境变量name-value. 这个环境变量将被所有已经启动的service继承
    
  12. hostname

    hostname <name> 
    
    设置主机名
    
  13. ifup

    ifup <interface>
    
    开启指定的网络接口
    
  14. insmod

    insmod [-f] <path> [<options>]
    
    安装path下的模块,指定参数options.
    
    -f 表示强制安装,即便是当前Linux内核版本与之不匹配
    
  15. load_all_props

    load_all_props
    
    加载/system, /vendor等目录下的属性,这个用在init.rc中
    
  16. load_persist_props

    load_persist_props
    
    加载/data 下的持久化属性. 这个用在init.rc中
    
  17. loglevel

    loglevel <level>
    
    设置日志输出等级,level表示等级
    
  18. mkdir

    mkdir <path> [mode] [owner] [group]
    
    创建一个目录,path是路径,mode是读写权限,默认值是755,owner是所有者,默认值root,group是用户组,
    默认值是root.如果该目录已存在,则覆盖他们的mode,owner等设置
    
  19. mount_all

    mount_all <fstab> [ <path> ]\* [--<option>]
    
    当手动触发 "early""late"时,调用fs_mgr_mount_all 函数,指定fstab配置文件,并导入指定目录下的.rc文件
    详情可以查看init.rc文件中的有关定义
    
  20. 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
    
  21. restart

    restart <service>
    
    终止后重启一个service,如果这个service刚被重启就什么都不做,如果没有在运行,就启动
    
  22. restorecon

    restorecon <path> [ <path>\* ]
    
    恢复指定目录下文件的安全上下文.第二个path是安全策略文件. 指定目录不需要必须存在,因为它只需要在init中正确标记
    
  23. restorecon_recursive

    restorecon_recursive <path> [ <path>\* ]
    
    递归地恢复指定目录下的安全上下文,第二个path是安全策略文件位置
    
  24. rm

    rm <path>
    
    调用 unlink(2)删除指定文件. 最好用exec -- rm ...代替,因为这样可以确保系统分区已经挂载好
    
  25. rmdir

    rmdir <path>
    
    调用 rmdir(2) 删除指定目录
    
  26. setprop

    setprop <name> <value>
    
    设置属性name-value 
    
  27. setrlimit

    setrlimit <resource> <cur> <max>
    
    指定一个进程的资源限制
    
  28. start

    start <service> 
    
    启动一个未运行的service
    
  29. stop

    stop <service>
    
    终止一个正在运行的service
    
  30. swapon_all

    swapon_all <fstab>
    
    调用 fs_mgr_swapon_all,指定fstab配置文件.
    
  31. symlink

    symlink <target> <path>
    
    在path下创建一个指向target的链接
    
  32. sysclktz

    sysclktz <mins_west_of_gmt>
    
    重置系统基准时间(如果是格林尼治标准时间则设置为0)
    
  33. trigger

    trigger <event>
    
    触发事件event,由一个action触发到另一个action队列
    
  34. umount

    umount <path>
    
    卸载指定path的文件系统
    
  35. verity_load_state

    verity_load_state
    
    内部实现是加载dm-verity的状态
    
  36. verity_update_state

    verity_update_state <mount-point>
    
    内部实现是设置dm-verity的状态,并且设置partition.mount-point.verified的属性. 用于adb重新挂载,
    因为fs_mgr 不能直接设置它。 
    
  37. wait

    wait <path> [ <timeout> ]
    
    查看指定路径是否存在. 如果发现则返回,可以设置超时时间,默认值是5秒
    
  38. wait_for_prop

    wait_for_prop <name> <value>
    
    等待name属性的值被设置为value,如果name的值一旦被设置为value,马上继续
    
  39. 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进程是如何解析这个启动配置文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ʚ兔子的先森ɞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值