I recently started reading the source code of the Android startup module, and write down what I learned from it. The source code in the article is based on Android8.0.0
Init process is the first process that Android starts in user space, and it is also the parent process of other processes in user space. Its process number is 1. The system uses init process to perform some initialization work, including starting Zygoto, SystemServer and other important processes.
After loading the Linux kernel, the system will create the init process and execute the main method in the init file. Next, we will analyze the code of the main method to see the init process start process.
1. Perform step 1 to mount the system running directory
Source path: \system\core\init\init. CPP
int main(int argc, char** argv) {
// 1. Check whether it is ueventd or watchdogd according to argv
// The STRCMP function is used to compare string values for equality. Equality returns 0
The basename function is used to retrieve the content after the last '/' in the string
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
if (REBOOT_BOOTLOADER_ON_PANIC) {
install_reboot_signal_handlers(a);// Set some semaphores
}
// Add environment variables
add_environment("PATH", _PATH_DEFPATH);
// 2. Check if it is the first stage
bool is_first_stage = (getenv("INIT_SECOND_STAGE") = =nullptr);
if (is_first_stage) {// The code inside if is executed only in the first phase
boot_clock::time_point start_time = boot_clock::now(a);// Clear file permissions
umask(0);
// 3. Mount and create directories necessary for system operation
mount("tmpfs"."/dev"."tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts".0755);
mkdir("/dev/socket".0755);
mount("devpts"."/dev/pts"."devpts".0.NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc"."/proc"."proc".0."hidepid=2,gid=" MAKE_STR(AID_READPROC));
// Don't expose the raw commandline to unprivileged processes.
chmod("/proc/cmdline".0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs"."/sys"."sysfs".0.NULL);
mount("selinuxfs"."/sys/fs/selinux"."selinuxfs".0.NULL);
mknod("/dev/kmsg", S_IFCHR | 0600.makedev(1.11));
mknod("/dev/random", S_IFCHR | 0666.makedev(1.8));
mknod("/dev/urandom", S_IFCHR | 0666.makedev(1.9));
// Initialize the log
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
// Prepare to move on to phase 2. }... }Copy the code
In the first comment, argv is used to determine whether ueventd or Watchdogd is the main argument. We read the source code mainly to understand the init process startup process, not ueventd and watchdogd.
In comment 2, the environment variable INIT_SECOND_STAGE is used to determine whether the current stage is phase 1 or phase 2. During the start of the init process, the main function is executed twice, respectively representing phase 1 and phase 2. If it is phase 1, the code in if is executed.
In note 3, mount and mkdir are used to mount and create directories that are needed when the system is running. These directories are created only when the system is running and disappear when the system is shut down.
Then preparations began for the second stage.
2. Move on to phase 2
// Initialize the Selinux security module. Selinux is the NSA's implementation of mandatory access control, which is a security subsystem of Linux
selinux_initialize(true); .// 1. Set the environment variable and set INIT_SECOND_STAGE to true
setenv("INIT_SECOND_STAGE"."true".1);
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT".StringPrintf("%" PRIu64, start_ms).c_str(), 1);
char* path = argv[0];
char* args[] = { path, nullptr };
//2. Run the main method again
execv(path, args);
Copy the code
Set the environment variable INIT_SECOND_STAGE to true at comment 1, and then re-execute the main method via execv at comment 2, where the value of path is the path of the current init file. Execv will terminate the current process and execute a new process based on path, but the process ID will not change.
3. Enable the property service and initialize the signal handler function
// The second stage re-executes the main method
int main(int argc, char** argv) {...// Since INIT_SECOND_STAGE is already true, the code in if (is_first_stage) will be skipped when main is run the second time.
bool is_first_stage = (getenv("INIT_SECOND_STAGE") = =nullptr);
if (is_first_stage) {
...
}
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";// Indicates that it is now in phase 2.property_init(a);// 1. Initialize the property service
process_kernel_dt(a);// Handle the DT attribute
process_kernel_cmdline(a);// Process command line attributes
export_kernel_boot_props(a);// Handle system properties.// Clear unnecessary environment variables
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
/ / load Selinux
selinux_initialize(false);
selinux_restore_context(a);// 2. Initialize the signal handler function
signal_handler_init(a);property_load_boot_defaults(a);export_oem_lock_status(a);// Start the properties service
start_property_service(a);set_usb_controller(a); .Copy the code
Now we move on to phase two. The above code has relatively simple logic, mainly turning on the property service and initializing the signal handler function.
The property service, which acts like a Registry on Windows, is initialized in comment 1.
In comment 2, the initialization signal handler is used to prevent children of the init process from becoming zombies.
Turn on the properties service in comment 3.
4. Parse the init.rc file
. Parser& parser = Parser::GetInstance(a);// 1. Get the parser object
parser.AddSectionParser("service",std::make_unique<ServiceParser>());// Load the parser that parses the Service statement
parser.AddSectionParser("on", std::make_unique<ActionParser>());// Load the parser that parses the ON statement
parser.AddSectionParser("import", std::make_unique<ImportParser>());// load a parser that parses import statements
std::string bootscript = GetProperty("ro.boot.init_rc"."");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");// 2. Parse 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 {
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
This code mainly parses the init.rc file. In comment 1, we get a Parser object from Parser::GetInstance(), and then add parsers for service, on, and import statements to the object using the AddSectionParser method.
ParseConfig method of parser is called in comment 2 to parse the init.rc file
Let’s take a look at what an.rc file is.
4.1. Rc File Introduction
The.rc file is a script written in the Android initialization language, which consists of sections that can be divided into two categories: Actions and services. The basic form of these sections is as follows:
// Action
on <trigger> [&&<trigger>]* // Set the trigger with the Action type on
<command> // The command to execute after the trigger
<command>
...
// Service ()
service <name> <pathname> [<argument>]* //
< execution path > < parameters >
<option> // Some options
<option>
Copy the code
Take this code in init.rc as an example:
on early-init // Set the early-init trigger
// The following statements are commands that are executed when early-init is triggered
write /proc/1/oom_score_adj - 1000.
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
# Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
mkdir /mnt 0775 root system
# Set the security context of /postinstall if present.
restorecon /postinstall
start ueventd
Copy the code
There is also a class of import statements in.rc files that import other.rc files.
4.2 Init. rc source code analysis
Let’s look at the source of the init.rc file:
Source path: \ System \ Core \ Rootdir \init.rc
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc // 1. Load init.zygote.rc
// The following is a series of triggers
on early-init
write /proc/1/oom_score_adj - 1000.
write /proc/sys/kernel/sysrq 0
restorecon /adb_keys
mkdir /mnt 0775 root system
restorecon /postinstall
start ueventd
on init
sysclktz 0
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom
symlink /system/etc /etc
symlink /sys/kernel/debug /d
symlink /system/vendor /vendor
mount cgroup none /acct cpuacct
mkdir /acct/uid
...
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger zygote-start // 2. Trigger zygote-start
trigger load_persist_props_action
trigger firmware_mounts_complete
trigger early-boot
trigger boot
...
on zygote-start && property:ro.crypto.state=unencrypted
exec_start update_verifier_nonencrypted
start netd
start zygote / / 3. Start the zygotestart zygote_secondary ... .Copy the code
The zygote.rc file is named init.zygote.rc, and the zygote.rc file is named zygote.rc. Split init.zygote.rc into multiple different files, such as init.zygote32.rc, init.zygote64.rc, etc.
We then define a series of triggers through the ON statement, including early-init, init, and so on.
Zygote-start triggers the zygote trigger when late init is triggered (comment 2). Zygote-start triggers start zygote when zygote-start is called (comment 3).
Source location: system core RootDir init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server 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/tasksCopy the code
As you can see, the file uses the service statement to create the Zygote process, whose code is in the /system/bin/app_process directory.
This will start the Zygote process when the relevant trigger is triggered.
4.3 Process for parsing init.rc files
Going back to the main method of init. CPP, we saw that the Parser object calls the AddSectionParser method to add service, ON, and import parsers to the object, and parses the init.rc file using the ParseConfig method. Let’s take a look at the source code of these two methods:
File path: \system\core\init\init_parser.cpp
void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) {
section_parsers_[name] = std::move(parser);
}
Copy the code
Section_parsers_ is a bit like a Map object in Java. The AddSectionParser method puts a trigger object into a map collection.
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) { // 1. Check whether the directory is a directory
return ParseConfigDir(path);
}
return ParseConfigFile(path);
}
bool Parser::ParseConfigFile(const std::string& path) {... std::string data;if (!read_file(path, &data)) { // 2. Read the data of the file to be parsed into data
return false; }...ParseData(path, data); //3. Call ParseData to parse. }Copy the code
ParseConfigDir is called if the file is a directory. ParseConfigFile is called if the file is a directory. Let’s go straight to the ParseConfigFile method.
In comment 2, the data for the file to be parsed is read into a string variable data, and then the ParseData method is called at comment 3 to parse the data.
Let’s look at the source of ParseData method:
void Parser::ParseData(const std::string& filename, const std::string& data) {
std::vector<char> data_copy(data.begin(), data.end());// 1. Store data to a linked list of type CHAR
data_copy.push_back('\ 0');// Add '\0' to the end as a closing sign
parse_state state; // Create a state object
state.filename = filename.c_str(a); state.line =0;
state.ptr = &data_copy[0]; // The PTR pointer in the state object points to the datA_copy list
state.nexttoken = 0;
SectionParser* section_parser = nullptr;/ / section parser
std::vector<std::string> args;
for (;;) {
switch (next_token(&state)) {
case T_EOF: // The end identifier is read
if (section_parser) {
section_parser->EndSection(a);/ / 1. Call EndSection
}
return;
case T_NEWLINE: // When a carriage return is read.//section_parsers_ is a map into which the on service import statement parser was created earlier
if (section_parsers_.count(args[0]) {//2. If args[0] is an on, service, or import statement.if(! section_parser->ParseSection(args, &ret_err)) {// Call ParseSection to parse. }}else if (section_parser) {
...
if(! section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {/ / call ParseLineSection. } } args.clear(a);/ / to empty
break;
case T_TEXT:
args.emplace_back(state.text); //3. Place the read data into arGS
break; }}}Copy the code
An infinite loop is opened in the ParseData function to read the data, and when a word is read, the word is placed in a string linked list args (note 3), thus breaking a line into multiple words.
In comment 2, we use args[0] to get parsers from the section_parsers_ map. In section 4, we create parsers of types ON, service, and import and put them into the map. So if args[0] is one of these three statements then the parser is available, otherwise the statement may be a command or option. The ParseSection or ParseLineSection methods are then called respectively to parse.
When the end symbol is read, the EndSection method is called (at comment 1).
The ParseSection method is an abstract method that is implemented differently by parser. Let’s look at the implementation of ServiceParser:
Source location: system core init service.cpp
bool ServiceParser::ParseSection(const std::vector<std::string>& args,
std::string* err) {... service_ = std::make_unique<Service>(name, str_args);//1. Generate a service_ object
return true;
}
Copy the code
ParseSection builds a Service_ object from the service statement passed in.
Let’s look at the implementation of the EndSection method in ServiceParser:
void ServiceParser::EndSection(a) {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_)); }}Copy the code
This method even the Service_ object is stored in the ServiceManager.
The ParseSection method in ActionParser is similar to ServiceParser, except that The ActionParser creates an action_ object from the Action statement and puts the action_ into the ActionManager.
At this point, the various actions and services in init.rc have been loaded.
5. Add the trigger to the queue
Let’s go back to the main method of init.cpp and continue:
ActionManager& am = ActionManager::GetInstance(a); am.QueueEventTrigger("early-init"); / / early - init triggers
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
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");
am.QueueEventTrigger("init"); / / init triggers
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
std::string bootmode = GetProperty("ro.bootmode"."");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init"); / / newest - init triggers
}
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
Copy the code
After parsing the init.rc file, we need to do some preparatory work before we can fire the various events. In the above code, we get the ActionManager instance, add the trigger to the trigger queue using the QueueEventTrigger method, and dynamically create some actions using the QueueBuiltinAction method. You can see that early-init, init, and late-init triggers have all been queued.
QueueEventTrigger source code: source path: system, core, init, action.cpp
//std::queue
<:unique_ptr>
> trigger_queue_; It's defined in dot h
void ActionManager::QueueEventTrigger(const std::string& trigger) {
trigger_queue_.push(std::make_unique<EventTrigger>(trigger));// Add trigger to queue
}
Copy the code
6. Trigger events and constantly listen for new events
while (true) {...if(! (waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand(a);// Execute a command
}
if(! (waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
restart_processes(a);// Restart the process to be restarted. }... }Copy the code
While (true) opens an event loop model in which Action commands are executed line by line through the ExecuteOneCommand of the ActionManager.
The init process is now started.
7. To summarize
To summarize what the init process starts to do:
- Mount and create a system directory
- Initialize the system log, enable the property service, and load the Selinux module
- Parse the init.rc file to load various actions and services
- Triggers actions and constantly listens for new actions
Reference data: www.jianshu.com/p/befff3d70… www.jianshu.com/p/464c3d120… Advanced Decryption of Android by Liu Wangshu