This is a series of blog posts, and I will continue to provide you with the best possible insight into Android source codeGithub serial address

preface

After the first two phases of init, the properties system and SELinux system have been set up, but the init process still needs to perform many other operations and start many key system services, but the lines of code like the properties and SELinux systems are messy and not easy to scale. So Android introduced init.rc

Rc is a configuration script to start the init process. This script is written in a Language called Android Init Language. Prior to 7.0, the init process only parsed the init.rc file in the root directory. Rc is becoming more and more bloated, so after 7.0, some services of init.rc were split into three directories: /system/etc/init, /vendor/etc/init, /odm/etc/init. In this article, I will explain some syntax of init.rc. How does the init process parse the init.rc file

This article mainly explains the following content

  • Android Init Language syntax
  • Parse the. Rc file
  • Add some events and some actions
  • Fires all events and keeps listening for new events

The files 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
Copy the code

Android Init Language syntax

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

The.rc file contains two main configurations: an action and a service. Trigger and command are complementary to the action. Action, trigger, and commands form a Section, and service, plus options, form a Section. The.rc file is made up of sections. The.rc file has a syntax for import in its header, which represents these items.

The action format is as follows:

    on <trigger> [&& <trigger>]*
       <command>
       <command>
       <command>
Copy the code

Starting with on, trigger is a judgment condition, and command is to perform specific operations. When trigger conditions are met, these command trigger can be a string, such as

on early // Trigger when trigger Early or QueueEventTrigger("early") is called
Copy the code

It can also be an attribute, such as

on property:sys.boot_from_charger_mode=1Boot_from_charger_mode is triggered when the value of sys. boot_froM_charger_mode is set to 1 using property_set
on property:sys.sysctl.tcp_def_init_rwnd=* // * represents any value

Copy the code

Conditions can be multiple, with && concatenation, as in

on zygote-start && property:ro.crypto.state=unencrypted
// Triggered when zygote-start is triggered and ro.crypto. State is unencrypted
Copy the code

A command is a specific operation such as

mkdir /dev/fscklogs 0770 root system // Create a directory
class_stop charger // Terminate the service
trigger late-init  / / triggers the newest - init
Copy the code

The format of services is as follows:

    service <name> <pathname> [ <argument> ]*
       <option>
       <option>
       ...
Copy the code

Start with service, name specifies the name of the service, pathName specifies the path to the execution file for the service, argument specifies the arguments in the execution file, and option specifies some configuration for the service

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    priority - 20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks
Copy the code

This is the service configured in /init.zygote64_32.rc. This is what we call the zygote startup configuration

Zygote is the process name. The executable file path is at /system/bin/app_process64. -xzygote /system/bin –zygote –start-system-server –socket-name=zygote

Other services can also be classified as main. They can be started or terminated together. A service has a name and a class, just like at work. You have a name called Foxleezh, so you belong to the Android department.

I said these things, the source has a special document used to indicate the path in the platform/system/core/init/README. Md, should say is very good, this document written carefully read this document, the basic grammar knowledge knows, I simply translation

Android Init Language

The Android Init Language consists of five syntax classes: Actions, Commands, Services, Options, and Imports. Each line is a single statement. Words are separated by Spaces. You can also use double quotation marks to quote text to avoid collisions with Spaces. If a line is too long, you can use \ newline. Actions and Services can be commented as a single Section with #. All Commands and Options are subordinate to the Actions or Services next to each other. Commands and Options defined in front of the first Section are ignored. Actions and Services are unique. If two identical Actions are defined, the Command of the second Action is appended to the first Action. If two identical services are defined, the second Service is ignored and an error log is printed

Init .rc Files

The Android Init Language is written in plain text with the suffix.rc and consists of several.rc files distributed in different directories. The main one is /init.rc, which is loaded by the Init process at initialization. ${ro.hardware}. Rc. This is the main. Rc file provided by the system core manufacturer when executing the mount_all statement. Init process will load all files in /{system,vendor,odm}/etc/init/. After mounting the file system, these directories will be used for Actions and Services. A special directory may be used to replace the three default directories above. This is mainly to support factory mode and other non-standard startup modes. The above three directories are used for normal startup process and the three directories for extension are

  1. /system/etc/init/ is used for the system itself, such as SurfaceFlinger, MediaService, and logcatd.
  2. /vendor/etc/init/ is used for SoC(system-level core vendors, such as Qualcomm) to provide some core functions and services for them
  3. / odM /etc/init/ Is used by device manufacturers (odM customized manufacturers, such as Huawei and Xiaomi) to provide some core functions and services for their sensors or peripherals

All Services binaries in these three directories must have a corresponding. Rc file in that directory and define the service structure in the. 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. Android. Mk file using LOCAL_INIT_RC this macro, will logcatd at compile time. The rc in/system/etc/init/directory, the init process when invoking mount_all will load it, It is better to split init.rc into different directories for different services by running its defined service and queuing the action at the right time, rather than putting it in a single init.rc file. This ensures that the Services and actions read by init more closely match the Services binaries in the same directory, rather than a single init.rc file. The mount_all statement has two options: “Early” and “late”. When early is set, The init process will skip the latemount labeled mount and trigger the FS encryption state event. When late is set, the init process will only perform the latemount labeled mount, but skip the execution of the imported.rc file. By default, no options are set and the init process will perform all mount operations

Actions

Actions consists of a line of commands. Trigger determines when these commands are triggered. When an event meets the trigger condition, the action is added to the processing queue (unless it already exists in the queue) and executed in sequence. The commands in action are executed sequentially. These commands are used to perform Actions (device creation/destruction, property setting, process restart). The format of Actions is as follows:

    on <trigger> [&& <trigger>]*
       <command>
       <command>
       <command>
Copy the code

Services

Services are programs that the init process starts, and they may restart automatically upon exit. The format of Services is as follows:

    service <name> <pathname> [ <argument> ]*
       <option>
       <option>
       ...
Copy the code

Options

Options is the parameter configuration of Services. They affect how and when the Service runs. Console [

] The Service requires a console. The second parameter console means that you can set the type of console you want. The default console is /dev/console. The /dev prefix is usually ignored. For example, if you want to set the console /dev/tty0, you only need to set the console tty0 to critical to indicate that Service is in strict mode. If the Service exits for more than four times within four minutes, the device restarts and enters recovery mode. Disabled indicates that the Service cannot be started as a class. Socket

[

[

[socket

[

]]] create a Unix domain socket named /dev/socket/name and return the fd to service. type can only be “dgram” The default values for “stream” or “seqpacket”. User and group are 0. ‘secLabel’ is the SELinux security context for this socket, and its default value is the Service security policy or security context based on its executable. Its local implementation opens a file in libcutils android_get_control_socket file
and returns a fd to the service. type can only be “r”, “w” or “rw”. The corresponding local implementation of libcutils android_get_control_file user

changes user to username before starting the Service, which is root by default (or none by default). In the Android M version, you can only set this value if a process wants to have Linux Capabilities. Previously, to have Linux Capabilities, a program had to run as root and then degrade to the desired UID. In its place, a new mechanism, fs_config, allows vendors to assign special binaries Linux capabilities. The documentation of this mechanism in source.android.com/devices/tec… When using this new mechanism, a program can select its own UID using the user parameter instead of running as root. In Android O, apps can apply for capabilities directly using the Capabilities parameter, [

\*] Group

[

\*] The default value is root (or none), the second groupname can be left unset, Capabilities

[

\*] Set capabilities to capability. ‘Capability’ when launching Service. Cannot be “CAP_” prefix, like “NET_ADMIN” or “SETPCAP”. Refer to the http://man7.org/linux/man-pages/man7/capabilities.7.html, Seclabel < secLabel > Set secLabel to secLabel before starting the Service. Services that are started on rootfs, such as ueventd, adBD. A service running on a system partition has its own SELinux security policy, and if not set, the default security policy is init. Oneshot does not restart after exiting class

[

\*] Specifies a class name for the service. Services with the same class name can be started or exited together. The default value is “default”. The second name is optional. Animation Class An animation class mainly contains services for startup or shutdown animations. They start early and do not exit until the last step of the shutdown. They don’t allow access to the /data directory, they can check the /data directory, but they can’t open the /data directory, Writepid

[

\*] Writes the pid of the child process to the specified file when the Service calls fork. For cgroup/ CPUSET use, If there is no file under /dev/cpuset/ but the value of ro.cpuset.default is not empty, write pid value to /dev/cpuset/cpuset_name/tasks file priority Set process priority. Between 20 and 19, the default value is 0, can realize the namespace a setpriority < pid | MNT > when the fork the service, Set the PID or MNT flag oom_score_adjust

Set the /proc/self_oom_score_adj value of the child process to value between -1000 and 1000.















Triggers

Triggers is a string, and when an event occurs that meets that condition, some actions are triggered into event Trigger and attribute Trigger EventTrigger is triggered by the Trigger command or the QueueEventTrigger method. Its format is a simple string like ‘boot’ or ‘late init’. A property Trigger is triggered when a property is set or changed. The format is ‘property:=’ or ‘Property :=*’, which is triggered when init initializes the property. An Action defined by an attribute Trigger may Trigger in several ways, but an Action defined by an event Trigger may Trigger in only one way, for example: On boot && Property :a=b defines an action that is triggered if the boot Trigger is triggered and the property a equals B on property:a=b && Property :c=d there are three ways to Trigger this definition:

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

Commands

Bootchart [start | stop] start or end bootcharting. This appears in the init.rc file, but only works if the /data/bootchart/enabled file exists, Otherwise, the file cannot work. Chmod
Changes the read and write permission of the file. Chown

Changes the owner or user group of the file. Start any unstarted service named serviceclass (a service has a name and a class),class_start,class_start,class_start,class_start Class_start is a class start, Class_stop

Terminates any running service class_reset

Terminates all running services named serviceclass, but can’t help using them. They can be restarted later by class_start class_restart

Restart all serviceclass named service copy < SRC > < DST > Copy a file, similar to write, Suitable for binary or large files. For SRC, copying from linked files, world-writable, or group-writable is not allowed. For DST, if the target file does not exist, the default permission is 0600. If it does, the default permission is 0600. Domainname

sets the domainname enable

to make a disabled service available. If the service is running, it will restart. It is used to set properties during bootloader and then start a service. Such as on the property: ro. Boot. Myfancyhardware = 1 enable my_fancy_service_for_my_fancy_hardware exec [< seclabel > [< user > [

\*]]] —
[

\*] Creates a child process and runs a command with the specified arguments. This command specifies seclabel (security policy), user(owner), group(user group). No other commands can be run until this command runs. Seclabel can be set to – to indicate the default value, and argument to indicate the attribute value. The init process does not continue until the child process is created. Exec_start

Start a service. The init process can continue only when the execution result is returned. Export


sets the environment variable name-value. Ifup

Enable the specified network interface insmod [-f] [
] Install the module under path, -f specifies the mandatory installation, even if the current Linux kernel version does not match the load_all_props /system, /vendor, etc. This is the persistence property used in the load_persist_props load /data in init.rc. Mkdir [mode] [owner] [group] create a directory where path is the path and mode is the read/write permission. Default value is 755,owner is the owner, default value is root,group is the user group, default value is root. Mount_all

[ ]\* [–


[< flag>\*] [
] Mount a device named _flag in the dir directory Including “ro”, “rw”, “remount”, “noatime”,… Options include “barrier=1”, “noauto_da_alloc”, “discard”,… Barrier =1,noauto_da_alloc restart

restart a service, if the service is restarted, do nothing, if it is not running, Start restorecon [ \*] to restore the security context of the files in the specified directory. The second path is the security policy file. The specified directory does not need to exist because it simply needs to recursively restore the security context under the specified directory by properly marking restoreCon_recursive [ \*] in init, The second path is the location of the security policy file. Rm calls unlink(2) to delete the specified file. Exec — rm… Instead, Rmdir call rmdir(2) to remove the specified directory setprop


Set property name-value setrlimit


< Max > Start

Start a service that is not running stop

Stop a service that is running swapon_all

Symlink
Create a link under path to target sysclktz < mins_west_of_GMT > Reset the system base time (0 if Greenwich Mean time) Trigger

Trigger event, Umount unmounts the file system with the specified path verity_load_state The internal implementation is to load the dM-verity state verity_update_state The

internal implementation is to set the state of dM-verity and set the attribute of partition. Mount-point.verified. For ADB remount, because fs_Mgr cannot be set directly. Wait [

] to check whether the specified path exists. Wait_for_prop


Waits for the value of the name property to be set to value. If the value of name is set to value, Immediately proceed to write
open the file under Path and write the content with write(2). The file is created if it doesn’t exist, and overwritten if it does























Imports

The import keyword is not a command, but if a. Rc file contains it, it will immediately parse the sections in it, as follows: import Parses the. Rc file under path, including the configuration of the current file. If path is a directory where all.rc files are parsed, but not recursively, import is used in two places: 1. {system,vendor,odm}/etc/init/. Rc: {system,vendor,odm}/etc/init/. Rc: {system,vendor,odm}/etc/init/. Ro.boottime. init records some key points in time. Bootcharting is a graphical performance monitoring tool

Okay, so the syntax of the.rc file, let’s see how the init process parses the.rc file, and turns that syntax into actual code, okay

Parse the.rc file

Rc: /init.rc: {system,vendor,odm}/etc/ init.rc: {system,vendor,odm}/etc/init/. The following code is easy to understand.

int main(int argc, char** argv) {...const BuiltinFunctionMap function_map;
    /* * 1. In C++ :: represents a static method call, equivalent to the Java static method */
    Action::set_function_map(&function_map); // Store function_map in Action as a member property


    Parser& parser = Parser::GetInstance();Parser object. // Parser object
	STD ::make_unique: STD ::make_unique: STD ::make_unique: STD ::make_unique: STD ::make_unique: STD ::make_unique: STD ::make_unique: STD ::make_unique No copy operation can be performed, only move operation * 3 can be performed. The function for the move operation is p1= STD ::move(p), so that the object pointed to by the pointer P is moved to p1 * 4. 5.ServiceParser, ActionParser, and ImportParser correspond to service Action import parsing */
    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 does not have a corresponding value, the. Rc files in /init.rc and /system/etc/init, /vendor/etc/init, and /odm/etc/init are parsed
        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 property has a value, the property 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);
    }
Copy the code

2.1 ParseConfig

Defined in the platform/system/core/init/init_parser CPP

ParseConfigDir is a call to ParseConfigFile. ParseConfigDir is a call to ParseConfigFile. ParseConfigDir is a call to ParseConfigFile.

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

ParseConfigFile is a function that reads the data in the file, passes the data to ParseData, and finally calls its EndFile function through section_parsers_

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

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

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

2.2 ParseData

ParseData defined in the platform/system/core/init/init_parser CPP

ParseData iterates through each character by calling the next_token function, splitting a line into words by Spaces or “”, calling T_TEXT to place the word in the ARgs array, and calling T_NEWLINE when carriage return is read. Find the corresponding parser on service import from the section_parsers_ map and execute ParseSection. If the corresponding key is not found in the map, execute ParseLineSection. Call T_EOF 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 to data_copy
    data_copy.push_back('\ 0'); // Appends a terminator 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)) { // Iterate over each character in data_copy
        case T_EOF: // If it is the end of a file, EndSection is called
            if (section_parser) {
                section_parser->EndSection();
            }
            return;
        case T_NEWLINE:// Read a row of data
            state.line++;
            if (args.empty()) {
                break;
            }
            Section_parsers_ is a STD :map *. Section_parsers_ is a STD :map *. Section_parsers_ contains three keys. On service import, */ was added to AddSectionParser
            if (section_parsers_.count(args[0]) {// Check whether on service import is included
                if (section_parser) {
                    section_parser->EndSection();
                }
                section_parser = section_parsers_[args[0]].get();// Fetch the corresponding parser
                std: :string ret_err;
                if(! section_parser->ParseSection(args, &ret_err)) {// Parse the corresponding Section
                    parse_error(&state, "%s\n", ret_err.c_str());
                    section_parser = nullptr; }}else if (section_parser) { // Without on service import is command or option
                std: :string ret_err;
                if(! section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {// Parse command or option
                    parse_error(&state, "%s\n", ret_err.c_str());
                }
            }
            args.clear();
            break;
        case T_TEXT: // Put a row of data into args. Args splits the row into words with Spaces or ""
            args.emplace_back(state.text);
            break; }}}Copy the code

Here actually involves on service import corresponding three parser ActionParser, ServiceParser, ImportParser, before they are added to the section_parsers_ in this map

    Parser& parser = Parser::GetInstance();
    parser.AddSectionParser("service".std::make_unique<ServiceParser>());
    parser.AddSectionParser("on".std::make_unique<ActionParser>());
    parser.AddSectionParser("import".std::make_unique<ImportParser>());

    void Parser::AddSectionParser(const std: :string& name,
                                  std: :unique_ptr<SectionParser> parser) {
        section_parsers_[name] = std::move(parser);
    }
Copy the code

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

class SectionParser {
public:
    virtual ~SectionParser() {
    }
    /* * 1. Pure virtual functions in C++ are defined in the form of virtual as the modifier and then assigned to 0, equivalent to the abstract method * 2 in Java. A virtual function that does not assign a value to 0 but uses virtual as a modifier is a virtual function. A virtual function can have a method body, which is equivalent to a method of a Java parent class, mainly used for subclass overloading * 3. Any class that contains pure virtual functions is abstract and cannot be new. It can only be implemented by subclasses, as in 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(a) = 0;
    virtual void EndFile(const std: :string& filename) = 0;
};
Copy the code

Next I will analyze the implementation of ParseSection, ParseLineSection, EndSection, and EndFile for these three persers

2.3 ActionParser

Defined in the platform/system/core/init/action. CPP

Let’s look at ParseSection, which copies the data from args with subscript 1 to the end into the Triggers array. Then we build the Action object and call InitTriggers to resolve these 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 into triggers, removing the 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 resolve trigger
        return false;
    }

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

InitTriggers identifies the type of trigger by comparing whether or not it starts with “Property :”. If it is a property trigger, it calls ParsePropertyTrigger, If it is an Event trigger, the args argument is assigned to event_trigger_ of type 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;
}
Copy the code

The ParsePropertyTrigger function splits the character “=” into name-value and stores the name-value into 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())); 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)); // get value prop_name. Erase (equal_pos); [IT, inserted] = property_triggers_. Emplace (prop_name, prop_value); // Delete equal_pos ("=" if (auto [it, inserted] = property_triggers_. ! Inserted) {// Insert name-value into map, emplace = put *err = "Multiple property triggers found for the same property"; return false; } return true; }Copy the code

If it is an event trigger, it will be assigned to event_trigger_. If it is an event trigger, it will be assigned to event_trigger_. If it’s a property trigger, it’s stored in the property_triggers_ map. Next, 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;
}
Copy the code

AddCommand, as the name indicates, adds commands. It does some checking for empty parameters, calls FindFunction to find the function executing the Command, and wraps this information into a Commands_ array. The key here is FindFunction

bool Action::AddCommand(const std: :vector<std: :string>& args,
                        const std: :string& filename, int line, std: :string* err) {
    ... // Check some parameters

    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 and emplace_back is the equivalent of add
}
Copy the code

FindFunction defined in the platform/system/core/init/keyword_map. H

This function is used to find the function to execute by command. For example, if chmod is defined in the.rc file, we need to find the function to execute. It starts by returning STD :map via map() and calls its find function, which is equivalent to get in Java but returns an entry. Key-value can be obtained via entry ->first and entry ->second. The first is the minimum number of parameters, the second is the maximum number of parameters, and the third is the number of parameters check, that is, the command parameters between the minimum and maximum value.

const Function FindFunction(const std: :string& keyword,
                                size_t num_args,
                                std: :string* err) const {
        using android::base::StringPrintf;

        auto function_info_it = map().find(keyword); // Find the entry corresponding to the keyword
        if (function_info_it == map().end()) { // end is the element after the last element, indicating that it could not be found
            *err = StringPrintf("invalid keyword '%s'", keyword.c_str());
            return nullptr;
        }

        auto function_info = function_info_it->second;/ / get the 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) {// Compare the actual number of 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 corresponding execution function of the command
    }
Copy the code

Let’s take a look at the map (), defined in the platform/system/core/init/builtins. CPP

This implementation is relatively simple, just construct a map and return it. For example, {“bootchart”, {1,1,do_bootchart}}, indicates that the command name is bootchart, the corresponding execution function is do_bootchart, and the minimum and maximum number of parameters that can be passed in is 1

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

Copy the code

Next we see EndSection, directly is called ActionManager: : GetInstance () AddAction

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

AddAction first looks for existing actions of the same name, consolidates their commands if so, 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 the template used for comparison in the collection

    if(old_action_it ! = actions_.end()) {// Merge command by finding actions that already have the same name
        (*old_action_it)->CombineAction(*action);
    } else { // Add to array if not found
        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 the event trigger and property trigger recorded previously
}

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

EndFile is an empty implementation, defined in the 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(a) override;
    void EndFile(const std: :string&) override { / / empty implementation
    }
private:
    std: :unique_ptr<Action> action_;
};
Copy the code

So with that said, let’s summarize what ActionParser does. It has three important overloaded functions, ParseSection, ParseLineSection, and EndSection.

  • The ParseSection function constructs an Action object and records the trigger condition into the Action object
  • ParseLineSection finds the corresponding function in a map based on the command and records the information into the previously constructed Action
  • The EndSection stores the actions created in the previous two steps into an array. Before storing the actions, the EndSection checks whether the array already contains an Action with the same name. If so, the EndSection merges the commands

2.4 ServiceParser

Defined in the platform/system/core/init/service. The CPP

ParseSection, ParseLineSection, EndSection and EndFile

ParseSection is used to determine the number of words at least three, as there must be a Service name and an executable file. It is also used to determine whether the name is valid

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

    const std: :string& name = args[1];
    if(! IsValidName(name)) {// Check whether the name is valid
        *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);// Construct the Service object
    return true;
}
Copy the code

ParseLineSection Directly executes the ParseLine function of the Service

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;
}
Copy the code

ParseLine executes the map function based on the option name. The main purpose of these functions is to do some processing on the parameters passed in, and then record the information to 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);// Find the execution function from the map

    if(! parser) {return false;
    }

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

The map () returns a map is as follows, defined in the definition in the platform/system/core/init/service. The CPP

Service::OptionParserMap::Map& Service::OptionParserMap::map(a)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;
}
Copy the code

Next we look at the EndSection, which calls the ServiceManager AddService function directly

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

The AddService command compares the name of the service and checks whether the array services_ contains a service with the same name. If yes, the AddService command prints an error log and returns it. If no, the AddService command adds it to the array

void ServiceManager::AddService(std: :unique_ptr<Service> service) {
    Service* old_service = FindServiceByName(service->name()); // Check whether a 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 an 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 the previous action, search through the group to find the service with the same name
    if(svc ! = services_.end()) {return svc->get(); // Return service when found
    }
    return nullptr;
}
Copy the code

EndFile remained an empty implementation, defined in the 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(a) override;
    void EndFile(const std: :string&) override { / / empty implementation
    }
private:
    bool IsValidName(const std: :string& name) const;

    std: :unique_ptr<Service> service_;
};
Copy the code

As you can see from the above, ServiceParser does the same thing as ActionParser, except that Action stores the execution function and waits for it to be executed when the Trigger is triggered, while Service executes as soon as it finds the execution function

2.4 ImportParser

Defined in the platform/system/core/init/import_parser CPP

ImportParser implements ParseLineSection and EndSection. It implements only ParseSection and EndFile because it has a single line syntax. Let’s look at its ParseSection function

Check that there are only two words, because import XXX is the only syntax, then call expand_props to process the parameters, and store the result in the array imports_

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); // Process the second argument
    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 to array
    return true;
}
Copy the code

Expand_props defined in the platform/system/core/init/util. CPP, main effect is foundThe syntax x.y is used to take x.y out as name, find the corresponding value in the attribute system, and replace 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. */
    ${x.y} is a part of the path. Represents the character $,
    ${foo.${bar}} is not supported recursively because bad things can happen
    ${x.y:-default} returns default as the default value, if no corresponding attribute value is found
    while (*src_ptr) {
        const char* c;

        c = strchr(src_ptr, '$');
        if(! c) {// if $is not found, return DST as SRC
            dst->append(src_ptr);
            return true;
        }

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

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

        std: :string prop_name;
        std: :string def_val;
        if (*c == '{') { ${x.y} = ${x.y} = ${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); // Intercepts the string between {} as name
            c = end + 1;
            size_t def = prop_name.find(: "-"); // If ":-" is found, the following values are saved as default
            if (def < prop_name.size()) {
                def_val = prop_name.substr(def + 2);
                prop_name = prop_name.substr(0, def); }}else { // the corresponding case of $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, ""); // Call __system_property_find from the previous property system
        if (prop_val.empty()) { // Return 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;
}
Copy the code

The implementation of EndFile is simple: copy the array of.rc files parsed by ParseSection, traverse the array, and call the original ParseConfig function to parse the entire 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 << "'"; }}}Copy the code

Thus, we will be Android Init Language syntax analysis of the conversion process, in fact they are the core of the parser is three, ActionParser, ServiceParser, ImportParser. These parsers mainly implement ParseSection, ParseLineSection, EndSection and EndFile

  • ParseSection is used to parse the first row of a Section, for example
on early
service ueventd /sbin/ueventd
import /init.${ro.zygote}.rc
Copy the code
  • ParseLineSection is used to parse a Section’s command or option, for example
write /proc/1/oom_score_adj - 1000.
class core
Copy the code
  • The EndSection handles cases where the Action and Service have the same name and stores parsed objects into arrays for later use
  • EndFile is only useful in ImportParser, mainly for parsing imported. Rc files

Add some events and some actions

After parsing in the previous step, the system reads the actions and services that need to be executed from various.rc files, but some additional configuration is required and trigger conditions need to be added to prepare them for firing

    // 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(); // Prints some information about the current Parser, which is not executed by default

    ActionManager& am = ActionManager::GetInstance();

    am.QueueEventTrigger("early-init");//QueueEventTrigger is used to trigger an Action, where an 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 an Action. The first argument is the Command the Action is to execute, and the second argument 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");
Copy the code

3.1 QueueEventTrigger

Defined in the 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_;
};
Copy the code

3.2 QueueBuiltinAction

Defined in the platform/system/core/init/action. CPP

This function takes two arguments. The first argument is a function pointer and the second argument is a string. Create an Action object, use the second parameter as the Action trigger condition, use the first parameter as the Action trigger command, and use the second parameter as the command parameter. Finally, add the Action to the trigger queue and join 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)) {// Invoke InitTriggers, as mentioned earlier, to add name to the trigger list for the Action
        return;
    }

    action->AddCommand(func, name_vector);// Add the command to the Action list

    trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));// Add the Action to the trigger queue
    actions_.emplace_back(std::move(action));// Add to the Action list
}
Copy the code

Trigger all events and constantly listen for new events

All the previous work had been storing information into various arrays and queues without actually firing, and the next work was actually firing those events and using Epoll to constantly listen for new events

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

         /* * 1. Waiting_for_prop and IsWaitingForExec both determine whether a Timer is null, which is equivalent to a flag bit * 2. IsWaitingForExe is responsible for running the service * 3. When a property is set or the Service is running, these values are not null until the execution is complete. The main purpose of these two criteria is to ensure the integrity of the property setting 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 the service

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

        epoll_event ev;
        Epoll_wait is used with epoll_create1 and epoll_ctl. Epoll_create1 is used with epoll_create1 and epoll_ctl. Epoll_ctl and epoll_wait both pass the fd they created as the first argument * 3. Epoll_ctl is used to operate on epoll, EPOLL_CTL_ADD: registers a new FD into an EPFD, and EPOLL_CTL_MOD: EPOLL_CTL_DEL: Deletes a fd from an EPFD. When epoll_ctl calls EPOLL_CTL_ADD, epoll_ctl calls EPOLL_CTL_ADD. * For example, EPOLLIN means to listen for a readable fd, and when the FD has readable data, Calling epoll_wait returns the event information to &ev */ after epoll_timeout_ms
        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 an event is returned, ev.data. PTR (the callback from the previous epoll_ctl registration) is retrieved and executed directly
            Signal_handler_init and start_property_service register two fd listeners, one for SIGCHLD and one for property setting}}return 0;
}
Copy the code

4.1 ExecuteOneCommand

Defined in the platform/system/core/init/action. CPP

As the name suggests, it only executes one command, yes, only one. Pull a trigger from the trigger_queue_ queue at the beginning of the function, then loop through all actions to find the actions that meet the trigger criteria and add them to the pending list current_executing_actions_. Then take an action from the list, execute its first command, and append the subscript to the command by 1. Since the outside of ExecuteOneCommand is an infinite loop, executing the above logic over and over again will execute the actions that satisfy the trigger conditions in the order of the trigger table, and then execute the commands in the actions.

void ActionManager::ExecuteOneCommand() {
    // Loop through the trigger queue until we have an action to execute
    while(current_executing_actions_.empty() && ! trigger_queue_.empty()) {//current_executing_actions_. Empty guarantees that only one trigger will be traversed at a time
        for (const auto& action : actions_) {// Iterate over all actions
            if (trigger_queue_.front()->CheckTriggers(*action)) {// Those meeting the current Trigger conditions join the queue current_executing_actions_
                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();// Remove an action from the action queue that meets 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 in this 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 + 1
    if (current_command_ == action->NumCommands()) { // If it is the last command
        current_executing_actions_.pop();// Remove the action from current_executing_actions_
        current_command_ = 0;
        if (action->oneshot()) {// If the action is executed only once, it is removed from the actions_ array
            auto eraser = [&action] (std: :unique_ptr<Action>& a) {
                return a.get() == action;
            };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser)); }}}Copy the code

4.1 restart_processes

Defined in the platform/system/core/init/init. CPP

Restart_processes calls ForEachServiceWithFlags, which iterates through the services_ array to see if its flags are SVC_RESTARTING. If so, its RestartIfNeeded function is executed

static void restart_processes(a)
{
    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_) { // Iterate over all services
        if (s->flags() & matchflags) {// Check that flags is SVC_REstarter, and run func, which is RestartIfNeededfunc(s.get()); }}}Copy the code

4.2 RestartIfNeeded

Defined in the platform/system/core/init/service. The CPP

This function leaves the bulk of the work to Start the service, but it makes some judgments before starting it, that is, only one service can be started in five seconds, and if there are multiple services, subsequent services will wait

void Service::RestartIfNeeded(time_t* process_needs_restart_at) {
    boot_clock::time_point now = boot_clock::now();
    boot_clock::time_point next_start = time_started_ + 5s; //time_started_ is the timestamp when the last service was started
    if (now > next_start) { // This means that the interval between the two service processes must be greater than 5s
        flags_ &= (~SVC_RESTARTING); // &= add ~ 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 interval between two services is less than 5s, assign the remaining time to process_needs_restart_AT}}Copy the code

4.2 Start

Defined in the platform/system/core/init/service. The CPP

Start starts the service specifically by calling clone or fork to create the child process, then calling execve to perform the configuration binary, and then performing the configuration based on the previous configuration in the. Rc file

bool Service::Start() {

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

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

    pid_t pid = - 1;
    if (namespace_flags_) {/ / this tag when the service defines the namespace assignment for CLONE_NEWPID | CLONE_NEWNS
        pid = clone(nullptr.nullptr, namespace_flags_ | SIGCHLD, nullptr); // Create a child process in clone mode in the new namespace
    } else {
        pid = fork();// Create a child process by fork
    }

    if (pid == 0) {// The subprocess is successfully created.// Execute other parameters of service configuration, such as setenv, writepID, etc

        std: :vector<char*> strs;
        ExpandArgs(args_, &strs);${x.y} and assign STRS to args_
        if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) { // Execute the system call execve, which is the binary file to execute the configuration, and pass the parameters in
            PLOG(ERROR) << "cannot execve('" << strs[0] < <"')";
        }

        _exit(127);
    }

    if (pid < 0) { // Failed to create the child process
        PLOG(ERROR) << "failed to fork for '" << name_ << "'";
        pid_ = 0;
        return false; }...// Run the oom_score_Adjust_ command to change the running status of the Service
}
Copy the code

summary

At this stage Init does a lot of important things, like parsing.rc files, which are configured with all the actions that need to be executed and services that need to be started, and parsing them syntax-wise into arrays, queues, It then starts an infinite loop to process the arrays, commands, and services in the queue, and listens for child process termination and property Settings via epoll.

Now that I have covered the three phases of the Init process, in the next article I will cover zygote, an important service configured in rc, which is the granddaddy of our app.