Android 8.0: init process of system startup process (3)

Posted by woolyg on Fri, 17 May 2019 11:10:57 +0200

This is a series of blog posts, I will continue to provide you with as thorough as possible Android source analysis github serial address

Preface

After the first two stages of init, the attribute system and SELinux system have been established, but the init process needs to perform many other operations and start many key system services.
But if it's all a line of code like attribute system and SELinux system, it's a bit cumbersome and not easy to expand, so the Android system introduces init.rc.

init.rc is a configuration script for init process startup, which is written in a language called Android Init Language(Android Initialization Language).
Prior to 7.0, init processes only parsed init.rc files in the root directory, but as versions iterated, init.rc became more and more bloated.
So after 7.0, some of init.rc's businesses were split into three directories: / system/etc/init, / vendor/etc/init, / odm/etc/init.
In this article, I will explain some of the syntax of init.rc, and then step by step analyze how the init process parses the init.rc file.

This article mainly explains the following contents.

  • Android Init Language Syntax
  • Parse. rc file
  • Add some events and some actions
  • Trigger all events and constantly monitor new events

Documents covered in this article

platform/system/core/init/README.md
platform/system/core/init/init.cpp
platform/system/core/init/init_parser.cpp
platform/system/core/init/action.cpp
platform/system/core/init/action.h
platform/system/core/init/keyword_map.h
platform/system/core/init/builtins.cpp
platform/system/core/init/service.cpp
platform/system/core/init/service.h
platform/system/core/init/import_parser.cpp
platform/system/core/init/util.cpp

I. Android Init Language Syntax

Defined in platform/system/core/init/README.md

The. rc file mainly configures two things, one is action, the other is service,trigger and command are complementary to action, and options are complementary to service.
action plus trigger and some command s, constitute a Section,service plus some option s, also form a Section, the. rc file is composed of a Section.
The header of the. rc file has an import syntax, which means that these. RCS are also included and parsed. Next, we will focus on action and service.

The format of action is as follows:

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

Starting with on, trigger is a judgment condition, and command is to perform specific operations, which are executed when trigger conditions are met.

trigger can be a string, such as

on early //Represents triggering when trigger early or QueueEventTrigger("early") is called

It can also be attributes, such as

on property:sys.boot_from_charger_mode=1//Represents triggering when the value of sys.boot_from_charger_mode is set to 1 through property_set
on property:sys.sysctl.tcp_def_init_rwnd=* // * Represents any value

Conditions can be multiple, connected with & e.g.

on zygote-start && property:ro.crypto.state=unencrypted
//Represents triggering when zygote-start triggers and the ro.crypto.state attribute value is unencrypted

command is some specific operation, such as

mkdir /dev/fscklogs 0770 root system //new directory
class_stop charger //Termination of service
trigger late-init  //Trigger late-init

The format of services is as follows:

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

Starting with service, name is the name of the service, pathname is the path of the execution file of the service, argument is the parameter of the execution file band, and option is the configuration of the service.
Let's look at a typical example.

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

This is the service configured in the / init.zygote64_32.rc file, which is what we often call the zygote process startup configuration.

zygote is the process name, the executable file path is in / system/bin/app_process64, and the executable file parameter (the args in the main function of the executable program) is
-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote

The option s that follow are some service configurations, such as
Classmain means that the class belongs to is main, which is equivalent to a classification. Other service s can also be classified as main, and they will be started or terminated together.
service has a name and a class, just like at work, you have a name foxleezh, or you belong to the android department.

These things I mentioned above, the source code has a special document to explain the path in platform/system/core/init/README.md, it should be said that the document is well written, read the document carefully, the basic grammar knowledge will be known, I simply translated.

Android Init Language

Android Init Language consists of five types of grammars: Actions, Commands, Services, Options, and Imports.


Each line is a sentence, separated by spaces. If there are spaces in a word, they can be escaped with backslash, or double quotation marks can be used to refer to the text to avoid conflicts with spaces. If a line is too long, it can be \ replaced with to indicate comments.


Actions and Services can act as a separate Section. All Commands and Options are subordinate to the next Actions or Services, and Commands and Options defined before the first Section will be ignored.


Actions and Services are unique. If two identical actions are defined, the Command of the second Action will be added to the first Action.
If two identical services are defined, the second Service will be ignored and the error log printed.

Init .rc Files

Android Init Language is written in plain text with a suffix of. rc and consists of multiple. rc files distributed in different directories, as described below.


/ init.rc is the most important. RC file. It is loaded by init process at the time of initialization. It is mainly responsible for system initialization. It will import / init.${ro.hardware}.rc, which is the main. RC file provided by system-level core vendors.


When the mount_all statement is executed, the init process loads all files in the /{system,vendor,odm}/etc/init/ directory, which will serve Actions and Services when the file system is mounted.


A special directory may be used to replace the three default directories above, mainly to support factory mode and other non-standard startup modes. The three directories above are used for normal startup process.


The three directories for extension are

  1. / system/etc/init/for the system itself, such as Surface Flinger, MediaService, and logcatd.
  2. / vendor/etc/init/for SoC (system-level core vendors, such as Qualcomm), to provide them with some core functions and services
  3. / odm/etc/init / for device manufacturers (odm customizers such as Huawei and Millet) to provide some core functions and services for their sensors or peripherals

All Services binary files placed in these three directories must have a corresponding. rc file placed in that directory, and service structure should be defined in. rc file.
There is a macro LOCAL_INIT_RC to help developers deal with this problem. Each. rc file should also contain some actions related to the service.


For example, in the system/core/logcat directory, there are logcatd.rc and Android.mk files. In the Android.mk file, the macro LOCAL_INIT_RC is used. At compilation time, logcatd.rc is placed in the / system/etc/init/directory. The init process loads the logcatd.rc when mount_all is called, runs its defined service at the right time, and queues the action.


It is better to split init.rc into different directories according to different Services than to put it in a single init.rc file. This scheme ensures that the service and action information read by init is more consistent with the service binary files in the same directory, and is no longer the same as the previous single init.rc.
In addition, this can also resolve conflicts when multiple services are added to the system, because they are split into different files.


In the mount_all statement, there are two options, early and late. When early is set, the init process skips the mount operation marked by late mount and triggers the fs encryption state event.
When late is set, the init process will only perform the latemount tag mount operation, but will skip the execution of the imported. rc file. By default, the init process will perform all mount operations without setting any options.

Actions

Actions consist of a line of commands. triggers are used to determine when to trigger these commands, when an event meets the trigger condition.
This action is added to the processing queue (unless it already exists)


The actions in the queue are taken out and executed sequentially, and the commands in the actions are executed sequentially. These commands are mainly used to perform some operations (device creation/destruction, property setting, process restart).


Actions are formatted as follows:

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

Services

Services yes init Procedure Startup,They may also automatically restart on exit. Services The format is as follows:

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

Options

Options are the parameters configuration of Services. They affect how the Service runs and runtime machine


console [<console>]

Service needs console. The second parameter console means that you can set the type of console you want. The default console is / dev / console, and the prefix / dev is usually ignored. For example, if you want to set console / dev/tty0, you just need to set it to console tty0.


critical

Represents that Service is in strict mode. If the Service exits more than four times in four minutes, the device will restart and enter recovery mode.


disabled

Represents that Service cannot be started as a class, but only as a name


setenv <name> <value>

Setting name-value environment variables at Service startup


socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]

Create a unix domain socket called / dev/socket/name and return fd to Service. type can only be "dgram", "stream" or "seqpacket".
The default values of User and group are 0.'seclabel'is the SELinux security context of the socket, and its default value is the service security policy or the security context based on its executable file.
Its corresponding local implementation is android_get_control_socket in libcutils


file <path> <type>

Open a file and return fd to this Service. type can only be "r", "w" or "rw". Its corresponding local implementation is android_get_control_file of libcutils.


user <username>

Change user to username before starting Service, and user to root by default (or none by default).
In Android M, if a process wants to have Linux capabilities (equivalent to the rights in Android), it can only set this value. Previously, if a program wants to have Linux capabilities, it must first run as root, and then downgrade to the required uid.
Now there is a new mechanism that allows vendors to assign special binary Linux capabilities through fs_config. The documentation for this mechanism is as follows http://source.android.com/devices/tech/config/filesystem.html.
When using this new mechanism, the program can select its own uid through the user parameter, instead of running with root privilege. In Android O version,
The program can directly apply for the required capabilities through the capabilities parameter, as shown in the capabilities description below.


group <groupname> [ <groupname>\* ]

Change the group to the first group name before starting the Service. The first group name is required.
The default value is root (or perhaps none), and the second groupname can be unset for additional groups (via set groups).


capabilities <capability> [ <capability>\* ]

When starting Service, set capabilities to capability.'capability'cannot be "CAP_" prefix, like "NET_ADMIN" or "SETPCAP" reference.
http://man7.org/linux/man-pages/man7/capabilities.7.html It has capability instructions.


seclabel <seclabel>

Set seclabel to seclabel before starting Service. It is mainly used for services started on rootfs, such as ueventd, adbd.
The service running on the system partition has its own SELinux security policy. If not set, the security policy of init is used by default.


oneshot

No Restart after Exit


class <name> [ <name>\* ]

Specify a class name for a service. Services with the same class name will be started or exited together. The default value is "default", and the second name can be set for the service group without setting it.


animation class

The animation class mainly consists of service s for startup or shutdown animations. They are started very early and do not exit until the last step of shutdown.
They do not allow access to the / data directory, they can check the / data directory, but they cannot open the / data directory, and they need to work normally when / data is not available.


onrestart

Execute commands when Service restarts.


writepid <file> [ <file>\* ]

When Service calls fork, the pid of the child process is written to the specified file. For the use of cgroup/cpuser, when there is no file under / dev/cpuset/but the value of ro.cpuset.default is not empty,
Write the value of pid into the / dev/cpuset/cpuset_name/tasks file


priority <priority>

Set process priority. Between - 20 and 19, the default value is 0, which can be implemented through setpriority.


namespace <pid|mnt>

When fork is the service, set the pid or mnt tag


oom_score_adjust <value>

Set the value of / proc/self/oom_score_adj of the child process between - 1000 and 1000.

Triggers

Triggers is a string, and when some event occurs to satisfy this condition, some actions are executed


Triggers are divided into event Trigger and attribute Trigger


Event Trigger is triggered by the trigger command or the QueueEventTrigger method. Its format is a simple string, such as'boot'or'late-init'.


Attribute Trigger is triggered when the property is set or changed. The format is'property:<name>=<value> or'property:<name>=*', which triggers when init initializes the setting of the property.


Action s defined by attribute Trigger may have multiple triggering modes, but actions defined by event Trigger may have only one triggering mode.


For example:

On Boot & property: a = b defines the trigger condition for action: boot Trigger trigger, and the value of attribute a equals b.


The definition of on property: a = B & & property: C = D has three triggering modes:

  1. At initialization, attribute a=b, attribute c=d.
  2. In the case of attribute c=d, attribute a is changed to b.
  3. In the case of attribute a=b, attribute c is changed to d.

Commands

bootchart [start|stop]

Start or terminate bootcharting. This appears in the init.rc file, but is valid only if the / data/bootchart/enabled file exists, otherwise it will not work.


chmod <octal-mode> <path>

Modify File Read and Write Permissions


chown <owner> <group> <path>

Modify the file owner or user group


class_start <serviceclass>

Start all services that are not started named after service class (service has a name and a class,
The service class here is class. class_start and the following start are two ways to start. class_start is the class form to start, and start is the name form to start.


class_stop <serviceclass>

Terminate all running services named after service class


class_reset <serviceclass>

Terminate all running services named after service class, but do not disable them. They can be restarted later by class_start


class_restart <serviceclass>

Restart all services named after service class


copy <src> <dst>

Copy a file, similar to write, which is more suitable for binary or larger files.

For src, copying from link files, world-writable or group-writable is not allowed.

For dst, if the target file does not exist, the default permission is 0600, which is overwritten if it exists.


domainname <name>

Set domain name


enable <servicename>

Set a disabled service to be available.
If the service is running, it will restart.
It's usually used to set properties when bootloader, and then start a service, such as
on property:ro.boot.myfancyhardware=1
enable my_fancy_service_for_my_fancy_hardware
exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]
Create a new subprocess and run a command with specified parameters. This command specifies seclabel (security policy), user (owner), group (user group).
No other commands can be run until this command is finished. seclabel can be set to - denote the default value, and argument denotes the attribute value.
The init process does not continue until the new child process is completed.


exec_start <service>

Start a service, and the init process can continue only when the execution result returns. This is similar to exec, except that a set of parameters is changed to be defined in the service.


export <name> <value>

Set the environment variable name-value. This environment variable will be inherited by all started service s


hostname <name>

Setting Host Name


ifup <interface>

Open the specified network interface


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

Install the module under path and specify parameter options.

- f means mandatory installation, even if the current Linux kernel version does not match it


load_all_props

Load attributes in / system, /vendor, etc., which are used in init.rc


load_persist_props

Persistence properties under load / data. This is used in init.rc


loglevel <level>

Set log output level, level represents level


mkdir <path> [mode] [owner] [group]

Create a directory, path is path, mode is read and write permission, default value is 755,owner is owner, default value is root,group is user group, default value is root.
If the directory already exists, override their mode,owner, and other settings


mount_all <fstab> [ <path> ]\* [--<option>]

When "early" and "late" are triggered manually, the fs_mgr_mount_all function is called, the fstab configuration file is specified, and the.rc file in the specified directory is imported.
For more details, see the definitions in the init.rc file


mount <type> <device> <dir> [ <flag>\* ] [<options>]

Mount a device called device under the dir directory

_ flag includes "ro", "rw", "remount", "noatime",...

options include "barrier=1", "noauto_da_alloc", "discard",... Separated by commas, such as barrier=1,noauto_da_alloc


restart <service>

Restart a service after termination. If the service has just been restarted, do nothing. If it is not running, start it.


restorecon <path> [ <path>\* ]

Restore the security context of files in the specified directory. The second path is the security policy file. The specified directory does not need to exist because it only needs to be correctly marked in init.


restorecon_recursive <path> [ <path>\* ]

Recursively restore the security context in the specified directory, and the second path is the location of the security policy file


rm <path>

Call unlink(2) to delete the specified file. It's better to replace it with exec -- rm... because this ensures that the system partition is mounted.


rmdir <path>

Call rmdir(2) to delete the specified directory


setprop <name> <value>

Set the property name-value


setrlimit <resource> <cur> <max>

Specify resource constraints for a process


start <service>

Start an unrun service


stop <service>

Termination of a running service


swapon_all <fstab>

Call fs_mgr_swapon_all to specify the fstab configuration file.


symlink <target> <path>

Create a link to target under path


sysclktz <mins_west_of_gmt>

Reset system reference time (set to 0 if Greenwich standard time)


trigger <event>

Trigger event, triggered by one action to another action queue


umount <path>

Unload the file system of the specified path


verity_load_state

The internal implementation is the state of loading dm-verity


verity_update_state <mount-point>

The internal implementation is to set the state of dm-verity and the properties of partition.mount-point.verified. It is used for adb reloading.
Because fs_mgr cannot set it directly.


wait <path> [ <timeout> ]

Check to see if the specified path exists. If found, return. You can set the timeout time. The default value is 5 seconds.


wait_for_prop <name> <value>

Wait for the value of the name attribute to be set to value, and if the value of name is set to value, continue immediately.


write <path> <content>

Open the file under path and write(2) to the content. If the file does not exist, it will be created, and if it exists, it will be overwritten.

Imports

The import keyword is not a command, but if there is a. rc file containing it, the section in it will be parsed immediately. The usage is as follows:


import <path>

Parse the.rc file under path, including the configuration of the current file. If path is a directory, all. rc files in this directory are parsed, but not recursively.
import is used in the following two places:

1. Parse init.rc file at initialization

2. Resolve. rc files in {system,vendor,odm}/etc/init/ etc. directories when mount_all


The latter is about debugging init processes, such as init. svc. <name> to see the status of service startup.
ro.boottime.init records some key time points. Bootcharting is a graphical performance monitoring tool and so on. Because it has little to do with grammar, it will not be translated.

Now that you understand the syntax of the. rc file, let's look at how the init process parses the. rc file and translates the syntax into the code actually executed.

Parsing.rc Files

Previously, we saw in the document that. rc files are mainly / init.rc in the root directory, and *. rc in {system,vendor,odm}/etc/init / three directories.
Then if there is a special directory set up, replace these directories, understand these, the following code is easy to understand.

int main(int argc, char** argv) {

    ...

    const BuiltinFunctionMap function_map;
    /*
     * 1.C++Chinese:: Represents static method calls, equivalent to static methods in java
     */
    Action::set_function_map(&function_map); //Store function_map in Action as a member attribute


    Parser& parser = Parser::GetInstance();//Singleton pattern, get Parser object
    /*
     * 1.C++Middle std::make_unique is equivalent to new, it will return a std::unique_ptr, that is, smart pointer, which can automatically manage memory.
     * 2.unique_ptr Holding exclusive rights to objects, two unique_ptr s cannot point to one object, cannot replicate, and can only move.
     * 3.The function of the mobile operation is p1=std::move(p), so that the object pointed by the pointer P moves to p1.
     * 4.The next three lines of code are all new Parser, which is then stored in a map.
     * 5.ServiceParser,ActionParser,ImportParser Analysis of service action import Corresponding to 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()) {//If ro.boot.init_rc has no corresponding value, then parse the.Rc files in the three directories of / init.rc and / system/etc/init, / vendor/etc/init, / odm/etc/init.
        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 {//If the ro.boot.init_rc attribute has a value, the attribute value is resolved
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }

2.1 ParseConfig

Defined in platform/system/core/init/init_parser.cpp

The first is to determine whether the incoming directory is a directory or a file. In fact, they call ParseConfigFile. ParseConfigDir is to traverse the files in the directory, sort the files, and then call ParseConfigFile.

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

ParseConfigFile is to read the data in the file, pass the data to the ParseData function, and finally traverse section_parsers_to call its EndFile function.
EndFile is analyzed later, because it's a polymorphic implementation, let's look at ParseData first.

bool Parser::ParseConfigFile(const std::string& path) {
    LOG(INFO) << "Parsing file " << path << "...";
    Timer t;
    std::string data;
    if (!read_file(path, &data)) { //Read data to data
        return false;
    }

    data.push_back('\n'); // TODO: fix parse_config.
    ParseData(path, data); //Analytical data
    for (const auto& sp : section_parsers_) {
        sp.second->EndFile(path);
    }

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

2.2 ParseData

ParseData is defined in platform/system/core/init/init_parser.cpp

ParseData traverses each character by calling the next_token function, splits a line into several words with spaces or "" as a partition, and calls T_TEXT to place the words in the args array.
When you read the carriage return, call T_NEWLINE, find the parser for on service import in section_parsers_map, and execute ParseSection if
If the corresponding key cannot be found in the map, the ParseLineSection is executed. When read to 0, it indicates that a Section reading is finished and T_EOF is called to execute 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()); //Copy the contents of data into data_copy
    data_copy.push_back('\0'); //Add an end 0

    parse_state state; //Define a structure
    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)) { // Traversing through each character in data_copy
        case T_EOF: //If it's the end of the file, call EndSection
            if (section_parser) {
                section_parser->EndSection();
            }
            return;
        case T_NEWLINE://A row of data was read
            state.line++;
            if (args.empty()) {
                break;
            }
            /*
             * 1.section_parsers_Is a std:map
             * 2.C++The count function of std:map in Java is to find the key, which is equivalent to the contains of Map in Java.
             * 3.section_parsers_Only three key s, on service import, were added to the previous AddSectionParser function
             */
            if (section_parsers_.count(args[0])) { //Determine whether on service import is included
                if (section_parser) {
                    section_parser->EndSection();
                }
                section_parser = section_parsers_[args[0]].get();//Remove the corresponding parser
                std::string ret_err;
                if (!section_parser->ParseSection(args, &ret_err)) {//Analyzing the corresponding Section
                    parse_error(&state, "%s\n", ret_err.c_str());
                    section_parser = nullptr;
                }
            } else if (section_parser) { //Not including on service import is command or option
                std::string ret_err;
                if (!section_parser->ParseLineSection(args, state.filename,
                                                      state.line, &ret_err)) {//Resolve command or option
                    parse_error(&state, "%s\n", ret_err.c_str());
                }
            }
            args.clear();
            break;
        case T_TEXT: //Put a read line of data into args, which splits a line of data into spaces or "" and puts words into an array.
            args.emplace_back(state.text);
            break;
        }
    }
}

ActionParser, Service Parser, ImportParser, the three parsers corresponding to on service import, were added to the section_parsers_map before.

    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(parser);
    }

They are all subclasses of SectionParser. SectionParser has four pure virtual functions: ParseSection, ParseLineSection, EndSection and EndFile.

class SectionParser {
public:
    virtual ~SectionParser() {
    }
    /*
     * 1.C++The definition format of pure virtual function in Java is virtual as modifier, and then assigns 0, which is equivalent to abstract method in Java.
     * 2.If we do not assign 0, we use virtual as modifier. This is a virtual function. The virtual function can have a method body, which is equivalent to the method of the parent class in Java. It is mainly used for the overloading of subclasses.
     * 3.As long as the class containing pure virtual functions is abstract class, it can not be new, but can only be implemented through subclasses, which is the same as 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;
};

Next, I will analyze the implementation of ParseSection, ParseLineSection, EndSection and EndFile of the three Perser s.

2.3 ActionParser

Defined in platform/system/core/init/action.cpp

Let's first look at ParseSection, which copies the data from subscript 1 to the end of args into the triggers array, then builds the Action object, calls InitTriggers, and parses the triggers.

bool ActionParser::ParseSection(const std::vector<std::string>& args,
                                std::string* err) {
    std::vector<std::string> triggers(args.begin() + 1, args.end()); //Copy args to triggers, remove subscript 0
    if (triggers.size() < 1) {
        *err = "actions must have a trigger";
        return false;
    }

    auto action = std::make_unique<Action>(false);
    if (!action->InitTriggers(triggers, err)) { //Call InitTriggers to parse trigger
        return false;
    }

    action_ = std::move(action);
    return true;
}

InitTriggers distinguishes the type of trigger by comparing whether it starts with "property:", and if it is a property trigger, it calls ParseProperty Trigger.
If it is event trigger, the parameters of args are assigned to event_trigger_, the type of which is 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(); ++i) {

        ...

        if (!args[i].compare(0, prop_str.length(), prop_str)) {
            if (!ParsePropertyTrigger(args[i], err)) {
                return false;
            }
        } else {
           ...
            event_trigger_ = args[i];
        }
    }

    return true;
}

The ParseProperty Trigger function first splits the character with "=" into name-value, and then stores name-value in the 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())); //Intercept property: what follows
    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)); //Remove value
    prop_name.erase(equal_pos); //Delete the character marked equal_pos, that is, delete "="

    if (auto [it, inserted] = property_triggers_.emplace(prop_name, prop_value); !inserted) {
    //Store name-value in map, emplace is equivalent to put operation
        *err = "multiple property triggers found for same property";
        return false;
    }
    return true;
}

As you can see from the above, the function of ParseSection is to construct an Action object, record the trigger condition into the Action object, and assign it to event_trigger_ if it is an event trigger.
If it's property trigger, it's stored in property_triggers_map. Next, let's analyze ParseLineSection.

ParseLineSection is the AddCommand function that calls the Action object directly.

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 probably knows by name that it is adding commands. It first checks the null values of parameters, and then calls FindFunction to find the corresponding execution function of the command.
Finally, the information is wrapped as Command objects and stored in commands_array. FindFunction is the key here.

bool Action::AddCommand(const std::vector<std::string>& args,
                        const std::string& filename, int line, std::string* err) {
    ... //Some parameter checks

    auto function = function_map_->FindFunction(args[0], args.size() - 1, err);//Find the execution function corresponding to the command
    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_is an array, emplace_back is equivalent to add
}

FindFunction is defined in platform/system/core/init/keyword_map.h

The main function of this function is to find the corresponding execution function by command, such as defining chmod in the. rc file. Then we have to find out which function chmod specifically performs. First, it returns a std:map through map() and calls its find function.
find is equivalent to get in Java, but returns entry, which can be obtained by entry - > first and entry - > second.
The value found is a structure with three values. The first is the minimum number of parameters, the second is the maximum number of parameters, and the third is the execution function.
After that, the number of parameters is checked, that is to say, the parameters after command should be between the minimum and the maximum.

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); //Find the entry corresponding to keyword
        if (function_info_it == map().end()) { // end is the element after the last element, indicating that it cannot be found
            *err = StringPrintf("invalid keyword '%s'", keyword.c_str());
            return nullptr;
        }

        auto function_info = function_info_it->second;//Get value

        auto min_args = std::get<0>(function_info);//Get the minimum number of parameters
        auto max_args = std::get<1>(function_info);//Get the maximum number of parameters
        if (min_args == max_args && num_args != min_args) {//Comparing the number of actual parameters with the maximum and minimum values
            *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_args)>::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);//Returns the execution function corresponding to the command
    }

Let's look at the implementation of map(), defined in platform/system/core/init/builtins.cpp

This implementation is relatively simple, that is, to construct a map directly, and then return. For example, {"bootchart", {1,1,do_bootchart},
The command name is bootchart, and the corresponding execution function is do_bootchart. The minimum and maximum number of parameters allowed to be passed in is 1.

BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max(); //Represents the maximum 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;
}

Next, let's look at EndSection, which directly calls ActionManager::GetInstance().AddAction

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

AddAction first finds out if there is an Action of the same name, merges their commands if there is one, and stores them in the array actions_if not.

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(*a);
                     });//find_if is a template for comparison in a collection, which is written as a lambda expression

    if (old_action_it != actions_.end()) {//Merge command s if you find an Action in the array actions indicating that the same name already exists
        (*old_action_it)->CombineAction(*action);
    } else { //Add arrays if you can't find them
        actions_.emplace_back(std::move(action));
    }
}

bool Action::TriggersEqual(const Action& other) const {
    return property_triggers_ == other.property_triggers_ &&
        event_trigger_ == other.event_trigger_;//Compare event trigger and property trigger recorded previously
}

void Action::CombineAction(const Action& action) {
    for (const auto& c : action.commands_) { //Merge command s from the new Action into the old Action
        commands_.emplace_back(c);
    }
}

EndFile is an empty implementation defined in platform/system/core/init/action.h

class ActionParser : public SectionParser {
public:
    ActionParser() : action_(nullptr) {
    }
    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 { //Empty implementation
    }
private:
    std::unique_ptr<Action> action_;
};

So much, let's summarize what ActionParser does. It has three important overloaded functions, ParseSection, ParseLineSection, EndSection.

  • The ParseSection function constructs an Action object and records trigger conditions into the Action object.
  • The ParseLineSection function is to find the corresponding execution function in a map according to the command, and then record the information into the previously constructed Action.
  • EndSection stores the actions constructed by the first two steps into an array, compares whether the actions with the same name already exist in the lower array, and merges command s if they do.

2.4 ServiceParser

Defined in platform/system/core/init/service.cpp

We also analyze its four functions: ParseSection, ParseLineSection, EndSection, EndFile.

ParseSection first judges the number of words at least three, because there must be a Service name and execution file, then judges whether the name is legitimate, mainly some length and content checks, and finally constructs a Service object.

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                 std::string* err) {
    if (args.size() < 3) { // At least three incoming words
        *err = "services must have a name and a program";
        return false;
    }

    const std::string& name = args[1];
    if (!IsValidName(name)) {//Check that the name is legitimate
        *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);// Constructing Service Objects
    return true;
}

ParseLineSection directly executes Service's ParseLine function

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;
}

The idea of ParseLine is to find the corresponding execution function from the map according to the option name, and then execute the function.
The main purpose of these execution functions is to do some processing of the incoming parameters, and then record the information into the Service object.

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);//Finding Execution Functions from map

    if (!parser) {
        return false;
    }

    return (this->*parser)(args, err);//Execute the function found
}

map() returns the following map, defined in 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;
}

Next, let's look at EndSection and call the AdService function of Service Manager directly.

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

The implementation of AddService is relatively simple. By comparing the name of service, we can see if there is a service with the same name in the service_array stored in service. If there is one, we can print the error log and return it directly.
If it doesn't exist, add it to the array

void ServiceManager::AddService(std::unique_ptr<Service> service) {
    Service* old_service = FindServiceByName(service->name()); //Find if the service with the same name already exists in services_
    if (old_service) {
        LOG(ERROR) << "ignored duplicate definition of service '" << service->name() << "'";
        return;
    }
    services_.emplace_back(std::move(service));//Add array
}

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();
                            });//As with previous action s, traverse the array to compare and find the service with the same name
    if (svc != services_.end()) {
        return svc->get(); //Find it and return to service
    }
    return nullptr;
}

EndFile is still an empty implementation, defined in platform/system/core/init/service.h

class ServiceParser : public SectionParser {
public:
    ServiceParser() : service_(nullptr) {
    }
    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 { //Empty implementation
    }
private:
    bool IsValidName(const std::string& name) const;

    std::unique_ptr<Service> service_;
};

As you can see from the above, Service Parser's processing is similar to ActionParser's. The difference is that Action saves the execution function and waits for Trigger to trigger. Service finds the execution function and executes it immediately.

2.4 ImportParser

Defined in platform/system/core/init/import_parser.cpp

Finally, let's look at Import Parser. Import Parser's ParseLine Section and EndSection are empty implementations, only ParseSection and EndFile are implemented.
Because it has a single grammar and only one line. Let's look at its ParseSection function.

First check that the word can only be two, because it can only be import xxx grammar, then call expand_props to process the parameters, and finally put the results into the array imports_to save.

bool ImportParser::ParseSection(const std::vector<std::string>& args,
                                std::string* err) {
    if (args.size() != 2) { //Check parameters can only be two
        *err = "single argument needed for import\n";
        return false;
    }

    std::string conf_file;
    bool ret = expand_props(args[1], &conf_file); //Processing the second parameter
    if (!ret) {
        *err = "error while expanding import";
        return false;
    }

    LOG(INFO) << "Added '" << conf_file << "' to import list";
    imports_.emplace_back(std::move(conf_file)); //Store array
    return true;
}

Exp_props is defined in platform/system/core/init/util.cpp. Its main function is to find the grammar of ${x.y} or $x.y. It takes x.y out as name, finds the corresponding value in the attribute system, and replaces it.

bool expand_props(const std::string& src, std::string* dst) {
    const char* src_ptr = src.c_str();

    if (!dst) {
        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.
     */
    //The parameter is either $x.y or ${x.y}, which are all part of the path, $denotes the character $,
    //Recursive writing of ${foo.${bar} is not supported because something bad happens.
    //${x.y:-default} returns default as the default value if the corresponding attribute value cannot be found
    while (*src_ptr) {
        const char* c;

        c = strchr(src_ptr, '$');
        if (!c) { // Can not find the $symbol, directly assign dst to src to return
            dst->append(src_ptr);
            return true;
        }

        dst->append(src_ptr, c);
        c++;

        if (*c == '$') { //Skip $
            dst->push_back(*(c++));
            src_ptr = c;
            continue;
        } else if (*c == '\0') {
            return true;
        }

        std::string prop_name;
        std::string def_val;
        if (*c == '{') { //Find the subscripts of {just ready to find} and intercept the strings between them, corresponding to the case of ${x.y}.
            c++;
            const char* end = strchr(c, '}');
            if (!end) {
                // failed to find closing brace, abort.
                LOG(ERROR) << "unexpected end of string in '" << src << "', looking for }";
                return false;
            }
            prop_name = std::string(c, end); //Intercept strings between {} as name s
            c = end + 1;
            size_t def = prop_name.find(":-"); //If a ":-" is found, the following values are saved as default values first.
            if (def < prop_name.size()) {
                def_val = prop_name.substr(def + 2);
                prop_name = prop_name.substr(0, def);
            }
        } else { //For $x.y
            prop_name = c;
            LOG(ERROR) << "using deprecated syntax for specifying property '" << c << "', use ${name} instead";
            c += prop_name.size();
        }

        if (prop_name.empty()) {
            LOG(ERROR) << "invalid zero-length property name in '" << src << "'";
            return false;
        }

        std::string prop_val = android::base::GetProperty(prop_name, ""); //By name, the corresponding value is found in the attribute system, and the _system_property_find function of the previous attribute system is called internally.
        if (prop_val.empty()) { //Returns the default value if no value is found
            if (def_val.empty()) {
                LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'";
                return false;
            }
            prop_val = def_val;
        }

        dst->append(prop_val);
        src_ptr = c;
    }

    return true;
}

The implementation of EndFile is relatively simple. It copies the array of. rc files parsed by the ParseSection function, then traverses the array and calls the original ParseConfig function to parse a complete path.

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

As a result, we have analyzed the transformation process of Android Init Language grammar. ActionParser, Service Parser and ImportParser are the three core parsers of Android Init Language.
These parsers mainly implement four functions: ParseSection, ParseLineSection, EndSection and EndFile.

  • ParseSection is used to parse the first line of Section, such as
on early
service ueventd /sbin/ueventd
import /init.${ro.zygote}.rc
  • ParseLineSection is used to parse Section command s or option s, such as
write /proc/1/oom_score_adj -1000
class core
  • EndSection is used to handle cases where Action and Service have the same name, and to store parsed objects in an array standby
  • EndFile is only useful in ImportParser, mainly for parsing imported. rc files.

3. Join some events and some actions

After the analysis of the previous step, the system reads the actions and services that need to be executed from various. rc files, but some additional configurations are needed and trigger conditions are added to prepare for triggering.

    // 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 (false) parser.DumpState(); //Print some current Parser information, which is not executed by default

    ActionManager& am = ActionManager::GetInstance();

    am.QueueEventTrigger("early-init");//QueueEventTrigger is used to trigger Action, where the early-init event is triggered

    // 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 is used to add actions. The first parameter is Command to be executed by Action, and the second is 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("init");

    // 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");

3.1 QueueEventTrigger

Defined in platform/system/core/init/action.cpp

Instead of triggering the trigger, it constructs an EventTrigger object and stores it in a queue.

void ActionManager::QueueEventTrigger(const std::string& trigger) {
    trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
}

class EventTrigger : public Trigger {
public:
    explicit EventTrigger(const std::string& trigger) : trigger_(trigger) {
    }
    bool CheckTriggers(const Action& action) const override {
        return action.CheckEventTrigger(trigger_);
    }
private:
    const std::string trigger_;
};

3.2 QueueBuiltinAction

Defined in platform/system/core/init/action.cpp

This function has two parameters. The first parameter is a function pointer and the second parameter is a string. First, an Action object is created, and the second parameter is used as the trigger condition of the Action.
The first parameter is used as the execution command after the Action is triggered, and the second parameter is used as the parameter of the command. Finally, the Action is added to the trigger queue and the Action list.

void ActionManager::QueueBuiltinAction(BuiltinFunction func,
                                       const std::string& name) {
    auto action = std::make_unique<Action>(true);
    std::vector<std::string> name_vector{name};

    if (!action->InitSingleTrigger(name)) { //Call InitTriggers, as I mentioned earlier, to add name to the trigger list for Action
        return;
    }

    action->AddCommand(func, name_vector);//Join Action's command list

    trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));//Add Action to the Trigger Queue
    actions_.emplace_back(std::move(action));//Join the Action List
}

4. Triggering all events and continuously monitoring new events

All of the previous work was to store information into arrays and queues, but not to trigger them. The next work was to trigger these events, and to use epoll to constantly monitor new events.

    while (true) {
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1; //epoll timeout, equivalent to blocking time

         /*
          * 1.waiting_for_prop Both IsWaiting ForExec and IsWaiting ForExec judge whether a Timer is empty or not, which is equivalent to a flag bit.
          * 2.waiting_for_prop Responsible for property settings and IsWaiting ForExe for service operation
          * 3.When a property setting or Service starts running, these two values are not empty until the execution is complete.
          * 4.In fact, the main function of these two judgment conditions is to ensure the integrity of property settings and service startup, or to synchronize.
          */
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            am.ExecuteOneCommand(); //Execute a command
        }
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            restart_processes(); //Restart service

            // If there's a process that needs restarting, wake up in time for that.
            if (process_needs_restart_at != 0) { //When a process needs to be restarted, set epoll_timeout_ms to restart wait time
                epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 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; //When there are commands to execute, set epoll_timeout_ms to 0
        }

        epoll_event ev;
        /*
         * 1.epoll_wait It is used in conjunction with the epoll_create1 and epoll_ctl described in the previous article.
         * 2.epoll_create1 The file descriptor used to create epoll is passed in as the first parameter by epoll_ctl and epoll_wait
         * 3.epoll_ctl EPOLL_CTL_ADD: Register new FD to epfd, EPOLL_CTL_MOD: Modify the listening events of registered fd, EPOLL_CTL_DEL: Delete a FD from epfd;
         * 4.epoll_wait To wait for events to occur, when epoll_ctl calls EPOLL_CTL_ADD, what types of events need to be monitored are passed in.
         *   For example, EPOLLIN means listening for fd readability, and when the fd has readable data, calling epoll_wait over epoll_timeout_ms time returns the event information to & ev
         */
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();//When event returns, ev.data.ptr (the callback function when epoll_ctl was registered) is taken out and executed directly.
            //In the previous article, two fd monitors were registered in signal_handler_init and start_property_service, one for SIGCHLD (end of sub-process signal) and one for attribute settings.
        }
    }

    return 0;
}

4.1 ExecuteOneCommand

Defined in platform/system/core/init/action.cpp

As you can see from the name, it only executes one command, yes, only one. At the beginning of the function, it takes a trigger from the trigger_queue_queue queue.
Then all actions are traversed to find the actions that satisfy the trigger condition and add them to the list of current_executing_actions_to be executed.
Then take an action from this list, execute its first command, and add the subscript of the command to 1. Because ExecuteOneCommand is an infinite loop outside.
Therefore, according to the above logic, the actions satisfying the trigger conditions will be executed in turn according to the order of the trigger table, and then the commands in the action will be executed in turn.

void ActionManager::ExecuteOneCommand() {
    // Loop through the trigger queue until we have an action to execute
    while (current_executing_actions_.empty() && !trigger_queue_.empty()) {//Curr_executing_actions_. empty guarantees that only one trigger is traversed at a time
        for (const auto& action : actions_) {//Travel through all actions
            if (trigger_queue_.front()->CheckTriggers(*action)) {//Join the queue current_executing_actions if the current Trigger condition is satisfied_
                current_executing_actions_.emplace(action.get());
            }
        }
        trigger_queue_.pop();//Remove a trigger from trigger_queue_
    }

    if (current_executing_actions_.empty()) {
        return;
    }

    auto action = current_executing_actions_.front();//Extract an action from the action queue that satisfies the trigger condition

    if (current_command_ == 0) {
        std::string trigger_name = action->BuildTriggersString();
        LOG(INFO) << "processing action (" << trigger_name << ")";
    }

    action->ExecuteOneCommand(current_command_);//Execute the current_command_command command in the action

    // 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_; //Subscript plus 1
    if (current_command_ == action->NumCommands()) { //If it's the last command
        current_executing_actions_.pop();//Kick the action out of current_executing_actions_
        current_command_ = 0;
        if (action->oneshot()) {//If the action is executed only once, kick the action out of the array actions_
            auto eraser = [&action] (std::unique_ptr<Action>& a) {
                return a.get() == action;
            };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
        }
    }
}

4.1 restart_processes

Defined in platform/system/core/init/init.cpp

The restart_processes call is actually the ForEachService WithFlags function, which mainly traverses the services_array and compares whether their flags are SVC_RESTARTING?
Is the current service waiting for reboot, and if so, execute its RestartIfNeeded function?

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 (*func)(Service* svc)) const {
    for (const auto& s : services_) { //Traversing through all service s
        if (s->flags() & matchflags) {//Find out that flags are SVC_RESTARTING, and execute func, which is the incoming Restart IfNeeded
            func(s.get());
        }
    }
}

4.2 RestartIfNeeded

Defined in platform/system/core/init/service.cpp

This function handed over the main work to Start, which is the specific startup service, but before handing it over, it made some judgments that only one service could be started in five seconds.
If there are multiple services, then subsequent services will be waiting

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_start_is the timestamp of the last service start
    if (now > next_start) { //That is to say, the startup interval of two service processes must be greater than 5s.
        flags_ &= (~SVC_RESTARTING); // &= To add or equal to cancel the mark
        Start();
        return;
    }

    time_t next_start_time_t = time(nullptr) +
        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;//If the startup interval of two service s is less than 5 s, assign the remaining time to process_needs_restart_at
    }
}

4.2 Start

Defined in platform/system/core/init/service.cpp

Start is specifically to start the service. It mainly calls clone or fork to create sub-processes, then calls execve to execute the configuration binary files, and according to the previous configuration in the. rc file, to execute these configurations.

bool Service::Start() {

    ... //Clear the tag, initialize console, SELinux policy, etc. according to service configuration

    LOG(INFO) << "starting service '" << name_ << "'...";

    pid_t pid = -1;
    if (namespace_flags_) {//This tag is assigned CLONE_NEWPID|CLONE_NEWNS when service defines namespace
        pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr); //Create subprocesses in clone mode in the new namespace
    } else {
        pid = fork();//Create child processes in fork mode
    }

    if (pid == 0) {//Represents the success of creating a subprocess

        ... //Other parameters to perform service configuration, such as setenv, writepid, and so on

        std::vector<char*> strs;
        ExpandArgs(args_, &strs);//Parse args_for example, ${x.y} and assign the table strs
        if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) { //The execution system calls execve, which is the binary file that executes the configuration and passes in the parameters.
            PLOG(ERROR) << "cannot execve('" << strs[0] << "')";
        }

        _exit(127);
    }

    if (pid < 0) { //Subprocess creation failed
        PLOG(ERROR) << "failed to fork for '" << name_ << "'";
        pid_ = 0;
        return false;
    }

    ... //Perform other service parameters such as oom_score_adjust_, change service running status, etc.
}

Summary

In this stage, the Init process does a lot of important things, such as parsing the. rc file, which configures all the action s that need to be executed and the service s that need to be started.
The Init process parses. rc step by step according to the grammar, converts these configurations into arrays and queues, then opens an infinite loop to process the command s and service s in these arrays and queues, and listens for the end of subprocesses and property settings through epoll.

So far, I've explained the three stages of the Init process. In the next article, I'll explain zygote, an important service configured in. rc, which is the originator of our app program.

Topics: Attribute Android Java socket