Android系统面经(3/20)系统启动流程全解析
牛客高级系列专栏:
安卓(安卓系统开发也要掌握)
- 想通关安卓面试,请看:《150道安卓高频面试题目录及答案链接》
- 想通关安卓系统面试,请看:《140道安卓系统Framework面试题目录及答案链接》
- 想进阶安卓开发,请看:《Android进阶知识体系解析_15大安卓进阶必备知识点》
- 想了解安卓APP完整开发流程,请看:《安卓APP完整开发流程》
- 想掌握安卓App性能优化,请看:《安卓性能优化讲解和实战专栏》
- 想掌握Gradle语法,制作Gradle插件,请看:《安卓Gradle语法解析和实践大全》
嵌入式
- 想通关嵌入式面试,请看: 《111道嵌入式面试题目录及答案链接》
- 想多掌握几个嵌入式项目,请看:《6个嵌入式项目交流分享(附源码)》
- 本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人从嵌入式Linux转Android系统开发过程中对常见安卓系统开发面试题的理解;
- 1份外卖价格助您提高安卓面试准备效率,为您面试保驾护航!!
正文开始⬇
安卓系统启动流程是每个安卓系统开发工程师必备知识!
面试题预览
- 你了解Android系统启动流程吗?⭐⭐⭐⭐⭐
- system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢?⭐⭐⭐
- 能说说具体是怎么导致死锁的吗?⭐⭐⭐
- Zygote 为什么不采用 Binder 机制进行 IPC 通信?⭐⭐⭐⭐
- 请简述从点击图标到app启动的流程。⭐⭐⭐⭐⭐
- 说说Activity加载的流程。⭐⭐⭐⭐⭐
- Zygote为什么需要用到Socket通讯而不是Binder?⭐⭐⭐⭐⭐
- Zygote进程最原始的进程是什么进程(或者Zygote进程是怎么来的)?⭐⭐⭐
1 概述
在init进程启动前,如上面“Android系统启动流程图”所示:
- 按Power键启动电源及系统启动 当按下电源键,引导芯片代码开始从固化在ROM中预定义的地方开始执行,加载引导程序Bootloader到RAM,然后执行引导程序。
- 引导程序Bootloader 引导程序BootLoader是Android系统运行前的第一个程序,它的主要作用是把系统OS拉起来并运行
- Linux内核启动 Linux kernel启动. kernel(内核)启动时会设置缓存、被保护存储器、计划列表、加载驱动.然后在系统文件中寻找init文件,并启动init进程
- init进程启动 详细见本章第二节分析。
2 Init启动进程详解
2.1 init进程概述
在运行Android程序后首先会启动init 进程,此进程是Linux 系统中用户空间的第一个进程,进程编号为1。init被赋予了很多重要的职责,比如我们熟悉的Zygote孵化器进程就是由init进程启动的。
2.2 启动前分析
在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置,从而来启动其他相关进程以及其他操作。
init进程被赋予了很多重要工作,init进程启动主要分为两个阶段:
- 第一个阶段完成以下内容:
- ueventd/watchdogd跳转及环境变量设置
- 挂载文件系统并创建目录
- 初始化日志输出、挂载分区设备
- 启用SELinux安全策略
- 开始第二阶段前的准备
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%内容,订阅专栏后可继续查看/也可单篇购买
2020年研究生毕业后,工作重心由嵌入式Linux转为安卓系统,Android发展已经很多年,网上面向中初级Android系统开发的面经还比较少,也不够集中,因此梳理出本专栏,本专栏收集了本人工作中持续积累的众多安卓系统知识,持续更新中。