Android系统面经(3/20)系统启动流程全解析

牛客高级系列专栏:

安卓(安卓系统开发也要掌握)


嵌入式


  • 本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人从嵌入式Linux转Android系统开发过程中对常见安卓系统开发面试题的理解;
  • 1份外卖价格助您提高安卓面试准备效率,为您面试保驾护航!!

正文开始⬇

安卓系统启动流程是每个安卓系统开发工程师必备知识!

面试题预览

  1. 你了解Android系统启动流程吗?⭐⭐⭐⭐⭐
  2. system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢?⭐⭐⭐
  3. 能说说具体是怎么导致死锁的吗?⭐⭐⭐
  4. Zygote 为什么不采用 Binder 机制进行 IPC 通信?⭐⭐⭐⭐
  5. 请简述从点击图标到app启动的流程。⭐⭐⭐⭐⭐
  6. 说说Activity加载的流程。⭐⭐⭐⭐⭐
  7. Zygote为什么需要用到Socket通讯而不是Binder?⭐⭐⭐⭐⭐
  8. Zygote进程最原始的进程是什么进程(或者Zygote进程是怎么来的)?⭐⭐⭐

1 概述

Android系统启动流程图

在init进程启动前,如上面“Android系统启动流程图”所示:

  1. 按Power键启动电源及系统启动 当按下电源键,引导芯片代码开始从固化在ROM中预定义的地方开始执行,加载引导程序Bootloader到RAM,然后执行引导程序。
  2. 引导程序Bootloader 引导程序BootLoader是Android系统运行前的第一个程序,它的主要作用是把系统OS拉起来并运行
  3. Linux内核启动 Linux kernel启动. kernel(内核)启动时会设置缓存、被保护存储器、计划列表、加载驱动.然后在系统文件中寻找init文件,并启动init进程
  4. init进程启动 详细见本章第二节分析。

2 Init启动进程详解

2.1 init进程概述

在运行Android程序后首先会启动init 进程,此进程是Linux 系统中用户空间的第一个进程,进程编号为1。init被赋予了很多重要的职责,比如我们熟悉的Zygote孵化器进程就是由init进程启动的。

2.2 启动前分析

在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置,从而来启动其他相关进程以及其他操作。

init进程被赋予了很多重要工作,init进程启动主要分为两个阶段:

  1. 第一个阶段完成以下内容:
  • ueventd/watchdogd跳转及环境变量设置
  • 挂载文件系统并创建目录
  • 初始化日志输出、挂载分区设备
  • 启用SELinux安全策略
  • 开始第二阶段前的准备

alt

2.第二个阶段完成以下内容:

  • 初始化属性系统
  • 执行SELinux第二阶段并恢复一些文件安全上下文
  • 新建epoll并初始化子进程终止信号处理函数
  • 设置其他系统属性并开启属性服务

Android 是一个基于Linux 内核的系统,与Linux、Fedora Linux最大的区别是,Android在应用层专门为移动设备添加了一些特有的支持。目前 Linux 有很多通信机制可以在用户空间和内核空间之间交互,例如设备驱动文件(位于/dev目录中)、内存文件(/proc、/sys 目录等)。Android在加载Linux基本内核后,就开始运行一个初始化进程init。从Android加载Linux内核时设置了如下参数。

Kemelcommand line: noinitrd root=/dev/nfs console=ttySACO 
init=/initnfsroot=192.168.1.103:/nfsbootip=192.168.1.20:192.168.1.103:192.168.
1.1:255.255.255.0::eth0:on

在上述命令中,Android系统一般会在根目录下放一个init的可执行文件,也就是说Linux系统的init进程在内核初始化完成后,就直接执行init这个文件。Init 进程的代码位于源码的目录 system/core/init,在分析 init 的核心代码之前,还需要做如下工作。

  • 初始化属性。
  • 处理配置文件的命令(主要是init.rc 文件),包括处理各种 Action。
  • 性能分析(使用b
  • ootchart 工具)。
  • 无限循环执行 command(启动其他的进程)。

2.3 init进程启动过程代码分析

init程序并不是由一个源代码文件组成的,而是由一组源代码文件的目标文件链接而成的。这些文件位于目录/system/core/init 中,例如system/core/init/init.cpp。 主要的 JNI代码放在路径frameworks/base/core/jni/中。

init进程的入口函数

在Linux内核加载完成之后,它首先在系统文件中寻找init.rc文件,并启动init进程,然后查看init进程的入口函数main,代码如下所示:

//system/core/init/init.cpp
int main(int argc, char** argv) {
    // ... 1
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    // ... 2
    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }
    // ... 3
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    // 添加环境变量
    add_environment("PATH", _PATH_DEFPATH);
 
    // 获取本次启动是否是系统启动的第一阶段,如果是第一阶段,
    // 则进入下面的if语句中,创建并挂载相关的文件系统。
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
    //创建文件并挂载
    if (is_first_stage) {
        ....
        // 创建和挂载启动所需的文件目录
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
 
        // 初始化Kernel的Log,这样就可以从外界获取Kernel的日志
        InitKernelLogging(argv);
 
        LOG(INFO) << "init first stage started!";
        ...
        // Set up SELinux, loading the SELinux policy.
        // 启动SELinux。
        selinux_initialize(true);
        ... 
    // 初始化属性服务
    property_init();   //  .......  5
 
    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    process_kernel_dt();
    process_kernel_cmdline();
 
    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();
 
    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
 
    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
 
    // Clean up our environment.
    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");
 
    // Now set up SELinux for second stage.
    selinux_initialize(false);
    selinux_restore_context();
 
 
    // 创建epoll句柄
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }
 
    // 用于设置子进程信号处理函数,如果子进程(Zygote进程)异常退出,init进程会调用该函数中
    // 设定的信号处理函数来进行处理
    signal_handler_init();   // ...... 6
 
    // 导入默认的环境变量
    property_load_boot_defaults();
    export_o

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android系统面试题全解析 文章被收录于专栏

2020年研究生毕业后,工作重心由嵌入式Linux转为安卓系统,Android发展已经很多年,网上面向中初级Android系统开发的面经还比较少,也不够集中,因此梳理出本专栏,本专栏收集了本人工作中持续积累的众多安卓系统知识,持续更新中。

全部评论

相关推荐

VirtualBool:都去逗他了?
点赞 评论 收藏
分享
Cherrycola01:0实习 0项目 约等于啥也没有啊 哥们儿这简历认真的吗
点赞 评论 收藏
分享
评论
2
13
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务