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:

  1. Mount and create a system directory
  2. Initialize the system log, enable the property service, and load the Selinux module
  3. Parse the init.rc file to load various actions and services
  4. 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