Android Init Startup Process (AOSP 11)

Before Init starts

When the user presses the power button, the hardware device boot chip code will be executed from the factory fixed place, loading the specified location of the BootLoader into RAM for execution. BootLoader is a program before the Android system starts, and its task is to pull up the entire system boot. After the BootLoader, the Linux Kernel is started. After the Kernel is started, the init.rc file is found in the system file and the init process is started.

Android is essentially an operating system running on the Linux kernel. The system starts from the Linux kernel.

Start_kernel in/Linux /init/main.c Start_kernel -> rest_init -> CPU_startuP_entry -> do_IDLE This starts the first kernel process, Idle, with process number 0.

Each CPU core has an Idle process. When the system does not schedule CPU resources, the Idle process enters the idle process. The idle process does not use CPU to save power.

In Android, the entry for the init process is: system/core/init, and the entry is main.cpp:

int main(int argc, char** argv) {
    // ...
   	// Sets the priority of the current process
    setpriority(PRIO_PROCESS, 0.- 20);
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    if (argc > 1) {
        if (!strcmp(argv[1]."subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap(a);return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1]."selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1]."second_stage")) {
            return SecondStageMain(argc, argv); }}return FirstStageMain(argc, argv);
}
Copy the code

setpriority

Setpriority is a Unix system call used to set the priority of processes, process groups, and user processes, as defined below. The lower the value of niceval, the higher the priority. The value of niceval is between -20 and 20, which indicates that the process has a higher priority and executes more frequently. The default value of niceval is 0, and only root users are allowed to lower this value.

long setpriority(int which,int who,int niceval)
// which takes the following values:
// define PRIO_PROCESS 0
// define PRIO_PGRP 1
Define PRIO_USER 2 // User process
Copy the code

When “which” is PRIO_PROCESS, if “who” is 0, the process priority of the current process is set. If who is not 0, set the priority of the process whose process id is who.

If “which” is set to “PRIO_PGRP” and “who” is 0, the priority of the current process group is set. If who is not 0, set the priority of the process group whose process group id is who.

If “which” is PRIO_USER and “who” is 0, the priority of the current user process is set. If who is not 0, set the priority of the user process whose ID is who.

After setting the priority of the init process to the highest, the init process enjoys the highest priority and gets more CPU time slices.

Stage 1: FirstStageMain

Continuing back to main, there are four main steps: FirstStateMain->SetupSelinux->SecondStageMain-> Ueventd_main.

First stage of init startup: FirstStageMain: (core/init/first_stage_init.cpp)

int FirstStageMain(int argc, char** argv) {
    // ...
		std::vector<std::pair<std::string, int>> errors;
    // Define a CHECKCALL. If the return value of the internal execution is non-zero, the corresponding step was executed incorrectly
    // Prints error information to errors.
#define CHECKCALL(x) \
    if((x) ! = 0) errors.emplace_back(#x" failed", errno);

    // umask is used to set the default permissions for files. 0 is 666 for files and 777 for directories
    // The following mkdir or mount calls, if not specified, are set to the default maximum permission
    umask(0);
		// Omit all files, directories, and device nodes needed to mount or create the system, and set related permissions on them
  	CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    CHECKCALL(mount("tmpfs"."/dev"."tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts".0755));
    // ...
#undef CHECKCALL
		// Due to SeLinux security policy, the descriptor corresponding to the console output console needs to be turned off
    SetStdioToDevNull(argv);
    // Initialize the Kernel Log system
    InitKernelLogging(argv);
		// If there is an error in mounting a node, an error message is displayed
    if(! errors.empty()) {
        for (const auto& [error_string, error_errno] : errors) {
            LOG(ERROR) << error_string << "" << strerror(error_errno);
        }
        LOG(FATAL) << "Init encountered errors starting first stage, aborting";
    }
  	// ...

    // Mount the system partition
    if (!DoFirstStageMount(! created_devices)) {LOG(FATAL) << "Failed to mount required partitions early ...";
    }
		// ...
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup".nullptr};
    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
    // execv will stop executing the current process
    // Replace the stopped process with the application process corresponding to path. The process ID is unchanged
    // Re-execute main. CPP with selinux_setup
    execv(path, const_cast<char**>(args));
    Execv will only be returned when an exception occurs in the incoming new program, so it is logically not executed here.
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
    return 1;
}

Copy the code

Stage 2: Set up Selinux

The Selinux kernel module’s main function is to minimize the resources available to the server process in the system. In SetupSelinux, the main job is to set up various security policies for this module to ensure the security of the system.

At the end of SetupSelinux, the execv process regenerates, returns to the main function in init, and sets the entry parameter to second_stage.

const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage".nullptr};
execv(path, const_cast<char**>(args));
Copy the code

Stage 3: SecondStageMain

Then we go to the second stage of init startup: SecondStageMain:

int SecondStageMain(int argc, char** argv) {
    // Set the handler for the restart signal
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers(a); }// ...
    // Process 1 is init. Oom_score_adj of process 1 is -1000
    Disable OOM Killer from killing init and its children
    if (auto result =
                WriteFile("/proc/1/oom_score_adj".StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST)); ! result.ok()) {
        LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
                   << " to /proc/1/oom_score_adj: " << result.error(a); }// ...
    // Initialize the system properties service, create the directory node of the properties service,
  	// Then load the various property services from the various local property profiles
    PropertyInit(a);// ...
    // Perform stage 2 Selinux security Settings
    SelinuxSetupKernelLogging(a);SelabelInitialize(a);SelinuxRestoreContext(a);// Create an Epoll handle
    Epoll epoll;
    if (auto result = epoll.Open(a); ! result.ok()) {
        PLOG(FATAL) << result.error(a); }// Register a fd for the Epoll handle created above to prevent init's child from becoming a zombie process.
  	// If a child of init (such as Zygote) hangs abnormally,
    // Init process will receive SIGCHLD signal with the following Settings
    // Then call the corresponding handler for processing, such as zygote hangs, will remove the dead Zygote process,
  	// Restart the zygote process
    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
    // Enable the properties service
    // When the properties service is enabled, a non-blocking socket is created, and the socket can be used simultaneously
    // Serve 8 clients and eventually create a PropertyServiceThread thread
    Init and its children can then communicate with the property service via property_fd.
    StartPropertyService(&property_fd);

    // ...
		// Create ActionManager and ServiceList data structures.
  	// used to hold configuration items after parsing init.rc
    ActionManager& am = ActionManager::GetInstance(a); ServiceList& sm = ServiceList::GetInstance(a);// parse init. Rc configuration file,
    // Resolve actions and services configured in init.rc into ActionManager and ServiceList
    LoadBootScripts(am, sm);

    // ...
    // Execute the task whose trigger is early-init
    am.QueueEventTrigger("early-init");
		// ...
    // Execute the task whose trigger is init in init.rc
    am.QueueEventTrigger("init");

    // ...
    // Since the following is the configuration of init.rc to start various system services,
    Init priority = 0;
    // Otherwise, the next loop will always occupy the CPU execution time slice.
    setpriority(PRIO_PROCESS, 0.0);
    while (true) {
        // ...
        // Get the shutdown command, start shutdown operation
        auto shutdown_command = shutdown_state.CheckShutdown(a);if (shutdown_command) {
            LOG(INFO) << "Got shutdown_command '" << *shutdown_command
                      << "' Calling HandlePowerctlMessage()";
            HandlePowerctlMessage(*shutdown_command);
            shutdown_state.set_do_shutdown(false);
        }
				// If the main thread loop is not in wait and the ServiceManager is not running,
        // Execute the action resolved above.
        if(! (prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand(a); }// ...

        if(! (prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            Epoll timeout is set to 0 if there are other actions that need to be executed
            // The thread will continue to loop the task
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }
				
        // epoll Wakes up the thread until the timeout expires or an event arrives
        auto pending_functions = epoll.Wait(epoll_timeout);
        // ...
        // Some service processes restart logic
        // ...
    }
    return 0;
}
Copy the code

The concept of a zombie process mentioned above, the so-called after the child is the parent process fork out a zombie process, if the child process terminates, but the parent didn’t know it, the system will remain the process in the process of some information, and the process is a very precious limited resources, if the process is running out, the system will not be able to create new processes.

The core part of SecondStageMain is parsing and executing various startup tasks defined in init.rc. LoadBootScripts creates the Parser and finds the target init.rc file (source location is core/rootdir/init.rc). The parsing is then done so that all the actions and services to be executed during the startup phase are loaded in the AM and SM.

init.rc

The init. rc file consists of two main statements, action statements and service statements.

action

The action statement has the following format:

Command1 command2 on <trigger trigger condition > command1 command2 eg: In early - the init event is triggered when executing the following command on early - init write/proc/sys/kernel/sysrq 0 write/proc/sys/kernel/modprobe \ n / /...Copy the code

When the action trigger is triggered, the init process executes commands one by one, which is what happens when the corresponding action occurs, such as write, which writes the specified character (0 or newline) to the corresponding file.

Also, trigger can have multiple conditions, such as this one:

on early-init && property:ro.product.cpu.abilist32=*
    exec_start boringssl_self_test32
Copy the code

In early – init is triggered, and satisfy the property: ro. Product. CPU. Abilist32 = * this condition (format can be understood as name = value), will carry out the action of the command.

Such as:

on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
    exec_start boringssl_self_test_apex64
Copy the code

This action will execute the corresponding command only if both conditions are met.

service

Action is to execute the corresponding command when the condition is met. If the conditions are met, init will run the corresponding service program. Service is an executable program.

The format of the service statement is as follows:

Service <name> <path> [<arguments>] options options # name is the name of the service, path is the path of the executable file, arguments are the startup parameter, Affects when and how to start service eg: Service ueventd /system/bin/ueventd # class specifies the class name of the service to be started. All services with the same class name must be started or stopped at the same time. Class core # critical constraint, if the service restarts four times in four minutes, the device will be restarted for recovery. Set the secLabel u:r: ueventD :s0 # shutdown constraint before the ueventd service is started. Ueventd is a shutdown critical that will not be killed while the process is being shutdownCopy the code

More explanation can be seen here: android.googlesource.com/platform/sy…

So, in the on Early-init and ON Init phases of the action, the main thing you do is you create various directories, you write characters to various directories, you change the permissions of various directories, and so on, and so forth, and so on, and so forth, and so on, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth.

In the on late-init action:

On late-init # trigger means to trigger other triggers in this action and execute other action trigger early-fs #... Zygote -start command trigger zygote-start #...Copy the code

Init starts zygote:

The zygote startup script comes in three copies: init.zygote32.rc (pure 32-bit mode), init.zygote64.rc (pure 64-bit mode), and init.zygote64_32.rc (64-bit main mode, 32-bit auxiliary mode). (There was actually 32_64-bit mode in earlier versions, which made 32-bit the main mode, but presumably 64-bit cpus are standard in higher system versions.)

In init.rc:

import /system/etc/init/hw/init.${ro.zygote}.rc
Copy the code

Using the system property ro.zygote, init knows which Zygote startup script to use to start the Zygote process.

Zygote64_32. rc: the name of the service and the path of the executing program are different.

// The 64-bit main mode's Zygote name is Zygote
// The path of the execution program is /system/bin/app_process64
// Finally, start parameters
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
  	// Zygote belongs to the main class. Other similar services include Storaged and installd
    class main
    // The process has the highest priority (-20)
    priority - 20
    // Before starting the service, switch the user to root
    user root
    // Switch the user group to the root user group before starting the service
    group root readproc reserved_disk
    // Create a socket named /dev/socket/zygote,
    // Then pass the socket fd to the process that started zygote, i.e. init process
    // There are three options for socket types: stream (connection-oriented, ensuring that data can be sent to the receiver in an ordered manner)
    // dgram (insecure message can be sent to the receiver, the message is not continuous and unreliable ->UDP)
    // seqpacket
    // The socket permission is 660
    // Finally, user and group
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    // When the service restarts, perform the following tasks
    onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    task_profiles ProcessCapacityHigh MaxPerformance
    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

// 64-bit like configuration
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority - 20
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    socket usap_pool_secondary stream 660 root system
    onrestart restart zygote
    task_profiles ProcessCapacityHigh MaxPerformance

Copy the code

Zygote init will start all services of the main category using the class_start main command at the appropriate time. Function do_class_start (core/init/builtins. CPP);

static Result<void> do_class_start(const BuiltinArguments& args) {
    // ...
    // Iterate over all services
    for (const auto& service : ServiceList::GetInstance()) {
        // Find the service with class name main
        if (service->classnames().count(args[1]) {// Run the StartIfNotDisabled method of service to start the service
            if (auto result = service->StartIfNotDisabled(a); ! result.ok()) {
                LOG(ERROR) << "Could not start service '" << service->name() < <"' as part of class '" << args[1] < <"'." << result.error(a); }}}return {};
}
Copy the code

Service: : StartIfNotDisabled – > Service: : Start, Start implementation is more long, for we need to focus on logic:

// The start of the Service is finally here
Result<void> Service::Start(a) {
		// ...
    LOG(INFO) << "starting service '" << name_ << "'...";
    std::vector<Descriptor> descriptors;
    // Create the socket specified in the service startup parameter
    for (const auto& socket : sockets_) {
        if (auto result = socket.Create(scon); result.ok()) {
            descriptors.emplace_back(std::move(*result));
        } else {
            LOG(INFO) << "Could not create socket '" << socket.name << "'." << result.error();
        }
    }
		// ...
  	// Check whether the service to be started is already in the running state. If so, there is no need to start again
  	// ...
  	// Check whether the executable file of the service to be started exists. If it does not, it cannot be started
  	// ...

    // fork out the corresponding child process. The parent process's resources are assigned to the child process
    // Clone the child process. The resources of the parent process are selectively copied to the child process
    pid_t pid = - 1;
    if (namespaces_.flags) {
        // The child processes created by clone share the signal processing table of the parent process
        pid = clone(nullptr.nullptr, namespaces_.flags | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    // pid == 0 Indicates the child process
    if (pid == 0) {
        // Set file permissions
        umask(077);
        // ...
        // Call execv internally, and the logic to start the Service child process will be executed
        if (!ExpandArgsAndExecv(args_, sigstop_)) {
            PLOG(ERROR) << "cannot execv('" << args_[0] < <"'). See the 'Debugging init' section of init's README.md for tips";
        }

        _exit(127);
    }
	// ...
}
Copy the code

For zygote processes, the startup file, as described in the Zygote startup script, is located at /system/bin/app_process64, and the source code is located at /framework/base/ CMDS /app_process/app_main.cpp, The main function has the following logic:

// ... 
// Create an ART instance
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
// ...
	  while (i < argc) {
        const char* arg = argv[i++];
        // If the startup parameter contains --zygote, the zygote process is started
        if (strcmp(arg, "--zygote") = =0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") = =0) {
            // The zygote startup parameter contains --start-system-server
            startSystemServer = true;
        } 
      // ...
    }
// ...
//         
				if (startSystemServer) {
            // The flag to start system-server is passed to the zygote startup process
            args.add(String8("start-system-server"));
        }
// ...
    // If it is zygote, call runtime.start to start zygote
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    }
// ...
Copy the code

The final step is art. start, which starts a static main method with the specified Java Class name. Zygote is then started with the Java Class ZygoteInit.