安卓启动流程梳理之 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 进程。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容