首页 > 其他分享 >Android 开机流程介绍

Android 开机流程介绍

时间:2024-02-01 14:11:39浏览次数:30  
标签:... kernel 流程 启动 system 进程 init 开机 Android

目录

一、目的

        从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

标签:...,kernel,流程,启动,system,进程,init,开机,Android
From: https://www.cnblogs.com/zhiqinlin/p/18001113

相关文章

  • Kurator V0.6.0:实现应用全流程生命周期管理
    本文分享自华为云社区《KuratorV0.6.0:实现应用全流程生命周期管理》,作者:云容器大未来。Kurator是华为云开源的面向分布式云原生环境的一站式解决方案。它利用Karmada作为多集群编排基础,内置集成了Istio、Prometheus、Thanos、Volcano、KubeEdge、Argo等主流云原生技术。基于......
  • Spring Boot的自动装配原理及流程
    自动装配的流程(原理)                       参考链接:https://blog.csdn.net/weixin_45764765/article/details/1102505311、main方法中SpringApplication.run(HelloBoot.class,args)的执行流程中有refreshContext(context)。2、而这个refreshContext(cont......
  • App requires Multidex support Multidex support is required for your android app
    flutterandroid报错64k!]ApprequiresMultidexsupportMultidexsupportisrequiredforyourandroidapptobuildsincethenumberofmethodshasexceeded64k.Seehttps://docs.flutter.dev/deployment/android#enabling-multidex-supportformoreinformation......
  • 企业招投标流程
    售前工程师平时的工作中经常会做协助投标员完成项目招投标的相关事宜。我们的工作主要集中在技术部分,即:撰写技术投标书。本文主要讲一下关于投标的大概过程是怎样的,欢迎阅读。 招投标的适用场景一个企业,大到上百亿、十几亿的项目采购,小到几百块的设备采购,都可以用招投标的......
  • 全流程机器视觉工程开发(三)任务前瞻 - 从opencv的安装编译说起,到图像增强和分割
    前言最近开始做这个裂缝识别的任务了,大大小小的问题我已经摸得差不多了,然后关于识别任务和分割任务我现在也弄的差不多了。现在开始做正式的业务,也就是我们说的裂缝识别的任务。作为前言,先来说说场景:现在相机返回过来的照片:都是jpeg格式的照片,当然也可能是别的格式,目前主流是......
  • 全流程机器视觉工程开发(四)PaddleDetection C++工程化应用部署到本地DLL以供软件调用
    前言我们之前跑了一个yolo的模型,然后我们通过PaddleDetection的库对这个模型进行了一定程度的调用,但是那个调用还是基于命令的调用,这样的库首先第一个不能部署到客户的电脑上,第二个用起来也非常不方便,那么我们可不可以直接将Paddle的库直接做成一个DLL部署到我们的软件上呢?答案是......
  • Android安卓开发:RecyclerView的快速使用
    Android安卓开发:RecyclerView的快速使用前言:1.我借鉴了网络上的代码2.我并没有深入了解,难免出错3.默认已经导入了依赖包,跳过效果(并非此次给出代码的效果):代码:1.准备layout文件(命名格式为adapter_*.xml)2.准备*Adapter.java文件3.默认存在Activity.java或Fragment.java......
  • 资深Android逆袭、华为鸿蒙为安卓程序员开辟了一条新道路
    本文章主要从以下5个方面来展开聊聊这个话题:1.什么是鸿蒙2.鸿蒙系统发展时间线3.鸿蒙是套壳Android吗?4.鸿蒙的生态(用户以及开发者)5.一些建议1月18日,在鸿蒙生态千帆启航仪式上,华为宣布了继鸿蒙4.0之后的鸿蒙操作系统,星河版的预览版本,引起了广泛的讨论,这是一款完全剥离安卓......
  • Java的流程控制
    Java流程控制Scanner对象我们通过Scanner类获取用户的输入。基础语法:Scanners=newScanner(System.in);通过Scanner类的next()与nextLine()方法获取输入的字符串,在读取前我们一般需要使用hasNext()与hasNextLine()判断是否还有输入的数据。next():一定要读取到有效字符才可以结束......
  • linux内核-3.Linux 内核启动流程
    1链接脚本vmlinux.lds先编译一下Linux源码,因为有很多文件是需要编译才会生成的。首先分析Linux内核的连接脚本文件arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到Linux内核的第一行程序是从哪里执行的。vmlinux.lds中有如下代码:492OUTPUT_ARCH(arm)493ENTRY(ste......