安卓启动流程梳理之 Init 进程

安卓系统从开机到桌面显示是一个长而复杂的流程,本文参考安卓源码记录安卓启动流程的梳理学习。(文章涉及的源码基于 Android 10.0)由于 Android 启动流程很长,所以分几篇来记录,本篇记录安卓第一个用户态进程 Init 进程的启动过程。

[TOC]

Init 进程是安卓系统启动的第一个用户态进程,其 PID 为1,是由内核态进程0 idle 启动,Init 是启动众多安卓系统服务进程的源头。

启动 Init 进程的流程包括以下几步:

  1. 用户长按电源键,固化在ROM中的 BOOT 加载引导程序Bootloader到 RAM 中
  2. 运行 Bootloader 启动 Linux 内核系统,启动第一个内核态进程 idle ,并初始化内核驱动程序。
  3. idle 进程启动 Init 进程,具体是执行到 system/core/Init 目录下的 main.cpp

下面将梳理下 system/core/Init/main.cpp 的执行流程。

Init 入口函数

int main(int argc, char** argv) {
56      if (!strcmp(basename(argv[0]), "ueventd")) {
57          return ueventd_main(argc, argv);
58      }
60      if (argc > 1) {
61          if (!strcmp(argv[1], "subcontext")) {
62              android::base::InitLogging(argv, &android::base::KernelLogger);
63              const BuiltinFunctionMap function_map;
64  
65              return SubcontextMain(argc, argv, &function_map);
66          }
68          if (!strcmp(argv[1], "selinux_setup")) {
69              return SetupSelinux(argv);
70          }
72          if (!strcmp(argv[1], "second_stage")) {
73              return SecondStageMain(argc, argv);
74          }
75      }
77      return FirstStageMain(argc, argv);
78  }
  • 传入参数为 ueventd, 走 eventd_main 流程。
  • 传入参数为 subcontext, 走 SubcontextMain 流程。
  • 传入参数为 selinux_setup , 走 SetupSelinux 流程。
  • 传入参数为 second_stage, 走 SecondStageMain 流程。
  • 默认无参数, 走 FirstStageMain 流程。

main 函数可以看出 Init 流程分为好几个过程,由函数的入参控制。从 idle 进程执行到 Init.main 时,不带入参,即默认首先执行 FirstStageMain 流程。(当然从函数名也能看出来)

FirstStageMain

main.cpp 执行到 first_state_init.cpp 中的 FirstStageMain 函数,进行 Init 的第一个过程。这个过程主要工作包括:

  • 挂载设备节点
  • 创建系统关键目录
  • 初始化 Log 系统

这一阶段代码代码较多,可以自行查看源码FirstStageMain 函数源码。在 FirstStageMain 函数执行结束的时候,通过 execv 命令启动了 Init 流程第二个过程 SetupSelinux

int FirstStageMain(int argc, char** argv) {
103      ...
116      CHECKCALL(clearenv());
117      CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
118      // Get the basic filesystem setup we need put together in the initramdisk
119      // on / and then we'll let the rc file figure out the rest.
120      CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
121      CHECKCALL(mkdir("/dev/pts", 0755));
         ...
168  #undef CHECKCALL
173      InitKernelLogging(argv);
236      setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
238      const char* path = "/system/bin/init";
239      const char* args[] = {path, "selinux_setup", nullptr};
240      execv(path, const_cast<char**>(args));
246      return 1;
247  }

SetupSelinux

SetupSelinux 函数在 selinux.cpp文件中,这个过程主要工作是初始化selinux、加载 selinux 规则。selinuxLinux 中的一个安全控制模块,在这种访问控制体系的限制下, 进程只能访问那些在他的任务中所需要的文件。在 SetupSelinux 函数执行结束的时候同样通过 execv 命令启动了 Init 的第三个过程 SecondStageMain

int SetupSelinux(char** argv) {
520      InitKernelLogging(argv);
522      if (REBOOT_BOOTLOADER_ON_PANIC) {
523          InstallRebootSignalHandlers();
524      }
527      SelinuxSetupKernelLogging();
528      SelinuxInitialize();
538      const char* path = "/system/bin/init";
539      const char* args[] = {path, "second_stage", nullptr};
540      execv(path, const_cast<char**>(args));
546      return 1;
547  }

SecondStageMain

Init 第三个过程在 init.cpp 文件中,这个过程主要做了以下几类工作:

  • 初始化并启动属性服务
  • 初始化子进程终止处理函数
  • 解析 init.rc 文件,在这期间启动了 system server 进程
  • 通过 ActionManager 执行一些开机前的准备工作,如显示静态的 Android 开机画面
  • 循环等待新的 Action 的到来

至此,init 进程启动过程结束。

    int SecondStageMain(int argc, char** argv) {
    ...
623      SetStdioToDevNull(argv);
624      InitKernelLogging(argv);
643      property_init();
    ...
679      Epoll epoll;
680      if (auto result = epoll.Open(); !result) {
681          PLOG(FATAL) << result.error();
682      }
684      InstallSignalFdHandler(&epoll);
686      property_load_boot_defaults(load_debug_prop);
687      UmountDebugRamdisk();
688      fs_mgr_vendor_overlay_mount_all();
689      export_oem_lock_status();
690      StartPropertyService(&epoll);
691      MountHandler mount_handler(&epoll);
692      set_usb_controller();
706      LoadBootScripts(am, sm);
    ...
730      am.QueueBuiltinAction(
731          [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
732              for (const auto& svc : ServiceList::GetInstance()) {
733                  keychords.Register(svc->keycodes());
734              }
735              keychords.Start(&epoll, HandleKeychord);
736              return Success();
737          },
738          "KeychordInit");
739      am.QueueBuiltinAction(console_init_action, "console_init");
741      // Trigger all the boot actions to get us started.
742      am.QueueEventTrigger("init");
    ...
744     
765      while (true) {
766          // By default, sleep until something happens.
767          auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
768  
769          if (do_shutdown && !shutting_down) {
770              do_shutdown = false;
771              if (HandlePowerctlMessage(shutdown_command)) {
772                  shutting_down = true;
773              }
774          }
775  
776          if (!(waiting_for_prop || Service::is_exec_service_running())) {
777              am.ExecuteOneCommand();
778          }
779          if (!(waiting_for_prop || Service::is_exec_service_running())) {
780              if (!shutting_down) {
781                  auto next_process_action_time = HandleProcessActions();
782  
783                  // If there's a process that needs restarting, wake up in time for that.
784                  if (next_process_action_time) {
785                      epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
786                              *next_process_action_time - boot_clock::now());
787                      if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
788                  }
789              }
790  
791              // If there's more work to do, wake up again immediately.
792              if (am.HasMoreCommands()) epoll_timeout = 0ms;
793          }
794  
795          if (auto result = epoll.Wait(epoll_timeout); !result) {
796              LOG(ERROR) << result.error();
797          }
798      }
799  
800      return 0;
801  }

属性服务

属性服务类似于 Windows 中的注册表功能,提供给上层应用记忆一些设置属性,并在启动应用时读取并生效。由于所有进程的属性键值对都存在一块内存中,所以对于读写权限的控制至关重要,避免跨进程修改属性。

Android 将属性键值对的管理统一交由 Init进程,其他进程不能直接修改属性,而只能通过和 Init 进程通信来修改,这样 Init 进程就可以根据消息来源进行权限控制。

  • Init 进程通过非阻塞式 Socket 接收其他进程修改属性的消息
  • Init 进程通过 property_set 函数修改属性
  • 系统属性分为普通属性和控制属性两种,控制属性用来执行一些命令,以 ctl. 开头。
  • Init 进程调用 property_set 函数时,会先从属性存储空间查找该属性,如果有则更新内容,如果没有则新增属性。但是如果属性是以 ro. 开头,则表明是只读属性,函数会直接返回。如果属性是以 persist. 开头,则写入持久化属性。

init.rc 脚本解析

Init.rc 是由 Android 初始化语言编写的配置脚本文件,主要有 Action、Service、Command、Option、Import 五类语句。具体语法不是本文重点。

zygote 脚本解析入口

init.rc 代码中可以看到,这里会根据 ro.zygote 属性 import 不同的 init.zygotexx.rc 脚本。

zygote 脚本

system/core/rootdir 中可以看到总共有四个 zygote init 脚本文件,系统会根据 ro.zygote 属性解析执行相应 的文件,以启动 zygote 进程。

总结

Init 进程启动粗略时序图

Init 进程启动流程概括为以下几点:

  • Linux 内核 idle 进程在启动过程中,调用到 Init 进程的 main() 方法。
  • main() 方法分为三个过程:
    • 过程①,调用 first_state_init.cpp 中的 FirstStageMain() 方法,完成设备挂载、关键系统目录创建、Log初始化。
    • 过程②,调用 selinux.cpp 中的 SetupSelinux() 方法,初始化 Selinux,加载 Selinux 规则。
    • 过程③,调用 init.cpp 中的 SecondStageMain() 方法,初始化并启动属性服务、加载启动相关 .rc 脚本。在这个过程中,通过 init.zygotexx.rc 启动了安卓系统中进程之父 zygote 进程。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容