Android13系统启动阶段大致分为FirstStageMain阶段和SecondStageMain,此章主要讲FirstStageMain阶段
(若分析有误敬请指教)
本章讲解的方向和你将收获的知识:
- 用户空间进程的调用流程
- 当进程挂掉后该如何处理
- 何时挂载上的基本文件系统和文件系统小知识
- FirstStageMain阶段会挂载上什么分区,会创建哪些设备节点
一. Android系统启动基本介绍
init进程是Android系统中用户空间的第一个进程,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init
1. Bootloader 引导
当按下设备电源键时,最先运行的就是 bootloader(固化在ROM的程序),bootloader 的主要作用就是硬件设备(如 CPU、flash、内存)的初始化并加载到RAM,通过建立内存空间映射,为装载 Linux 内核做好准备,。如果 bootloader 在运行期间,按下预定义的组合按键,可以进入系统fastboot模式 或者 Receiver 模式。
2. 装载和启动 Linux 内核
在编译完AOSP时会生成boot.img或者boot_debug.img,该镜像就是 Linux 内核和根文件系统,bootloader 会把该镜像装载到内存中,然后 linux 内核会执行整个系统的初始化,完成后装载根文件系统,最后启动 init 进程。
3. 启动 Init 进程
Linux 内核加载完毕后,会从kernel内核层调用到用户空间层,则会首先启动 init 进程,init 进程是系统的第一个进程,在 init 进程的启动过程中会解析最主要的 init.rc 脚本,根据 init.rc 文件的描述,系统会创建文件及目录以及权限的赋予,初始化属性和启动 Android 系统重要的守护进程
4. 启动 ServiceManager
ServiceManager 由 init 进程启动。它的主要作用是管理 Binder 服务,service 服务的注册和查找,如 AMS、PMS, 都是通过 ServiceManger 来管理。
5. 启动 MediaServer
MediaServer 是由 init 进程启动,它包含了一些多媒体 binder 服务,包括 CameraService、MediaPlayerService、AudioPolicyService 等等
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart media.tuner
onrestart restart netd
onrestart restart wificond
6. 启动 Zygote 进程
init 进程初始化结束后,会启动 Zygote 进程。在 Android 系统中所有的应用程序进程和系统服务进程都是通过Zygote 进程 fork 出来的。预装载系统的资源文件,所有从 Zygote 进程 fork 出的子进程都会共享这些资源,节省了资源加载的时间,提高的应用的启动速度。Zygote 启动结束后也会变为守护进程,负责响应启动 APK 的请求。
7. 启动 SystemServer
SystemServer 是跟随Zygote创建出来的第一个子进程,同时也是整个 Android 系统的核心。在系统中运行的大部分系统服务都是有 SystemServer 创建,接着会启动 AMS、WMS、PMS 等。阅读过源码可以发现大部分服务会继承自systemServer
PD2183:/ $ ps --pid 1
USER PID PPID VSZ RSS WCHAN ADDR S NAME
root 1 0 13133644 8264 0 0 S init
init进程由init_task进程fork而来,在kernel初始化完成后init_task便化身为idle进程,可以看到init的进程pid为:1
而init_task进程是Linux中第一个进程,也即0号进程(PID为0),这里不进一步分析linux内核层
二. FirstStageMain阶段分析
| 镜像 | 内容 |
|---|---|
| ramdisk.img | 系统root根目录,挂载点 / |
| boot.img | 包含了kernel + ramdisk.img |
| super.img | 在Android R版本开始引入的动态分区结构,目的是为了解决system和vender等分区size不能动态调整的问题 |
| super.img | 包含了system_a(b).img,vendor_a(b).img,product_a(b).img,拿到super.img可以进行解包拿到子镜像 |
| userdata.img | 挂载点/data,包含了用户的所有数据,例如应用数据等 |
针对super分区,在开机init的first stage第一阶段运行期间,会解析并验证metadata元数据并创建虚拟block设备来表示每个子分区,super分区头部信息就是metadata元数据,用于映射(mapping)出虚拟block设备,这里盗用一张大佬的图来看一下super结构
super分区除了包含system\product\vendor,在头部信息包含了描述分区布局的metadata,系统加载动态分区时读取metadata,对其进行解析。
Metadata中的信息会被转换成device mapper中的映射表Mapping Table,基于这个映射表,super分区对应设备/dev/block/by-name/super的不同部分被映射成多个虚拟设备,如/dev/block/mapper/system_a ,/dev/block/mapper/vendor_a等
1. 用户空间层main.cpp
using namespace android::init;
int main(int argc/*1*/, char** argv/*init*/) {
// 内核层传过来的,argc:1,argv:init
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)
__hwasan_set_error_report_callback(AsanReportCallback);
#endif
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20); // 设置进程最高优先级 -20最高,20最低
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (argc > 1) {
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
return SubcontextMain(argc, argv, &function_map);
}
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
return FirstStageMain(argc, argv); //第一阶段执行
}
内核层传过来的参数:argc:1,argv:init,所以第一阶段仍然是调用return FirstStageMain(argc, argv); 这里不详细介绍subcontext,ueventd,只关注FirstStageMain阶段即可,调用顺序如下
- FirstStageMain:主要创建和挂载基本的文件系统,挂载特定分区,启用log等
- SetupSelinux:挂载并启用selinux权限系统,之前文章有讲过关于权限问题,传送门:基于Android13的系统启动流程分析(一)之SeLinux权限介绍
- SecondStageMain:主要解析ini.rc文件,创建zygote孵化器,fork 进程等
2. FirstStageMain(int argc, char** argv)解析
该阶段所挂载的文件系统都属于ramdisk,运行在虚拟内存上
int FirstStageMain(int argc, char** argv) {
// init阶段的启动引导加载程序(bootLoader),若发生异常重启也会再次执行,主要处理init || fork的子进程进程异常行为
// init信号处理器,调试版本当init crash,默认重启到 bootLoader
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();// setup1
}
// 用来设置创建目录或文件时所应该赋予权限的掩码
// Linux中,文件默认最大权限是666,目录最大权限是777,当创建目录时,假设掩码为022,那赋予它的权限为(777 & ~022)= 755
// 在执行init第一阶段时,先执行umask(0),使创建的目录或文件的默认权限为最高
umask(0);
// 第一次执行时清除环境变量,reset path
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// setup 2
// 设置linux最基本的文件系统并且挂载到 / 目录(init ram disk)上,
// 并给0755权限(即用户具有读/写/执行权限,组用户和其它用户具有读写权限),后续会通过rc文件处理一些分区权限和进程
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); //将/dev设置为tmpfs并挂载,设置0755权限,tmpfs是在内存上建立的文件系统(Filesystem)
CHECKCALL(mkdir("/dev/pts", 0755));//tmpfs文件系统类型
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mkdir("/dev/dm-user", 0755));//tmpfs文件系统类型
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
// setup 3
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// 修改 「保存操作系统的启动参数」 的权限:0440,
// 修改权限的目的是为了 不要将原始bootConfig暴露给非特权进程,部分文件系统只能是0440权限,如果修改权限则无法读取和操作
// /proc/cmdline中保存bootloader 启动linux kernel 时 的参数
CHECKCALL(chmod("/proc/cmdline", 0440));
std::string cmdline;
// 读取操作系统的启动参数
android::base::ReadFileToString("/proc/cmdline", &cmdline);
// 修改权限的目的是为了 不要将原始bootConfig暴露给非特权进程
// 部分文件系统只能是0440权限,如果修改权限则无法读取和操作
chmod("/proc/bootconfig", 0440);
std::string bootconfig;
// 读取系统启动参数配置
android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
gid_t groups[] = {
AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
// setup 4
// 挂载/sys内核,并设置为sysfs文件系统类型,sysfs是一个伪文件系统。
// 不代表真实的物理设备,在linux内核中,sysfs文件系统将长期存在于RAM中
// sysfs文件系统将每个设备抽象成文件,挂载sysfs文件系统在sys目录,用来访问内核信息
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) {
CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
}
// 文件系统:/dev/random和 /dev/urandom是 Linux 上的字符设备文件,它们是随机数生成器,为系统提供随机数
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
// 创建日志系统的串口log(伪终端),这是日志包装器所需要的,它在ueventd运行之前被调用。
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
// setup 5
// 重要文件系统及分区在第一阶段挂载,其他可以在rc执行流程中挂载
// 挂载/mnt/{vendor,product},这些相对比较重要,所以只挂载重要的,其余的动作会在第二阶段的解析rc文件中处理
// tmpfs之前说过了,是运作在RAM的文件系统
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid

Android13的启动过程中,FirstStageMain阶段主要任务是初始化基本文件系统,如tmpfs、devpts、sysfs等,挂载/dev、/proc等,创建设备节点,并处理selinux和log。这一阶段还涉及到内核模块加载、设备权限设置以及日志系统的初始化。此外,FirstStageMain会创建/first_stage_ramdisk作为新的根目录,确保系统安全性和分区校验。
最低0.47元/天 解锁文章
3278

被折叠的 条评论
为什么被折叠?



