美高梅网址注册-澳门mgm4858集团登录网址
做最好的网站
来自 澳门mgm4858集团登录网址 2019-10-03 21:14 的文章
当前位置: 美高梅网址注册 > 澳门mgm4858集团登录网址 > 正文

所以Android系统引入了init.rc,通过行尾添加反斜杠

init.rc文件由系统第一个启动的init程序进行解析.它由"Android Init Language"语言编写而成.init.rc文件可以在你android设备根目录下找到.还记得我们上次编译的Android源码么?如果你已经编译过源码了,那么可以在out/target/generic/root/目录下找到该文件.

这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 github连载地址

Android的init过程(一)

要想读懂init.rc文件,首先要掌握Android Init Language语言,即AIL.在/system/core/init/下有一份readme.txt文件,为我们详细介绍了有关AIL的知识.我们下面的学习同样是借助了该文档来的.

init经过前两个阶段后,已经建立了属性系统和SELinux系统,但是init进程还需要执行很多其他的操作,还要启动许多关键的系统服务,但是如果都是像属性系统和SELinux系统那样一行行代码去做,显得有点杂乱繁琐,而且不容易扩展,所以Android系统引入了init.rc

本文使用的软件版本

AIL语言非常简单,主要包括两部分:结构语法及注释语法.下面我们就这两点进行说明

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的一些语法,然后一步步分析init进程是如何去解析init.rc文件的

Android:4.2.2

AIL语言包含主要包含五种结构语法:

本文主要讲解以下内容

Linux内核:3.1.10

  1. Actions
  2. Services
  3. Options
  4. Commands
  5. Imports
  • Android Init Language语法
  • 解析.rc文件
  • 加入一些事件和一些Action
  • 触发所有事件并不断监听新的事件

    在上一篇文章中介绍了init的初始化第一阶段,也就是处理各种属性。在本文将会详细分析init最重要的一环:解析init.rc文件。

需要注意,AIL采用是面向行的代码风格,即用换行符作为一条语句的分隔符,也就是在init.rc中以一条语句通常占据一行.如果一行写不下,可以在行尾添加反斜杠来链接到下一行,换言之,通过行尾添加反斜杠符可以将多行代码链接为一行代码.

本文涉及到的文件

init.rc 文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析 init.c及其相关文件的源代码毫无意义。

init.rc有许多Service和Action组成.那么什么是Service和Action呢?Action和Service显式声明了一个语句块,而Commands和Options则分别用来定义Actions和Service(你可以理解为这是Action或者Service的属性).

platform/system/core/init/README.mdplatform/system/core/init/init.cppplatform/system/core/init/init_parser.cppplatform/system/core/init/action.cppplatform/system/core/init/action.hplatform/system/core/init/keyword_map.hplatform/system/core/init/builtins.cppplatform/system/core/init/service.cppplatform/system/core/init/service.hplatform/system/core/init/import_parser.cppplatform/system/core/init/util.cpp

     为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码, 在<Android源代码根目录>out/target/product/generic/root目录也可找到init.rc文件。

另外,我们声明的Commands和Options属于最近声明的语句块,即就近原则.需要注意,在第一个语句块之前的commands和options会被忽略.

定义在platform/system/core/init/README.md

AIL由如下4部分组成。

每个Actions或者Services应该有唯一的名字.对于名字重复的情况,Action和Service有自己不同的处理方式:

.rc文件主要配置了两个东西,一个是action,一个是service,trigger和command是对action的补充,options是对service的补充.action加上trigger以及一些command,组成一个Section,service加上一些option,也组成一个Section ,.rc文件就是由一个个Section组成..rc文件头部有一个import的语法,表示这些.rc也一并包含并解析,接下来我们重点讲下action和service.

1.  动作(Actions)

如果第二个定义的Action的名字和之前存在Action的名字相同,第二个Action中定义的Commands将会被添加到已经存在的同名Action中.如果第二个定义的Service的名字和之前存在的Service的名字相同,第二个Service会被忽略并输出错误信息.

action的格式如下:

2.  命令(Commands)

AIL中的注释语法和Shell脚本一致,以#开头即可

 on <trigger> [&& <trigger>]* <command> <command> <command>

3. 服务(Services)

Actions代表一些Action.Action代表一组命令,它包含一个触发器,该触发器决定了何时执行这个Action,即在什么情况下才能执行该Action中的定义命令.当一些条件满足触发器的条件时,该Action中定义的命令会被添加到要执行命令队列的尾部(如果这组命令已经在队列中,则不会再次添加).

以on开头,trigger是判断条件,command是具体执行一些操作,当满足trigger条件时,执行这些commandtrigger可以是一个字符串,如

4.  选项(Options)

当一个Action从队列移除时,该Action定义的命令会依次被执行.

on early //表示当trigger early或QueueEventTrigger调用时触发

     这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。可以使用反斜杠转义符在 Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是 说,可以用反斜杠将多行代码连接成一行代码。

Action的格式如下:

也可以是属性,如

     AIL的注释与很多Shell脚本一行,以#开头。

on <trgger> [&& <trigger>]* <command> <command> <command> ...
on property:sys.boot_from_charger_mode=1//表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发on property:sys.sysctl.tcp_def_init_rwnd=* // *表示任意值

     AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或 Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和 Options在第一个Section之前被定义,它们将被忽略。

不难发现Action都是以on开始,随后会定义触发器,接着便是为其定义命令.在开始讲解Trigger和Command之前,我们先来看一段Action的示例代码:

条件可以是多个,用&&连接,如

Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。

on boot # 初始化网络 ifup lo hostname localhost domainname localdomain
on zygote-start && property:ro.crypto.state=unencrypted//表示当zygote-start触发并且ro.crypto.state属性值为unencrypted时触发

下面来看看Actions、Services、Commands和Options分别应如何设置。

trigger

trigger即我们上面所说的触发器,本质上是一个字符串,能够匹配某种包含该字符串的事件.trigger又被细分为事件触发器(event trigger)和属性触发器(property trigger).

事件触发器可由"trigger"命令或初始化过程中通过QueueEventTrigger()触发,通常是一些事先定义的简单字符串,例如:boot,late-init属性触发器是当指定属性的变量值变成指定值时触发,其格式为property:<name>=*

一个Action可以有多个属性触发器,但是最多有一个事件触发器.下面我们看两个例子:

on boot && property:a=b

该Action只有在boot事件发生时,并且属性a和b相等的情况下才会被触发.

on property:a=b && property:c=d

该Action会在以下三种情况被触发:

  • 在启动时,如果属性a的值等于b并且属性c的值等于d
  • 在属性c的值已经是d的情况下,属性a的值被更新为b
  • 在属性a的值已经是b的情况下,属性c的值被更新为d

当前AIL中常用的有以下几种事件触发器:

类型 说明
boot init.rc被装载后触发
device-added-<path> 指定设备被添加时触发
device-removed-<path> 指定设备被移除时触发
service-exited-<name> 在特定服务退出时触发
early-init 初始化之前触发
late-init 初始化之后触发
init 初始化时触发

command就是一些具体的操作,如

Actions的语法格式如下:

Commands

Commands代表一组命令,在为Action设置了触发器后,就需要为其定义一组命令了.AIL中内置了众多的命令,下面我们做个简单的说明:

命令 解释
bootchart_init 如果配置了bootcharing,则启动.包含在默认的init.rc中
chmod 更改文件权限
chown <owner> <group> <path> 更改文件的所有者和组
calss_start <serviceclass> 启动指定类别服务下的所有未启动的服务
class_stop <serviceclass> 停止指定类别服务类下的所有已运行的服务
class_reset <serviceclass> 停止指定类别的所有服务,但不会禁用这些服务.后面可以通过class_start重启这些服务
copy <src> <dst> 复制文件,对二进制/大文件非常有用
domainname <name> 设置域名称
enable <servicename> 启用已经禁用的服务
exec [ <seclabel> [ <user> [ <group> ]* ]]--<command> [ <argument> ]* fork一个进程执行指定命令,如果有参数,则带参数执行
export <name> 在全局环境中,将<name>变量的值设置为<value>,即以键值对的方式设置全局环境变量.这些变量对之后的任何进程都有效
hostname 设置主机名
ifup <interface> 启动某个网络接口
insmod [-f] <path> [<options>] 加载指定路径下的驱动模块。-f强制加载,即不管当前模块是否和linux kernel匹配
load_all_props 从/system,/vendor加载属性。默认包含在init.rc
load_persist_props 当/data被加密时,加载固定属性
loglevel <level> 设置kernel日志等级
mkdir <path> [mode] [owner] [group] 在制定路径下创建目录
mount_all <fstab> [ <path> ]* 在给定的fs_mgr-format上调用fs_mgr_mount和引入rc文件
mount <type> <device> <dir>[ <flag> ]* [<options>] 挂载指定设备到指定目录下.
powerct 用来应对sys.powerctl中系统属性的变化,用于系统重启
restart <service> 重启制定服务,但不会禁用该服务
restorecon <path> [ <path> ]* 恢复指定文件到file_contexts配置中指定的安全上线文环境
restorecon_recursive <path> [ <path> ]* 以递归的方式恢复指定目录到file_contexts配置中指定的安全上下文中
rm <path> 删除指定路径下的文件
rmdir <path> 删除制定路径下的目录
setprop <name> <value> 将系统属性<name>的值设置为<value>,即以键值对的方式设置系统属性
setrlimit <resource> <cur> <max> 设置资源限制
start <service> 启动服务(如果该服务还未启动)
stop <service> 关闭服务(如果该服务还未停止)
swapon_all <fstab>
symlink <target> <path> 创建一个指向<path>的符合链接<target>
sysclktz <mins_west_of_gmt> 设置系统时钟的基准,比如0代表GMT,即以格林尼治时间为准
trigger <event> 触发一个事件,将该action排在某个action之后(用于Action排队)
verity_load_state
verity_update_state <mount_point>
wait <path> [ <timeout> ] 等待一个文件是否存在,存在时立刻返回或者超时后返回.默认超时事件是5s
write <path> <content> 写内容到指定文件中

Services代表一些Service.Service是一些在系统初始化时就启动或者退出时需要重启的程序.其格式如下:

service <name> <pathname> [ <argument> ]* <option> <option> ...

不难发现,首先需要为服务定义名字,并指定程序路径,然后便是通过option来修饰服务.同样先来看一下示例:

service ueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0

Options代表一些option.option用来修饰服务,决定了服务在什么时候运行以及怎样运行.AIL中提供了非常多的option,下面我们做个简单说明:

选项 解释
console 服务需要一个控制台.
critical 表示这是一个关键设备服务.如果4分钟内此服务退出4次以上,那么这个设备将重启进入recovery模式
disabled 服务不会自动启动,必须通过服务名显式启动
setenv <name> <value> 在进程启动过程中,将环境变量<name>的值设置为<value>,即以键值对的方式设置环境变量
socket <name> <type> <perm> [ <user> [ <group> [seclabel]]] 创建一个unix域下的socket,其被命名/dev/socket/<name>. 并将其文件描述符fd返回给服务进程.其中,type必须为dgram,stream或者seqpacke,user和group默认是0.seclabel是该socket的SELLinux的安全上下文环境,默认是当前service的上下文环境,通过seclabel指定.
user <username> 在执行此服务之前切换用户名,当前默认的是root.自Android M开始,即使它要求linux capabilities,也应该使用该选项.很明显,为了获得该功能,进程需要以root用户运行
group <groupname> 在执行此服务之前切换组名,除了第一个必须的组名外,附加的组名用于设置进程的补充组(借助setgroup,当前默认的是root
seclabel <seclabel> 在执行该服务之前修改其安全上下文,默认是init程序的上下文
oneshot 当服务退出时,不重启该服务
class <name> 为当前service设定一个类别.相同类别的服务将会同时启动或者停止,默认类名是default.
onrestart 当服务重启时执行该命令
priority <priority> 设置服务进程的优先级.优先级取值范围为-20~19,默认是0.可以通过setpriority()设置

用来引入一个要解析的其他配置文件,通常用于当前配置文件的扩展.其格式如下:

import <path>

如果path是个一个目录,则该目录下的每个.rc文件都被引入.

在初始化过程中,共有两次使用import来引入.rc文件:

  1. 在初始化引导期间,引入/init.rc文件
  2. 在执行mount_all命令时,引入/{system,vendor,odm}/etc/init/或者指定路径下的.rc文件

我们来看看init.rc文件引入的.rc文件:

import /init.environ.rcimport /init.usb.rcimport /init.${ro.hardware}.rcimport /init.${ro.zygote}.rc

Properties代表Init进程运行中的一些属性信息.在Init运行中,通过以下属性能够获取当前程序内部信息:

类型 说明
init.svc.<name> 指定名称服务的状态,有stopped,stopping,runing,restarting这种四种状态
init.action 获取当前正在执行的action
init.command 获取当前正在执行的command

到现在为止,有关AIL相关的知识基本介绍完毕,下面截取init.rc文件中的一段来做个简单的说明:

//引入其他要解析的rc文件import /init.environ.rcimport /init.usb.rcimport /init.${ro.hardware}.rcimport /init.usb.configfs.rcimport /init.${ro.zygote}.rc#定义了一个action,在init初始化之前触发on early-init # Set init and its forked children's oom_adj. write /proc/1/oom_score_adj -1000 # Disable sysrq from keyboard write /proc/sys/kernel/sysrq 0 # Set the security context of /adb_keys if present. restorecon /adb_keys # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628. mkdir /mnt 0775 root system # Set the security context of /postinstall if present. restorecon /postinstall #启动ueventd服务 start ueventd #...省略多行... #定义ueventd服务,设置服务为/sbin/ueventdservice ueventd /sbin/ueventd class core#为其设置类名为core critical#表明这是一个关键服务 seclabel u:r:ueventd:s0 #设置其安全上下文

AIL是一种非常简单的语言,主要用于定义启动流程中需要做的事情.

mkdir /dev/fscklogs 0770 root system //新建目录class_stop charger //终止服务trigger late-init //触发late-init
on <trigger>  
   <command>  
   <command>  
   <command>

services的格式如下:

      也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action。

 service <name> <pathname> [ <argument> ]* <option> <option> ...
    on boot  
        ifup lo  
        hostname localhost  
        domainname localdomain  

以service开头,name是指定这个服务的名称,pathname表示这个服务的执行文件路径,argument表示执行文件带的参数,option表示这个服务的一些配置我们看一个典型的例子就知道了

其中boot是触发器,下面三行是command

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.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。

这个是配置在 /init.zygote64_32.rc文件中的service, 它就是我们常说的zygote进程的启动配置

1.  boot

zygote是进程名,可执行文件路径在/system/bin/app_process64,执行文件参数(就是可执行程序main函数里面的那个args)是-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote

   这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger

后面的option是一些服务配置,比如class main表示所属class是main,相当于一个归类,其他service也可以归为main,他们会被一起启动或终止,service有一个name,也有一个class,就像工作中,你有一个名字叫foxleezh,也可以说你属于android部门.

2.  <name>=<value>

我上面说的这些东西,源码中已经有一个专门的文档用来说明,路径在platform/system/core/init/README.md,应当说这个文档写得还是挺不错的,认真读这个文档的话,基本的语法知识就都知道了,我简单翻译下

   当属性<name>被设置成<value>时被触发。例如,

Android Init Language

Android Init Language中由5类语法组成,分别是Actions, Commands, Services, Options, and Imports 每一行是一个语句,单词之间用空格分开,如果单词中有空格可以用反斜杠转义,也可以用双引号来引用文本避免和空格冲突,如果一行语句太长可以用 换行,用 # 表示注释Actions和Services可以作为一个独立的Section,所有的Commands和Options从属于紧挨着的Actions或Services,定义在第一个Section前的Commands和Options将被忽略掉 Actions和Services都是唯一的,如果定义了两个一样的Action,第二个Action的Command将追加到第一个Action,如果定义了两个一样的Service,第二个Service将被忽略掉并打印错误日志

Init .rc Files

Android Init Language是用后缀为.rc的纯文本编写的,而且是由多个分布在不同目录下的.rc文件组成,如下所述 /init.rc 是最主要的一个.rc文件,它由init进程在初始化时加载,主要负责系统初始化,它会导入 /init.${ro.hardware}.rc ,这个是系统级核心厂商提供的主要.rc文件当执行 mount_all 语句时,init进程将加载所有在 /{system,vendor,odm}/etc/init/ 目录下的文件,挂载好文件系统后,这些目录将会为Actions和Services服务有一个特殊的目录可能被用来替换上面的三个默认目录,这主要是为了支持工厂模式和其他非标准的启动模式,上面三个目录用于正常的启动过程这三个用于扩展的目录是

  1. /system/etc/init/ 用于系统本身,比如SurfaceFlinger, MediaService, and logcatd.
  2. /vendor/etc/init/ 用于SoC(系统级核心厂商,如高通),为他们提供一些核心功能和服务
  3. /odm/etc/init/ 用于设备制造商(odm定制厂商,如华为、小米),为他们的传感器或外围设备提供一些核心功能和服务

所有放在这三个目录下的Services二进制文件都必须有一个对应的.rc文件放在该目录下,并且要在.rc文件中定义service结构,有一个宏LOCAL_INIT_RC,可以帮助开发者处理这个问题. 每个.rc文件还应当包含一些与该服务相关的actions举个例子,在system/core/logcat目录下有logcatd.rc和Android.mk这两个文件. Android.mk文件中用LOCAL_INIT_RC这个宏,在编译时将logcatd.rc放在/system/etc/init/目录下,init进程在调用 mount_all 时将其加载,在合适的时机运行其定义的service并将action放入队列将init.rc根据不同服务分拆到不同目录,要比之前放在单个init.rc文件好. 这种方案确保init读取的service和action信息能和同目录下的Services二进制文件更加符合,不再像以前单个init.rc那样.另外,这样还可以解决多个services加入到系统时发生的冲突,因为他们都拆分到了不同的文件中在 mount_all 语句中有 "early" 和 "late" 两个可选项,当 early 设置的时候,init进程将跳过被 latemount 标记的挂载操作,并触发fs encryption state 事件,当 late 被设置的时候,init进程只会执行 latemount 标记的挂载操作,但是会跳过导入的 .rc文件的执行. 默认情况下,不设置任何选项,init进程将执行所有挂载操作

Actions

Actions由一行行命令组成. trigger用来决定什么时候触发这些命令,当一个事件满足trigger的触发条件时,这个action就会被加入到处理队列中(除非队列中已经存在)队列中的action按顺序取出执行,action中的命令按顺序执行. 这些命令主要用来执行一些操作(设备创建/销毁,属性设置,进程重启)Actions的格式如下:

on property:vold.decrypt=trigger_reset_main

 on <trigger> [&& <trigger>]* <command> <command> <command>

    class_reset main

Services

Services是init进程启动的程序,它们也可能在退出时自动重启. Services的格式如下:

3.  device-added-<path>

 service <name> <pathname> [ <argument> ]* <option> <option> ...

    当设备节点被添加时触发

Options

Options是Services的参数配置. 它们影响Service如何运行及运行时机console [<console>]Service需要控制台. 第二个参数console的意思是可以设置你想要的控制台类型,默认控制台是/dev/console ,/dev 这个前缀通常是被忽略的,比如你要设置控制台 /dev/tty0 ,那么只需要设置为console tty0critical表示Service是严格模式. 如果这个Service在4分钟内退出超过4次,那么设备将重启进入recovery模式disabled表示Service不能以class的形式启动,只能以name的形式启动setenv <name> <value>在Service启动时设置name-value的环境变量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_socketfile <path> <type>打开一个文件,并将fd返回给这个Service. type 只能是 "r", "w" or "rw". 它对应的本地实现在libcutils的android_get_control_file user <username>在启动Service前将user改为username,默认启动时user为root.在Android M版本,如果一个进程想拥有Linux capabilities(相当于Android中的权限吧),也只能通过设置这个值. 以前,一个程序要想有Linux capabilities,必须先以root身份运行,然后再降级到所需的uid.现在已经有一套新的机制取而代之,它通过fs_config允许厂商赋予特殊二进制文件Linux capabilities. 这套机制的说明文档在. 在Android O版本,程序可以通过capabilities参数直接申请所需的能力,参见下面的capabilities说明group <groupname> [ <groupname>* ]在启动Service前将group改为第一个groupname,第一个groupname是必须有的,默认值为root,第二个groupname可以不设置,用于追加组(通过setgroups).capabilities <capability> [ <capability>* ]在启动Service时将capabilities设置为capability. 'capability' 不能是"CAP_" prefix, like "NET_ADMIN" or "SETPCAP". 参考 ,里面有capability的说明.seclabel <seclabel>在启动Service前将seclabel设置为seclabel. 主要用于在rootfs上启动的service,比如ueventd, adbd.在系统分区上运行的service有自己的SELinux安全策略,如果不设置,默认使用init的安全策略.oneshot退出后不再重启class <name> [ <name>* ]为Service指定class名字. 同一个class名字的Service会被一起启动或退出,默认值是"default",第二个name可以不设置,用于service组.animation classanimation class 主要包含为开机动画或关机动画服务的service. 它们很早被启动,而且直到关机最后一步才退出.它们不允许访问/data 目录,它们可以检查/data目录,但是不能打开 /data 目录,而且需要在 /data 不能用时也正常工作 .onrestart 在Service重启时执行命令.writepid <file> [ <file>* ]当Service调用fork时将子进程的pid写入到指定文件. 用于cgroup/cpuset的使用,当/dev/cpuset/下面没有文件但ro.cpuset.default的值却不为空时,将pid的值写入到/dev/cpuset/cpuset_name/tasks文件中priority <priority>设置进程优先级. 在-20~19之间,默认值是0,能过setpriority实现namespace <pid|mnt>当fork这个service时,设置pid或mnt标记oom_score_adjust <value>设置子进程的 /proc/self/oom_score_adj 的值为 value,在 -1000 ~ 1000之间.

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的值等于bon property:a=b && property:c=d 这个定义有三种触发方式:

  1. 在初始化时,属性a=b,属性c=d.
  2. 在属性c=d的情况下,属性a被改为b.
  3. A在属性a=b的情况下,属性c被改为d.

Commands

bootchart [start|stop]启动或终止bootcharting. 这个出现在init.rc文件中,但是只有在/data/bootchart/enabled文件存在的时候才有效,否则不能工作chmod <octal-mode> <path>修改文件读写权限chown <owner> <group> <path>修改文件所有者或所属用户组class_start <serviceclass>启动所有以serviceclass命名的未启动的service(service有一个name,也有个class,这里的serviceclass就是class,class_start和后面的start是两种启动方式,class_start是class形式启动,start是name形式启动)class_stop <serviceclass> 终止所有以serviceclass命名的正在运行的serviceclass_reset <serviceclass>终止所有以serviceclass命名的正在运行的service,但是不禁用它们. 它们可以稍后被class_start重启class_restart <serviceclass>重启所有以serviceclass命名的servicecopy <src> <dst>复制一个文件,与write相似,比较适合二进制或比较大的文件.对于src,从链接文件、world-writable或group-writable复制是不允许的.对于dst,如果目标文件不存在,则默认权限是0600,如果存在就覆盖掉domainname <name>设置域名enable <servicename>将一个禁用的service设置为可用.如果这个service在运行,那么就会重启.一般用在bootloader时设置属性,然后启动一个service,比如on property:ro.boot.myfancyhardware=1enable my_fancy_service_for_my_fancy_hardwareexec [ <seclabel> [ <user> [ <group>* ] ] ] -- <command> [ <argument>* ]新建子进程并运行一个带指定参数的命令.

这个命令指定了seclabel,user,group.直到这个命令运行完才可以运行其他命令,seclabel可以设置为

表示用默认值,argument表示属性值.直到子进程新建完毕,init进程才继续执行.exec_start <service>启动一个service,只有当执行结果返回,init进程才能继续执行. 这个跟exec相似,只是将一堆参数的设置改在在service中定义export <name> <value>设置环境变量name-value. 这个环境变量将被所有已经启动的service继承hostname <name> 设置主机名ifup <interface>开启指定的网络接口insmod [-f] <path> [<options>]安装path下的模块,指定参数options.-f 表示强制安装,即便是当前Linux内核版本与之不匹配load_all_props加载/system, /vendor等目录下的属性,这个用在init.rc中load_persist_props加载/data 下的持久化属性. 这个用在init.rc中loglevel <level>设置日志输出等级,level表示等级mkdir <path> [mode] [owner] [group]创建一个目录,path是路径,mode是读写权限,默认值是755,owner是所有者,默认值root,group是用户组,默认值是root.如果该目录已存在,则覆盖他们的mode,owner等设置mount_all <fstab> [ <path> ]* [--<option>]当手动触发 "early" 和 "late"时,调用fs_mgr_mount_all 函数,指定fstab配置文件,并导入指定目录下的.rc文件详情可以查看init.rc文件中的有关定义mount <type> <device> <dir> [ <flag>* ] [<options>]在dir目录下挂载一个名叫device的设备_flag 包括 "ro", "rw", "remount", "noatime", ...options 包括 "barrier=1", "noauto_da_alloc", "discard", ... 用逗号分开,比如 barrier=1,noauto_da_allocrestart <service>终止后重启一个service,如果这个service刚被重启就什么都不做,如果没有在运行,就启动restorecon <path> [ <path>* ]恢复指定目录下文件的安全上下文.第二个path是安全策略文件. 指定目录不需要必须存在,因为它只需要在init中正确标记restorecon_recursive <path> [ <path>* ]递归地恢复指定目录下的安全上下文,第二个path是安全策略文件位置rm <path>调用 unlink删除指定文件. 最好用exec -- rm ...代替,因为这样可以确保系统分区已经挂载好rmdir <path>调用 rmdir 删除指定目录setprop <name> <value>设置属性name-value setrlimit <resource> <cur> <max>指定一个进程的资源限制start <service> 启动一个未运行的servicestop <service>终止一个正在运行的serviceswapon_all <fstab>调用 fs_mgr_swapon_all,指定fstab配置文件.symlink <target> <path>在path下创建一个指向target的链接sysclktz <mins_west_of_gmt>重置系统基准时间(如果是格林尼治标准时间则设置为0)trigger <event>触发事件event,由一个action触发到另一个action队列umount <path>卸载指定path的文件系统verity_load_state内部实现是加载dm-verity的状态verity_update_state <mount-point>内部实现是设置dm-verity的状态,并且设置partition.mount-point.verified的属性. 用于adb重新挂载,因为fs_mgr 不能直接设置它。 wait <path> [ <timeout> ]查看指定路径是否存在. 如果发现则返回,可以设置超时时间,默认值是5秒wait_for_prop <name> <value>等待name属性的值被设置为value,如果name的值一旦被设置为value,马上继续write <path> <content>打开path下的文件,并用write写入content内容. 如果文件不存在就会被创建,如果存在就会被覆盖掉

Imports

import关键字不是一个命令,但是如果有.rc文件包含它就会马上解析它里面的section,用法如下:import <path>解析path下的.rc文件 ,括展当前文件的配置。如果path是个目录,这个目录下所有.rc文件都被解析,但是不会递归,import被用于以下两个地方:1.在初始化时解析init.rc文件2.在mount_all时解析{system,vendor,odm}/etc/init/等目录下的.rc文件后面的内容主要是一些跟调试init进程相关的东西,比如init.svc.<name>可以查看service启动的状态,ro.boottime.init记录一些关键的时间点,Bootcharting是一个图表化的性能监测工具等,由于与语法关系不大,就不作翻译了

4.  device-removed-<path>

明白了.rc文件的语法,我们再来看看init进程是如何解析.rc文件,将这些语法转化为实际执行的代码的

   当设备节点被移除时添加

之前我们在文档中看到.rc文件主要有根目录下的 /init.rc ,以及{system,vendor,odm}/etc/init/这三个目录下的 *.rc ,然后就是如果有一个特殊目录被设置的话,就替代这些目录,明白这些,下面的代码就好理解了.

  1. service-exited-<name>
int main(int argc, char** argv) { ... const BuiltinFunctionMap function_map; /* * 1.C++中::表示静态方法调用,相当于java中static的方法 */ Action::set_function_map(&function_map); //将function_map存放到Action中作为成员属性 Parser& parser = Parser::GetInstance();//单例模式,得到Parser对象 /* * 1.C++中std::make_unique相当于new,它会返回一个std::unique_ptr,即智能指针,可以自动管理内存 * 2.unique_ptr持有对对象的独有权,两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作 * 3.移动操作的函数是 p1=std::move ,这样指针p指向的对象就移动到p1上了 * 4.接下来的这三句代码都是new一个Parser,然后将它们放到一个map里存起来 * 5.ServiceParser、ActionParser、ImportParser分别对应service action import的解析 */ parser.AddSectionParser("service",std::make_unique<ServiceParser>; parser.AddSectionParser("on", std::make_unique<ActionParser>; parser.AddSectionParser("import", std::make_unique<ImportParser>; std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty {//如果ro.boot.init_rc没有对应的值,则解析/init.rc以及/system/etc/init、/vendor/etc/init、/odm/etc/init这三个目录下的.rc文件 parser.ParseConfig("/init.rc"); parser.set_is_system_etc_init_loaded( parser.ParseConfig("/system/etc/init")); parser.set_is_vendor_etc_init_loaded( parser.ParseConfig("/vendor/etc/init")); parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init")); } else {//如果ro.boot.init_rc属性有值就解析属性值 parser.ParseConfig(bootscript); parser.set_is_system_etc_init_loaded; parser.set_is_vendor_etc_init_loaded; parser.set_is_odm_etc_init_loaded; }

   会在一个特定的服务退出时触发

2.1 ParseConfig

定义在 platform/system/core/init/init_parser.cpp

首先是判断传入的是目录还是文件,其实他们都是调用ParseConfigFile,ParseConfigDir就是遍历下该目录中的文件,对文件排个序,然后调用ParseConfigFile.

bool Parser::ParseConfig(const std::string& path) { if (is_dir(path.c_str { return ParseConfigDir; } return ParseConfigFile;}

而ParseConfigFile就是读取文件中的数据后,将数据传递给ParseData函数,最后遍历section_parsers_调用其EndFile函数,EndFile后面再分析,因为是多态实现,我们先看看ParseData

bool Parser::ParseConfigFile(const std::string& path) { LOG << "Parsing file " << path << "..."; Timer t; std::string data; if (!read_file(path, &data)) { //将数据读取到data return false; } data.push_back; // TODO: fix parse_config. ParseData(path, data); //解析数据 for (const auto& sp : section_parsers_) { sp.second->EndFile; } LOG << "(Parsing " << path << " took " << t << ".)"; return true;}

Actions后需要跟若干个命令,这些命令如下:

2.2 ParseData

ParseData 定义在 platform/system/core/init/init_parser.cpp

ParseData通过调用next_token函数遍历每一个字符,以空格或""为分割将一行拆分成若干个单词,调用T_TEXT将单词放到args数组中,当读到回车符就调用T_NEWLINE,在section_parsers_这个map中找到对应的on service import的解析器,执行ParseSection,如果在map中找不到对应的key,就执行ParseLineSection,当读到0的时候,表示一个Section读取结束,调用T_EOF执行EndSection.

void Parser::ParseData(const std::string& filename, const std::string& data) { //TODO: Use a parser with const input and remove this copy std::vector<char> data_copy(data.begin(), data.end; //将data的内容复制到data_copy中 data_copy.push_back; //追加一个结束符0 parse_state state; //定义一个结构体 state.filename = filename.c_str(); state.line = 0; state.ptr = &data_copy[0]; state.nexttoken = 0; SectionParser* section_parser = nullptr; std::vector<std::string> args; for  { switch (next_token(&state)) { // 遍历data_copy中每一个字符 case T_EOF: //如果是文件结尾,则调用EndSection if (section_parser) { section_parser->EndSection(); } return; case T_NEWLINE://读取了一行数据 state.line++; if (args.empty { break; } /* * 1.section_parsers_是一个std:map * 2.C++中std:map的count函数是查找key,相当于Java中Map的contains * 3.section_parsers_中只有三个key,on service import,之前AddSectionParser函数加入 */ if (section_parsers_.count { //判断是否包含 on service import if (section_parser) { section_parser->EndSection(); } section_parser = section_parsers_[args[0]].get();//取出对应的parser std::string ret_err; if (!section_parser->ParseSection(args, &ret_err)) {//解析对应的Section parse_error(&state, "%sn", ret_err.c_str; section_parser = nullptr; } } else if (section_parser) { //不包含 on service import则是command或option std::string ret_err; if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {//解析command或option parse_error(&state, "%sn", ret_err.c_str; } } args.clear(); break; case T_TEXT: //将读取的一行数据放到args中,args以空格或""作为分割,将一行数据拆分成单词放进数组中 args.emplace_back(state.text); break; } }}

这里其实涉及到on service import对应的三个解析器ActionParser,ServiceParser,ImportParser,它们是在之前加入到section_parsers_这个map中的

 Parser& parser = Parser::GetInstance(); parser.AddSectionParser("service",std::make_unique<ServiceParser>; parser.AddSectionParser("on", std::make_unique<ActionParser>; parser.AddSectionParser("import", std::make_unique<ImportParser>; void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) { section_parsers_[name] = std::move; }

它们都是SectionParser的子类,SectionParser有四个纯虚函数,分别是ParseSection、ParseLineSection、EndSection,EndFile.

class SectionParser {public: virtual ~SectionParser() { } /* * 1.C++中纯虚函数的定义格式是 virtual作为修饰符,然后赋值给0,相当于Java中的抽象方法 * 2.如果不赋值给0,却以virtual作为修饰符,这种是虚函数,虚函数可以有方法体,相当于Java中父类的方法,主要用于子类的重载 * 3.只要包含纯虚函数的类就是抽象类,不能new,只能通过子类实现,这个跟Java一样 */ virtual bool ParseSection(const std::vector<std::string>& args, std::string* err) = 0; virtual bool ParseLineSection(const std::vector<std::string>& args, const std::string& filename, int line, std::string* err) const = 0; virtual void EndSection() = 0; virtual void EndFile(const std::string& filename) = 0;};

接下来我将分析这三个Perser的ParseSection、ParseLineSection、EndSection,EndFile具体实现

1.  exec <path> [<argument> ]*

2.3 ActionParser

定义在platform/system/core/init/action.cpp

我们先看ParseSection,它先将args中下标1到结尾的数据复制到triggers数组中,然后是构建Action对象,调用InitTriggers,解析这些trigger

bool ActionParser::ParseSection(const std::vector<std::string>& args, std::string* err) { std::vector<std::string> triggers(args.begin() + 1, args.end; //将args复制到triggers中,除去下标0 if (triggers.size { *err = "actions must have a trigger"; return false; } auto action = std::make_unique<Action>; if (!action->InitTriggers(triggers, err)) { //调用InitTriggers解析trigger return false; } action_ = std::move; return true;}

InitTriggers通过比较是否以"property:"开头,区分trigger的类型,如果是property trigger,就调用ParsePropertyTrigger,如果是event trigger,就将args的参数赋值给event_trigger_,类型是string

bool Action::InitTriggers(const std::vector<std::string>& args, std::string* err) { const static std::string prop_str("property:"); for (std::size_t i = 0; i < args.size { ... if (!args[i].compare(0, prop_str.length(), prop_str)) { if (!ParsePropertyTrigger(args[i], err)) { return false; } } else { ... event_trigger_ = args[i]; } } return true;}

ParsePropertyTrigger函数先是将字符以"="分割为name-value,然后将name-value存入property_triggers_这个map中

bool Action::ParsePropertyTrigger(const std::string& trigger, std::string* err) { const static std::string prop_str("property:"); std::string prop_name(trigger.substr(prop_str.length; //截取property:后的内容 size_t equal_pos = prop_name.find; if (equal_pos == std::string::npos) { *err = "property trigger found without matching '='"; return false; } std::string prop_value(prop_name.substr(equal_pos + 1)); //取出value prop_name.erase(equal_pos); //删除下标为equal_pos的字符,也就是删除"=" if (auto [it, inserted] = property_triggers_.emplace(prop_name, prop_value); !inserted) { //将name-value存放到map中,emplace相当于put操作 *err = "multiple property triggers found for same property"; return false; } return true;}

从上面看出,ParseSection函数的作用就是构造一个Action对象,将trigger条件记录到Action这个对象中,如果是event trigger就赋值给event_trigger_,如果是property trigger就存放到property_triggers_这个map中. 接下来我们分析ParseLineSection

ParseLineSection是直接调用Action对象的AddCommand函数

bool ActionParser::ParseLineSection(const std::vector<std::string>& args, const std::string& filename, int line, std::string* err) const { return action_ ? action_->AddCommand(args, filename, line, err) : false;}

AddCommand看名字就大概知道是添加命令,它首先是做一些参数空值的检查,然后是调用FindFunction查找命令对应的执行函数,最后将这些信息包装成Command对象存放到commands_数组中,这里比较关键的就是FindFunction

bool Action::AddCommand(const std::vector<std::string>& args, const std::string& filename, int line, std::string* err) { ... //一些参数检查 auto function = function_map_->FindFunction(args[0], args.size() - 1, err);//查找命令对应的执行函数 if (!function) { return false; } AddCommand(function, args, filename, line); return true;}void Action::AddCommand(BuiltinFunction f, const std::vector<std::string>& args, const std::string& filename, int line) { commands_.emplace_back(f, args, filename, line);//commands_是个数组,emplace_back就相当于add}

FindFunction定义在platform/system/core/init/keyword_map.h

这个函数主要作用是通过命令查找对应的执行函数,比如.rc文件中定义chmod,那我们得找到chmod具体去执行哪个函数. 它首先是通过map()返回一个std:map,调用其find函数,find相当于Java中的get,但是返回的是entry,可以通过entry ->first和entry ->second获取key-value.找到的value是一个结构体,里面有三个值,第一个是参数最小数目,第二个是参数最大数目,第三个就是执行函数,之后作了参数的数目检查,也就是说命令后的参数要在最小值和最大值之间.

const Function FindFunction(const std::string& keyword, size_t num_args, std::string* err) const { using android::base::StringPrintf; auto function_info_it = map().find; //找到keyword对应的entry if (function_info_it == map { // end是最后一个元素后的元素,表示找不到 *err = StringPrintf("invalid keyword '%s'", keyword.c_str; return nullptr; } auto function_info = function_info_it->second;//获取value auto min_args = std::get<0>(function_info);//获取参数数量最小值 auto max_args = std::get<1>(function_info);//获取参数数量最大值 if (min_args == max_args && num_args != min_args) {//将实际参数数量与最大值最小值比较 *err = StringPrintf("%s requires %zu argument%s", keyword.c_str(), min_args, (min_args > 1 || min_args == 0) ? "s" : ""); return nullptr; } if (num_args < min_args || num_args > max_args) { if (max_args == std::numeric_limits<decltype>::max { *err = StringPrintf("%s requires at least %zu argument%s", keyword.c_str(), min_args, min_args > 1 ? "s" : ""); } else { *err = StringPrintf("%s requires between %zu and %zu arguments", keyword.c_str(), min_args, max_args); } return nullptr; } return std::get<Function>(function_info);//返回命令对应的执行函数 }

我们看看map()的实现,定义在platform/system/core/init/builtins.cpp

这个实现比较简单,就是直接构造一个map,然后返回. 比如{"bootchart", {1,1,do_bootchart}},表示命令名称叫bootchart,对应的执行函数是do_bootchart,允许传入的最小和最大参数数量是1

BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const { constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max(); //表示size_t的最大值 // clang-format off static const Map builtin_functions = { {"bootchart", {1, 1, do_bootchart}}, {"chmod", {2, 2, do_chmod}}, {"chown", {2, 3, do_chown}}, {"class_reset", {1, 1, do_class_reset}}, {"class_restart", {1, 1, do_class_restart}}, {"class_start", {1, 1, do_class_start}}, {"class_stop", {1, 1, do_class_stop}}, {"copy", {2, 2, do_copy}}, {"domainname", {1, 1, do_domainname}}, {"enable", {1, 1, do_enable}}, {"exec", {1, kMax, do_exec}}, {"exec_start", {1, 1, do_exec_start}}, {"export", {2, 2, do_export}}, {"hostname", {1, 1, do_hostname}}, {"ifup", {1, 1, do_ifup}}, {"init_user0", {0, 0, do_init_user0}}, {"insmod", {1, kMax, do_insmod}}, {"installkey", {1, 1, do_installkey}}, {"load_persist_props", {0, 0, do_load_persist_props}}, {"load_system_props", {0, 0, do_load_system_props}}, {"loglevel", {1, 1, do_loglevel}}, {"mkdir", {1, 4, do_mkdir}}, {"mount_all", {1, kMax, do_mount_all}}, {"mount", {3, kMax, do_mount}}, {"umount", {1, 1, do_umount}}, {"restart", {1, 1, do_restart}}, {"restorecon", {1, kMax, do_restorecon}}, {"restorecon_recursive", {1, kMax, do_restorecon_recursive}}, {"rm", {1, 1, do_rm}}, {"rmdir", {1, 1, do_rmdir}}, {"setprop", {2, 2, do_setprop}}, {"setrlimit", {3, 3, do_setrlimit}}, {"start", {1, 1, do_start}}, {"stop", {1, 1, do_stop}}, {"swapon_all", {1, 1, do_swapon_all}}, {"symlink", {2, 2, do_symlink}}, {"sysclktz", {1, 1, do_sysclktz}}, {"trigger", {1, 1, do_trigger}}, {"verity_load_state", {0, 0, do_verity_load_state}}, {"verity_update_state", {0, 0, do_verity_update_state}}, {"wait", {1, 2, do_wait}}, {"wait_for_prop", {2, 2, do_wait_for_prop}}, {"write", {2, 2, do_write}}, }; // clang-format on return builtin_functions;}

接下来我们看看EndSection,直接是调用ActionManager::GetInstance().AddAction

void ActionParser::EndSection() { if (action_ && action_->NumCommands { ActionManager::GetInstance().AddAction(std::move; }}

AddAction首先是查找是否有存在的同名Action,如果有就将他们的命令合并,没有就将它存入数组actions_中

void ActionManager::AddAction(std::unique_ptr<Action> action) { auto old_action_it = std::find_if(actions_.begin(), actions_.end(), [&action] (std::unique_ptr<Action>& a) { return action->TriggersEqual;//find_if是集合中用于比较的模板,上面这种写法是lambda表达式 if (old_action_it != actions_.end {//在数组actions中找到Action说明已经存在同名,就合并command (*old_action_it)->CombineAction; } else { //找不到就加入数组 actions_.emplace_back(std::move; }}bool Action::TriggersEqual(const Action& other) const { return property_triggers_ == other.property_triggers_ && event_trigger_ == other.event_trigger_;//比较之前记录的event trigger和property trigger}void Action::CombineAction(const Action& action) { for (const auto& c : action.commands_) { //将新的Action中的command合并到老的Action commands_.emplace_back; }}

EndFile是一个空实现,定义在platform/system/core/init/action.h

class ActionParser : public SectionParser {public: ActionParser() : action_ { } bool ParseSection(const std::vector<std::string>& args, std::string* err) override; bool ParseLineSection(const std::vector<std::string>& args, const std::string& filename, int line, std::string* err) const override; void EndSection() override; void EndFile(const std::string&) override { //空实现 }private: std::unique_ptr<Action> action_;};

讲了这么多,小结一下ActionParser做的事情. 它有三个重要的重载函数,ParseSection、ParseLineSection、EndSection.

  • ParseSection函数的作用是构造一个Action对象,将trigger条件记录到Action这个对象中
  • ParseLineSection作用是根据命令在一个map中找到对应的执行函数,然后将信息记录到之前构造的Action中
  • EndSection作用是将前两步构造的Action存入一个数组中,存入之前比较下数组中是否已经存在同名的Action,如果有就合并command

  创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。

2.4 ServiceParser

定义在platform/system/core/init/service.cpp

我们还是分析它的四个函数ParseSection、ParseLineSection、EndSection、EndFile

ParseSection首先是判断单词个数至少有三个,因为必须有一个服务名称和执行文件,然后是判断名称是否合法,主要是一些长度及内容的检查,最后就是构造一个Service对象

bool ServiceParser::ParseSection(const std::vector<std::string>& args, std::string* err) { if (args.size { // 传入单词个数至少三个 *err = "services must have a name and a program"; return false; } const std::string& name = args[1]; if (!IsValidName {//检查名称是否合法 *err = StringPrintf("invalid service name '%s'", name.c_str; return false; } std::vector<std::string> str_args(args.begin() + 2, args.end; service_ = std::make_unique<Service>(name, str_args);// 构造Service对象 return true;}

ParseLineSection直接执行Service的ParseLine函数

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args, const std::string& filename, int line, std::string* err) const { return service_ ? service_->ParseLine(args, err) : false;}

ParseLine的思路跟之前Action一样,就是根据option名称从map中找到对应的执行函数,然后执行这个函数.这些执行函数主要作用就是对传入参数做一些处理,然后将信息记录到Service对象中

bool Service::ParseLine(const std::vector<std::string>& args, std::string* err) { if (args.empty { *err = "option needed, but not provided"; return false; } static const OptionParserMap parser_map; auto parser = parser_map.FindFunction(args[0], args.size() - 1, err);//从map中找出执行函数 if  { return false; } return (this->*parser)(args, err);//执行找到的这个函数}

map()返回的map如下,定义在定义在platform/system/core/init/service.cpp中

Service::OptionParserMap::Map& Service::OptionParserMap::map() const { constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max(); // clang-format off static const Map option_parsers = { {"capabilities", {1, kMax, &Service::ParseCapabilities}}, {"class", {1, kMax, &Service::ParseClass}}, {"console", {0, 1, &Service::ParseConsole}}, {"critical", {0, 0, &Service::ParseCritical}}, {"disabled", {0, 0, &Service::ParseDisabled}}, {"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}}, {"ioprio", {2, 2, &Service::ParseIoprio}}, {"priority", {1, 1, &Service::ParsePriority}}, {"keycodes", {1, kMax, &Service::ParseKeycodes}}, {"oneshot", {0, 0, &Service::ParseOneshot}}, {"onrestart", {1, kMax, &Service::ParseOnrestart}}, {"oom_score_adjust", {1, 1, &Service::ParseOomScoreAdjust}}, {"namespace", {1, 2, &Service::ParseNamespace}}, {"seclabel", {1, 1, &Service::ParseSeclabel}}, {"setenv", {2, 2, &Service::ParseSetenv}}, {"socket", {3, 6, &Service::ParseSocket}}, {"file", {2, 2, &Service::ParseFile}}, {"user", {1, 1, &Service::ParseUser}}, {"writepid", {1, kMax, &Service::ParseWritepid}}, }; // clang-format on return option_parsers;}

接下来我们看看EndSection,直接调用ServiceManager的AddService函数

void ServiceParser::EndSection() { if  { ServiceManager::GetInstance().AddService(std::move); }}

AddService的实现比较简单,就是通过比较service的name,查看存放Service的数组services_中是否有同名的service,如果有就打印下错误日志,直接返回,如果不存在就加入数组中

void ServiceManager::AddService(std::unique_ptr<Service> service) { Service* old_service = FindServiceByName(service->name; //查找services_中是否已存在同名service if (old_service) { LOG << "ignored duplicate definition of service '" << service->name() << "'"; return; } services_.emplace_back(std::move;//加入数组}Service* ServiceManager::FindServiceByName(const std::string& name) const { auto svc = std::find_if(services_.begin(), services_.end(), [&name] (const std::unique_ptr<Service>& s) { return name == s->name;//跟之前action一样,遍历数组进行比较,查找同名service if (svc != services_.end { return svc->get(); //找到就返回service } return nullptr;}

EndFile依然是一个空实现,定义在platform/system/core/init/service.h

class ServiceParser : public SectionParser {public: ServiceParser() : service_ { } bool ParseSection(const std::vector<std::string>& args, std::string* err) override; bool ParseLineSection(const std::vector<std::string>& args, const std::string& filename, int line, std::string* err) const override; void EndSection() override; void EndFile(const std::string&) override { //空实现 }private: bool IsValidName(const std::string& name) const; std::unique_ptr<Service> service_;};

从上面可以看出,ServiceParser的处理跟ActionParser差不多,区别在于Action将执行函数存起来等待Trigger触发时执行,Service找到执行函数后是马上执行

    2.  export <name> <value>

2.4 ImportParser

定义在platform/system/core/init/import_parser.cpp

最后我们看看ImportParser,ImportParser的ParseLineSection、EndSection都是空实现,只实现了ParseSection和EndFile,因为它的语法比较单一,只有一行. 我们来看看它的ParseSection函数

首先检查单词只能是两个,因为只能是import xxx 这种语法,然后调用expand_props处理下参数,最后将结果放入数组imports_存起来

bool ImportParser::ParseSection(const std::vector<std::string>& args, std::string* err) { if (args.size { //检查参数只能是两个 *err = "single argument needed for importn"; return false; } std::string conf_file; bool ret = expand_props(args[1], &conf_file); //处理第二个参数 if  { *err = "error while expanding import"; return false; } LOG << "Added '" << conf_file << "' to import list"; imports_.emplace_back(std::move(conf_file)); //存入数组 return true;}

expand_props 定义在platform/system/core/init/util.cpp ,主要作用就是找到${x.y}或$x.y这种语法,将x.y取出来作为name,去属性系统中找对应的value,然后替换

bool expand_props(const std::string& src, std::string* dst) { const char* src_ptr = src.c_str(); if  { return false; } /* - variables can either be $x.y or ${x.y}, in case they are only part * of the string. * - will accept $$ as a literal $. * - no nested property expansion, i.e. ${foo.${bar}} is not supported, * bad things will happen * - ${x.y:-default} will return default value if property empty. */ //这段英文大概的意思是 参数要么是$x.y,要么是${x.y},它们都是路径的一部分,$$表示字符 $ , //${foo.${bar}}这种递归写法是不支持的,因为会发生一些糟糕的事情 //${x.y:-default}会将default作为默认值返回,如果找不到对应的属性值的话 while  { const char* c; c = strchr(src_ptr, '$'); if  { // 找不到$符号,直接将dst赋值为src返回 dst->append; return true; } dst->append(src_ptr, c); c++; if (*c == '$') { //跳过$ dst->push_back; src_ptr = c; continue; } else if (*c == '') { return true; } std::string prop_name; std::string def_val; if (*c == '{') { //找到 { 就准备找 }的下标,然后截取它们之间的字符串,对应${x.y}的情况 c++; const char* end = strchr; if  { // failed to find closing brace, abort. LOG << "unexpected end of string in '" << src << "', looking for }"; return false; } prop_name = std::string; //截取{}之间的字符串作为name c = end + 1; size_t def = prop_name.find; //如果发现有 ":-" ,就将后面的值作为默认值先存起来 if (def < prop_name.size { def_val = prop_name.substr; prop_name = prop_name.substr; } } else { //对应$x.y的情况 prop_name = c; LOG << "using deprecated syntax for specifying property '" << c << "', use ${name} instead"; c += prop_name.size(); } if (prop_name.empty { LOG << "invalid zero-length property name in '" << src << "'"; return false; } std::string prop_val = android::base::GetProperty(prop_name, ""); //通过name在属性系统中找对应的value,内部调用的是之前属性系统的__system_property_find函数 if (prop_val.empty { //没有找到值就返回默认值 if (def_val.empty { LOG << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'"; return false; } prop_val = def_val; } dst->append; src_ptr = c; } return true;}

EndFile的实现比较简单,就是复制下ParseSection函数解析的.rc文件数组,然后遍历数组,调用最开始的ParseConfig函数解析一个完整的路径

void ImportParser::EndFile(const std::string& filename) { auto current_imports = std::move; imports_.clear(); for (const auto& s : current_imports) { if (!Parser::GetInstance().ParseConfig { PLOG << "could not import file '" << s << "' from '" << filename << "'"; } }}

由此,我们将Android Init Language语法的转化过程分析完毕,其实它们核心的解析器就三个,ActionParser,ServiceParser,ImportParser.而这几个解析器主要是实现ParseSection、ParseLineSection、EndSection、EndFile四个函数

  • ParseSection用于解析Section的第一行,比如
on earlyservice ueventd /sbin/ueventdimport /init.${ro.zygote}.rc
  • ParseLineSection用于解析Section的command或option,比如
write /proc/1/oom_score_adj -1000class core
  • EndSection用于处理Action和Service同名的情况,以及将解析的对象存入数组备用
  • EndFile只有在ImportParser中有用到,主要是解析导入的.rc文件

经过上一步的解析,系统从各种.rc文件中读取了需要执行的Action和Service,但是还是需要一些额外的配置,也需要加入触发条件准备去触发

 // Turning this on and letting the INFO logging be discarded adds 0.2s to // Nexus 9 boot time, so it's disabled by default. if  parser.DumpState(); //打印一些当前Parser的信息,默认是不执行的 ActionManager& am = ActionManager::GetInstance(); am.QueueEventTrigger("early-init");//QueueEventTrigger用于触发Action,这里触发 early-init事件 // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); //QueueBuiltinAction用于添加Action,第一个参数是Action要执行的Command,第二个是Trigger // ... so that we can start queuing up actions that require stuff from /dev. am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits"); am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict"); am.QueueBuiltinAction(keychord_init_action, "keychord_init"); am.QueueBuiltinAction(console_init_action, "console_init"); // Trigger all the boot actions to get us started. am.QueueEventTrigger; // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random // wasn't ready immediately after wait_for_coldboot_done am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); // Don't mount filesystems or start core system services in charger mode. std::string bootmode = GetProperty("ro.bootmode", ""); if (bootmode == "charger") { am.QueueEventTrigger("charger"); } else { am.QueueEventTrigger("late-init"); } // Run all property triggers based on current state of the properties. am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)

3.1 QueueEventTrigger

定义在platform/system/core/init/action.cpp

它并没有去触发trigger,而是构造了一个EventTrigger对象,放到队列中存起来

void ActionManager::QueueEventTrigger(const std::string& trigger) { trigger_queue_.push(std::make_unique<EventTrigger>;}class EventTrigger : public Trigger {public: explicit EventTrigger(const std::string& trigger) : trigger_ { } bool CheckTriggers(const Action& action) const override { return action.CheckEventTrigger; }private: const std::string trigger_;};

3.  ifup <interface>

3.2 QueueBuiltinAction

定义在platform/system/core/init/action.cpp

这个函数有两个参数,第一个参数是一个函数指针,第二参数是字符串. 首先是创建一个Action对象,将第二参数作为Action触发条件,将第一个参数作为Action触发后的执行命令,并且又把第二个参数作为命令的参数,最后是将Action加入触发队列并加入Action列表

void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) { auto action = std::make_unique<Action>; std::vector<std::string> name_vector{name}; if (!action->InitSingleTrigger { //调用InitTriggers,之前讲过用于将name加入Action的trigger列表 return; } action->AddCommand(func, name_vector);//加入Action的command列表 trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get;//将Action加入触发队列 actions_.emplace_back(std::move;//加入Action列表}

之前的所有工作都是往各种数组、队列里面存入信息,并没有真正去触发,而接下来的工作就是真正去触发这些事件,以及用epoll不断监听新的事件

 while  { // By default, sleep until something happens. int epoll_timeout_ms = -1; //epoll超时时间,相当于阻塞时间 /* * 1.waiting_for_prop和IsWaitingForExec都是判断一个Timer为不为空,相当于一个标志位 * 2.waiting_for_prop负责属性设置,IsWaitingForExe负责service运行 * 3.当有属性设置或Service开始运行时,这两个值就不为空,直到执行完毕才置为空 * 4.其实这两个判断条件主要作用就是保证属性设置和service启动的完整性,也可以说是为了同步 */ if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec { am.ExecuteOneCommand(); //执行一个command } if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec { restart_processes(); //重启服务 // If there's a process that needs restarting, wake up in time for that. if (process_needs_restart_at != 0) { //当有进程需要重启时,设置epoll_timeout_ms为重启等待时间 epoll_timeout_ms = (process_needs_restart_at - time * 1000; if (epoll_timeout_ms < 0) epoll_timeout_ms = 0; } // If there's more work to do, wake up again immediately. if (am.HasMoreCommands epoll_timeout_ms = 0; //当还有命令要执行时,将epoll_timeout_ms设置为0 } epoll_event ev; /* * 1.epoll_wait与上一篇中讲的epoll_create1、epoll_ctl是一起使用的 * 2.epoll_create1用于创建epoll的文件描述符,epoll_ctl、epoll_wait都把它创建的fd作为第一个参数传入 * 3.epoll_ctl用于操作epoll,EPOLL_CTL_ADD:注册新的fd到epfd中,EPOLL_CTL_MOD:修改已经注册的fd的监听事件,EPOLL_CTL_DEL:从epfd中删除一个fd; * 4.epoll_wait用于等待事件的产生,epoll_ctl调用EPOLL_CTL_ADD时会传入需要监听什么类型的事件, * 比如EPOLLIN表示监听fd可读,当该fd有可读的数据时,调用epoll_wait经过epoll_timeout_ms时间就会把该事件的信息返回给&ev */ int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms)); if  { PLOG << "epoll_wait failed"; } else if  {  ev.data.ptr)();//当有event返回时,取出ev.data.ptr(之前epoll_ctl注册时的回调函数),直接执行 //上一篇中在signal_handler_init和start_property_service有注册两个fd的监听,一个用于监听SIGCHLD,一个用于监听属性设置 } } return 0;}

   启动网络接口

4.1 ExecuteOneCommand

定义在platform/system/core/init/action.cpp

从名字可以看出,它只执行一个command,是的,只执行一个. 在函数一开始就从trigger_queue_队列中取出一个trigger,然后遍历所有action,找出满足trigger条件的action加入待执行列表current_executing_actions_中,接着从这个列表中取出一个action,执行它的第一个命令,并将命令所在下标自加1. 由于ExecuteOneCommand外部是一个无限循环,因此按照上面的逻辑一遍遍执行,将按照trigger表的顺序,依次执行满足trigger条件的action,然后依次执行action中的命令.

void ActionManager::ExecuteOneCommand() { // Loop through the trigger queue until we have an action to execute while (current_executing_actions_.empty() && !trigger_queue_.empty {//current_executing_actions_.empty保证了一次只遍历一个trigger for (const auto& action : actions_) {//遍历所有的Action if (trigger_queue_.front()->CheckTriggers {//满足当前Trigger条件的就加入队列current_executing_actions_ current_executing_actions_.emplace(action.get; } } trigger_queue_.pop();//从trigger_queue_中踢除一个trigger } if (current_executing_actions_.empty { return; } auto action = current_executing_actions_.front();//从满足trigger条件的action队列中取出一个action if (current_command_ == 0) { std::string trigger_name = action->BuildTriggersString(); LOG << "processing action (" << trigger_name << ")"; } action->ExecuteOneCommand(current_command_);//执行该action中的第current_command_个命令 // If this was the last command in the current action, then remove // the action from the executing list. // If this action was oneshot, then also remove it from actions_. ++current_command_; //下标加1 if (current_command_ == action->NumCommands { //如果是最后一条命令 current_executing_actions_.pop();//将该action从current_executing_actions_中踢除 current_command_ = 0; if (action->oneshot {//如果action只执行一次,将该action从数组actions_中踢除 auto eraser = [&action] (std::unique_ptr<Action>& a) { return a.get() == action; }; actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser)); } }}

4.  import <filename>

4.1 restart_processes

定义在platform/system/core/init/init.cpp

restart_processes调用的其实是ForEachServiceWithFlags函数,这个函数主要是遍历services_数组,比较它们的flags是否是SVC_RESTARTING,也就是当前service是否是等待重启的,如果是就执行它的RestartIfNeeded函数

static void restart_processes(){ process_needs_restart_at = 0; ServiceManager::GetInstance().ForEachServiceWithFlags(SVC_RESTARTING, [](Service* s) { s->RestartIfNeeded(&process_needs_restart_at); });}void ServiceManager::ForEachServiceWithFlags(unsigned matchflags, void (Service* svc)) const { for (const auto& s : services_) { //遍历所有service if (s->flags() & matchflags) {//找出flags是SVC_RESTARTING的,执行func,也就是传入的RestartIfNeeded func; } }}

   指定要解析的其他配置文件。常被用于当前配置文件的扩展

4.2 RestartIfNeeded

定义在platform/system/core/init/service.cpp

这个函数将主要工作交给了Start,也就是具体的启动service,但是交给它之前做了一些判断,也就是5秒内只能启动一个服务,如果有多个服务,那么后续的服务将进入等待

void Service::RestartIfNeeded(time_t* process_needs_restart_at) { boot_clock::time_point now = boot_clock::now(); boot_clock::time_point next_start = time_started_ + 5s; //time_started_是上一个service启动的时间戳 if (now > next_start) { //也就是说两个服务进程启动的间隔必须大于5s flags_ &= (~SVC_RESTARTING); // &= 加 ~ 相当于取消标记 Start(); return; } time_t next_start_time_t = time + time_t(std::chrono::duration_cast<std::chrono::seconds>(next_start - now).count; if (next_start_time_t < *process_needs_restart_at || *process_needs_restart_at == 0) { *process_needs_restart_at = next_start_time_t;//如果两个service启动间隔小于5s,将剩余时间赋值给process_needs_restart_at }}

5.  hostname <name>

4.2 Start

定义在platform/system/core/init/service.cpp

Start是具体去启动服务了,它主要是调用clone或fork创建子进程,然后调用execve执行配置的二进制文件,另外根据之前在.rc文件中的配置,去执行这些配置

bool Service::Start() { ... //清空标记,根据service的配置初始化console、SELinux策略等 LOG << "starting service '" << name_ << "'..."; pid_t pid = -1; if (namespace_flags_) {//这个标记当service定义了namespace时会赋值为CLONE_NEWPID|CLONE_NEWNS pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr); //以clone方式在新的namespace创建子进程 } else { pid = fork();//以fork方式创建子进程 } if  {//表示创建子进程成功 ... //执行service配置的其他参数,比如setenv、writepid等 std::vector<char*> strs; ExpandArgs(args_, &strs);//将args_解析一下,比如有${x.y},然后赋值表strs if (execve(strs[0],  &strs[0],  ENV) < 0) { //执行系统调用execve,也就是执行配置的二进制文件,把参数传进去 PLOG << "cannot execve('" << strs[0] << "')"; } _exit; } if (pid < 0) { //子进程创建失败 PLOG << "failed to fork for '" << name_ << "'"; pid_ = 0; return false; } ... //执行service其他参数如oom_score_adjust_,改变service运行状态等}

小结

这一阶段Init进程做了许多重要的事情,比如解析.rc文件,这里配置了所有需要执行的action和需要启动的service,Init进程根据语法一步步去解析.rc,将这些配置转换成一个个数组、队列,然后开启无限循环去处理这些数组、队列中的command和service,并且通过epoll监听子进程结束和属性设置.

至此,我已经将Init进程的三个阶段讲解完了,下一篇我将讲解.rc中配置的一个重要的service--zygote,它是我们app程序的鼻祖.

   设置主机名

6.  chdir <directory>

   改变工作目录

7.  chmod <octal-mode><path>

   改变文件的访问权限

本文由美高梅网址注册发布于澳门mgm4858集团登录网址,转载请注明出处:所以Android系统引入了init.rc,通过行尾添加反斜杠

关键词: