Android init 启动

941 阅读5分钟

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方法中。但调用initSecondStageMain方法是通过main.cpp中的main方法进行的。

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

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.cppmain方法中,主要分为三步:

  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还没有自己的系统日志,采用kernellog系统,打开的设备节点/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文件主要包含ActionServiceCommandOptionsImport,其中对于ActionService的名称都是唯一的,对于重复的命名视为无效。

init.rc中的ActionService语句都有相应的类来解析,即ActionParserServiceParser

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

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

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

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

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

项目

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

AwesomeGithub: 基于Github客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于Jetpack&DataBindingMVVM;项目中使用了ArouterRetrofitCoroutineGlideDaggerHilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。