Android开机流程解析
⽬录
第⼀章概述
开机作为使⽤⼿机的第⼀步操作,在长按电源键之后到我们可视化可操作的界⾯中间包含了很多任务,诸如⽂件系统挂载、native rvice 启动、zygote启动、launcher启动等,⽽这些任务是怎么执⾏的,⼜是在哪个阶段执⾏的?
我们先来看下当我们按下电源键之后,我们的⼿机执⾏这些任务的流程图:
从上图中我们可以看到,当按下开机键的时候,⼿机开始从固化在ROM的预设代码开始执⾏,加载Bootloader到RAM中运⾏,接着Bootloader把kernel加载到内存,最终把kernel跑起来。在kernel启动的最后执⾏了“init”这个Android祖先进程,⼜init进程去进⾏⽂件系统挂载、启动native rvice、启动zygote等,之后把Home intent应⽤(Launcher等)启动起来。这个启动流程线可简略归纳为:BootRom -> bootloader -> kernel -> init -> mount fs-> native rvice -> launcher。
Kernel启动的这⼀部分为linux操作系统通⽤流程,相关介绍可参考Linux Boot,本⽂中对此不进⾏展开。后续章节中将会就init启动开始直到launcher启动结束这个过程进⾏讲述开机启动流程。
第⼆章 Init启动
所有的Linux系统都有⼀个较为特殊的进程, 它是Kernel启动结束后, 于urspace启动的第⼀个进程, 它负责启动系统, 是系统中其他进程的祖先进程,在不同系统上,它有不同的名称,但在传统意义上, 这个进程被统称为init进程。
Init作为第⼀个ur space的进程,它是所有Android系统native rvice的祖先,从最根本上讲,它是为了引导/启动⽤户空间的各项rvice⽽存在,⽽为了确保各个rvice能正常运⾏,⼜会创建⽂件系统,设置权限,初始化属性等⼯作。Init的功能繁杂,后续章节会挑选开机相关的重点项来讲述,⼤致可以分为下⾯⼏项:
· 创建⽂件系统⽬录并挂载相关的⽂件系统
· 初始化/设置/启动属性相关的资源
· 解析
· action/rvice管理
2.1 init进程的创建
内核在启动初期,会调⽤跟平台架构相关的汇编代码,在架构相关的汇编代码运⾏完之后,程序跳⼊了架构⽆关的内核C语⾔代码:
init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进⼊初始化阶段。接着后续会调⽤rest_init和kernel_thread,init进程的创建也是从这⾥开始的 ⽽通过调⽤kernel_thread,1号进程被创建出来,但此时,它运⾏的还不是init,只有经过如下步骤,init才会正式启动。中间调⽤的函数也⽐较多,还涉及到空间的切换,这⾥⽤⼀个图来表⽰:
2.1章节讲了init进程的创建过程,接下来看下init进程在做些什么。Init进程的⼊⼝是main函数,查看init的功能实现,就从这个函数开始,这⾥⾸先来关注下⽂件系统挂载。
2.2 ⽂件系统挂载
Android有很多分区,如system/urdata/cache/vendor/odm等分区,它们是何时挂载的?如何挂载的?接下去会进⾏相应分析。
2.2.1 system/vendor/product分区
在描述这⼀部分之前得先提⼀下动态分区。动态分区是Android 10上新功能,是Android系统的⽤户空间分区系
统,system,vendor,product等只读分区被打包到super.img;在super.img的元数据(metadata)中记录着每个动态分区的名称和在Super分区中的存储范围。
在Init First Stage执⾏期间会解析和校验Super 分区的元数据,并创建虚拟的块设备对应各个动态分区,从⽽对
system/vendor/product等分区进⾏挂载。⽽这⼀切的发起者是DoFirstStageMount()。
1. bool FirstStageMount::DoFirstStageMount() {
2. if (!IsDmLinearEnabled() && fstab_.empty()) { //判断fstab中是否有logical标志,若⽆,则返回
3. // Nothing to mount.
4. LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
5. return true;
6. }
7.
8. if (!InitDevices()) return fal; // add super name、system/vendor/product/etc. to
required_devices_partition_names_ and its info to lp_metadata_partition_
9.
10. if (!CreateLogicalPartitions()) return fal; //创建super内动态分区对应的逻辑分区
11.
12. if (!MountPartitions()) return fal; //挂载system并切换为root、vendor、product、overlayfs
13.
14. return true;
15. }
DFSM对应的执⾏流向如下:
⽂件系统挂载是需要挂载信息的,⽽这个信息通常保存在fstab⽂件中:fstab.$(TARGET_BOARD),对应在device board⾥配置的是fstab.ramdisk⽂件。
1. #Dynamic partitions fstab file
2. #<dev> <mnt_point> <type> <mnt_flags options> <fs_mgr_flags>
3.
4. system /system ext4 ro,barrier=1 wait,avb=vbmeta_system,logical,first_stage_mount,avb_keys=/avb/q-
gsi.avbpubkey:/avb/r-gsi.avbpubkey:/avb/s-gsi.avbpubkey
5. vendor /vendor ext4 ro,barrier=1 wait,avb=vbmeta_vendor,logical,first_stage_mount
6. product /product ext4 ro,barrier=1 wait,avb=vbmeta,logical,first_stage_mount
7. /dev/block/platform/soc/soc:ap-ahb/20600000.sdio/by-name/metadata /metadata ext4
nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount
需注意的是,必须包含“avb=xxx, logical,first_stage_mount”这样配置才可以在first_stage 进⾏mount。
2.2.2 metadata分区
参考system/endor/product mount流程。
2.2.3 Other分区
⾄此system/vendo/odm分区已经成功挂载,⽽其它分区的挂载则通过do_mount_all来实现。看下这个流程:
init进程会根据的规则启动进程或者服务。通过"import /"语句导⼊平台的规则, 例如XXX/中就有如下规则:
1. on fs
2. ubiattach 0 ubipac
3. # exec /sbin/resize2fs -ef /fstab.${ro.hardware}
4. mount_all /vendor/etc/fstab.${ro.hardware}
5. mount pstore pstore /sys/fs/pstore
mount_all是⼀条命令,fstab.${ro.hardware}是传⼊的参数,在XXXboard上就是fstab.XXX。接着通过ActionManager来解
析“mount_all“指令,找到指令所对应的解析函数。这个指令解析函数的对应关系,定义在system/core/init/builtins.cpp:
1. static const Map builtin_functions = {
2. .....
3. {
4. "mount_all", {1, kMax, do_mount_all}},
5. .....
5. .....
6. }
从上⾯可以看出,mount_all命令对应的是do_mount_all函数,/vendor/etc /fstab.XXX是do_mount_all函数的传⼊参数。
do_mount_all的解析流程如下:
2.3 Start Property Service
Android property系统其实可以理解为键值的对应关系,即属性名字和属性值。⼤部分property是记录在某些⽂件中的, init进程启动的时候,会加载这些⽂件,完成property系统初始化。
2.3.1 property rvice的启动过程
来看property rvice的启动过程,代码如下:
1. void StartPropertyService(Epoll* epoll) {
2. linux_callback cb;
3. cb.func_audit = SelinuxAuditCallback;
4. linux_t_callback(SELINUX_CB_AUDIT, cb);
5.
6. property_t("ro.property_rvice.version", "2");
7.
8. property_t_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
9. fal, 0666, 0, 0, nullptr);
10. if (property_t_fd == -1) {
11. PLOG(FATAL) << "start_property_rvice socket creation failed";
12. }
13.
14. listen(property_t_fd, 8);
15.
16. if (auto result = epoll->RegisterHandler(property_t_fd, handle_property_t_fd); !result) {
17. PLOG(FATAL) << ();
18. }
19. }
创建property_rvice socket⽤于监听进程修改property请求,通过handle_property_t_fd来处理请求,t property msg分为两类处理,msg name以“ctl.”为起始的msg 通过handle_control_message处理,主要是启动、停⽌、重启服务。修改其它prop时会调⽤property_get,然后通过bionic的__system_property_t函数来实现,⽽这个函数会通过socket与init的property rvice取得联系。整个property访问的过程可以⽤下图来表述:
2.3.2 property 的加载顺序
property⽂件的加载是在property_load_boot_defaults()中实现的,⽽在整个启动流程中,property的加载是在Rc⽂件加载之前的,其代码如下:
代码如下:
1. void property_load_boot_defaults(bool load_debug_prop) {
2. ......
3. std::map<std::string, std::string> properties;
4. if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
5. // Try recovery path
6. if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
7. // Try legacy path
8. load_properties_from_file("/default.prop", nullptr, &properties);
9. }
10. }
11. load_properties_from_file("/system/build.prop", nullptr, &properties);
12. load_properties_from_file("/vendor/default.prop", nullptr, &properties);
13. load_properties_from_file("/vendor/build.prop", nullptr, &properties);
14. if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) {
15. load_properties_from_file("/odm/etc/build.prop", nullptr, &properties);
16. } el {
17. load_properties_from_file("/odm/default.prop", nullptr, &properties);
18. load_properties_from_file("/odm/build.prop", nullptr, &properties);
19. }
20. load_properties_from_file("/product/build.prop", nullptr, &properties);
21. load_properties_from_file("/product_rvices/build.prop", nullptr, &properties);
22. load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
23.
24. ......
25. }
从代码的实现中可以看到,在正常启动流程中,property⽂件的加载顺序为:
2.4 Rc⽂件解析
从init main函数的代码可以看出来,根路径中只解析了,其它根路径的init.*.rc就是通过import导⼊进来的。
2.4.1 Rc⽂件的加载顺序
Rc⽂件的加载是在LoadBootScripts ()中实现的,⽽在整个启动流程中,
Rc⽂件按加载是在property⽂件加载之后的,其代码如下:
1. static void LoadBootScripts(ActionManager& action_manager, ServiceList& rvice_list) {
2. Parr parr = CreateParr(action_manager, rvice_list);
3.
4. std::string bootscript = GetProperty("ro.boot.init_rc", "");
5. if (pty()) {
6. std::string bootmode = GetProperty("ro.bootmode", "");
7. if (bootmode == "charger") {
8. parr.ParConfig("/vendor/etc/");
9. } el {
10. parr.ParConfig("/");
11. if (!parr.ParConfig("/system/etc/init")) {
12. late_place_back("/system/etc/init");
13. }
14. if (!parr.ParConfig("/product/etc/init")) {
15. late_place_back("/product/etc/init");
16. }
17. if (!parr.ParConfig("/product_rvices/etc/init")) {
18. late_place_back("/product_rvices/etc/init");
19. }
20. if (!parr.ParConfig("/odm/etc/init")) {