Android is based on Linux, so Android startup is started by the Linux Kernel and init process is created. This process is the granddaddy of all user Spaces.

During the init process, servicemanager(binder servicemanager) and Zygote (Java process) are started successively. Zygote creates the system_server and app processes.

So you’ve probably heard the phrase: App processes are created by the Zygote process by fork.

Let me try to analyze the creation of the init process during Android startup.

The analysis was based on Android 10.0

init

The init process is the first process in Linux user space during Android startup. The init startup entry is in its SecondStageMain method. But the SecondStageMain method that calls init is done through the main method in main.cpp.

So we’ll start with the main method of main. CPP.

main

system/core/init/main.cpp

int main(int argc, char** argv) { #if __has_feature(address_sanitizer) __asan_set_error_report_callback(AsanReportCallback); #endif // create device nodes, permissions, etc. strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (argc > 1) {// Initialize the log system if (! strcmp(argv[1], "subcontext")) { android::base::InitLogging(argv, &android::base::KernelLogger); const BuiltinFunctionMap function_map; return SubcontextMain(argc, argv, &function_map); } // 2. Create an enhanced Linux if (! strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); } // 3. Parse init.rc file, provide service, create epoll and handle termination of child process, etc. strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); Return FirstStageMain(argc, argv); }Copy the code

In the main method of main. CPP, there are three main steps:

  1. FirstStageMain
  2. SetupSelinux
  3. SecondStageMain

FirstStageMain

system/core/init/first_stage_init.cpp

It is the first step to start the init process and its main task is to mount the associated file system

int FirstStageMain(int argc, char** argv) { if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } boot_clock::time_point start_time = boot_clock::now(); std::vector<std::pair<std::string, int>> errors; #define CHECKCALL(x) \ if (x ! = 0) errors.emplace_back(#x " failed", errno); // Clear the umask. umask(0); // create file system CHECKCALL(clearenv()); CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1)); // Get the basic filesystem setup we need put together in the initramdisk // on / and then we'll let the rc file figure out the rest. CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); CHECKCALL(mkdir("/dev/pts", 0755)); CHECKCALL(mkdir("/dev/socket", 0755)); CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL)); #define MAKE_STR(x) __STRING(x) CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC))); #undef MAKE_STR // Don't expose the raw commandline to unprivileged processes. CHECKCALL(chmod("/proc/cmdline", 0440)); gid_t groups[] = {AID_READPROC}; CHECKCALL(setgroups(arraysize(groups), groups)); CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL)); CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL)); . . // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually // talk to the outside world... // Initialize the logging system InitKernelLogging(argv); . . Const char* path = "/system/bin/init"; const char* args[] = {path, "selinux_setup", nullptr}; execv(path, const_cast<char**>(args)); // execv() only returns if an error happened, in which case we // panic and never fall through this conditional. PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }Copy the code

Use mount to mount the file system, mkdir to create the corresponding file directory, and configure the corresponding access permission.

Note that these files exist only while the application is running and disappear with the application once the application is finished.

There are four types of mounted file systems:

  1. tmpfs: a virtual memory file system that stores all files in virtual memory. Due to thetmpfsIs residing in theRAMTherefore, its content is not durable. After power off,tmpfsThe content disappears, which is also known astmpfsThe root cause of…
  2. devpts: provides a standard interface for the pseudo-terminal, and its standard hook point is/dev/pts. As long asptyThe main composite equipment/dev/ptmxWhen it’s opened, it’s in/dev/ptsCreate a new one dynamicallyptyDevice file.
  3. proc: is also a virtual file system, which can be viewed as an interface to the kernel’s internal data structure, through which we can obtain system information and also modify specific kernel parameters at run time.
  4. sysfsAnd:procSimilarly, a file system is a virtual file system that does not occupy any disk space. It is usually hooked up to/sysDirectory.

The log logging system is also initialized at FirstStageMain via InitKernelLogging(argv). At this time, Android does not have its own system log, but uses the kernel log system. If the device node /dev/kmsg is opened, you can run cat /dev/kmsg to obtain the kernel log.

Finally, the corresponding path and the parameter selinux_setup for the next stage are passed through the execv method.

SetupSelinux

system/core/init/selinux.cpp

// This function initializes SELinux then execs init to run in the init SELinux context. int SetupSelinux(char** argv) {  InitKernelLogging(argv); if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } // Set up SELinux, loading the SELinux policy. SelinuxSetupKernelLogging(); SelinuxInitialize(); // We're in the kernel domain and want to transition to the init domain. File systems that // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here, // but other file systems do. In particular, this is needed for ramdisks such as the // recovery image for A/B devices. if (selinux_android_restorecon("/system/bin/init", 0) == -1) { PLOG(FATAL) << "restorecon failed of /system/bin/init failed"; } const char* path = "/system/bin/init"; const char* args[] = {path, "second_stage", nullptr}; execv(path, const_cast<char**>(args)); // execv() only returns if an error happened, in which case we // panic and never return from this function. PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }Copy the code

It is used to improve Linux security and further restrict access permissions.

Finally, SecondStageMain, the core stage of init startup through execv.

SecondStageMain

system/core/init/init.cpp

int SecondStageMain(int argc, char** argv) {
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
     
    ...
    ...
     
    // 初始化属性服务
    property_init();
 
    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    process_kernel_dt();
    process_kernel_cmdline();
 
    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();
 
    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
 
    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
 
    // See if need to load debug props to allow adb root, when the device is unlocked.
    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
    if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
        load_debug_prop = "true"s == force_debuggable_env;
    }
 
    // Clean up our environment.
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");
    unsetenv("INIT_FORCE_DEBUGGABLE");
 
    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();
 
    Epoll epoll;
    if (auto result = epoll.Open(); !result) {
        PLOG(FATAL) << result.error();
    }
 
    // 初始化single句柄
    InstallSignalFdHandler(&epoll);
 
    property_load_boot_defaults(load_debug_prop);
    UmountDebugRamdisk();
    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
    // 开启属性服务
    StartPropertyService(&epoll);
    MountHandler mount_handler(&epoll);
    set_usb_controller();
 
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
 
    if (!SetupMountNamespaces()) {
        PLOG(FATAL) << "SetupMountNamespaces failed";
    }
 
    subcontexts = InitializeSubcontexts();
 
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
 
    // 解析init.rc等相关文件
    LoadBootScripts(am, sm);
 
    // 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) DumpState();
 
    // Make the GSI status available before scripts start running.
    if (android::gsi::IsGsiRunning()) {
        property_set("ro.gsid.image_running", "1");
    } else {
        property_set("ro.gsid.image_running", "0");
    }
 
    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
 
    am.QueueEventTrigger("early-init");
 
    // 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");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    Keychords keychords;
    am.QueueBuiltinAction(
        [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
            for (const auto& svc : ServiceList::GetInstance()) {
                keychords.Register(svc->keycodes());
            }
            keychords.Start(&epoll, HandleKeychord);
            return Success();
        },
        "KeychordInit");
    am.QueueBuiltinAction(console_init_action, "console_init");
 
    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");
 
    // Starting the BoringSSL self test, for NIAP certification compliance.
    am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
 
    // 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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
 
    // Initialize binder before bringing up other system services
    am.QueueBuiltinAction(InitBinder, "InitBinder");
 
    // 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");
 
    while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};

        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }
 
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            if (!shutting_down) {
                auto next_process_action_time = HandleProcessActions();

                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_action_time) {
                    epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                            *next_process_action_time - boot_clock::now());
                    if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
                }
            }
 
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }
 
        if (auto result = epoll.Wait(epoll_timeout); !result) {
            LOG(ERROR) << result.error();
        }
    }

    return 0;
}
Copy the code

There are four main steps in SecondStageMain

  1. Initialize the property service
  2. Initialize the single handle
  3. Enabling the Property Service
  4. Parse the. Rc file

Initialize the property service

system/core/init/property_service.cpp

void property_init() {
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    CreateSerializedPropertyInfo();
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }
    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }
}
Copy the code

The main method is __system_property_area_init(), which is used to create cross-process memory

  1. performopenOpen,dev/properitiesShared memory file, size is128KB
  2. performmmap, maps memory toinitprocess
  3. Store the head address of this memory in a global variable__system_property_area__, subsequent additions or modifications to attributes are based on this variable.

Initialize the single handle

system/core/init/init.cpp

static void InstallSignalFdHandler(Epoll* epoll) { // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving // SIGCHLD when a child process stops or continues (b/77867680#comment9). const struct sigaction  act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP }; sigaction(SIGCHLD, &act, nullptr); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); if (! IsRebootCapable()) { // If init does not have the CAP_SYS_BOOT capability, it is running in a container. // In that case, receiving SIGTERM will cause the system to shut down. sigaddset(&mask, SIGTERM); } if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) { PLOG(FATAL) << "failed to block signals"; } // Register a handler to unblock signals in the child processes. const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals); if (result ! = 0) { LOG(FATAL) << "Failed to register a fork handler: " << strerror(result); } signal_fd = signalfd(-1, &mask, SFD_CLOEXEC); if (signal_fd == -1) { PLOG(FATAL) << "failed to create signalfd"; } if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); ! result) { LOG(FATAL) << result.error(); }}Copy the code

The init process is the parent process of all user-space processes and generates a signal when its child process terminates. For the parent process to process.

The main purpose is to prevent child processes from becoming zombie processes.

What is zombie progression?

Parent use the fork to create the child, the child process terminates, if the parent process does not know line process has been terminated, then the child has already been out, but in the system the process still retains some information for it (such as process, operation time, exit status, etc.), the child process is what is called a zombie process. The system process table is a limited resource. If it is exhausted by zombie processes, the system may not be able to create new processes.

The handle is registered with epoll and is finally called back by HandleSignalFd.

What is epoll?

In the new kernel of Linux, epoll is used to replace SELECT /poll, which is an improved version of the Linux kernel for handling mass file descriptors, and an enhanced version of the multiplex I/O interface select/poll under Linux. It can significantly improve system CPU utilization for applications with only a small amount of activity in a large number of concurrent connections.

Epoll uses red-black trees internally, so lookups are faster than select using arrays.

Enabling the Property Service

system/core/init/property_service.cpp

void StartPropertyService(Epoll* epoll) {
    selinux_callback cb;
    cb.func_audit = SelinuxAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
 
    property_set("ro.property_service.version", "2");

    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr);
    if (property_set_fd == -1) {
        PLOG(FATAL) << "start_property_service socket creation failed";
    }
 
    listen(property_set_fd, 8);
 
    if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
        PLOG(FATAL) << result.error();
    }
}
Copy the code

The main tasks are:

  1. Create non-blockingSocketAnd returnproperty_set_fdFile descriptor.
  2. uselisten()Function to listenproperty_set_fdAt this time,SocketThat is, the attribute server, and it can be at most8The user who tries to set the property provides the service.
  3. useepollTo register, whenproperty_set_fdWhen the data comes in,initThe process will callhandle_property_set_fd()Function.

Parse the. Rc file

system/core/init/init.cpp

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/init.rc"); if (! parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } if (! parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/etc/init"); } if (! parser.ParseConfig("/product_services/etc/init")) { late_import_paths.emplace_back("/product_services/etc/init"); } if (! parser.ParseConfig("/odm/etc/init")) { late_import_paths.emplace_back("/odm/etc/init"); } if (! parser.ParseConfig("/vendor/etc/init")) { late_import_paths.emplace_back("/vendor/etc/init"); } } else { parser.ParseConfig(bootscript); }}Copy the code

ParseConfig parses the init.rc configuration file.

The.rc file syntax is end-of-line, space-separated syntax, starting with # to represent comment lines. The. Rc file contains Action, Service, Command, Options, and Import files. The Action and Service names are unique and duplicate names are invalid.

The Action and Service statements in init.rc have corresponding classes to parse, namely ActionParser and ServiceParser.

Start Zygote by parsing the configuration in init.rc.

Follow-up analysis of Zygote startup.

Today I will try to analyze the main process involved in Android init startup under Linux system.

Init startup involves the following tasks:

  1. Create and mount the file system required for startup
  2. Initialize the property service
  3. createsingleHandle to listen for child processes to prevent zombie processes
  4. Enabling the Property Service
  5. parsing.rcFile and startZygoteprocess

project

Android_startup: provides a simple and efficient way to initialize components during application startup, optimizing startup speed. Not only does it support all the features of Jetpack App Startup, but it also provides additional synchronous and asynchronous waiting, thread control, and multi-process support.

AwesomeGithub: Based on Github client, pure exercise project, support componentized development, support account password and authentication login. Kotlin language for development, the project architecture is based on Jetpack&DataBinding MVVM; Popular open source technologies such as Arouter, Retrofit, Coroutine, Glide, Dagger and Hilt are used in the project.

Flutter_github: a cross-platform Github client based on Flutter, corresponding to AwesomeGithub.

Android-api-analysis: A comprehensive analysis of Knowledge points related to Android with detailed Demo to help readers quickly grasp and understand the main points explained.

Daily_algorithm: an algorithm of the day, from shallow to deep, welcome to join us.