Android开机流程解析
⽬录
第⼀章概述
开机作为使⽤⼿机的第⼀步操作,在长按电源键之后到我们可视化可操作的界⾯中间包含了很多任务,诸如⽂件系统挂载、native service 启动、zygote启动、launcher启动等,⽽这些任务是怎么执⾏的,⼜是在哪个阶段执⾏的?
制作android软件流程我们先来看下当我们按下电源键之后,我们的⼿机执⾏这些任务的流程图:
从上图中我们可以看到,当按下开机键的时候,⼿机开始从固化在ROM的预设代码开始执⾏,加载Bootloader到RAM中运⾏,接着Bootloader把kernel加载到内存,最终把kernel跑起来。在kernel启动的最后执⾏了“init”这个Android祖先进程,⼜init进程去进⾏⽂件系统挂载、启动native service、启动zygote等,之后把Home intent应⽤(Launcher等)启动起来。这个启动流程线可简略归纳为:BootRom -> bootloader -> kernel -> init -> mount fs-> native service -> launcher。
Kernel启动的这⼀部分为linux操作系统通⽤流程,相关介绍可参考Linux Boot,本⽂中对此不进⾏展开。后续章节中将会就init启动开始直到launcher启动结束这个过程进⾏讲述开机启动流程。
第⼆章 Init启动
所有的Linux系统都有⼀个较为特殊的进程, 它是Kernel启动结束后, 于userspace启动的第⼀个进程, 它负责启动系统, 是系统中其他进程的祖先进程,在不同系统上,它有不同的名称,但在传统意义上, 这个进程被统称为init进程。
Init作为第⼀个user space的进程,它是所有Android系统native service的祖先,从最根本上讲,它是为了引导/启动⽤户空间的各项service⽽存在,⽽为了确保各个service能正常运⾏,⼜会创建⽂件系统,设置权限,初始化属性等⼯作。Init的功能繁杂,后续章节会挑选开机相关的重点项来讲述,⼤致可以分为下⾯⼏项:
·    创建⽂件系统⽬录并挂载相关的⽂件系统
·    初始化/设置/启动属性相关的资源
·    解析
·    action/service管理
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/userdata/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 false;  // add super name、system/vendor/product/etc. to
required_devices_partition_names_ and its info to lp_metadata_partition_
9.
10.        if (!CreateLogicalPartitions()) return false;  //创建super内动态分区对应的逻辑分区
11.
12.        if (!MountPartitions()) return false;  //挂载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 service的启动过程
来看property service的启动过程,代码如下:
1.    void StartPropertyService(Epoll* epoll) {
2.        selinux_callback cb;
3.        cb.func_audit = SelinuxAuditCallback;
4.        selinux_set_callback(SELINUX_CB_AUDIT, cb);
5.
6.        property_set("ro.property_service.version", "2");
7.
8.        property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
9.                                      false, 0666, 0, 0, nullptr);
10.        if (property_set_fd == -1) {
11.            PLOG(FATAL) << "start_property_service socket creation failed";
12.        }
13.
14.        listen(property_set_fd, 8);
15.
16.        if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
17.            PLOG(FATAL) << ();
18.        }
19.    }
创建property_service socket⽤于监听进程修改property请求,通过handle_property_set_fd来处理请求,set property msg分为两类处理,msg name以“ctl.”为起始的msg 通过handle_control_message处理,主要是启动、停⽌、重启服务。修改其它prop时会调⽤property_get,然后通过bionic的__system_property_set函数来实现,⽽这个函数会通过socket与init的property service取得联系。整个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.        } else {
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_services/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& service_list) {
2.        Parser parser = CreateParser(action_manager, service_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.            parser.ParseConfig("/vendor/etc/");
9.        } else {
10.                parser.ParseConfig("/");
11.                if (!parser.ParseConfig("/system/etc/init")) {
12.                        late_place_back("/system/etc/init");
13.                }
14.                if (!parser.ParseConfig("/product/etc/init")) {
15.                        late_place_back("/product/etc/init");
16.                }
17.                if (!parser.ParseConfig("/product_services/etc/init")) {
18.                        late_place_back("/product_services/etc/init");
19.                }
20.                if (!parser.ParseConfig("/odm/etc/init")) {

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。