添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Android 是基于 Linux 系统的,所以 Android 启动将由 Linux Kernel 启动并创建 init 进程。该进程是所有用户空间的鼻祖。

init 进程启动的过程中,会相继启动 servicemanager (binder服务管理者)、 Zygote 进程(java进程)。而 Zygote 又会创建 system_server 进程以及 app 进程。

所以你一定听到过这句话:app进程是由 Zygote 进程通过 fork 创建出来的。

下面我尝试来分析 Android 启动过程中关于 init 进程的创建过程。

此次分析过程基于 Android 10.0

init

init 进程是 Android 启动过程中在 Linux 系统中用户空间的第一个进程。 init 启动入口是在它的 SecondStageMain 方法中。但调用 init SecondStageMain 方法是通过 main.cpp 中的 main 方法进行的。

所以我们就从 main.cpp main 方法开始。

main

system/core/init/main.cpp

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
 
    // 创建设备节点、权限设定等
    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;

            return SubcontextMain(argc, argv, &function_map);
        }

        // 2. 创建增强型Linux
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        // 3. 解析init.rc文件、提供服务、创建epoll与处理子进程的终止等
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    // 1. 挂载相关文件系统
    return FirstStageMain(argc, argv);
}

main.cpp main 方法中,主要分为三步:

  1. FirstStageMain
  2. SetupSelinux
  3. SecondStageMain

FirstStageMain

system/core/init/first_stage_init.cpp

它是 init 进程启动的第一步,主要任务是挂载相关的文件系统

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);
 
    // 创建于挂载相关文件系统
    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...
    
    // 初始化日志系统
    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;
}

主要通过 mount 挂载对应的文件系统, mkdir 创建对应的文件目录,并配置相应的访问权限。

需要注意的是,这些文件只是在应用运行的时候存在,一旦应用运行结束就会随着应用一起消失。

挂载的文件系统主要有四类:

  1. tmpfs : 一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中。由于 tmpfs 是驻留在 RAM 的,因此它的内容是不持久的。断电后, tmpfs 的内容就消失了,这也是被称作 tmpfs 的根本原因。
  2. devpts : 为伪终端提供了一个标准接口,它的标准挂接点是 /dev/pts 。只要 pty 的主复合设备 /dev/ptmx 被打开,就会在 /dev/pts 下动态的创建一个新的 pty 设备文件。
  3. proc : 也是一个虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
  4. sysfs : 与 proc 文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在 /sys 目录下。

FirstStageMain 还会通过 InitKernelLogging(argv) 来初始化 log 日志系统。此时 Android 还没有自己的系统日志,采用 kernel log 系统,打开的设备节点 /dev/kmsg , 那么可通过 cat /dev/kmsg 来获取内核 log

最后会通过 execv 方法传递对应的 path 与下一阶段的参数 selinux_setup

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;
}

主要是用来提高 linux 的安全,进一步约束访问的权限。

最后也是通过 execv 来进程 init 启动的核心阶段 SecondStageMain

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;
}

SecondStageMain 中主要分为4步

  1. 初始化属性服务
  2. 初始化single句柄
  3. 开启属性服务
  4. 解析.rc文件

初始化属性服务

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";
    }
}

主要方法是 __system_property_area_init() ,用来创建跨进程内存,主要操作为

  1. 执行 open ,打开 dev/properities 共享内存文件,大小为 128KB
  2. 执行 mmap ,将内存映射到 init 进程
  3. 将该内存的首地址保存在全局变量 __system_property_area__ ,后续的增加或者修改属性都基于该变量来计算位置。

初始化single句柄

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();
    }
}

每个进程在处理其他进程发送的 signal 信号时都需要先注册,当进程的运行状态改变或终止时会产生某种 signal 信号, init 进程是所有用户空间进程的父进程,当其子进程终止时产生 signal 信号。以便父进程进行处理。

主要目的是为了防止子进程成为僵尸进程。

何为僵尸进程?

父进程使用 fork 创建子进程,子进程终止后,如果父进程不知道子进程已经终止的话,这时子进程虽然已经退出,但是在系统进程表中还为它保留了一些信息(如进程号、运行时间、退出状态等),这个子进程就是所谓的僵尸进程。其中系统进程表是一项有限的资源,如果它被僵尸进程耗尽的话,系统可能会无法创建新的进程。

通过 epoll 进行注册句柄,最后会由 HandleSignalFd 进行回调操作。

epoll 又是什么?

Linux 的新内核中, epoll 是用来取代 select/poll 的,它是 Linux 内核为处理大批量文件描述符的改进版 poll ,是 Linux 下多路复用 I/O 接口 select/poll 的增强版,它能显著提升程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。

epoll 内部使用了红黑树,所以查找效率比使用数组的 select 更快。

开启属性服务

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();
    }
}

主要做的任务是:

  1. 创建非阻塞式的 Socket ,并返回 property_set_fd 文件描述符。
  2. 使用 listen() 函数去监听 property_set_fd ,此时 Socket 即成为属性服务端,并且它最多同时可为 8 个试图设置属性的用户提供服务。
  3. 使用 epoll 注册,当 property_set_fd 中有数据到来时, init 进程将调用 handle_property_set_fd() 函数进行处理。

解析.rc文件

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);
    }
}

通过 ParseConfig 来解析 init.rc 配置文件。

.rc 文件语法是以行尾单位,以空格间隔的语法,以 # 开始代表注释行。 .rc 文件主要包含 Action Service Command Options Import ,其中对于 Action Service 的名称都是唯一的,对于重复的命名视为无效。

init.rc 中的 Action Service 语句都有相应的类来解析,即 ActionParser ServiceParser

在解析 init.rc 中的配置,进行启动 Zygote

关于 Zygote 的启动后续再分析。

今天主要尝试分析了一下 Android Linux 系统下的 init 启动涉及的主要流程。

可见 init 启动主要涉及的工作是:

  1. 创建与挂载启动所需要的文件系统
  2. 初始化属性服务
  3. 创建 single 句柄 ,来监听子进程,防止僵尸进程的产生
  4. 开启属性服务
  5. 解析 .rc 文件 并启动 Zygote 进程

推荐阅读

进阶必备的工具

Jetpack:DataStore必知的几个优点

算法之旅:复杂度分析



感谢老铁们的支持