Android启动流程源码分析(基于Android S)

news2024/11/26 10:22:24

从上图我们可以清楚的看到Android系统的启动分为以下几个步骤

  1. 启动电源以及启动系统

当我们按下电源键时, 引导芯片代码开始从预定义的地方(固化在ROM)开始执行, 加载引导程序到RAM, 然后执行

  1. 引导程序

引导程序是在Android操作系统开始运行前的一个小程序. 引导程序是运行的第一个程序, 因此它是针对特性的主板和芯片的. 设备制造商要么使用很受欢迎的引导程序比如redboot, uboot, qi bootloader或者开发自己的引导程序, 在此注意一下, 引导程序并不是Android操作系统的一部分. 引导程序是OEM厂商或运营商加锁和限制的地方.

引导程序分为两个阶段执行.

第一: 检查外部的RAM以及加载对第二阶段有用的程序

第二: 引导程序设置网络, 内存等等. 这些对于运行内核是有必要的. 为了达到特殊的目标, 引导程序可以根据配置参数或者输入数据设置内核.

传统得加载器主要是2个文件:

/external/arm-trusted-firmware/lib/romlib/init.s

这个主要是初始化堆栈, 清零BSS段, 调用main.c的_main()函数

/external/rootdev/main.c

初始化硬件(闹钟,主板,键盘, 控制台等), 创建Linux标签

  1. 内核

Android内核与桌面Linux内核的启动方式差不多, 内核启动时, 设置缓存 被保护的存储器 计划列表 加载驱动. 当内核完成系统设置时, 它首先在系统文件中寻找"init"文件, 然后启动root进程或者系统的第一个进程(据我之前的了解这个进程叫idle进程, pid=0, 如有记错的话请大家指出)

  1. init进程

init进程是Linux系统中用户空间的第一个进程, 进程号固定为1. Kernel启动后, 在用户空间启动init进程, 并调用init中的main()方法执行init进程的职责

  1. 启动Launcher App

init进程分析

init进程是Android系统中及其重要的用户空间的第一个进程. 接下来我们看看init进程做了些什么事情.

  1. 创建和挂载启动所需要的目录文件

  2. 初始化和启动属性服务

  3. 解析init.rc配置文件并启动Zygote进程

 

比较重要的两个文件

/system/core/init/init.cpp

/system/core/rootdir/init.rc

在这里init.cpp里面干了啥我们就不去详细解析了, 有兴趣得同学自己去研究

init.rc解析

init.rc是什么? 它是一个非常重要的配置文件, 由android初始化语言(Android Init Language)编写的脚本, 它里面包含了Action, Service, Command, Options. 这里就不过多讲解, 有兴趣了解的同学自己google

Zygote

  1. Zygote概述

Zygote的翻译为"受精卵", 如其名, 它的主要作用是用来孵化进程的, 在Android系统中主要有以下两种程序

Java应用程序---主要基于ART虚拟机, 所有的应用程序APK都是属于这一类程序

Native程序---也就是利用c/cpp开发的程序, 例如bootanimation.

所有的Java程序都以及系统服务进程SystemServer 都是由Zygote孵化而来的, 而native程序是由init程序创建启动的.

Binder 机制中存在 Binder 线程池, 是多线程的, 如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。 其实严格来说, Binder 机制不一定要多线程, 所谓的 Binder 线程只不过是在循环读 取 Binder 驱动的消息而已, 只注册一个 Binder 线程也是可以工作的, 比如 service manager 就是这样的。 实际 上 Zygote 尽管没有采取 Binder 机制, 它也不是单线程的, 但它在 fork() 前主动停止了其他线程, fork() 后重 新启动了。

  1. Zygote触发过程.

之前介绍了Zygote,那么Zygote是怎么被唤起来的?

在init.rc中, 上面有这一行代码:

import /system/etc/init/hw/init.${ro.zygote}.rc

${ro.zygote}这个会被替换成ro.zygote对应的属性值, 这个是由不同厂商自己定制的, 有4个值:

  • zygote32: zygote进程对应的执行程序是app_process(纯32bit模式)

  • zygote64:zygote进程对应的执行程序是app_process64(纯64bit模式)

  • zygote32_64: 启动两个zygote进程(名为zygote和zygote_secondary), 对应的执行程序是app_process32(主模式), app_process64

  • zygote64_32: 启动两个zygote进程(名为zygote和zygote_secondary), 对应的执行程序是app_process64(主模式), app_process32

Start zygote

system/core/rootdir/init.rc 960L

# 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

on zygote-start && property:ro.crypto.state=unsupported
    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

on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
    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

app_processXXX对应的位置为:out/target/product/xxx/system/bin

源位置app_process 中有一个比较重要的文件:::::/frameworks/base/cmds/app_process/app_main.cpp

L336 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

这里, 就是由native的世界进入java世界的地方, 在此之前都是native和kernel的世界.

这里有个很重要的东西AppRunTime 从代码上看, 它是AnroidRunTime的子类, 也就是Android中比较核心的一个东西叫作ART /frameworks/base/core/jni/AndroidRunTime.cpp

既然是是进入了java世界, 那我们不是应该有虚拟机吗? 虚拟机是在哪里搞出来的?

AndroidRunTime.cpp start()函数中的L1246

if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

我们可以看到, 这里ART先给我们干了两件事 启动java虚拟机, 然后注册JNI

然后呢? 不是说好进入java世界的吗? 只干这个可不行, start函数L1287

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
        }
    }

OK, 我们应该都能看到了, 这里是通过反射去调用某个类的main方法, 哪个类? OK, 之前在app_main里面那一行 runtime.start 传进来的参数是ZygoteInit, 就是调的它.. 从这里.我们真正的进入了Java的世界

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

zygoteServer = new ZygoteServer(isPrimaryZygote);

if (startSystemServer) {
    Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);

    // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
    // child (system_server) process.
    if (r != null) {
        r.run();
        return;
    }
}

Log.i(TAG, "Accepting command socket connections");

// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
pid = Zygote.forkSystemServer(
        parsedArgs.mUid, parsedArgs.mGid,
        parsedArgs.mGids,
        parsedArgs.mRuntimeFlags,
        null,
        parsedArgs.mPermittedCapabilities,
        parsedArgs.mEffectiveCapabilities);

/* For child process */
if (pid == 0) {
    if (hasSecondZygote(abiList)) {
        waitForSecondaryZygote(socketName);
    }

    zygoteServer.closeServerSocket();
    return handleSystemServerProcess(parsedArgs);
}

这里对怎么启动SystemServer就不去过多的赘述了, 大概的套路是一样的, Zygote调用的是nativeFork之类的方法, 然后通过反射调用到SystemServer的main, 大概就是这个套路

当SystemServer启动完成之后, 当SystemReady, 就会启动Launcher, 到此 Android的大体启动流程到此结束

下面给出Zygote的流程图

我们都知道, 当我们点到Launcher中的某个icon要启动某个app的时候, Launcher通过Binder和AMS通信, AMS如果发现当前没有这个进程,则通知Zygote fork一个进程,然后AMS再通过Binder与ActivityThread进行交互, 这里不深究交互细节, 抛出一个问题: 既然Binder是Android中很重要的IPC, 那为什么Zygote和SystemServer之间用Socket通信? 用Binder是不是会更好?

其实并不然, Binder固然是好, 基于mmap能达到在内存中只做一次拷贝.. 但是Binder是多线程的.. 多线程环境下fork会不会出问题? 答案是肯定的.

别说什么在fork之前暂停所有线程, fork完之后再启动之类的话.. 这只是其中一个规避方案而已.

大家可以了解下 unix3 c++程序设计守则,

https://blog.csdn.net/wallwind/article/details/7036733

这个守则中有明确说明, 多线程程序里不准使用fork, 多线程环境下fork可能会导致死锁

Binder 机制中存在 Binder 线程池, 是多线程的, 如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。 其实严格来说, Binder 机制不一定要多线程, 所谓的 Binder 线程只不过是在循环读 取 Binder 驱动的消息而已, 只注册一个 Binder 线程也是可以工作的, 比如 service manager 就是这样的。 实际 上 Zygote 尽管没有采取 Binder 机制, 它也不是单线程的, 但它在 fork() 前主动停止了其他线程, fork() 后重 新启动了。

浅谈fork()导致死锁的原因

复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以 实现的速度很快) 以及所有系统对象, 然后仅复制当前线程到子进程。这里: 所有父进程中别 的线程, 到了子进程 中都是突然蒸发掉的 对于锁来说, 从 OS 看, 每个锁有一个所有者, 即最后一次 lock 它的线程。 假设这么一个环境, 在 fork 之前, 有 一个子线程 lock 了某个锁, 获得了对锁的所有权。 fork 以后, 在子进程中, 所有的额外线程都人间蒸发了。 而锁却 被正常复制了, 在子进程看来, 这个锁没有主人, 所以没有任何人可以对它解锁。 当子进程想 lock 这个锁时, 不再有任何手段可以解开了。 程序发生死锁

init脚本语言相关

https://blog.csdn.net/chaihuasong/article/details/50456113

汇编指ARM指令集

https://blog.csdn.net/TuxedoLinux/article/details/111462307

Unix c++程序设计守则

https://blog.csdn.net/xiaohangyu/article/details/3341135

Linux 进程托孤

沙盒机制

init脚本语言属性

Copy-on-write

mmap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图片转PDF怎么弄?这几个方法值得你试一试

PDF是一种特殊的文件格式&#xff0c;它可以在任何设备和平台上进行传输&#xff0c;并且能够保证文件版式不被修改&#xff0c;此外&#xff0c;还可以兼容不同的系统&#xff0c;因为它的这些优势&#xff0c;大多数的人就喜欢将自己编辑好的WORD、PPT、EXCEL、图片等文件转换…

MySQL InnoDB的MVCC实现机制

MySQL InnoDB的MVCC实现机制1.MVCC概述2.MVCC的实现原理隐式字段undo日志Read View(读视图)RR隔离级别的Read View方案1.MVCC概述 什么是MVCC&#xff1f; MVCC&#xff0c;即多版本并发控制。MVCC是一种并发控制的方法&#xff0c;一般在数据库管理系统中&#xff0c;实现对…

YOLOV8——快速训练指南(上手教程、自定义数据训练)

概述 本篇主要用于说明如何使用自己的训练数据&#xff0c;快速在YOLOV8 框架上进行训练。当前&#xff08;20230116&#xff09;官方文档和网上的资源主要都是在开源的数据集上进行测试&#xff0c;对于算法“小白”或者“老鸟”如何快速应用到自己的项目中&#xff0c;这…

操作系统IO控制方式

操作系统I&O控制方式 视频地址&#xff1a;https://www.bilibili.com/video/BV1YE411D7nH?p64 I&O设备按照信息交换的单位可以分为以下两类&#xff1a; 块设备 数据传输的基本单位是块&#xff0c;传输速率较高&#xff0c;可寻址&#xff0c;可随机读写任意一块。…

78.循环神经网络(RNN)

1. 潜变量自回归模型 2. 循环神经网络 计算损失是比较ot和xt之间来计算损失&#xff0c;但是xt是用来更新ht&#xff0c;使得其挪到下一个单元。 用一个额外的whh来存时序信息。 3. 使用循环神经网络的语言模型 4. 困惑度&#xff08;perplexity&#xff09; 5. 梯度剪裁 g表…

《Stealth秘密行动》游戏开发记录

游戏开发的学习记录项目&#xff1a;Stealth秘密行动开始时间&#xff1a;2022.12.30一、新学到的&#xff1a;二、遇到的问题&#xff1a;三、成品部分展示&#xff1a;游戏开发的学习记录⑧ 项目&#xff1a;Stealth秘密行动 开始时间&#xff1a;2022.12.30 &#xff08;…

数据分析-深度学习Pytorch Day6

卷积神经网络如何运用到图片分类问题感受野 Receptive Field步长Stride填充Padding参数共享share parameter最大池化MaxPoolingCNN全过程仅个人理解学习引言CNN卷积神经网络最初主要是用于计算机视觉和图像处理中&#xff0c;比如图像分类&#xff1a;最终的分类数绝对维度&…

【代码随想录】哈希表-golang

哈希表 from 代码随想录 hash表解法可以是slice,map…&#xff0c;目的是将时间复杂度降为O(1) 有效的字母异位词 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 排序 思路&#xff1a;直接重新声明字符的字节形式&#xff0c;然后对其进行…

Vue的依赖收集和性能问题

什么是依赖收集Vue能够实现当一个数据变更时&#xff0c;视图就进行刷新&#xff0c;而且用到这个数据的其他地方也会同步变更&#xff1b;而且&#xff0c;这个数据必须是在有被依赖的情况下&#xff0c;视图和其他用到数据的地方才会变更。 所以&#xff0c;Vue要能够知道一个…

逻辑思维训练1200题-蓝桥杯计算思维参考

黑格尔曾说过&#xff0c;逻辑是一切思考的基础。逻辑思维能力强的人能迅速、准确地把握住问题的实质&#xff0c;面对纷繁复杂的事情能更容易找到解决的办法。《逻辑思维训练1200 题》介绍了排除法、递推法、倒推法、作图法、假设法、计算法、分析法、类比法、推理法、判断法、…

自动化 | 这些常用测试平台,你们公司在用的是哪些呢?

本文节选自霍格沃兹测试学院内部教材测试管理平台是贯穿测试整个生命周期的工具集合&#xff0c;它主要解决的是测试过程中团队协作的问题。在整个测试过程中&#xff0c;需要对测试用例、Bug、代码、持续集成等等进行管理。下面分别从这四个方面介绍现在比较流行的管理平台。6…

四、template模板

模板 之前的案例中&#xff0c;能够返回简单的字符串信息给浏览器。那如果想要返回html页面给浏览器该怎么做呢&#xff1f; 当然&#xff0c;我们可以这么写&#xff1a; def index(request):return HttpResponse(<h1 style"color:red">我是硬编码的</h…

23年3月如何准备pmp考试?

首先要把PMP考试如何报名、考试内容等都要了解清楚&#xff0c;再去备考。<<PMP入门知识>>PMP考试时长&#xff1a;230分钟。PMP考试形式&#xff1a;笔试。PMP考试题型&#xff1a;题型包括单选题和多选题&#xff0c;多选题将说明需选择几个正确选项。PMP考试题量…

NEUQ week11题解

P1796 汤姆斯的天堂梦 汤姆斯的天堂梦 题目描述 汤姆斯生活在一个等级为 000 的星球上。那里的环境极其恶劣&#xff0c;每天 121212 小时的工作和成堆的垃圾让人忍无可忍。他向往着等级为 NNN 的星球上天堂般的生活。 有一些航班将人从低等级的星球送上高一级的星球&#…

【Java寒假打卡】Java基础-网络编程UDP和TCP

【Java寒假打卡】Java基础-网络编程UDP和TCP网络编程的三要素网络编程的常见命令InetAddress类端口协议UDP发送数据UDP接受数据UDP通信程序的练习TCP通信程序-发送数据TCP通信程序-接受数据网络编程的三要素 网络编程的常见命令 ipconfig 查看本机IP地址ping IP地址&#xff1…

【系列03】方法的学习 方法重载 命令行传参 递归 简单计算机 [有目录]

方法的学习 什么是方法 方法是解决一类问题的步骤的有序组合包含于类或者对象之中方法在程序中被创建,在其他地方被引用 就比如输出方法如:System.out.println(); 就是被封装好的方法 方法设计原则:一个方法完成一个功能,利于后期扩展 [原子性] 使用方法: public class D…

vue中使用echarts 动态渲染数据

一、业务场景&#xff1a; 最近在vue中使用echarts时 引入的时候怎么也引不上&#xff0c;后面发现需要绑定在原型上就可以完美解决(也可以直接在需要引入的页面用ES5中的require引入require&#xff08;‘echarts’&#xff09;) 为了避免大家走弯路&#xff0c;下面整合了一下…

FLStudio水果21最新Daw (宿主软件)电音混音编曲制作工具

对许多音乐爱好者来说&#xff0c;从单独欣赏作品过渡到按自己的心情创作作品&#xff0c;是一种奇妙而令人高兴的体验。 音乐的编曲&#xff0c;特别是电子的编曲&#xff0c;Daw (宿主软件)是重要的要素。 编曲软件有很多。 “专业”这个词取决于你是否在行业的某个部分。 就…

MyBatis 详解 (1) -- 环境搭建、查询操作

MyBatis 详解 1 -- 环境搭建、查询操作一、MyBatis 是什么二、为什么学习 MyBatis三、MyBatis 查询3.1 前言3.2 创建数据库和表3.3 添加 MyBatis 框架支持添加 MyBatis 和 MySQL 驱动3.4 配置连接字符串 和 MyBatis3.4.1 配置连接字符串3.4.2 配置 MyBatis 中的 XML 路径3.5 添…

CSS造成阻塞的原理

目录 1.原理解析 A.webkit渲染过程 B.Gecko渲染过程 C.解析原理 接上篇文章CSS会造成阻塞吗&#xff1f; 我们来说一下CSS造成阻塞的原理 1.原理解析 那么为什么会出现上面的现象呢&#xff1f;我们从浏览器的渲染过程来解析下。 不用浏览器使用的内核不同&#xff0c…