Android开机流程介绍

转载 作者:来者不拒 更新时间:2024-02-02 09:47:15 24 4

目录
  • 一、目的
  • 二、环境
  • 三、相关概念
    • 3.1 Android平台架构
    • 3.2 Android启动架构
    • 3.3 zImage
    • 3.4 RAMDISK
    • 3.5 RC文件
  • 四、详细设计
    • 4.1 Boot Rom
    • 4.2 BootLoader
    • 4.3 Kernel
      • 4.3.1 zImage解压缩阶段
      • 4.3.2 kernel的汇编启动阶段
      • 4.3.3 Kernel的C启动阶段
        • 4.3.3.1 kernel启动log
        • 4.3.3.2 init进程&kthreadd进程
        • 4.3.3.3 idle进程启动
    • 4.4 Init进程
      • 4.4.1 init程序编译
      • 4.4.2 init程序入口函数
      • 4.4.3 FirstStageMain
      • 4.4.4 SetupSelinux
      • 4.4.5 SecondStageMain
    • 4.5 Zygote进程
      • 4.5.1 Zygote进程启动脚本
      • 4.5.2 Zygote进程
      • 4.5.3 Zygote进程(java)
    • 4.6 SystemServer进程
      • 4.6.1 main函数
      • 4.6.2 SystemServer中启动服务
    • 4.7 Home进程
      • 4.7.1 Home进程的编译
      • 4.7.2 Launcher进程启动
      • 4.7.3 FallbackHome进程启动
  • 五、相关资源
    • 5.1 开机log
  • 六、小结
  • 七、参考资料

一、目的

        从2014年Android4.0开始接触机器人,开发过App应用软件,研究过Framework层框架结构、也梳理过Native层的系统流程,但是对于Hal层,以及底下的kernel方向,知之甚少。         本着一份对于底层知识的渴望,也为方便以后debug问题,故开始尝试分析下Android的开机流程.

二、环境

  1. 版本:Android 12
  2. 平台:展锐 SPRD8541E
  3. kernel:5.4

三、相关概念

3.1 Android平台架构

        如下是Google官网提供的平台架构图,很直观地攘括了Android的层级,关于各个层级的结构,有想了解可以参考:https://developer.android.google.cn/guide/platform?hl=zh-cn 。

ps: Android系统为什么要有Hal层?

3.2 Android启动架构

        如下是从某大佬的文章里摘取的图片,很详细地描述了Android系统启动过程:Boot Loader引导开机 -> Kernel -> Native -> Framework -> App 。

3.3 zImage

        zImage是一般情况下默认的压缩内核映像文件,压缩vmlinux,加上一段解压启动代码得到,只能从0X0地址运行.

3.4 RAMDISK

        RAMDISK(initrd) 是一个小的分区像,在引导时内核以只读方式挂载它。它只保护/int和一些置文件,它用于初始化和挂载其它的文件系统镜像。         ramdisk.img被包含Google android SDK中(SSDK ROOT/toolslibimages/ramdiskimg) , 也可以编生成(SSDK ROOT/outarget/productSPRODUT NAME/ramdisk.img) 。这是一个gzip压缩的CPIO文件.

3.5 RC文件

        rc文件,是用Android Init Language编写的特殊文件。用这种语法编写的文件,统一用".rc"后缀。所有rc文件,不会被编译/链接。它是配置文件,不是程序,是一种用于android init的配置文件。真正加载rc文件,并进行解析,做事情的是 Init进程.

四、详细设计

4.1 Boot Rom

        当长按电源开机的时候,引导芯片开始从固化在ROM的预设代码开始执行,然后将加载引导程序到RAM中.

4.2 BootLoader

        BootLoader又称为引导程序,它在运行操作系统之前运行的一段程序,是运行的第一个程序。主要的功能有检查RAM、初始化一些硬件外设等功能,它最终的目的是启动操作系统.

4.3 Kernel

        Kernel初始化可以分成三部分:zImage解压缩、kernel的汇编启动阶段、Kernel的C启动阶段 。

4.3.1 zImage解压缩阶段

  1. 内核加载到ram(内存)后,内核映像并不能直接运行,它是一个压缩的zImage文件。
  2. zImage映像中的并非一切被压缩,映像中包含未被压缩部分,这部分中包含解压缩程序,解压缩程序会解压缩映像中被压缩的部分。
  3. zImage使用gzip压缩的,它不仅仅是一个压缩文件,而且在这个文件的开头部分内嵌有gzip解压缩代码。
  4. 当zImage被调用时它从arch/arm/boot/compressed/head.S的start汇编例程开始执行,并最终调用arch/arm/boot/compressed/misc.c中的decompress_kernel()解压缩内核。

4.3.2 kernel的汇编启动阶段

        idle进程的启动是用汇编语言编写(感兴趣可以去研究下),对应的启动如下:

@bsp\kernel\kernel5.4\arch\arm\kernel\head-common.S
ldmia	r4, {r0, r1, r2, r3}
str	r9, [r0]			@ Save processor ID
str	r7, [r1]			@ Save machine type
str	r8, [r2]			@ Save atags pointer
cmp	r3, #0
strne	r10, [r3]			@ Save control register values
mov	lr, #0
b	start_kernel //启动start_kernel函数

        其中,语句b start_kernel,b 是跳转的意思,即跳转到start_kernel函数,对应的实现在bsp/kernel/kernel5.4/init/main.c,至此idle进程被启动.

4.3.3 Kernel的C启动阶段

        start_kernel()函数是内核初始化C语言部分的主体。这个函数完成系统底层基本机制,包括处理器、存储管理系统、进程管理系统、中断机制、定时机制等的初始化工作.

@bsp\kernel\kernel5.4\init\main.c
asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

    set_task_stack_end_magic(&init_task);
    smp_setup_processor_id();//打印了驱动加载的第一行log
    ... //初始化一系列系统底层机制
    pr_notice("%s", linux_banner);//打印内核版本信息
    ...
    pr_notice("Kernel command line: %s\n", saved_command_line);//打印从uboot传递过来的command_line字符串
    ...
    /* Do the rest non-__init'ed, we're now alive */
    arch_call_rest_init();//创建init进程、kthread进程、idle进程
    prevent_tail_call_optimization();
}

4.3.3.1 kernel启动log

(1)kernel内核启动阶段,smp_setup_processor_id()函数会打印的第一条log(文中log可以在 [目录5.1开机log] 下载查看),如下:

[    0.000000] c0 Booting Linux on physical CPU 0x0

(2)接着会打印内核的一些信息,版本,作者,编译器版本,日期等,如下:

[    0.000000]c0  Linux version 5.4.147+ (lzq@cz-PowerEdge-R730) (Android (7284624, based on r416183b) clang version 12.0.5 (https://android.googlesource.com/toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee), LLD 12.0.5 (/buildbot/src/android/llvm-toolchain/out/llvm-project/lld c935d99d7cf2016289302412d708641d52d2f7ee)) #5 SMP PREEMPT Thu Sep 28 15:17:40 CST 2023

(3)打印出从uboot传递过来的command_line字符串,在setup_arch函数中获得的(proc/cmdline) 。

[    0.000000]c0  Kernel command line: earlycon=sprd_serial_ex,0x508d0000,115200n8 console=ttySE0,921600n8 loglevel=7 init=/init root=/dev/ram0 rw vmalloc=360M printk.devkmsg=on androidboot.boot_devices=soc/soc:ap-ahb/20600000.sdio initcall_debug=1 swiotlb=1  androidboot.selinux=permissive androidboot.hardware=sl8541e_1h10_32b androidboot.dtbo_idx=0 lcd_id=ID40396 lcd_name=lcd_st7123_truly_mipi_hd lcd_base=9e000000 lcd_size=1440x720 logo_bpix=24 androidboot.ddrsize=1024M androidboot.ddrsize.range=[1024,2048)  sysdump_magic=80001000 sysdump_re_flag=1  androidboot.wdten=0  androidboot.dswdten=disable modem=shutdown ltemode=lcsfb rfboard.id=-1 rfhw.id=0 crystal=2 32k.less=1 androidboot.pmic.chipid=2721 modemboot.method=emmcboot cpcmdline=end  androidboot.verifiedbootstate=orange androidboot.flash.locked=0  androidboot.serialno=LE210210001326000028 buildvariant=userdebug androidboot.vbmeta.device=PARTUUID=1.0 androidboot.vbmeta.avb_version=1.1 androidboot.vbmeta.device_state=unlocked androidboot.

4.3.3.2 init进程&kthreadd进程

        创建了Linux系统中两个重要的进程init和kthreadd,并且将当前进程设置为idle进程:

@bsp\kernel\kernel5.4\init\main.c
void __init __weak arch_call_rest_init(void)
{
    rest_init();
}

noinline void __ref rest_init(void)
{
    ...
    pid = kernel_thread(kernel_init, NULL, CLONE_FS);//创建init进程
    ...
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建kthreadd进程
    ...
    /* Call into cpu_idle with preempt disabled */
    cpu_startup_entry(CPUHP_ONLINE);//设置当前进程为idle进程
}

        Linux下有三个特殊的进程,idle(swapper)进程(PID = 0),init进程(PID = 1)和看threadd(PID = 2):

(1)idle(swapper)进程由系统自动创建,运行在内核态。idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换,常常被称为交换进程。 (2)init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init进程,并最终转变为用户空间的init进程,是系统中所有其他用户进程的祖先进程。 (3)kthreadd进程是idle通过kernel_thread创建,并始终运行在内核空间 ,负责所有内核线程的调度和管理.

4.3.3.3 idle进程启动

        这个函数是Linux内核为非引导CPU初始化和进入空闲循环的入口函数,负责在系统没有任务需要执行时,让CPU进入空闲状态.

@bsp\kernel\kernel5.4\kernel\sched\idle.c
void cpu_startup_entry(enum cpuhp_state state)
{
    arch_cpu_idle_prepare();
    cpuhp_online_idle(state);
    while (1)
        do_idle();
}

4.4 Init进程

        由上一节可知,Init进程由0号idle进程启动,kernel_init()为其入口函数,该函数主要通过三种方式启动init程序.

static int __ref kernel_init(void *unused)
{
    ...
    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);//Step 1. 根据ramdisk_execute_command的值来启动init程序
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
                ramdisk_execute_command, ret);
    }

    if (execute_command) {
        ret = run_init_process(execute_command);//Step 2. 根据execute_command的值来启动init程序
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
                execute_command, ret);
    }
    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")) //Step 3.根据系统默认位置来启动init程序
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
            "See Linux Documentation/admin-guide/init.rst for guidance.");//Step 4.异常重启
}

(1)ramdisk_execute_command是一个全局的char指针变量,值为“/init”,也就是根目录下的init程序。该值通过uboot传递过来,具体可参考上一节的command_line。(如未配置该值,即会将该值默认设置为"/init",该项目是从该处启动init进程) (2)execute_command也是一个全局的char指针变量,值通过uboot传递; (3)在前面两种情况都不满足的情况下,从系统默认位置加载init程序; (4)如上三种情况都不执行的话,进入panic函数,则设备会异常重启; (5)可以通过如下log确认系统启动的是哪个init程序,该log由run_init_process函数打印 。

[    1.000614]c1  Run /init as init process

4.4.1 init程序编译

        我们知道init程序是引用的根目录下的init,即/init,但是其对应的软链接指向:system/bin/init,相关信息如下

编译makefile文件如下:

@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"],
    ...
    srcs: ["main.cpp"],
    ...
}

4.4.2 init程序入口函数

        init程序的入口函数,根据不同的入参,响应init不同阶段、处理不同业务逻辑.

@system\core\init\main.cpp
int main(int argc, char** argv) {
    ...
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);//初始化uevent事件
    }

    if (argc > 1) {
        ...
        if (!strcmp(argv[1], "selinux_setup")) {
            android::mboot::mdb("SELinux Setup ...");
            return SetupSelinux(argv);//selinux权限
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);//第二阶段
        }
    }

    return FirstStageMain(argc, argv);//第一阶段
}

        由于在kernel阶段启动init程序时,未配置参数,故首先会引用FirstStageMain函数。另外,根据log可以看到,其引用顺序如下:

 [    1.004958]c1  init: init first stage started!
 [    2.547025]c0  init: Opening SELinux policy
 [    3.226672]c0  init: init second stage started!
 [    3.715657]c1  ueventd: ueventd started!
  1. FirstStageMain: 主要创建和挂载基本的文件系统,挂载特定分区,启用log等;
  2. SetupSelinux: 挂载并启用selinux权限系统;
  3. SecondStageMain: 主要解析ini.rc文件,创建zygote孵化器,fork 进程等;

4.4.3 FirstStageMain

        第一阶段,该阶段所挂载的文件系统都属于ramdisk,运行在虚拟内存上.

@system\core\init\first_stage_mount.cpp
int FirstStageMain(int argc, char** argv) {
    ...
    //Step 1. 创建&挂载最基本的文件系统
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));//将/dev设置为tmpfs并挂载,设置0755权限,tmpfs是在内存上建立的文件系统(Filesystem)
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mkdir("/dev/dm-user", 0755));
    ...
    //Step 2. 初始化log系统并打印
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
    ...
    LOG(INFO) << "init first stage started!";
    ...
    //Step 3. 加载内核驱动模块
    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
                           module_count)) {
        ...
    }
    ...
    //Step 4.挂载分区(system、vendor、product等分区)
    if (!DoFirstStageMount(!created_devices)) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
    ...
    //Step 5.初始化Android的安全框架Android Verified Boot
    SetInitAvbVersionInRecovery();
    ...
    //Step 6.执行下一个阶段
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    ...
    execv(path, const_cast<char**>(args));//exec系列函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID,即重新回到main.cpp
    ...
}

Step 1. 创建&挂载最基本的文件系统,创建了如下五类文件系统

文件系统 挂载路径 描述
tmpfs /dev 一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。
devpts /dev/pts 为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
proc /proc 一个虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
sysfs /sys 与proc文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
selinuxfs /sys/fs/selinux 用于支持SELinux的文件系统,SELinux提供了一套规则来编写安全策略文件,这套规则被称之为 SELinux Policy 语言。

相关挂载情况如下:

Step 2. 结合dev/kmsg节点,初始化log系统,并在第一阶段开始时,打印第一条log 。

 [    1.004958]c1  init: init first stage started!

Step 3. 加载内核驱动模块,第一阶段加载的内核模块如下:

[    1.012653]c1  init: Loaded kernel module /lib/modules/sprd_wdt.ko
[    1.020197]c0  init: Loaded kernel module /lib/modules/sc2721-regulator.ko
[    1.022427]c0  init: Loaded kernel module /lib/modules/nvmem-sc27xx-efuse.ko
[    1.026126]c0  init: Loaded kernel module /lib/modules/spool.ko
[    1.033668]c0  init: Loaded kernel module /lib/modules/sipx.ko
[    1.047553]c0  init: Loaded kernel module /lib/modules/seth.ko
[    1.048526]c0  init: Loaded kernel module /lib/modules/usb_f_vser.ko
...

Step 4. 挂载分区。创建/first_stage_ramdisk并挂载,然后将根目录切换到/first_stage_ramdisk,并挂载system、vendor 、product等系统分区,挂载信息如上。 Step 5. 初始化Android的安全框架Android Verified Boot,用于防止系统文件本身被篡改、防止系统回滚,以免回滚系统利用以前的漏洞。 Step 6. 启动下一个阶段SetupSelinux.

4.4.4 SetupSelinux

        该阶段主要是初始化Selinux权限相关业务,同时在业务流程最后一步时,重新执行system/bin/init程序,再次启动下一个阶段SecondStageMain.

@system\core\init\selinux.cpp
int SetupSelinux(char** argv) {
    ...
    LOG(INFO) << "Opening SELinux policy";
    ...
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));
    ...
    return 1;
}

该阶段log打印如下:

 [    2.547025]c0  init: Opening SELinux policy

4.4.5 SecondStageMain

        第二阶段,涉及到文件系统挂载、属性服务等系统相关业务,其中最主要的一点去解析rc文件(创建目录,修改权限,挂载分区,启动服务进程等),以期让开机流程进入下一阶段.

@system\core\init\init.cpp
int SecondStageMain(int argc, char** argv) {
    //Step 1.初始化log
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
    ...
    //Step 2.属性服务初始化,读取默认属性配置
    PropertyInit();
    ...
    //Step 3.挂载其他文件系统,如/apex
    MountExtraFilesystems();
    ...
    //Step 4.启动属性服务
    StartPropertyService(&property_fd);
    ...
    //Step 5.加载开机rc文件
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
    LoadBootScripts(am, sm);
    ...
    //Step 6.设置进程优先级,主进程不能退出
    setpriority(PRIO_PROCESS, 0, 0);
    while (true) {
        ...
    }
    return 0;
}

Step 1. 初始化log,该阶段log打印如下:

 [    3.226672]c0  init: init second stage started!

Step 2. 属性服务初始化,读取默认属性配置。获取system/build.prop、vendor/build.prop、/odm/build.prop、/product/build.prop等其他build.prop属性,并加载到properties map结构中,然后通过MMAP映射到全局内存中,供所有进程调用; Step 3. 挂载其他文件系统,如/apex。挂载第二阶段相关的文件系统; Step 4. 启动属性服务。创建socket,处理客户端发来的请求,决定是更新属性值还是新增属性值; Step 5. 加载开机rc文件,rc文件的加载顺序如下; 。

[    3.481952]c0  init: Parsing file /system/etc/init/hw/init.rc...
[    3.489693]c0  init: Parsing file /init.environ.rc...
[    3.490110]c0  init: Parsing file /system/etc/init/hw/init.usb.rc...
[    3.491867]c0  init: Parsing file /init.sl8541e_1h10_32b.rc...
[    3.493283]c0  init: Parsing file /vendor/etc/init/hw/init.sl8541e_1h10_32b.rc...
[    3.494372]c0  init: Parsing file /vendor/etc/init/hw/init.sl8541e_1h10_32b.usb.rc...
[    3.509664]c0  init: Parsing file /vendor/etc/init/hw/init.ram.rc...
...

解析init.rc会把一条条命令映射到内存中,然后依次启动,启动顺序如下:

on on early-init:在初始化早期阶段触发
on init:在初始化阶段触发
on late-init:在初始化晚期阶段触发
on boot/charger:当系统启动/充电时触发
on property:当属性值满足条件时触发

Step 6. 设置进程优先级,主进程不能销毁和退出.

4.5 Zygote进程

4.5.1 Zygote进程启动脚本

(1)rc文件编译         init.rc文件是一个配置文件,最终会被打包到该目录:/system/etc/init/hw/ 。

@system\core\rootdir\Android.bp
prebuilt_etc {
    name: "init.rc",
    src: "init.rc",
    sub_dir: "init/hw",
    required: [
        "fsverity_init",
        "platform-bootclasspath",
    ],
}

(2)init.rc         由上一节我们知道,当init程序启动到第二阶段时候,会去加载rc文件,且最开始加载的rc文件是:system/etc/init/hw/init.rc 。

@system\core\rootdir\init.rc
...
import /system/etc/init/hw/init.${ro.zygote}.rc//导入zygote的rc文件,ro.zygote属性可根据系统读取
...
# Mount filesystems and start core system services.
on late-init
    ...
    # Now we can start zygote for devices with file based encryption
    trigger zygote-start //在开机初始化晚期阶段,触发zygote启动
    ...

# It is recommended to put unnecessary data/ initialization from post-fs-data
# to start-zygote in device's init.rc to unblock zygote start.
on zygote-start && property:ro.crypto.state=unencrypted
    wait_for_prop odsign.verification.done 1
    # A/B update verifier that marks a successful boot.
    exec_start update_verifier_nonencrypted
    start statsd
    start netd
    start zygote
    start zygote_secondary

(3)init.zygote32.rc         启动 app_process,并改名为zygote,使用:-Xzygote、/system/bin、–zygote和–start-system-server等参数,同时重启了audioserver、cameraserver、media等服务.

@system\core\rootdir\init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks
    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

(4)zygote启动log         在解析完rc文件后,会启动对应的服务,zygote启动log如下:

[    7.916749]c3  init: starting service 'zygote'...

4.5.2 Zygote进程

(1)zygote编译文件 。

@frameworks\base\cmds\app_process\Android.bp
cc_binary {
    name: "app_process",
    srcs: ["app_main.cpp"], 
    ...
}

(2)app_process启动         zygote是一个名为zygote的app_process进程,解析rc文件参数,启动ZygoteInit.

int main(int argc, char* const argv[])
{
    ...
    while (i < argc) {//读取rc文件传进来的参数
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } 
        ...
    }
    ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//启动ZygoteInit
    } 
    ...
}

(3)AndroidRuntime         通过反射去启动ZygoteInit的main函数,相关引用如下:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
            className != NULL ? className : "(unknown)", getuid());
    ...
    //通过反射启动函数
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    ...
}

当前流程会打印如下log:

01-01 08:00:08.538   387   387 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<<

4.5.3 Zygote进程(java)

(1)ZygoteInit 。

@frameworks\base\core\java\com\android\internal\os\ZygoteInit.java
public static void main(String[] argv) {
    ...
    try {
        //Step 1. 预加载资源文件
        if (!enableLazyPreload) {
            bootTimingsTraceLog.traceBegin("ZygotePreload");
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                    SystemClock.uptimeMillis());
            preload(bootTimingsTraceLog);
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                    SystemClock.uptimeMillis());
            bootTimingsTraceLog.traceEnd(); // ZygotePreload
        }
        ...
        //Step 2.注册Zygote的socket监听接口
        zygoteServer = new ZygoteServer(isPrimaryZygote);
        //Step 3.创建system_server进程
        if (startSystemServer) {
            Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
            if (r != null) {
                r.run();
                return;
            }
        }
        //Step 4.主线程loop消息循环
        caller = zygoteServer.runSelectLoop(abiList);
    } 
    ...
}

Step 1. 提前加载类,加载系统资源(如一些公共的库、SDK等),这样当程序被fork处理后,应用的进程内已经包含了这些系统资源,大大节省了应用的启动时间。 Step 2. 注册Zygote的socket监听接口,用来接收启动应用程序的消息; Step 3. frok出SystemServer进程; Step 4. 主线程loop消息循环 。

(2)forkSystemServer         fork出SystemServer进程,并且去调用SystemServer进程的main函数 。

@frameworks\base\core\java\com\android\internal\os\ZygoteInit.java
private static Runnable forkSystemServer(String abiList, String socketName,
        ZygoteServer zygoteServer) {
    ...
    /* Hardcoded command line to start the system server */
    String[] args = {
            "--setuid=1000",
            "--setgid=1000",
            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
                    + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011",
            "--capabilities=" + capabilities + "," + capabilities,
            "--nice-name=system_server",
            "--runtime-args",
            "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
            "com.android.server.SystemServer",
    };//启动SystemServer相关参数
    ...
    try {
        ...
        /* Request to fork the system server process */
        pid = Zygote.forkSystemServer(
                parsedArgs.mUid, parsedArgs.mGid,
                parsedArgs.mGids,
                parsedArgs.mRuntimeFlags,
                null,
                parsedArgs.mPermittedCapabilities,
                parsedArgs.mEffectiveCapabilities);//fork出SystemServer进程
    } catch (IllegalArgumentException ex) {
        throw new RuntimeException(ex);
    }

    /* For child process */
    if (pid == 0) {
        ...
        return handleSystemServerProcess(parsedArgs);//通过反射调用SystemServer进程的main函数
    }
    return null;
}

(3)handleSystemServerProcess         此函数为调用SystemServer进程,最终会通过反射调用SystemServer进程的main函数,其方法调用栈如下: handleSystemServerProcess()->ZygoteInit.zygoteInit()->RuntimeInit.applicationInit()->RuntimeInit.findStaticMain() 。

@frameworks\base\core\java\com\android\internal\os\RuntimeInit.java
protected static Runnable findStaticMain(String className, String[] argv,
        ClassLoader classLoader) {
    Class<?> cl;

    try {
        cl = Class.forName(className, true, classLoader);//反射调用类:com.android.server.SystemServer
    }
    ...
    Method m;
    try {
        m = cl.getMethod("main", new Class[] { String[].class });//反射调用方法:main
    } 
    ...
    return new MethodAndArgsCaller(m, argv);
}

4.6 SystemServer进程

        Android系统在启动的时候,在启动两个重要的进程,一个是Zygote进程,另一个是由zygote进程fork出来的system_server进程。SystemSever负责启动系统的各项服务,Android系统中Java世界的核心Service都在这里启动.

4.6.1 main函数

由上一章节我们知道,SystemServer的入口函数是main方法,如下:

@frameworks\base\services\java\com\android\server\SystemServer.java
public static void main(String[] args) {
    new SystemServer().run();
}

private void run() {
    ...    
    Slog.i(TAG, "Entered the Android system server!");
    ...
    // Start services.
    try {
        t.traceBegin("StartServices");
        startBootstrapServices(t);//引导服务
        startCoreServices(t);//核心服务
        startOtherServices(t);//其他服务
    } 
    ...
    // Loop forever.
    Looper.loop();//主线程循环队列
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

SystemServer进程成功启动,会打印如下log:

01-01 08:00:21.430  1001  1001 I SystemServer: Entered the Android system server!

4.6.2 SystemServer中启动服务

        在一系列的java服务中,可以分为三类:系统boot级别服务、核心服务、其他服务,其对应如下:

类型 服务 备注
startBootstrapServices ActivityManagerService 简称AMS,管理Android四大组件的生命周期
PowerManagerService 电源管理服务
PackageManagerService 简称PMS,用于APK的安装、卸载、权限验证等
UserManagerService 用户创建、删除、查询等
... ...
startCoreServices BatteryService 对设备电池状态进行监控
UsageStatsService 收集App的使用频率等信息
... ...
startOtherServices CameraService 管理设备相机功能
BluetoothService 管理蓝牙服务
WindowManagerService 简称WMS,管理窗口服务
FingerPrintService 管理指纹服务
... ...

4.7 Home进程

        一般情况下,Android原生的软体会包含两个home进程,一个是Settings进程的Fallbackhome,一个是Launcher进程.

4.7.1 Home进程的编译

(1)FallbackHome编译         Fallbackhome位于Settings进程内,随系统启动时被拉起.

@packages\apps\Settings\Android.bp
android_app {
    name: "Settings",
    defaults: ["platform_app_defaults"],
    platform_apis: true,
    certificate: "platform",
    system_ext_specific: true,
    privileged: true,
    ...
}

(2)Launcher进程编译         Launcher进程主要用于显示App的界面信息。随着Android版本的迭代、以及层出不穷的产品,目前Android的Launcher版本较多,如Home、Launcher2、Launcher3、Launcher3QuickStep,要根据自身项目的配置,找到对应的Launcher应用.

@packages\apps\Launcher3\Android.mk
include $(CLEAR_VARS)
...
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
...

4.7.2 Launcher进程启动

        FallbackHome是系统由未解密到解密过程的一个过度界面,只要用户把系统解锁过一次后,FallbackHome收到解锁广播就会退出,而WMS检测到当前Acitivity栈是空的,进而启动真正的Launcher。由于FallbackHome没有界面,所以可能会出现一个问题,home进程切换时会出现空白界面,接下来才是显示Launcher的一个图标界面.

@packages\apps\Settings\src\com\android\settings\FallbackHome.java
protected void onCreate(Bundle savedInstanceState) {
    ...
    registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));//注册ACTION_USER_UNLOCKED广播
    maybeFinish();
}

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        maybeFinish();//接收ACTION_USER_UNLOCKED广播
    }
};

private void maybeFinish() {
    if (getSystemService(UserManager.class).isUserUnlocked()) {
        final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_HOME);
        final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);//查询home包名信息,此处一般是返回Launcher的信息
        if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
            Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
            mHandler.sendEmptyMessageDelayed(0, 500);//间隔500ms轮询
        } else {
            Log.d(TAG, "User unlocked and real home found; let's go!");
            getSystemService(PowerManager.class).userActivity(
                    SystemClock.uptimeMillis(), false);
            finish();//结束当前Activity,启动Launcher应用
        }
    }
}

4.7.3 FallbackHome进程启动

(1) 启动home进程         刚开机时,SystemSever进程会启动WMS服务,如果WMS未检测到Activity栈有任务时,会启动一个默认的home进程,此进程即FallbackHome.

@frameworks\base\services\core\java\com\android\server\wm\RootWindowContainer.java
void startHomeOnEmptyDisplays(String reason) {
    forAllTaskDisplayAreas(taskDisplayArea -> {
        if (taskDisplayArea.topRunningActivity() == null) {
            startHomeOnTaskDisplayArea(mCurrentUser, reason, taskDisplayArea,
                    false /* allowInstrumenting */, false /* fromHomeKey */);
        }
    });
}

(2) ACTION_USER_UNLOCKED广播发送         系统解锁时会发送该广播,相关代码如下:

@frameworks\base\services\core\java\com\android\server\am\UserController.java
void finishUserUnlocked(final UserState uss) {
    ...
    if (!mInjector.getUserManager().isPreCreated(userId)) {
        // Dispatch unlocked to external apps
        final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
        unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        unlockedIntent.addFlags(
                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
        mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
                null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                Binder.getCallingUid(), Binder.getCallingPid(), userId);
    }
    ...
}

五、相关资源

5.1 开机log

串口+logcat:https://download.csdn.net/download/u013320490/88800008 。

六、小结

        本文章涉猎Android多个层级,旨在梳理整体流程,对Android设备的启动有一个感性的认识,能够达到一定逻辑自洽。         Android的每个模块、每个进程、每行代码都有其深度,没有细细揣测与推敲,是有点"亵渎"了,他日必对其中感兴趣模块加以研究,respect! 。

七、参考资料

  1. Android系统架构图
    https://developer.android.google.cn/guide/platform?hl=zh-cn
  2. Android Hal层的由来
    https://blog.csdn.net/Xiaoma_Pedro/article/details/130253665
  3. 开机流程
    https://www.ancii.com/acglj4qvw/
    https://gityuan.com/android/#二android架构
  4. Kernel内核
    https://blog.51cto.com/u_16213627/8681479
    https://blog.csdn.net/CAUC_learner/article/details/120435753
    https://blog.csdn.net/daringtodoit/article/details/24675867
    https://blog.csdn.net/marshal_zsx/article/details/80225854
    https://blog.51cto.com/u_11947739/6360569
    https://blog.csdn.net/qq_38499859/article/details/88187602
  5. init进程分析
    https://www.yii666.com/blog/693472.html
    https://blog.csdn.net/hai_qing_xu_kong/article/details/85707697
    https://www.yii666.com/blog/423299.html
  6. RC文件
    https://blog.csdn.net/zxc024000/article/details/111473100

最后此篇关于Android开机流程介绍的文章就讲到这里了,如果你想了解更多关于Android开机流程介绍的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

24 4 0
Copyright 2021 - 2022 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com