美文网首页
Android启动过程(一)

Android启动过程(一)

作者: ____Rainbow | 来源:发表于2022-12-17 22:32 被阅读0次

    Android启动过程基本可以划分为三个阶段:BootLoader引导、Linux Kernel启动、Android启动。

    Boot

    Boot ROM: 电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行.加载引导程序到RAM,然后执行.
    Boot Loader: 这是Android系统系统之前的引导程序,主要用来检测外部的RAM以及设置网络、内存、初始化硬件参数等.

    Linux Kernel

    Linux内核启动主要涉及3个特殊的进程:idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2),这三个进程是内核的基础。

    1.idle进程

    Linux内核启动后,便会创建第一个进程idle。idle进程是Linux中的第一个进程,pid为0,是唯一一个没有通过fork产生的进程,它的优先级非常低,用于CPU没有任务的时候进行空转。
    https://elixir.bootlin.com/linux/latest/source/arch/arm64/kernel/head.S#L472
    https://elixir.bootlin.com/linux/latest/source/init/main.c#L940
    start_kernel完成Linux内核的初始化工作。包括初始化页表,初始化中断向量表,初始化系统时间等。

    asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
    {
    ...
        /* Do the rest non-__init'ed, we're now alive */
        //创建线程
        arch_call_rest_init();
    
        prevent_tail_call_optimization();
    }
    
    void __init __weak arch_call_rest_init(void)
    {
        rest_init();
    }
    noinline void __ref rest_init(void)
    {
        struct task_struct *tsk;
        int pid;
    ...
    //kernel_init 创建第一个用户进程 init进程
        pid = kernel_thread(kernel_init, NULL, CLONE_FS);
    ...
    //kthreadd 进程
        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    ...
    }
    

    2.init进程

    init进程由idle进程创建,是Linux系统的第一个用户进程,pid为1,是系统所有用户进程的直接或间接父进程。
    init进程在kernel_init函数中启动

    static int __ref kernel_init(void *unused)
    {
        int ret;
    
        kernel_init_freeable();
        /* need to finish all async __init code before freeing the memory */
        async_synchronize_full();
        kprobe_free_init_mem();
        ftrace_free_init_mem();
        free_initmem();
        mark_readonly();
    
        /*
         * Kernel mappings are now finalized - update the userspace page-table
         * to finalize PTI.
         */
        pti_finalize();
    
        system_state = SYSTEM_RUNNING;
        numa_default_policy();
    
        rcu_end_inkernel_boot();
    
    #ifdef CONFIG_QGKI_MSM_BOOT_TIME_MARKER
        place_marker("M - DRIVER Kernel Boot Done");
    #endif
    
        if (ramdisk_execute_command) {
            ret = run_init_process(ramdisk_execute_command);
            if (!ret)
                return 0;
            pr_err("Failed to execute %s (error %d)\n",
                   ramdisk_execute_command, ret);
        }
    
        /*
         * We try each of these until one succeeds.
         *
         * The Bourne shell can be used instead of init if we are
         * trying to recover a really broken machine.
         */
        if (execute_command) {
            ret = run_init_process(execute_command);
            if (!ret)
                return 0;
            panic("Requested init %s failed (error %d).",
                  execute_command, ret);
        }
    //调用源码的init
        if (!try_to_run_init_process("/sbin/init") ||
            !try_to_run_init_process("/etc/init") ||
            !try_to_run_init_process("/bin/init") ||
            !try_to_run_init_process("/bin/sh"))
            return 0;
    
        panic("No working init found.  Try passing init= option to kernel. "
              "See Linux Documentation/admin-guide/init.rst for guidance.");
    }
    

    3.kthreadd进程

    kthreadd进程同样由idle进程创建,pid为2,它始终运行在内核空间,负责所有内核线程的调度与管理。

    int kthreadd(void *unused)
    {
        struct task_struct *tsk = current;
    
        /* Setup a clean context for our children to inherit. */
        set_task_comm(tsk, "kthreadd");
        ignore_signals(tsk);
        set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD));
        set_mems_allowed(node_states[N_MEMORY]);
    
        current->flags |= PF_NOFREEZE;
        cgroup_init_kthreadd();
    
        for (;;) {
            set_current_state(TASK_INTERRUPTIBLE);
    //判断内核线程链表是否为空
    //若没有需要创建内核线程,进行一次调度,让出cpu
            if (list_empty(&kthread_create_list))
                schedule();
            __set_current_state(TASK_RUNNING);
    
            spin_lock(&kthread_create_lock);
            while (!list_empty(&kthread_create_list)) {
                struct kthread_create_info *create;
    
                create = list_entry(kthread_create_list.next,
                            struct kthread_create_info, list);
                list_del_init(&create->list);
                spin_unlock(&kthread_create_lock);
    //只要kthread_create_list不为空,就根据表中元素创建内核线程
                create_kthread(create);
    
                spin_lock(&kthread_create_lock);
            }
            spin_unlock(&kthread_create_lock);
        }
    
        return 0;
    }
    

    init进程

    init进程是一切的开始,在Android系统中,所有进程的进程号都是不确定的,唯独init进程的进程号一定是1。
    因为这个进程一定是系统起来的第一个进程。
    并且,init进程掌控了整个系统的启动逻辑。
    init进程的功能包含但不限于以下:

    • 挂载系统分区和加载一些内核模块
    • 加载sepolicy 及使能 selinux
    • 支持属性服务
    • 启动脚本rc文件解析
    • 执行事件触发器和属性改变的事件
    • 子进程死亡监听,回收僵尸进程
    • 非oneshot服务保活
      /system/core/init/main.cpp
      Android的init进程代码在system/core/init/main.cpp中,以main方法作为入口,分为几个阶段:
    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);
            }
    
            if (!strcmp(argv[1], "selinux_setup")) {
                return SetupSelinux(argv);
            }
    
            if (!strcmp(argv[1], "second_stage")) {
                return SecondStageMain(argc, argv);
            }
        }
    
        return FirstStageMain(argc, argv);
    }
    

    进入到FirstStageMain函数中,代码在system/core/init/first_stage_init.cpp

    创建和挂载启动所需要的文件目录

    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);// 清理umask
    
        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"));//挂载tmpfs文件
        CHECKCALL(mkdir("/dev/pts", 0755));
        CHECKCALL(mkdir("/dev/socket", 0755));
        CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));//挂载devpts文件系统
    #define MAKE_STR(x) __STRING(x)
        CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));//挂载proc文件系统
    #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));//挂载sysfs文件系统
        CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
    //提前创建了kmsg设备节点文件,用于输出log信息
        CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
    
        if constexpr (WORLD_WRITABLE_KMSG) {
            CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
        }
    
        CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
        CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
    
        // This is needed for log wrapper, which gets called before ueventd runs.
        CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
        CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
    
        // These below mounts are done in first stage init so that first stage mount can mount
        // subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,
        // should be done in rc files.
        // Mount staging areas for devices managed by vold
        // See storage config details at http://source.android.com/devices/storage/
        CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                        "mode=0755,uid=0,gid=1000"));
        // /mnt/vendor is used to mount vendor-specific partitions that can not be
        // part of the vendor partition, e.g. because they are mounted read-write.
        CHECKCALL(mkdir("/mnt/vendor", 0755));//创建可供读写的vendor目录
        // /mnt/product is used to mount product-specific partitions that can not be
        // part of the product partition, e.g. because they are mounted read-write.
        CHECKCALL(mkdir("/mnt/product", 0755));//创建可供读写的product目录
    
        // /apex is used to mount APEXes
        CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                        "mode=0755,uid=0,gid=0"));
    
        // /debug_ramdisk is used to preserve additional files from the debug ramdisk
        CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                        "mode=0755,uid=0,gid=0"));
    #undef CHECKCALL
    
        SetStdioToDevNull(argv);
        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
    //初始化kernel Log系统
        InitKernelLogging(argv);
    ...
        return 1;
    }
    

    初始化日志

    SetStdioToDevNull

    由于Linux内核打开了/dev/console作为标准输入输出流(stdin/stdout/stderr)的文件描述符,而init进程在用户空间,无权访问/dev/console,后续如果执行printf的话可能会导致错误,所以先调用SetStdioToDevNull函数来将标准输入输出流(stdin/stdout/stderr)用/dev/null文件描述符替换
    /dev/null被称为空设备,是一个特殊的设备文件,它会丢弃一切写入其中的数据,读取它会立即得到一个EOF

    InitKernelLogging

    接着调用InitKernelLogging函数,初始化了一个简单的kernel日志系统

    init 进程的第一个阶段主要是用来创建和挂载启动所需的文件目录,其中挂载了 tmpfs、devpts、proc、sysfs 和 selinuxfs 共 5 种文件系统,这些都是系统运行时目录,也就是说,只有在系统运行时才会存在,系统停止时会消失。
    四类文件系统:

    • tmpfs:一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果将 tmpfs 文件系统卸载后,那么其下的所有内容将不复存在。 tmpfs 既可以使用 RAM,也可以使用交换分区,会根据实际需要改变大小。tmpfs 的速度非常惊人,它是驻留在 RAM 中的,即使用了交换分区,性能仍然非常卓越。由于 tmpfs 是驻留在 RAM 的,因此它的内容是不持久的。断电后,tmpfs 的内容就消失了,这也是被称作 tmpfs 的根本原因;
    • devpts:为伪终端提供了一个标准接口,用于管理远程虚拟终端文件设备,它的标准挂接点是 /dev/pts。只要 pty 的主复合设备 /dev/ptmx 被打开,就会在 /dev/pts 下动态的创建一个新的 pty 设备文件;
    • proc:一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统信息,同时也能够在运行时修改特定的内核参数;
    • sysfs:与 proc 文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在 /sys 目录下。sysfs 文件系统是 Linux 2.6 内核引入的,它把连接在系统上的设备和总线组织成一个分级的文件,使得它们可以在用户空间存取;

    创建设备,挂载分区

    int FirstStageMain(int argc, char** argv) {
      ...
    
        auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline) : 0;
    
        if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline), want_console)) {
            if (want_console != FirstStageConsoleParam::DISABLED) {
                LOG(ERROR) << "Failed to load kernel modules, starting console";
            } else {
                LOG(FATAL) << "Failed to load kernel modules";
            }
        }
    
        if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
            StartConsole();
        }
    
        if (ForceNormalBoot(cmdline)) {
            mkdir("/first_stage_ramdisk", 0755);
            // SwitchRoot() must be called with a mount point as the target, so we bind mount the
            // target directory to itself here.
            if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
                LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
            }
            SwitchRoot("/first_stage_ramdisk");
        }
    ...
        if (!DoFirstStageMount()) {
            LOG(FATAL) << "Failed to mount required partitions early ...";
        }
    
        struct stat new_root_info;
        if (stat("/", &new_root_info) != 0) {
            PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
            old_root_dir.reset();
        }
    
        if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
            FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
        }
    
        SetInitAvbVersionInRecovery();
    
        setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
               1);
    ...
        return 1;
    }
    

    通过execv函数带参执行init文件,进入SetupSelinux
    用exec系列函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID。

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

    SetupSelinux

    /system/core/init/selinux.cpp
    启动Selinux安全机制,初始化selinux,加载SELinux规则,配置SELinux相关log输出,并启动第二阶段:SecondStageMain

    int SetupSelinux(char** argv) {
        SetStdioToDevNull(argv);
        InitKernelLogging(argv);
    
        if (REBOOT_BOOTLOADER_ON_PANIC) {
            InstallRebootSignalHandlers();
        }
    
        boot_clock::time_point start_time = boot_clock::now();
    
        MountMissingSystemPartitions();
    
        // 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";
        }
    
        setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);
    
        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;
    }
    

    SecondStageMain

    使用second_stage参数启动init的话,便会开始init第二阶段,进入到SecondStageMain函数中,代码在system/core/init/init.cpp
    system/core/init/init.cpp

    int SecondStageMain(int argc, char** argv) {
        ...
    //初始化日志
        SetStdioToDevNull(argv);
        InitKernelLogging(argv);
        LOG(INFO) << "init second stage started!";
        {
            struct sigaction action = {.sa_flags = SA_RESTART};
            action.sa_handler = [](int) {};
            sigaction(SIGPIPE, &action, nullptr);
        }
    //设置init进程和以后fork出来的进程的OOM等级,DEFAULT_OOM_SCORE_ADJUST -1000;
    //保证进程不会因为OOM被杀死
        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();
        }
    
        // Set up a session keyring that all processes will have access to. It
        // will hold things like FBE encryption keys. No process should override
        // its session keyring.
        keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
    
    //设置一个标记,代表正在启动过程中
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
    ...
    //1.初始化系统属性
        PropertyInit();
        if (load_debug_prop) {
            UmountDebugRamdisk();
        }
    
        // Mount extra filesystems required during second stage init
        MountExtraFilesystems();
    
        // Now set up SELinux for second stage.
        SelinuxSetupKernelLogging();
        SelabelInitialize();
        SelinuxRestoreContext();
    //创建epoll句柄,注册信号处理函数,守护进程服务
        Epoll epoll;
        if (auto result = epoll.Open(); !result.ok()) {
            PLOG(FATAL) << result.error();
        }
    //2.创建handler处理子进程的终止信号,如果子进程(Zygote)异常退出,init进程会调用该函数中设定的信号处理函数来进行处理
        InstallSignalFdHandler(&epoll);
        InstallInitNotifier(&epoll);
    //3 启动系统属性服务
        StartPropertyService(&property_fd);
    
        // Make the time that init stages started available for bootstat to log.
        RecordStageBoottimes(start_time);
    
        // Set libavb version for Framework-only OTA match in Treble build.
        if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) {
            SetProperty("ro.boot.avb_version", avb_version);
        }
        unsetenv("INIT_AVB_VERSION");
    
        fs_mgr_vendor_overlay_mount_all();
        export_oem_lock_status();
        MountHandler mount_handler(&epoll);
        SetUsbController();
    //设置commands命令所对应的函数map
        const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
        Action::set_function_map(&function_map);
    
        if (!SetupMountNamespaces()) {
            PLOG(FATAL) << "SetupMountNamespaces failed";
        }
    
        InitializeSubcontext();
    //解析init.rc脚本
        ActionManager& am = ActionManager::GetInstance();
        ServiceList& sm = ServiceList::GetInstance();
    
        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.
        auto is_running = android::gsi::IsGsiRunning() ? "1" : "0";
        SetProperty(gsi::kGsiBootedProp, is_running);
        auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
        SetProperty(gsi::kGsiInstalledProp, is_installed);
    //构建了一些action,trigger等事件对象加入事件队列中
        am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
        am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
        am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
        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");
        Keychords keychords;
        am.QueueBuiltinAction(
                [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
                    for (const auto& svc : ServiceList::GetInstance()) {
                        keychords.Register(svc->keycodes());
                    }
                    keychords.Start(&epoll, HandleKeychord);
                    return {};
                },
                "KeychordInit");
    
        // Trigger all the boot actions to get us started.
        am.QueueEventTrigger("init");
    
        // 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");
    
        // 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>{};
    
            auto shutdown_command = shutdown_state.CheckShutdown();
            if (shutdown_command) {
                HandlePowerctlMessage(*shutdown_command);
            }
    //执行从init.rc脚本解析出来的每条指令
            if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
                am.ExecuteOneCommand();
            }
            if (!IsShuttingDown()) {
                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 (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
                // If there's more work to do, wake up again immediately.
                if (am.HasMoreCommands()) epoll_timeout = 0ms;
            }
    
            auto pending_functions = epoll.Wait(epoll_timeout);
            if (!pending_functions.ok()) {
                LOG(ERROR) << pending_functions.error();
            } else if (!pending_functions->empty()) {
                // We always reap children before responding to the other pending functions. This is to
                // prevent a race where other daemons see that a service has exited and ask init to
                // start it again via ctl.start before init has reaped it.
    //处理子进程退出后的相关事项
                ReapAnyOutstandingChildren();
                for (const auto& function : *pending_functions) {
                    (*function)();
                }
            }
            if (!IsShuttingDown()) {
                HandleControlMessages();
                SetUsbController();
            }
        }
        return 0;
    }
    
    

    在注释 1 处调用 property_init() 函数来对属性进行初始化,并在注释 3 处调用 StartPropertyService 函数启动属性服务。
    在注释 2 处调用 InstallSignalFdHandler 函数用于设置子进程信号处理函数,主要是用来防止 init 进程的子进程成为僵尸进程。为了防止僵尸进程的出现,系统会在子进程暂停或者终止的时候发出 SIGCHLD 信号,而 InstallSignalFdHandler 函数是用来接收 SIGCHLD 信号的(其内部只处理进程终止的 SIGCHLD 信号)。
    僵尸进程
    僵尸进程与危害:在 UNIX/Linux 中,父进程使用 fork 创建子进程,在子进程终止后,如果无法通知到父进程,这时虽然子进程已经退出了,但是在系统进程表中还为它保留了一定的信息(比如进程号、退出状态、运行时间等),这个进程就被称为是僵尸进程。系统进程表是一项有限资源,如果系统进程表被僵尸进程耗尽的话,系统就可能无法创建新的进程了。
    属性服务
    Window 平台上有一个注册表管理器,注册表的内容采取键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,其还是能够根据之前注册表中的记录,进行相应的初始化工作。Android 也提供了类似的机制,叫做属性服务。
    init 进程启动时会启动属性服务,并为其分配内存,用来存储这些属性,如果需要这些属性直接读取就可以了, 在 init.cpp 的 main 函数中与属性服务相关的代码有以下两行:

    // /system/core/init/init.cpp
    property_init();
    StartPropertyService(&epoll);
    

    这两行代码用来初始化属性服务配置并启动属性服务。
    1 属性服务初始化与启动
    property_init 函数的具体实现如下所示:
    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() 函数用来初始化属性内存区域。接下来看 StartPropertyService 函数的代码:

    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");
    //创建接收属性的socket
        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();
        }
    }
    

    CreateSocket创建非阻塞的 Socket。随后调用 listen 函数对 property_set_fd 进行监听,这样创建的 Socket 就成为 server,也就是属性服务;listen 函数的的第二个参数设置为 8,意味着属性服务最多可以同时为 8 个试图设置属性的用户提供服务。RegisterHandler代码将 property_set_fd 放入了 epoll 中,用 epoll 来监听 property_set_fd:当 property_set_fd 中有数据到来时,init 进程将调用 RegisterHandler 进行处理。
    epoll 是 Linux 内核为处理大批量文件描述符而做了改进的 poll,是 Linux 下多路复用 I/O 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。epoll 内部用于保存时间的数据类型是红黑树,查找速度快,select 采用的数组保存信息,查找速度很慢,只有当等待少量文件描述符时,epoll 和 select 的效率才会差不多。
    服务处理客户端请求
    属性服务接收到客户端的请求时,会调用 handle_property_set_fd 函数进行处理:

    static void handle_property_set_fd() {
        static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
    
        int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
        if (s == -1) {
            return;
        }
    
        ucred cr;
        socklen_t cr_size = sizeof(cr);
        if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
            close(s);
            PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
            return;
        }
    
        SocketConnection socket(s, cr);
        uint32_t timeout_ms = kDefaultSocketTimeout;
    
        uint32_t cmd = 0;
        if (!socket.RecvUint32(&cmd, &timeout_ms)) {
            PLOG(ERROR) << "sys_prop: error while reading command from the socket";
            socket.SendUint32(PROP_ERROR_READ_CMD);
            return;
        }
    
        switch (cmd) {
        case PROP_MSG_SETPROP: {
            char prop_name[PROP_NAME_MAX];
            char prop_value[PROP_VALUE_MAX];
    //如果socket读取不到属性数据则返回
            if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
                !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
              PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
              return;
            }
    
            prop_name[PROP_NAME_MAX-1] = 0;
            prop_value[PROP_VALUE_MAX-1] = 0;
    
            const auto& cr = socket.cred();
            std::string error;
            uint32_t result =
                HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
            if (result != PROP_SUCCESS) {
                LOG(ERROR) << "Unable to set property '" << prop_name << "' to '" << prop_value
                           << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                           << error;
            }
    
            break;
          }
    
        case PROP_MSG_SETPROP2: {
            std::string name;
            std::string value;
            if (!socket.RecvString(&name, &timeout_ms) ||
                !socket.RecvString(&value, &timeout_ms)) {
              PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
              socket.SendUint32(PROP_ERROR_READ_DATA);
              return;
            }
    
            const auto& cr = socket.cred();
            std::string error;
            uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
            if (result != PROP_SUCCESS) {
                LOG(ERROR) << "Unable to set property '" << name << "' to '" << value
                           << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                           << error;
            }
            socket.SendUint32(result);
            break;
          }
    
        default:
            LOG(ERROR) << "sys_prop: invalid command " << cmd;
            socket.SendUint32(PROP_ERROR_INVALID_CMD);
            break;
        }
    }
    
    // This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
    uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                               const std::string& source_context, const ucred& cr, std::string* error) {
        if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
            return ret;
        }
    //如果属性名称以 ctl开头,说明是控制属性
        if (StartsWith(name, "ctl.")) {
    //设置控制属性
            HandleControlMessage(name.c_str() + 4, value, cr.pid);
            return PROP_SUCCESS;
        }
    
        // sys.powerctl is a special property that is used to make the device reboot.  We want to log
        // any process that sets this property to be able to accurately blame the cause of a shutdown.
        if (name == "sys.powerctl") {
            std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
            std::string process_cmdline;
            std::string process_log_string;
            if (ReadFileToString(cmdline_path, &process_cmdline)) {
                // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
                // path.
                process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
            }
            LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                      << process_log_string;
        }
    
        if (name == "selinux.restorecon_recursive") {
            return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
        }
    
        return PropertySet(name, value, error);
    }
    

    系统属性分为两种类型,一种是普通属性,另一种是控制属性,控制属性用来执行一些命令,比如开机的动画就使用了这种属性。因此,HandlePropertySet 分为两个处理分之,一部分处理控制属性,另一部分用于处理普通属性,这里只分析普通属性。如果属性名称以 ctl. 开头,说明是控制属性,如果客户端权限满足,则会调用 HandleControlMessage 函数来修改控制属性。如果是普通属性,则会在客户端权限满足的条件下调用 PropertySet 来对普通属性进行修改,如下所示:

    static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
        size_t valuelen = value.size();
    //判断是否合法
        if (!IsLegalPropertyName(name)) {
            *error = "Illegal property name";
            return PROP_ERROR_INVALID_NAME;
        }
    ..
    //从属性存储空间查找该属性
        prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    // 如果属性存在
        if (pi != nullptr) {
            // ro.* properties are actually "write-once". 如果属性名称以ro开头,表示只读,不能修改
            if (StartsWith(name, "ro.")) {
                *error = "Read-only property was already set";
                return PROP_ERROR_READ_ONLY_PROPERTY;
            }
    // 如果属性存在,更新属性
            __system_property_update(pi, value.c_str(), valuelen);
        } else {
            int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
            if (rc < 0) {
                *error = "__system_property_add failed";
                return PROP_ERROR_SET_FAILED;
            }
        }
    
        // Don't write properties to disk until after we have read all default
        // properties to prevent them from being overwritten by default values.
    //属性名称以persist开头
        if (persistent_properties_loaded && StartsWith(name, "persist.")) {
            WritePersistentProperty(name, value);
        }
        property_changed(name, value);
        return PROP_SUCCESS;
    }
    

    PropertySet 函数主要对普通属性进行修改,首先要判断该属性是否合法,如果合法就从属性存储空间中查找该属性,如果属性存在,就更新属性值,否则就添加该属性。另外,还对名称以 ro. persist. 开头的属性进行了相应的处理。
    解析init.rc配置文件
    init.rc通过Android Init Language
    来进行配置。 可大致阅读一下其 语法说明
    init.rc 文件大致分为两大部分,一部分是以 on 关键字开头的“动作列表”(action list),另一部分是以 service 关键字开头的“服务列表”(service list)。
    动作列表用于创建所需目录以及为某些特定文件指定权限,服务列表用来记录 init 进程需要启动的一些子进程。
    system/core/rootdir/init.rc
    system/core/init/init.cpp
    system/core/init/builtins.cpp
    system/core/rootdir/init.zygote64.rc
    frameworks/base/cmds/app_process/app_main.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);
        }
    }
    

    这个函数会从这些地方寻找rc文件解析,/system/etc/init/hw/init.rc是主rc文件,剩下的目录,如果system分区尚未挂载的话,就把它们加入到late_import_paths中,等到后面mount_all时再加载。

    相关文章

      网友评论

          本文标题:Android启动过程(一)

          本文链接:https://www.haomeiwen.com/subject/fynycftx.html