安卓开机启动流程,安卓开机启动流程详解

马肤

温馨提示:这篇文章已超过458天没有更新,请注意相关的内容是否还可用!

摘要:安卓开机启动流程包括多个阶段,从按下电源按钮开始,系统启动引导程序,加载内核,初始化硬件,运行init进程等。接着系统加载启动项,启动系统服务,如启动系统服务器等。启动用户应用程序界面,完成系统启动过程。整个过程涉及多个组件和步骤,确保手机能够正常启动并运行各种应用程序和服务。

目录

  • 一、整体框架
  • 二、流程+代码分析
    • 2.1 Boot ROM
    • 2.2 Boot Loader
    • 2.3 Kernel层
      • Kernel代码部分
      • 2.4 Init进程
        • Init进程代码部分
        • 2.5 zygote进程
          • zygote代码部分
          • 2.6 SystemServer进程
            • SystemServer代码部分
            • 2.7 启动Launcher与SystemUI
            • 三、SystemServices
              • 3.1 引导服务
              • 3.2 核心服务
              • 3.3 其他服务
              • 四、总结

                一、整体框架

                安卓开机启动流程,安卓开机启动流程详解 第1张

                安卓开机启动流程,安卓开机启动流程详解 第2张


                二、流程+代码分析

                按照开机流程,重点分析安卓init进程及之后上层部分的代码

                2.1 Boot ROM

                BOOT ROM :当手机处于关机状态时,长按Power键开机,引导芯片开始从固化在ROM里的预设出代码开始执行,然后加载引导程序到RAM;

                ROM是存储在设备芯片中的只读存储器(ROM),负责在设备上电后最先运行的引导程序。Boot ROM的主要作用是初始化硬件设备(如内存、CPU、外设等),加载并执行Boot Loader。Boot ROM通常是设备制造商预先写入芯片中的固化程序,用于引导设备启动。

                2.2 Boot Loader

                Boot Loader:这是启动Android系统之前的引导程序,主要是检查RAM,初始化硬件参数等功能。

                Boot Loader是位于设备存储器中的引导加载程序,负责在Boot ROM之后被加载和执行。Boot Loader的主要任务包括:

                初始化设备硬件,如内存管理、外设初始化等。

                加载Linux Kernel到内存中,并启动Linux Kernel。

                提供启动选项和引导参数的设置。

                启动Linux Kernel后,Boot Loader的任务就完成了,控制权交给Linux Kernel。

                2.3 Kernel层

                Kernel是指Android内核层,到这里才刚刚开始进入Android系统。

                swapper进程(pid=0):

                启动Kernel的swapper进程(pid=0):该进程又称为idle进程, 系统初始化过程Kernel由无到有开创的第一个进程, 用于初始化进程管理、内存管理,加载Display,Camera Driver,Binder Driver等相关工作;

                kthreadd进程(pid=2):

                启动kthreadd进程(pid=2):是Linux系统的内核进程,会创建内核工作线程kworkder,软中断线程ksoftirqd,thermal等内核守护进程。kthreadd进程是所有内核进程的鼻祖。

                Linux Kernel是整个系统的核心部分,负责管理硬件资源、提供系统调度和内存管理等功能。Android系统开机启动流程中的Linux Kernel阶段:

                • 1.加载Linux Kernel:

                  在Boot Loader加载完成后,Boot Loader会将Linux Kernel从存储器中加载到设备的内存中,并开始执行Linux Kernel的启动代码。

                • 2.初始化阶段:

                  Linux Kernel启动后,首先会进行一系列初始化操作,包括初始化内核数据结构、硬件设备、内存管理等。这些初始化操作是确保系统能够正常运行的基础。

                • 3.设备检测和驱动加载:

                  Linux Kernel会进行设备检测,识别设备硬件,并加载相应的设备驱动程序。这些设备驱动程序负责与硬件设备进行通信和控制,确保系统能够正确地访问和操作硬件设备。

                  Kernel代码部分

                  不同版本略有区别

                  kernel/msm-4.14/init/main.c

                  kernel_init开始启动init进程

                  asmlinkage __visible void __init start_kernel(void)
                  {
                      /**
                      *
                      *省略部分代码,涉及到内核、堆栈、进程管理等内容的初始化
                      *
                      **/
                   
                      /* Do the rest non-__init'ed, we're now alive */
                      //进入用户空间,执行后续初始化操作
                      rest_init();
                   
                      prevent_tail_call_optimization();
                  }
                   
                  static noinline void __ref rest_init(void)
                  {
                      struct task_struct *tsk;
                      int pid;
                   
                      rcu_scheduler_starting();
                      /*
                       * We need to spawn init first so that it obtains pid 1, however
                       * the init task will end up wanting to create kthreads, which, if
                       * we schedule it before we create kthreadd, will OOPS.
                       */
                      //通过 kernel_thread 函数创建两个内核线程 kernel_init 和 kthreadd。
                      //其中 kernel_init 进程用于执行 /bin/init 程序,成为用户空间的第一个进程(PID 为 1),而 kthreadd 则是系统中所有内核线程的父进程。
                      //kernel_thread() 是一个创建内核线程的函数,它的原型定义在 include/linux/kthread.h 头文件中
                      //第一个参数是一个函数指针,指向要在新线程中执行的函数。第二个参数是传递给 fn 函数的参数,如果不需要传递参数,则可以将其设置为 NULL。第三个参数用于指定新线程的行为,可以使用标志值对其进行设置。
                      //kernel_thread(kernel_init, NULL, CLONE_FS) 的作用是创建一个内核线程,并在其中执行 kernel_init 函数。由于第二个参数为 NULL,因此 kernel_init 函数不会接收任何参数。而 CLONE_FS 标志表示新线程会继承当前进程的文件系统相关的属性,例如根目录、当前工作目录等等
                      pid = kernel_thread(kernel_init, NULL, CLONE_FS);
                   
                      /*
                       * Pin init on the boot CPU. Task migration is not properly working
                       * until sched_init_smp() has been run. It will set the allowed
                       * CPUs for init to the non isolated CPUs.
                       */
                      rcu_read_lock();
                      tsk = find_task_by_pid_ns(pid, &init_pid_ns);
                      set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
                      rcu_read_unlock();
                   
                      numa_default_policy();
                      pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
                      rcu_read_lock();
                      kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
                      rcu_read_unlock();
                   
                      /*
                       * Enable might_sleep() and smp_processor_id() checks.
                       * They cannot be enabled earlier because with CONFIG_PRREMPT=y
                       * kernel_thread() would trigger might_sleep() splats. With
                       * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
                       * already, but it's stuck on the kthreadd_done completion.
                       */
                      //system_state 是一个全局变量,表示系统的当前状态。它的取值包括以下几种:
                      //SYSTEM_BOOTING:表示系统正在启动。
                      //SYSTEM_RUNNING:表示系统已经启动并正在正常运行。
                      //SYSTEM_HALT:表示系统已经关闭并停止了所有运行。
                      //SYSTEM_POWER_OFF:表示系统已经关闭,但是可能仍然有硬件在运行。
                      //SYSTEM_SCHEDULING:表示系统正在进行进程调度,即系统已经进入正常运行状态。
                      //即表示系统已经完成了启动过程,并正常地开始了进程调度。这意味着内核已经初始化完毕,可以正常地执行用户进程和系统服务。
                      system_state = SYSTEM_SCHEDULING;
                   
                      //通过complete(&kthreadd_done) 向 kthreadd 进程发出完成信号,以便 kthreadd 进程可以开始创建其它内核线程
                      complete(&kthreadd_done);
                   
                      /*
                       * The boot idle thread must execute schedule()
                       * at least once to get things moving:
                       */
                      schedule_preempt_disabled();
                      /* Call into cpu_idle with preempt disabled */
                      cpu_startup_entry(CPUHP_ONLINE);
                  }
                   
                   
                  static int __ref kernel_init(void *unused)
                  {
                      /**
                      *
                      *省略部分代码,设置进程的状态
                      *
                      **/
                      //设置系统状态为运行状态
                      system_state = SYSTEM_RUNNING;
                   
                      /**
                      *
                      *省略部分代码,设置进程的状态
                      *
                      **/
                   
                      //如果ramdisk_execute_command与execute_command非空,则调用run_init_process()函数尝试执行该命令,并将返回值保存在ret变量中。如果返回值为0,表示执行成功,就直接返回0表示初始化成功
                      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);
                      }
                   
                      //会依次寻找以下目录中的可执行文件进行执行,只要一个执行成功就直接返回0,否则触发异常
                      //"/bin/init"是在Android系统源码编译时编译出的一个可执行程序,路径为Android设备上的"system/bin/init",而这个init程序则是由system/core/init/main.cpp文件编译生成的
                      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.");
                  }
                   
                  static int try_to_run_init_process(const char *init_filename)
                  {
                      int ret;
                   
                      ret = run_init_process(init_filename);
                   
                      if (ret && ret != -ENOENT) {
                          pr_err("Starting init: %s exists but couldn't execute it (error %d)\n",
                                 init_filename, ret);
                      }
                   
                      return ret;
                  }
                   
                  static int run_init_process(const char *init_filename)
                  {
                      argv_init[0] = init_filename;
                   
                      //do_execve()函数的作用是加载并执行一个新的用户程序,它接受以下参数:
                      //filename:一个struct filename类型的指针,表示要执行的可执行文件的路径和名称。
                      //argv:一个以NULL结尾的字符串数组,表示要传递给新程序的命令行参数。
                      //envp:一个以NULL结尾的字符串数组,表示要传递给新程序的环境变量。
                      //函数返回一个整数值,代表执行结果。如果执行成功,函数不会返回,而是直接切换到新程序的上下文;如果执行失败,函数返回一个负值,代表错误代码。
                      return do_execve(getname_kernel(init_filename),
                          (const char __user *const __user *)argv_init,
                          (const char __user *const __user *)envp_init);
                  }
                  

                  system/core/init/Android.bp

                  phony {
                      name: "init",
                      required: [
                          "init_second_stage",
                      ],
                  }
                   
                  cc_binary {
                      name: "init_second_stage",
                      recovery_available: true,
                      stem: "init",
                      defaults: ["init_defaults"],
                      static_libs: ["libinit"],
                      required: [
                          "e2fsdroid",
                          "init.rc",
                          "mke2fs",
                          "sload_f2fs",
                          "make_f2fs",
                          "ueventd.rc",
                      ],
                      srcs: ["main.cpp"],
                      symlinks: ["ueventd"],
                      target: {
                          recovery: {
                              cflags: ["-DRECOVERY"],
                              exclude_shared_libs: [
                                  "libbinder",
                                  "libutils",
                              ],
                          },
                      },
                  }
                  

                  2.4 Init进程

                  init进程是linux系统中用户空间的第一个进程,进程号为1。

                  当bootloader启动后,启动kernel,kernel启动完后,在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置。

                  从而来启动其他相关进程以及其他操作。 init进程被赋予了很多重要工作,init进程启动主要分为两个阶段:

                  • 1.第一个阶段完成以下内容:

                    ueventd/watchdogd跳转及环境变量设置

                    挂载文件系统并创建目录

                    初始化日志输出、挂载分区设备

                    启用SELinux安全策略

                    开始第二阶段前的准备

                  • 2.第二个阶段完成以下内容:

                    初始化属性系统

                    执行SELinux第二阶段并恢复一些文件安全上下文

                    新建epoll并初始化子进程终止信号处理函数

                    设置其他系统属性并开启属性服务

                    Init进程代码部分

                    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 = GetBuiltinFunctionMap();
                     
                                return SubcontextMain(argc, argv, &function_map);
                            }
                     
                            if (!strcmp(argv[1], "selinux_setup")) {
                                //第二次通过first_stage_init执行SetupSelinux
                                return SetupSelinux(argv);
                            }
                     
                            if (!strcmp(argv[1], "second_stage")) {
                                //第三次设置selinux后启动SecondStageMain
                                return SecondStageMain(argc, argv);
                            }
                        }
                     
                        //代码会执行多次,首次通过try_to_run_init_process执行时没有额外的命令行参数,所以会直接执行FirstStageMain
                         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 errors;
                     
                        //定义了一个宏CHECKCALL(x),如果参数x执行返回的结果为不成功,则会将错误结果保存到errors中。
                        //errors.emplace_back(#x " failed", errno) 的作用是将错误信息添加到 errors 容器中。#x 是一个预处理器宏,表示参数 x 的字符串字面值。这里 #x " failed" 将会被替换为类似 "mount() failed" 的字符串。
                        //errno 是一个全局变量,用于保存最近一次系统调用失败的错误码。通过将 errno 作为第二个参数传递给 emplace_back() 函数,可以将错误码与错误信息一起存储在 errors 容器中。
                    #define CHECKCALL(x) \
                        if ((x) != 0) errors.emplace_back(#x " failed", errno);
                     
                        // Clear the umask.
                        umask(0);
                     
                        //创建和挂载启动所需的目录文件
                        //clearenv():清除当前进程的环境变量。
                        //setenv():设置环境变量 PATH。
                        //mount():挂载文件系统,包括 tmpfs、devpts、proc、sysfs 和 selinuxfs 等。
                        //mkdir():创建目录 /dev/pts 和 /dev/socket。
                        //mknod():创建设备节点,包括 /dev/kmsg、/dev/kmsg_debug、/dev/random、/dev/urandom、/dev/ptmx 和 /dev/null 等。
                     
                        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));
                        std::string cmdline;
                        android::base::ReadFileToString("/proc/cmdline", &cmdline);
                        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));
                     
                        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));
                        // /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));
                     
                        // /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
                     
                        //将标准输入输出重定向到 /dev/null 设备节点,为了避免在启动过程中产生不必要的输出。
                        SetStdioToDevNull(argv);
                        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
                        // talk to the outside world...
                        //初始化内核日志记录器,并调用 LogInit() 函数记录系统启动日志
                        InitKernelLogging(argv);
                     
                        //函数遍历 errors 变量,如果出现错误,则打印错误信息并终止系统启动。否则,函数将打印一条 INFO 级别的日志,表明第一阶段初始化已经开始
                        if (!errors.empty()) {
                            for (const auto& [error_string, error_errno] : errors) {
                                LOG(ERROR) opendir("/"), closedir};
                        if (!old_root_dir) {
                            PLOG(ERROR) 
                            PLOG(ERROR) 
                            if (want_console != FirstStageConsoleParam::DISABLED) {
                                LOG(ERROR) 
                                LOG(FATAL) 
                            StartConsole();
                        }
                     
                        //通过检查 ForceNormalBoot(cmdline) 的返回值来判断当前是否处于强制正常引导模式。如果是,则继续执行下面的操作;否则,直接跳过该代码段。
                        if (ForceNormalBoot(cmdline)) {
                            //使用 mkdir() 函数创建一个名为 "/first_stage_ramdisk" 的目录,并将其权限设置为 0755。
                            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.
                            //接着,使用 mount() 函数将 "/first_stage_ramdisk" 目录绑定到自身。这样做的目的是为了确保在切换根文件系统之前,"/first_stage_ramdisk" 目录已经被挂载。
                            if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
                                LOG(FATAL) 
                            std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
                            if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
                                !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
                                LOG(ERROR) 
                                // setenv for second-stage init to read above kDebugRamdisk* files.
                                setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
                            }
                        }
                     
                        //根据DoFirstStageMount()函数,检查挂载必需分区是否成功。如果挂载失败,则输出错误
                        if (!DoFirstStageMount()) {
                            LOG(FATAL) 
                            //输出log,指示无法获取根目录的文件信息
                            PLOG(ERROR) 
                            //代表着在根文件系统更改的情况下,需要释放旧的 RAM 磁盘资源,以便重新分配给新的根文件系统
                            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);
                     
                        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);
                        //再次执行/system/bin/init文件,并设置selinux_setup
                        execv(path, const_cast
                        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();
                        //加载SeLinux配置
                        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) path, "second_stage", nullptr};
                        execv(path, const_cast
                        LOG(INFO) 
                            selinux_android_restorecon("/dev/kmsg_debug", 0);
                        }
                        selinux_android_restorecon("/dev/null", 0);
                        selinux_android_restorecon("/dev/ptmx", 0);
                        selinux_android_restorecon("/dev/socket", 0);
                        selinux_android_restorecon("/dev/random", 0);
                        selinux_android_restorecon("/dev/urandom", 0);
                        selinux_android_restorecon("/dev/__properties__", 0);
                     
                        selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
                        selinux_android_restorecon("/dev/device-mapper", 0);
                     
                        selinux_android_restorecon("/apex", 0);
                     
                        selinux_android_restorecon("/linkerconfig", 0);
                     
                        // adb remount, snapshot-based updates, and DSUs all create files during
                        // first-stage init.
                        selinux_android_restorecon(SnapshotManager::GetGlobalRollbackIndicatorPath().c_str(), 0);
                        selinux_android_restorecon("/metadata/gsi", SELINUX_ANDROID_RESTORECON_RECURSE |
                             SELINUX_ANDROID_RESTORECON_SKIP_SEHASH);
                    }
                    
                        if (REBOOT_BOOTLOADER_ON_PANIC) {
                            InstallRebootSignalHandlers();
                        }
                     
                        /**
                        *
                        *省略部分代码,设置环境状态
                        *
                        **/
                     
                        //初始化系统属性Property
                        PropertyInit();
                     
                        // Umount the debug ramdisk after property service has read the .prop files when it means to.
                        if (load_debug_prop) {
                            UmountDebugRamdisk();
                        }
                     
                        // Mount extra filesystems required during second stage init
                        //挂载额外的目录
                        MountExtraFilesystems();
                     
                        // Now set up SELinux for second stage.
                        SelinuxSetupKernelLogging();
                        //初始化 SELinux 标签库
                        SelabelInitialize();
                        //恢复 SELinux 上下文。
                        SelinuxRestoreContext();
                     
                        Epoll epoll;
                        if (auto result = epoll.Open(); !result.ok()) {
                            PLOG(FATAL) 

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码