Android Init Language

Android Init Language

在 Android 项目开发过程中,我们经常会碰到大量的 .rc 文件,例如:init.rcinit.mtxxxx.usb.rc 等。这些 .rc 文件究竟是什么?按照什么规则去编写?又如何去被使用的?接下来让我们一起学习一下。

一、基本概念

Android Init Language 一般被称为 “Android 初始化语言” ,简称为 AIL 。Android Init Language 主要由五类语句组成:ActionsCommandsServicesOptionsImports

Android Init Language 是 line-oriented,每一行由若干个被空格分开的 token 组成。

所谓的 line-oriented 意思是面向行的,以行为单位运行,工作方式与操作系统的命令行相似。token 指的是计算机语言中的一个单词。如果想要将空格插入到 token 中,可以类似 C 语言采用反斜杠转义符 \ 进行转义。也可以使用 "" 双引号防止空格将文本分成多个 token。 \ 被用在最后一行时,可用于换行,代表将下一行与当前行合并成一行进行处理,主要是用于避免一行的字符太长,用法类似于 C 语言。Android Init Language 使用 # 来注释。

Android Init Language 通常被用于以 .rc 为扩展名的纯文本文件。例如:init.rc、init.usb.rc 等。

二、语句解析

前面有说过 Android Init Language 主要由五类语句组成,它们分别是:Actions、Commands、Services、Options、Imports。在介绍这些语句之前,需要先了解一下 Section(段落/分组)。一个 rc 文件会有很多个 Section,Actions 和 Services 隐式声明了一个新的 Section,所有的 Commands 或者 Options 属于离它本身最近的 Section。Services 是唯一的,不能有两个名称相同 Service,如果重复定义 Service,它会被忽略并被记录错误信息。

2.1 Actions

Actions(动作)是一系列命令的集合,简单来说一个 Action 是由若干条 Command 组成的。每一个 Action 都会有一个 trigger 用于确定 Action 何时执行。为便于理解,我们可以将 Actions 类比成 if 条件语句,trigger 就是我们的判断条件,判断条件满足时就会执行 Action 的 Commands。当然在这里不能将 trigger 叫做判断条件,而应该是触发条件或者触发器。当一个事件的发生匹配到一个 Action 的 trigger,该 Action 将被添加到执行队列的尾部(除非它已经在队列中)。队列里的每一个 Action 都会按照顺序出队列执行,每一个 Action 下的每条 Command 也会按照顺序一一执行。在执行这些命令时,init 还同时处理着其他活动,包括设备节点的创建和销毁、设置属性、重新启动进程等。

Actions 格式如下:

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

如上可以看出:Action 是以关键字 on 开头的,然后紧接着是 trigger(触发条件),接下来是若干条 Command。例如:

# 在 early-init 阶段会被触发,并执行 Commands
on early-init
    write /sys/module/musb_hdrc/parameters/kernel_init_done 1

# 在 boot 阶段会被触发,并执行 Commands
on boot
    setprop sys.usb.configfs 1
    setprop sys.usb.ffs.aio_compat 0
    setprop vendor.usb.controller "musb-hdrc"
    setprop vendor.usb.acm_cnt 0
    setprop vendor.usb.acm_port0 ""
    setprop vendor.usb.acm_port1 ""
    setprop vendor.usb.acm_enable 0

#  当属性 sys.usb.config 等于 none 并且 sys.usb.configfs 等于 1 时触发,执行 Commands
on property:sys.usb.config=none && property:sys.usb.configfs=1
    write /config/usb_gadget/g1/idVendor ${vendor.usb.vid}
    rm /config/usb_gadget/g1/configs/b.1/f4
    rm /config/usb_gadget/g1/configs/b.1/f5
    write /sys/devices/platform/mt_usb/saving 0

2.2 Services

Services(服务)是一个程序,它在初始化时启动,并在退出时重启(可选)。Services 通常是以 service 开始,后面跟着服务的名称以及服务路径和可选参数,然后接着是一系列的 Options。

Services 格式如下:

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

例如下面两个 Service:ueventd、console:

service ueventd /system/bin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group shell log readproc
    seclabel u:r:shell:s0
    setenv HOSTNAME console

2.3 Options

Options 是 Services 的修饰符。Services 运行及初始化的时间和方式都会受 Options 的影响。

简单介绍几个 Option:

capabilities [ <capability>\* ]

Capabilities(能力) 的主要思想在于分割 root 用户的特权,即将 root 的特权分割成不同的能力,每种能力代表一定的特权操作。在执行服务时,设置 capabilities 选项需要注意:我们所添加的 ‘capability’ 应该是 Linux Capability 机制中不带 “CAP_” 前缀的 capability。例如 Linux Capabilities 中的 CAP_SETPCAP,在 这里应该是 “SETPCAP”

如果没有提供任何 Capabilities,那么所有的 capabilities 都将从该 Service 中移除,即使它以 root 身份运行。

Linux Capabilities 机制:capabilities(7) - Linux manual page (man7.org)

class <name> [ <name>\* ]

为 Services 指定类名。命令类中所有服务可以一起启动或停止。如果一个 Service 未通过 class 选项定义所属类,那么该 Service 属于“默认”类。

console [<console>]

表明该 Service 需要启用控制台。可以通过可选参数选择一个特定的控制台而不是默认的。默认的 “/dev/console” 可以通过设置内核参数 “androidboot.console” 来改变。

disabled 

此 Service 并不会跟随它的类去启动,必须通过名称或者接口名称显示启动。

2.4 Commands

Commands 就是命令,Action 实际上就是 Commands 的集合。

常见的 Commands 如下:

chmod <octal-mode> <path>

修改文件访问权限

chown <owner> <group> <path>

更改文件所有者和组

export <name> <value>

将环境变量名称设置为全局环境中的值(该命令执行后启动的所有进程都会继承)

insmod [-f] <path> [<option>]

使用指定的 option 在 path 安装模块。-f:强制安装模块(即使运行的 kernel 版本和编译模块的 kernel 版本不匹配)

loglevel <level>

设置 init 的日志级别,日志级别:0 - 7。0 代表只输出致命 log,7 代表输出所有 log。数值对应内核日志级别,但此命令不影响内核日志级别

mkdir <path> [<mode>] [<owner>] [<group>] [encryption=<action>] [key=<key>] 

在指定路径创建目录,可设置读写权限、所有者以及群组。如果未设置则默认权限为 755,owner 和 group 为 root。

action 选项如下:

  • None:不采取加密操作,如果父级是加密的,该目录会被加密。
  • Require:加密目录,如果加密失败则终止启动过程。
  • Attempt:尝试设置加密策略,但是失败会继续。
  • DeleteIfNecessary:如果需要设置加密策略,递归删除目录。

key 选项如下:

  • ref:使用系统范围的 DE 密钥。
  • per_boot_ref:使用每次启动时新生成的密钥。
symlink <target> <path>

创建符号链接。

rm <path>

删除命令,调用 unlink(2) 函数

rmdir <path>

调用 rmdir(2) 删除一个目录,该目录必须为空。

mount_all [ <fstab> ] [--<option>]

调用 do_mount_all 挂载 fstab。option 可以是 --early 也可以是 --late

设置--early 后,init 将跳过带有 “latemount” 标签的挂载项,并触发 fs 加密状态事件。设置 --late 后,init 将只挂在带有 "latemount" 标签的挂载项。默认情况下没有设置选项,mount_all 将处理给定 fstab 中的所有挂载项。如果不指定 fstab,将在运行时按顺序扫描 /odm/etc/vendor/etc/ 下的 fstab.${ro.boot.fstab_fuffix}fstab.${ro.hardware} 或者 fstab.${ro.hardware.platform}.

setprop <name> <value>

设置属性。

write <path> <content>

在指定 path 打开文件并写入字符串。如果文件不存在,它将被创建。

trigger <event>

触发一个事件。

2.5 Imports

Imports 用于在当前 .rc 文件中导入其他 .rc 文件。import 不是命令,是一个单独的 section,它不会作为 action 的一部分而发生。Import 会在解析 .rc 文件时处理,在init 解析完当前 rc 文件后,会继续解析引入的 rc 文件。

import <path>

解析 init 配置文件,扩展当前配置。如果 path 是一个目录,则目录中的每个文件都会被解析为一个配置文件。由于不是递归的,嵌套目录将不被解析。

Imports 遵循以下规则。

  • init .rc 文件的时机

    1. 导入 /system/etc/init/hw/init.rc 或者在初始引导期间属性 ro.boot.init_rc 被触发。
    2. /system/etc/init/hw/init.rc 后导入 /{system,system_ext,vendor,odm,product}/etc/init/下的 rc 文件。
  • 由于遗留原因,导入文件的顺序有点复杂。

    1. /system/etc/init/hw/init.rc 被解析后,递归的解析它的每个 import。
    2. /system/etc/init/ 下的文件按字母顺序排列并按顺序解析,import 会在每个文件被解析后发生。
    3. /system_ext/etc/init/vendor/etc/init/odm/etc/init/product/etc/init 与步骤 2 保持一致。

伪代码:

fn Import(file)
  Parse(file)
  for (import : file.imports)
    Import(import)
Import(/system/etc/init/hw/init.rc)
Directories = [/system/etc/init, /system_ext/etc/init, /vendor/etc/init, 
/odm/etc/init, /product/etc/init]
for (directory : Directories)
  files = <Alphabetical order of directory's contents>
  for (file : files)
    Import(file)

Action 按照它们被解析的顺序执行。例如 post-fs-data 操作在 /system/etc/init/hw/init.rc 中总是按照他在文件中的显示顺序被第一个执行。

2.6 Triggers

Triggers 是可用于匹配某些类型的字符串事件并用于触发动作发生。Trigger 分为事件触发器和属性触发器。 事件触发器是由 ‘trigger’ 命令或由 init 可执行文件中的 QueueEventTrigger() 函数触发。事件触发器采用简单字符串的形式,例如“boot”或“late-init”。 属性触发器是在给属性值发生变化时触发,比如给定新的 value,或者由旧的 value 更改为新的 value 。一般格式为:property:=property:=*

一个 Action 可以有多个属性触发器,但只能有一个事件触发器。

例如:

on boot && property:a=b 定义了一个 action,它只在 ‘boot’ 事件触发器发生且属性 a 等于 b。

on property:a=b && property:c=d 定义了一个 action,它可能会执行三次:

​ 1. 再初始引导期间,如果属性 a=b 且 属性 c=d。

​ 2. 任何时候属性 a 的值被设置为 b,而且属性 c 的值已经设置为 d。

​ 3. 任何时候属性 c 的值被设置为 d,而且属性 a 的值已经设置为 b。

2.7 Trigger Sequence

Init 在早起启动期间使用以下触发器序列。这些是 init.cpp 中定义的内置触发器。

  1. early-init:序列中的第一个,在 cgroups 被配置之后触发,但是在 ueventd 冷启动完成之前
  2. init:在冷启动完成之后触发。
  3. charger:如果 ro.bootmode == "charger" 则触发。
  4. late-init:如果ro.bootmode != "charger" 触发,或者通过 healthd 从充电模式触发启动。

其余触发器在 init.rc 中配置,不是内置的。

  1. early-fs :启动 vold
  2. fsvold 已经启动,挂载分区未标记为 first-stage 或者 latemounted。
  3. post-fs :配置任何依赖于早期挂载的东西。
  4. late-fs :挂载标记为 latemunted 的分区
  5. post-fs-data :挂载和配置 /data,设置加密; /metadata 如果无法在 first-stage init 中挂载,则在此处重新格式化。
  6. zygote-start :启动 zygote
  7. early-bootzygote 启动之后。
  8. bootearly-boot 动作完成之后。

2.8 Properties

Init 通过以下属性提供状态信息。

init.svc.<name>

命名服务的状态(“stopped”, “stopping”, “running”, “restarting”)

dev.mnt.blk.<mount_point>

该属性将挂载点和存储设备名称关联。

mount_point:挂载路径,将 / 替换为 . 。比如:/mnt/rescue,则属性为 dev.mnt.blk.mnt.rescue

viva:/ # getprop | grep dev.mnt.blk.
[dev.mnt.blk.cust]: [sdc]
[dev.mnt.blk.data]: [dm-9]
[dev.mnt.blk.metadata]: [sdc]
[dev.mnt.blk.mnt.rescue]: [sdc]
[dev.mnt.blk.mnt.vendor.nvcfg]: [sdc]
[dev.mnt.blk.product]: [dm-6]
[dev.mnt.blk.root]: [dm-4]
[dev.mnt.blk.vendor]: [dm-5]

三、Init Rc 解析流程

3.1 init.rc

前面说过 Android Init Language 被用于以 .rc 为扩展名的纯文本文件。Android 系统里有大量的 .rc 文件。其中 /system/etc/init/hw/init.rc 为基础 .rc 文件,在系统启动时由 init 执行加载,负责系统的初始化设置。然后 Init 会去加载 /{system,system_ext,vendor,odm,product}/etc/init/ 文件夹下的 rc 文件,加载规则在 Imports 部分已经说过。

init.rc 文件源码位于:system/core/rootdir/init.rcinit.rc 是一个可配置的初始化文件,一般定制厂商会配置额外的初始化配置。如下:

import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc

# Cgroups are mounted right before early-init using list from /etc/cgroups.json
on early-init
    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0

    # Android doesn't need kernel module autoloading, and it causes SELinux
    # denials.  So disable it by setting modprobe to the empty string.  Note: to
    # explicitly set a sysctl to an empty string, a trailing newline is needed.
    write /proc/sys/kernel/modprobe \n

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    # Set the security context of /postinstall if present.
    restorecon /postinstall

    mkdir /acct/uid

    # memory.pressure_level used by lmkd
    chown root system /dev/memcg/memory.pressure_level
    chmod 0040 /dev/memcg/memory.pressure_level
    # app mem cgroups, used by activity manager, lmkd and zygote
    mkdir /dev/memcg/apps/ 0755 system system
    # cgroup for system_server and surfaceflinger
    mkdir /dev/memcg/system 0550 system system

3.2 Android Init

Android 是基于Linux系统的,所以 Android 启动将由 Linux Kernel 启动并创建 init 进程。该进程是所有用户空间的鼻祖。

init 进程启动的过程中,会相继启动 servicemanager (binder服务管理者)、 Zygote 进程(java进程)。而 Zygote 又会创建 system_server 进程以及 app 进程。由于我们的重点是 init.rc 的解析,所以我们只关注 init 进程。

init 进程是 Android 启动过程中在 Linux 系统中用户空间的第一个进程,它的进程号为 1 ,所以也被称为:1 号进程。init 启动入口是在它的 SecondStageMain 方法中。但调用 initSecondStageMain 方法是通过 main.cpp 中的 main 方法进行的。

首先看一下 main.cpp 源码。

system/core/init/main.cpp

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
    // Boost prio which will be restored later
    setpriority(PRIO_PROCESS, 0, -20);
    
    // 创建设备节点、权限设定等
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        // 初始化日志系统
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        // 2. 启动 SeLinux 权限
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        // 3. 解析 init.rc 文件、提供服务、创建 epoll 与处理子进程的终止等
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }
	
    // 1. 挂载相关文件系统
    return FirstStageMain(argc, argv);
}

从上面源码可以看出,在 main.cpp 中主要是分为三步,简单了解一下。

  1. FirstStageMain():它是init进程启动的第一步,主要任务是创建挂载相关的文件系统。
  2. SetupSelinux():启动 SeLinux 权限,建立安全机制。
  3. SecondStageMain():初始化属性服务;初始化 single 句柄;开启属性服务;解析 .rc 文件。

由于我们的重点在于 .rc 文件的解析,所以只需要关注一下 SecondStageMain()

SecondStageMain() 函数中,主要分为 4 步:

  1. 初始化属性服务
  2. 初始化 single 句柄
  3. 开启属性服务
  4. 解析 .rc 文件并启动 Zygote 进程

/system/core/init/init.cpp

int SecondStageMain(int argc, char** argv) {
 	...
    // 初始化日志系统
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
	...
    // 初始化属性域
    PropertyInit();
	...
    // 装载子进程信号处理;防止僵尸子进程无法回收
    InstallSignalFdHandler(&epoll);
    ...
    // 开启属性服务
    StartPropertyService(&property_fd);
	...
  	// 加载脚本
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
	// 解析 init.rc 脚本
    LoadBootScripts(am, sm);
	...
}

同样,我们只需要关注 LoadBootScripts() 函数,该函数主要是通过 ParseConfig() 函数去解析 .rc 配置文件。init.rc 中的 ActionService 语句都有相应的类来解析,即ActionParserServiceParser

3.3 rc 文件解析

在上面我们说过,init 流程里调用 LoadBootScripts() 函数解析处理 init.rc 文件,接下来让我们逐一剖析,先上函数源码。

system/core/init/init.cpp

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    // 1. 构造一个解析器
    Parser parser = CreateParser(action_manager, service_list);
	// 2. 获取 ro.boot.init_rc
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        // 3. 解析 .rc 文件
        parser.ParseConfig("/system/etc/init/hw/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        // late_import is available only in Q and earlier release. As we don't
        // have system_ext in those versions, skip late_import for system_ext.
        parser.ParseConfig("/system_ext/etc/init");
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}

由此可见,整个 LoadBootScripts() 函数结构比较简单。可以分为以下三部分来看:

  1. 创建一个Parser 解析器,Parser 类中有创建 SectionParser 类型的容器 section_parsers_

    Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
        Parser parser;
    	// 
        parser.AddSectionParser("service", std::make_unique<ServiceParser>(
                                 &service_list, GetSubcontext(), std::nullopt));
      
        parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, 									 GetSubcontext()));
        parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
    
        return parser;
    }
    
    /*******************************************************************************
    * AddSectionParser:把 ServiceParser、ActionParser和ImportParser添加到 Parser 中的 
    * section_parsers中。 section_parsers_ 类型是 map,key 是 “server”、"on"、"import",
    * value 是 SectionParser。如果是"service",则是ServiceParser;如果是"on",则是ActionParser;
    * 如果是"import",则是ImportParser。
    *******************************************************************************/
    void Parser::AddSectionParser(const std::string& name, 
                                  std::unique_ptr<SectionParser> parser) 
    {
        section_parsers_[name] = std::move(parser);
    }
    
  2. 获取 ro.boot.init_rc 属性。ro.boor.init_rc 属性值来源于 androidboot.init_rc ,在 kernel 中的 boot_linux_fdt 函数去赋予对应的属性值。init_rcmeta_init.rc 或者 factory_init.rc

int boot_linux_fdt()
{
	...
	if(g_boot_mode == META_BOOT || g_boot_mode == ADVMETA_BOOT){
        // 
		snprintf(tmpbuf, TMPBUF_SIZE), "androidboot.init_rc=%s", META_INIT_RC);
		...
	} else {
		snprintf(tmpbuf, TMPBUF_SIZE), "androidboot.init_rc=%s", FACTORY_INIT_RC);
		...
	}
	...
}

  1. 调用 ParseConfig() 函数解析 .rc 文件。该函数会调用 ParseConfigDir()ParseConfigFile() 分别处理 path下文件夹和文件。
bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {
        return ParseConfigDir(path);
    }
    return ParseConfigFile(path);
}
3.3.1 Parser

首先,让我们来认识几个类:ParserSectionParserActionParerServiceParserImportParser

从下面的类图中可以看出:

  • ActionParserServiceParserImprotParser 继承于 SectionParser
  • Parser 类依赖于 SectionParser 类,Parser使用SectionParser 类创建私有变量 section_parsers
    Parser类

    在前面我们了解到 : LoadBootScripts() 函数中,在正式解析 .rc 文件前会调用 CreateParser() 函数创建一个 Parser 类型的对象。而在 CreateParser() 函数中会初始化 ServiceParser 用来解析 service 块,初始化 ActionParser 来解析 on 块,初始化 ImportParser 用来解析 import 块。

ActionParserServiceParserImportParser 都有自己的接口函数去解析各自对应的 section 块,在这里我们主要说一下 SectionParser 类中的几个接口函数。

  • ParseSection() :处理段落首行。匹配到 section 关键字,就视为一个 section 的首行,section 的后续操作就交由对应的解析器去处理,并调用该解析器的ParseSection() 函数。
  • ParseLineSection() :处理段落内容。对匹配不到 Section 关键字的行,视为 Section 的内容,加入此时存在段落解析器,则调用该解析器的 ParseLineSection() 函数。
  • EndSection() :处理段落结束。段落结束时,假如存在段落解析器,则调用该解析器的 EndSection() 函数。
  • EndFile() :处理文件结束。文件解析结束时,调用所有存在的段落解析器中的 EndFile() 函数。
3.3.2 ParseConfig

到这儿才正式进入到主菜环节,ParseConfig 是用来解析我们的 .rc 文件的,我们在看一下 ParseConfig 函数的定义。ParseConfigParser 类的一个方法,从定义来看这个函数比较简单,调用 ParseConfigDir 来处理文件夹,调用 ParseConfigFile 处理 .rc 文件。

bool Parser::ParseConfig(const std::string& path) {
    // 判断参数 path 是否为文件目录
    if (is_dir(path.c_str())) {
        return ParseConfigDir(path); //递归目录,最终还是 ParseConfigFile 来解析实际的文件
    }
    // 如果参数 path 是文件,则调用 ParseConfigFile 函数来处理。
    return ParseConfigFile(path);
}

首先看一下 ParseConfigDir 函数。该函数会去对文件夹中的文件挨个读取,如果是常规文件也就是我们的 rc 文件就会被添加到 files 数组中,遍历完成后会对 files 数组中的所有 rc 文件进行排序,然后再调用 ParConfigFile 函数按顺序解析 rc 文件。

bool Parser::ParseConfigDir(const std::string& path) {
    LOG(INFO) << "Parsing directory " << path << "...";
    std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
    if (!config_dir) {
        PLOG(INFO) << "Could not import directory '" << path << "'";
        return false;
    }
    // dirent: 文件目录结构体
    dirent* current_file;
    std::vector<std::string> files;
    // readdir: 读取目录文件,返回 dirent 结构体指针
    // 调用 readdir 读取文件夹里的文件,每次只能读取一个
    while ((current_file = readdir(config_dir.get()))) {
        // d_type: 文件类型, DT_REG: 常规文件
        // 如果文件是一个常规文件,则将此文件加入 vector 数组中
        // Ignore directories and only process regular files.
        if (current_file->d_type == DT_REG) {
            std::string current_path =
                android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);
            files.emplace_back(current_path);
        }
    }
   
    // Sort first so we load files in a consistent order (bug 31996208)
    // 对数组 files 里的文件进行排序
    std::sort(files.begin(), files.end());
    // 遍历 files 里的文件,挨个调用 ParseConfigFile 函数去解析
    for (const auto& file : files) {
        if (!ParseConfigFile(file)) {
            LOG(ERROR) << "could not import file '" << file << "'";
        }
    }
    return true;
}

​ 接下来看看 ParseConfigFile 函数。通过下面的代码可以看到 ParseConfigFile 的逻辑比较简单,就是读取文件的内容为字符串,然后调用 ParseData 函数进行解析。

bool Parser::ParseConfigFile(const std::string& path) {
    LOG(INFO) << "Parsing file " << path << "...";
    android::base::Timer t;
    // ReadFile:读取文件内容,并将文件内容保存在 string 类型的对象中。
    auto config_contents = ReadFile(path);
    // 如果读取失败,返回false
    if (!config_contents.ok()) {
        LOG(INFO) << "Unable to read config file '" << path << "': " \
            	  << config_contents.error();
        return false;
    }
	// 调用 ParseData 函数处理
    ParseData(path, &config_contents.value());

    LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
    return true;
}
3.3.3 ParseData

ParseData()Parser 的核心解析函数。ParseData() 主要通过调用 next_token 函数遍历每一个字符,然后对不同的字符进行判断,采取不同的规则进行处理,其主要流程如下:

  • 以空格或 " " 为分割将一行拆分成若干个单词,调用 T_TEXT 将单词放到 args 数组中。
  • 当读到回车符就调用 T_NEWLINE ,在 section_parsers_ 这个 map 中找到对应的 onservice 以及 import 的解析器,执行 ParseSection
  • 如果在 map 中找不到对应的 key,就执行 ParseLineSection()
  • 当读到 0 的时候,表示一个 Section 读取结束,调用 T_EOF 执行 EndSection()

在上述流程中会涉及一个非常重要的结构体:parse_statenext_token()处理的数据以 parse_state 结构体指针返回,以行为单位分割传递的字符串。结构体及成员变量如下:

/* FilePath:system/core/init/tokenizer.h */
#define T_EOF 0         // 表示 rc 文件解析完成
#define T_TEXT 1        // 表示解析到一个单词,在代码中会填充到 args vector 向量中,后续解析处理
#define T_NEWLINE 2     // 表示解析完一行的数据
struct parse_state
{
    char *ptr;			// 将要解析的字符串
    char *text;			// 解析得到的字符串,即解析返回的一行数据
    int line;  			// 解析到 init.rc 字符串的多少行 
    int nexttoken;      // 解析状态,共有三种分别是 T_EOF ,T_TEXT ,T_NEWLINE 
};

下面是 ParseData() 函数的源码,结合代码看一下解析流程。

void Parser::ParseData(const std::string& filename, std::string* data) {
    // 在解析的 rc 文件末端插入 \n\0
    data->push_back('\n');
    data->push_back('\0');
	// 创建 parse_state 结构体 state 并初始化,用于存放解析过程中的状态和产生的临时数据
    parse_state state;
    state.line = 0;
    state.ptr = data->data();
    state.nexttoken = 0;

    SectionParser* section_parser = nullptr;
    int section_start_line = -1; 
    std::vector<std::string> args;  // 存放一行命令解析出的所有参数

    // If we encounter a bad section start, there is no valid parser  \ 
    // object to parse the subsequent
    // sections, so we must suppress errors until the next valid section is found.
    bool bad_section_found = false;

    // Lambda 表达式,用于结束段落解析和重置解析相关数据
    auto end_section = [&] {
        bad_section_found = false;
        if (section_parser == nullptr) return;

        if (auto result = section_parser->EndSection(); !result.ok()) {
            parse_error_count_++;
            LOG(ERROR) << filename << ": " << section_start_line << ": " \
            		   << result.error();
        }

        section_parser = nullptr;
        section_start_line = -1;
    };
	
    // 死循环,通过 next_token 解析 
    for (;;) {
        // next_token 以行为单位分割参数传递过来的字符串,初始没有分割符时,最先走到 T_TEXT 分支
        switch (next_token(&state)) {
            case T_EOF:
                // 调用 end_section() 重置 section_parser 以及 section_start_line
                end_section();
				// 遍历 section_parsers_,调用所有段落解析器的 EndFile()函数
                for (const auto& [section_name, section_parser] : section_parsers_) {
                    section_parser->EndFile();
                }

                return;
            // 处理新的一行
            case T_NEWLINE: {
                state.line++;
                // 如果 args 为空,不处理
                if (args.empty()) break;
                // If we have a line matching a prefix we recognize, 
                // call its callback and unset any
                // current section parsers.  This is meant for /sys/ 
                // and /dev/ line entries for uevent.
                auto line_callback = std::find_if(
                    line_callbacks_.begin(), line_callbacks_.end(),
                    [&args](const auto& c) 
                    { return android::base::StartsWith(args[0], c.first); });
                if (line_callback != line_callbacks_.end()) {
                    end_section();

                    if (auto result = line_callback->second(std::move(args));
                        !result.ok()) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line 
                             	   << ": " << result.error();
                    }
                /********************************************************************
                * section_parsers 调用 count 方法遍历 args 查找 key,如果有 on、service、						
                * import 则意味匹配到段落解析器 ActionParser | ServiceParser | ImportParser 
                ********************************************************************/
                } else if (section_parsers_.count(args[0])) {
                    end_section();
                    section_parser = section_parsers_[args[0]].get();
                    section_start_line = state.line;
                    // 调用 ParseSection() 去处理当前段落
                    if (auto result = section_parser->ParseSection(std::move(args)
                        , filename, state.line); !result.ok()) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line   \ 
                            	   << ": " << result.error();
                        section_parser = nullptr;
                        bad_section_found = true;
                    }
                /* ******************************************************************
                * 不包含 on server import 则是 command或option,则调用前一个Parser 的 
                * ParseLineSection() 函数处理行
                ********************************************************************/
                } else if (section_parser) {
                    if (auto result = section_parser->ParseLineSection(std::move(args),
                        state.line); !result.ok()) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line \ 
                                   << ": " << result.error();
                    }
                } else if (!bad_section_found) {
                    parse_error_count_++;
                    LOG(ERROR) << filename << ": " << state.line
                               << ": Invalid section keyword found";
                }
                args.clear();
                break;
            }
            // 如果是一个单词就尾插在 args 中。
            case T_TEXT:
                args.emplace_back(state.text);
                break;
        }
    }
}

补上一张流程图,快速了解 ParseData() 函数的处理流程。

ParseData流程

一个 rc 文件是由很多个 section 块组成的,而 section 主要有三类:action、service、import。所谓术业有专攻,我们整个 rc 文件解析成一个个的 section,然后对应的 SectionParser 去处理各自的 section 块。

3.4 ServiceParser

ServiceParser

有时间再写。。。。。。。。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值