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
- /system/etc/init/ is used for the system itself, such as SurfaceFlinger, MediaService, and logcatd.
- /vendor/etc/init/ is used for SoC(system-level core vendors, such as Qualcomm) to provide some core functions and services for them
- / 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 prioritySet 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:
- At initialization, attribute A =b and attribute C =d.
- In the case of attribute c=d, attribute A is changed to b.
- 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
[]\* [–
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.