Android启动流程_Init阶段

前言

本文将会介绍 Android 启动流程,将基于 Android 10 代码逻辑介绍原生启动过程。

bootloader 上电 -> 加载 recovery 镜像或者 boot 镜像 -> linux kernel 启动 -> 加载 init 进程 -> 加载 zygote 进程 -> systemserver 进程 -> 系统启动

本文将会从启动过程中的大的部分展开,主要重点将会集中在 Android 上层的启动,对于启动过程中其他部分,例如 bootloader 上电等原有的名词描述,将会进而介绍是什么样的逻辑,从此前仅有的名词了解,继续了解,加深印象。

正文

本篇文章将会介绍 Android 启动流程

1、Android 启动流程图

下面是 Android 启动流程图:

请添加图片描述

本篇文档依据上述启动流程结构,描述整个 Android 启动的过程,对于原生的启动流程,在项目中的应用有所不同,不同点在于在项目中 Android 端的上电是由 MCU 管理的,在本篇文档中,会结合项目补充描述下车载 Android 在启动时 MCU 的行为。

2、MCU 端启动

在车机上电后,MCU 会先启动,当 MCU 启动完毕后,会给 SOC 上电。

这一块此前遇到一个问题,在 Chery 项目上,遇到持续发送 mcu reset 软复位信号时,概率出现 SOC 无法启动的情况,此时电流为 0.4A 左右,在复现台架中,从 SOC 与 MCU 的串口输出来看,SOC 没有任何日志输出,于是分析 MCU 端串口,确认 MCU 端也没有日志。那么此问题就怀疑为 MCU 并没有启动,或者说并没有给 SOC 上电。

3、SOC 端启动

3.1 芯片 bootloader 逻辑

此部分内容待补充

3.2 Android 端启动

Android 端启动将从 init 进程、zygote 进程、systemserver 进程展开描述。

3.2.1 Recovery 模式

对于 Recovery 模式请参考此文档 https://blog.csdn.net/Yang_Mao_Shan/article/details/133939560?spm=1001.2014.3001.5502

3.2.2 MainSystem 启动
3.2.2.1 Init 进程
3.2.2.1.1 编译

我们来分析下 init 进程的编译,以此来了解 init 中包含哪些部分。

init 文件的代码路径在:/android/system/core/init 下。

路径下有两个编译文件,Android.mk 和 Android.bp。

下面是 Android.mk 文件内容:

# Copyright 2005 The Android Open Source ProjectLOCAL_PATH:= $(call my-dir)-include system/sepolicy/policy_version.mk# ----------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
# 编译 BUILD_EXECUTABLE 可执行文件 init_first_stage。# Do not build this even with mmma if we're system-as-root, otherwise it will overwrite the symlink.
ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
include $(CLEAR_VARS)
LOCAL_CPPFLAGS := $(init_cflags)
LOCAL_SRC_FILES := \devices.cpp \first_stage_init.cpp \first_stage_main.cpp \first_stage_mount.cpp \mount_namespace.cpp \reboot_utils.cpp \selinux.cpp \switch_root.cpp \uevent_listener.cpp \util.cpp \LOCAL_MODULE := init_first_stage
LOCAL_MODULE_STEM := initLOCAL_FORCE_STATIC_EXECUTABLE := trueLOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)
LOCAL_UNSTRIPPED_PATH := $(TARGET_RAMDISK_OUT_UNSTRIPPED)# Install adb_debug.prop into debug ramdisk.
# This allows adb root on a user build, when debug ramdisk is used.
LOCAL_REQUIRED_MODULES := \adb_debug.prop \# Set up the same mount points on the ramdisk that system-as-root contains.
LOCAL_POST_INSTALL_CMD := mkdir -p \$(TARGET_RAMDISK_OUT)/apex \$(TARGET_RAMDISK_OUT)/debug_ramdisk \$(TARGET_RAMDISK_OUT)/dev \$(TARGET_RAMDISK_OUT)/mnt \$(TARGET_RAMDISK_OUT)/proc \$(TARGET_RAMDISK_OUT)/sys \LOCAL_STATIC_LIBRARIES := \libc++fs \libfs_avb \libfs_mgr \libfec \libfec_rs \libsquashfs_utils \liblogwrap \libext4_utils \libfscrypt \libseccomp_policy \libcrypto_utils \libsparse \libavb \libkeyutils \liblp \libcutils \libbase \liblog \libcrypto \libdl \libz \libselinux \libcap \libgsi \libcom.android.sysprop.apex \liblzma \libdexfile_support \libunwindstack \libbacktrace \LOCAL_SANITIZE := signed-integer-overflow
# First stage init is weird: it may start without stdout/stderr, and no /proc.
LOCAL_NOSANITIZE := hwaddress
include $(BUILD_EXECUTABLE)
endifinclude $(CLEAR_VARS)--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
# 指定编译虚拟目标 init_system,其内容是 init_second_stage
LOCAL_MODULE := init_system
LOCAL_REQUIRED_MODULES := \init_second_stage \include $(BUILD_PHONY_PACKAGE)include $(CLEAR_VARS)--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
# 指定编译虚拟目标 init_vendor,其内容是 init_first_stage
LOCAL_MODULE := init_vendor
ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
LOCAL_REQUIRED_MODULES := \init_first_stage \endif
include $(BUILD_PHONY_PACKAGE)

从 Android.mk 文件中,主要编译了 init_first_stage 这个可执行文件。

下面是 Android.bp 文件

//
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//cc_defaults {name: "init_defaults",cpp_std: "experimental",sanitize: {misc_undefined: ["signed-integer-overflow"],},cflags: ["-DLOG_UEVENTS=0","-Wall","-Wextra","-Wno-unused-parameter","-Werror","-DALLOW_LOCAL_PROP_OVERRIDE=0","-DALLOW_PERMISSIVE_SELINUX=0","-DREBOOT_BOOTLOADER_ON_PANIC=0","-DWORLD_WRITABLE_KMSG=0","-DDUMP_ON_UMOUNT_FAILURE=0","-DSHUTDOWN_ZERO_TIMEOUT=0",],product_variables: {debuggable: {cppflags: ["-UALLOW_LOCAL_PROP_OVERRIDE","-DALLOW_LOCAL_PROP_OVERRIDE=1","-UALLOW_PERMISSIVE_SELINUX","-DALLOW_PERMISSIVE_SELINUX=1","-UREBOOT_BOOTLOADER_ON_PANIC","-DREBOOT_BOOTLOADER_ON_PANIC=1","-UWORLD_WRITABLE_KMSG","-DWORLD_WRITABLE_KMSG=1","-UDUMP_ON_UMOUNT_FAILURE","-DDUMP_ON_UMOUNT_FAILURE=1",],},eng: {cppflags: ["-USHUTDOWN_ZERO_TIMEOUT","-DSHUTDOWN_ZERO_TIMEOUT=1",],},uml: {cppflags: ["-DUSER_MODE_LINUX"],},},static_libs: ["libseccomp_policy","libavb","libc++fs","libcgrouprc_format","libprotobuf-cpp-lite","libpropertyinfoserializer","libpropertyinfoparser",],shared_libs: ["libbacktrace","libbase","libbinder","libbootloader_message","libcutils","libcrypto","libdl","libext4_utils","libfs_mgr","libfscrypt","libgsi","libhidl-gen-utils","libkeyutils","liblog","liblogwrap","liblp","libprocessgroup","libprocessgroup_setup","libselinux","libutils",],bootstrap: true,
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
# 编译 libinit 动态库。
cc_library_static {name: "libinit",recovery_available: true,defaults: ["init_defaults", "selinux_policy_version"],srcs: ["action.cpp","action_manager.cpp","action_parser.cpp","boringssl_self_test.cpp","bootchart.cpp","builtins.cpp","capabilities.cpp","descriptors.cpp","devices.cpp","epoll.cpp","firmware_handler.cpp","first_stage_init.cpp","first_stage_mount.cpp","import_parser.cpp","init.cpp","keychords.cpp","modalias_handler.cpp","mount_handler.cpp","mount_namespace.cpp","parser.cpp","persistent_properties.cpp","persistent_properties.proto","property_service.cpp","property_type.cpp","reboot.cpp","reboot_utils.cpp","security.cpp","selinux.cpp","service.cpp","sigchld_handler.cpp","subcontext.cpp","subcontext.proto","switch_root.cpp","rlimit_parser.cpp","tokenizer.cpp","uevent_listener.cpp","ueventd.cpp","ueventd_parser.cpp","util.cpp",],whole_static_libs: ["libcap", "com.android.sysprop.apex"],header_libs: ["bootimg_headers"],proto: {type: "lite",export_proto_headers: true,},target: {recovery: {cflags: ["-DRECOVERY"],exclude_shared_libs: ["libbinder", "libutils"],},},
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
# 编译 init_second_stage 可执行文件。
cc_binary {name: "init_second_stage",recovery_available: true,stem: "init",defaults: ["init_defaults"],static_libs: ["libinit"],required: ["e2fsdroid","mke2fs","sload_f2fs","make_f2fs",],srcs: ["main.cpp"],symlinks: ["ueventd"],target: {recovery: {cflags: ["-DRECOVERY"],exclude_shared_libs: ["libbinder", "libutils"],},},ldflags: ["-Wl,--rpath,/system/${LIB}/bootstrap"],
}// Tests
// ------------------------------------------------------------------------------cc_test {name: "init_tests",defaults: ["init_defaults"],compile_multilib: "first",srcs: ["devices_test.cpp","init_test.cpp","keychords_test.cpp","persistent_properties_test.cpp","property_service_test.cpp","property_type_test.cpp","result_test.cpp","rlimit_parser_test.cpp","service_test.cpp","subcontext_test.cpp","tokenizer_test.cpp","ueventd_parser_test.cpp","ueventd_test.cpp","util_test.cpp",],static_libs: ["libinit"],test_suites: ["device-tests"],
}cc_benchmark {name: "init_benchmarks",defaults: ["init_defaults"],srcs: ["subcontext_benchmark.cpp",],static_libs: ["libinit"],
}// Host Verifier
// ------------------------------------------------------------------------------genrule {name: "generated_stub_builtin_function_map",out: ["generated_stub_builtin_function_map.h"],srcs: ["builtins.cpp"],cmd: "sed -n '/Builtin-function-map start/{:a;n;/Builtin-function-map end/q;p;ba}' $(in) | sed -e 's/do_[^}]*/do_stub/g' > $(out)",
}
cc_binary {name: "host_init_verifier",host_supported: true,cpp_std: "experimental",cflags: ["-Wall","-Wextra","-Wno-unused-parameter","-Werror",],static_libs: ["libbase","libselinux",],whole_static_libs: ["libcap"],shared_libs: ["libprotobuf-cpp-lite","libhidl-gen-utils","libprocessgroup","liblog","libcutils",],srcs: ["action.cpp","action_manager.cpp","action_parser.cpp","capabilities.cpp","descriptors.cpp","epoll.cpp","keychords.cpp","import_parser.cpp","host_import_parser.cpp","host_init_verifier.cpp","host_init_stubs.cpp","parser.cpp","rlimit_parser.cpp","tokenizer.cpp","service.cpp","subcontext.cpp","subcontext.proto","util.cpp",],proto: {type: "lite",},generated_headers: ["generated_stub_builtin_function_map","generated_android_ids"],target: {android: {enabled: false,},darwin: {enabled: false,},},
}subdirs = ["*"]

从 Android.bp 文件中,主要编译了 init_second_stage、测试相关文件和 Verify 验证相关文件。

3.2.2.1.2 init 启动流程
init 启动流程序列图

请添加图片描述

init 启动流程

我们先来看下 init 进程的 main 函数,文件为:android/system/core/init/main.cpp

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#endif//wangslsetpriority(PRIO_PROCESS, 0, -20);//wangsl// 启动 ueventd 服务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;return SubcontextMain(argc, argv, &function_map);}// 设置 Selinux,初始化 Selinux。if (!strcmp(argv[1], "selinux_setup")) {return SetupSelinux(argv);}// 启动流程第二阶段if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}// 启动流程第一阶段return FirstStageMain(argc, argv);
}

从 main 函数中,可以通过对传入参数的判断,进行不同的流程,在 kernel 启动 init 时,可以通过参数控制逻辑。

init 默认不带参数时会调用 FirstStageMain() 进行第一阶段的启动;

FirstStageMain 启动 init 进程第一阶段,会进行目录创建、设备节点创建和设备挂载的动作。

init 带参数时会调用不同的函数:

参数函数功能
ueventdueventd_maininit 进程创建子进程 ueventd,负责设备节点的创建、权限设定等一些列工作
subcontextSubcontextMain初始化日志系统,初始化上下文
selinux_setupSetupSelinux启动 Selinux 安全策略
second_stageSecondStageMain启动 init 进程第二阶段
无参数FirstStageMain启动 init 进程第一阶段

在启动过程中,会进行多次 init 函数的调用。

通过日志分析,执行顺序如下:

FirstStageMain -> selinux_setup -> second_stage -> ueventd -> subcontext

下面我们将按照此执行顺序整理 init 进程的执行逻辑。

3.2.2.1.3 first_stage

init 进程第一次启动时,会传入 argc:1、argv:init,那么将会执行 first_stage 第一阶段逻辑。

下面是启动第一阶段 FirstStageMain 函数的执行逻辑,文件在/android/system/core/init/first_stage_init.cpp 中。

int FirstStageMain(int argc, char** argv) {LOG(INFO) << "FirstStageMain() argc = " << argc << ", argv = " << **argv;if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x) \if (x != 0) errors.emplace_back(#x " failed", errno);// Clear the umask.// 用来设置创建目录或文件时所应该赋予权限的掩码// Linux中,文件默认最大权限是666,目录最大权限是777,当创建目录时,假设掩码为022,那赋予它的权限为(777 & ~022)= 755// 在执行init第一阶段时,先执行umask(0),使创建的目录或文件的默认权限为最高umask(0);--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 1、下面是对做了一些 目录创建、设备节点创建和设备挂在的动作// 第一次执行时清除环境变量,reset pathCHECKCALL(clearenv());CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));// Get the basic filesystem setup we need put together in the initramdisk// on / and then we'll let the rc file figure out the rest.// 设置 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));CHECKCALL(mkdir("/dev/socket", 0755));CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)// 挂载proc文件系统(驻留在RAM中),Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种虚拟文件系统CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR// Don't expose the raw commandline to unprivileged processes.// 修改 「保存操作系统的启动参数」 的权限:0440,// 修改权限的目的是为了 不要将原始 bootConfig 暴露给非特权进程,部分文件系统只能是 0440 权限,如果修改权限则无法读取和操作// /proc/cmdline 中保存 bootloader 启动 linux kernel 时的参数CHECKCALL(chmod("/proc/cmdline", 0440));gid_t groups[] = {AID_READPROC};CHECKCALL(setgroups(arraysize(groups), groups));// 挂载 /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)));// This is needed for log wrapper, which gets called before ueventd runs.// 创建日志系统的串口 log(伪终端),这是日志包装器所需要的,它在 ueventd 运行之前被调用。CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));// 创建并挂在 /mnt/vendor 和 /mnt/product 目录,这些相对比较重要,其他子目录由 rc 文件管理。// These below mounts are done in first stage init so that first stage mount can mount// subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,// should be done in rc files.// Mount staging areas for devices managed by vold// See storage config details at http://source.android.com/devices/storage/CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=1000"));// /mnt/vendor is used to mount vendor-specific partitions that can not be// part of the vendor partition, e.g. because they are mounted read-write.CHECKCALL(mkdir("/mnt/vendor", 0755));// /mnt/product is used to mount product-specific partitions that can not be// part of the product partition, e.g. because they are mounted read-write.CHECKCALL(mkdir("/mnt/product", 0755));// /apex is used to mount APEXesCHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));// /debug_ramdisk is used to preserve additional files from the debug ramdiskCHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));
#undef CHECKCALL--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 2、初始化 kernel 日志。读取根目录文件信息,例如文件列表、访问时间、修改时间等。切换根目录为 /first_stage_ramdisk。// 初始化kernel的日志,之前已经创建过了/dev/kmsg系统,用于处理日志的SetStdioToDevNull(argv);// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually// talk to the outside world...InitKernelLogging(argv);if (!errors.empty()) {for (const auto& [error_string, error_errno] : errors) {LOG(ERROR) << error_string << " " << strerror(error_errno);}LOG(FATAL) << "Init encountered errors starting first stage, aborting";}LOG(INFO) << "init first stage started!";// 打开根目录 / ,隶属 ramdisk,就是上面挂载的基本文件系统auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};if (!old_root_dir) {PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";}struct stat old_root_info;// 用 stat 函数获取根目录的文件信息给(old_root_info),例如访问的时间,修改的时间,目录下的文件数量// 若!=0 则是获取失败,提示未释放 ramdisk,估计是基本文件系统还未处理完成if (stat("/", &old_root_info) != 0) {PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";old_root_dir.reset();}// 如果是正常启动if (ForceNormalBoot()) {LOG(ERROR) << "ForceNormalBoot";// 创建第一阶段 ramdisk 目录 /first_stage_ramdiskmkdir("/first_stage_ramdisk", 0755);// SwitchRoot() must be called with a mount point as the target, so we bind mount the// target directory to itself here.if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";}// 将根目录(/)切换为 /first_stage_ramdisk ,将根切换到 first_stage_ramdiskSwitchRoot("/first_stage_ramdisk");} else {// 非正常启动,那么可能是启动了 recovery 模式LOG(ERROR) << "maybe recovery boot";}// If this file is present, the second-stage init will use a userdebug sepolicy// and load adb_debug.prop to allow adb root, if the device is unlocked.// 如果存在“/force_debugable”,则第二阶段 init 将使用 userdebug sepolicy 并加载adb_debug.prop 以允许adb root// /userdebug_plat_sepolicy.cil 属于 selinux 策略里的规则// 如果设备 unlocked(解锁了),则会修改 selinux 规则,放大用户权限if (access("/force_debuggable", F_OK) == 0) {std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||!fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {LOG(ERROR) << "Failed to setup debug ramdisk";} else {// setenv for second-stage init to read above kDebugRamdisk* files.setenv("INIT_FORCE_DEBUGGABLE", "true", 1);}}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 3、进行第一阶段的挂载,挂载 system、vendor、product 等系统分区。在 recovery 模式下初始化 avb 版本// 挂载 system、vendor、product 等系统分区if (!DoFirstStageMount()) {LOG(FATAL) << "Failed to mount required partitions early ...";}// 此时 new_root_info 应该是 /first_stage_ramdisk,而 old_root_info 是 /root// 读取 /first_stage_ramdisk 根目录信息,例如有多少个目录等struct stat new_root_info;if (stat("/", &new_root_info) != 0) {PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";old_root_dir.reset();}// 根目录发生变化,则释放 old ramdisk,用 new ramdiskif (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);}//初始化安全框架 Android Verified Boot,用于防止系统文件本身被篡改、防止系统回滚,以免回滚系统利用以前的漏洞。// 包括 Secure Boot, verified boot 和 dm-verity(会校验只读分区大小,若只读分区二进制改变则可能上被串改了,例如 user 强制 root),// 原理都是对二进制文件进行签名,在系统启动时进行认证,确保系统运行的是合法的二进制镜像文件。其中认证的范围涵盖:bootloader,boot.img,system.img。// 此处是在 recovery模式下初始化 avb 的版本,不是 recovery 模式直接跳过SetInitAvbVersionInRecovery();static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 4、fork 进程,父进程继续调用 init 程序;子进程调用 bootreadahead 程序// first_stage_init 运行结束后会 fork 进程,执行 /system/bin/bootreadahead 文件。Android 10 新特性,在 first_stage_init 阶段调用 /system/bin/bootreadahead 文件,将 /system/etc/readahead 文件中记录的文件预加载到内存中,从而在使用时,可以不用再去从磁盘中读取,直接从内存中复制即可。从而可以缩减时间,提高读取效率。//wangslint pid = fork();if(pid == 0) {setpriority(PRIO_PROCESS, 0, 0);const char* bin_path = "/system/bin/bootreadahead";const char* args[] = {bin_path, "inject", nullptr};execv(bin_path, const_cast<char**>(args));LOG(ERROR) << "first stage start bootreadahead complete,err is "<<strerror(errno);_exit(0);}//wangsl// 调用 init 进程传入 “selinux_setup” 参数,进行 selinux_setup 逻辑继续启动流程。const char* path = "/system/bin/init";const char* args[] = {path, "selinux_setup", nullptr};execv(path, const_cast<char**>(args));// execv() only returns if an error happened, in which case we// panic and never fall through this conditional.PLOG(FATAL) << "execv(\"" << path << "\") failed";return 1;
}

对于 first_stage 中初始化的文件系统,可以简单分为:/dev <-> tmpfs 、/proc <-> proc 、/sysfs <-> sysfs。

下面介绍下 tmpfs 文件系统:

​ tmpfs 文件系统,是基于 RAM 存储的,有易失性。特点如下:

  • 基于内存的文件系统
  • 能够动态地使用虚拟内存
  • 不需要格式化文件系统
  • tmpfs 数据在重新启动之后不会保留,因为虚拟内存本质上就是易失的,其优点是读写速度很快,但存在掉电丢失的风险(ramfs 与 tmpfs 有着对比性),这也许就是它叫 tmpfs 的缘故
  • 由于 tmpfs 基于 RAM,运行在内存上,因此它比硬盘的速度肯定要快,因此我们可以利用这个优点使用它来提升机器的性能,tmpfs 的另一个主要的好处是它闪电般的速度,因为典型的 tmpfs 文件系统会完全驻留在 RAM 中,读写几乎可以是瞬间的
  • tmpfs 使用了虚拟内存的机制,它会进行 swap,用例:达到空间上限时继续写入 结果:提示错误信息并终止,且 tmpfs 是有上限的,超过时会提示错误信息并终止 所以相比 ramfs 是比较安全的。tmpfs 和 ramfs 有着对比性,tmpfs 是相对安全的,因为达到空间上限时仍继续写入数据,那么提示错误信息并终止,而 ramfs 没有空间上限,会持续写入尚未分配的空间(占用其他未分配的内存)。因此 tmpfs 是固定大小,ramfs 不固定其大小。

可以通过命令来查看系统使用的是 tmpfs 还是 ramfs,命令如下:

​ adb shell mount | grep -E “(tmpfs|ramfs)”

或者 adb shell df -h | grep -E “(tmpfs|ramfs)”

下面介绍下 proc 文件系统:

proc 文件系统(驻留在RAM中),Linux 系统上的 /proc 目录是一种文件系统,即 proc 文件系统。与其它常见的文件系统不同的是,/proc 是一种虚拟文件系统。

proc 该目录下保存的并不是真正的文件和目录(虚拟文件系统),而是一些【运行时】的信息,如 CPU 信息、负载信息、系统内存信息、磁盘 IO 信息等。
存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系的硬件及当前【正在运行进程的信息】,甚至可以通过更改其中某些文件来改变内核的运行状态:

/proc/cmdline         # 保存操作系统的启动参数,/proc/cmdline中保存bootloader 启动linux kernel 时 的参数
/proc/cpuinfo         # 保存CPU的相关信息。对应lscpu命令。
/proc/devices         # 系统已经加载的所有块设备和字符设备的信息。
/proc/diskstats       # 统计磁盘设备的I/O信息。
/proc/filesystems     # 保存当前系统支持的文件系统。
/proc/kcore	          # 物理内存的镜像。该文件大小是已使用的物理内存加上4K。
/proc/loadavg	      # 保存最近1分钟、5分钟、15分钟的系统平均负载。
/proc/meminfo	      # 保存当前内存使用情况。对应free命令
/proc/mounts -> self/mounts	# 系统中当前挂载的所有文件系统。mount命令。# mounts文件是链接到self/mounts。
/proc/partitions      # 每个分区的主设备号(major)、次设备号(minor)、包含的块(block)数目。
/proc/uptime          # 系统自上次启动后的运行时间。
/proc/version         # 当前系统的内核版本号
/proc/vmstat          # 当前系统虚拟内存的统计数据

下面介绍下 sysfs 文件系统:

sysfs(常驻于 RAM 中)是一个伪文件系统,不占用任何磁盘空间的虚拟文件系统。

/sys下存放的都是设备驱动,网络环境,偏硬件的文件
1./sys/firmware : 固件 文件目录
2./sys/kernel : 内核文件目录
3./sys/module : 内核驱动模块
4./sys/power : 电源相关模块
5./sys/bus : 驱动总线文件目录
6./sys/block : 块设备目录(映射的/sys/devices目录)
7./sys/devices : 设备目录(也有虚拟设备目录),例如:sys/devices/virtual/block/dm-28
8./sys/fs/selinux : selinux机制,也就是处理selinux权限机制文件存放的位置,判断是否开启严格模式等

总结第一阶段完成的任务如下:

挂载最基本的文件系统,该文件系统是运行于 RAM 上的,优点是相比 disk 磁盘来说运行速度快,不占存储空间,特点是易失性,断电即丢失,挂载上最基本的文件系统后会根据根目录"/"来挂载 /mnt/{vendor,product} 等重要的分区,其他不重要的文件挂载在第二阶段 rc 文件中处理

在第一阶段并开启 kernel log

挂载 /first_stage_ramdisk 新的根目录,根据设备树(fstab)来创建逻辑分区 system,system_ext,vendor,product 并挂载到 /first_stage_ramdisk 根目录上,然后将 old 根目录切换到 /first_stage_ramdisk 根目录,释放 old 根目录,/first_stage_ramdisk 根目录将赋予较为安全的权限

创建 AVB 数据校验,启用 overlayfs 机制来保护分区原子性,初始化恢复模式下的 AVB 校验方案

调用 “/system/bin/bootreadahead” 将 /system/etc/readahead 文件中记录的文件预加载到内存中,从而在使用时,可以不用再去从磁盘中读取,直接从内存中复制即可。从而可以缩减时间,提高读取效率。

调用 “/system/bin/init” 进入下一个阶段:selinux_setup

3.2.2.1.4 selinux_setup

first_stage 在最后会调用 init 进程,传入 “selinux_setup” 参数,进行 selinux_setup 流程。

selinux_setup 的代码路径在:/android/system/core/init/selinux.cpp。

int SetupSelinux(char** argv) {// 初始化 Kernel 日志InitKernelLogging(argv);if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}// Set up SELinux, loading the SELinux policy.SelinuxSetupKernelLogging();// 这里初始化 Selinux 规则,会通过 /system/etc/selinux/plat_sepolicy.cil 获取 Selinux 规则SelinuxInitialize();// We're in the kernel domain and want to transition to the init domain.  File systems that// store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,// but other file systems do.  In particular, this is needed for ramdisks such as the// recovery image for A/B devices.if (selinux_android_restorecon("/system/bin/init", 0) == -1) {PLOG(FATAL) << "restorecon failed of /system/bin/init failed";}// 调用 "/system/bin/init" 启动第二阶段const char* path = "/system/bin/init";const char* args[] = {path, "second_stage", nullptr};execv(path, const_cast<char**>(args));// execv() only returns if an error happened, in which case we// panic and never return from this function.PLOG(FATAL) << "execv(\"" << path << "\") failed";return 1;
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 初始化 Selinux 服务,包括打开读取 sepolicy 策略,开启或者关闭 Selinux 服务。
void SelinuxInitialize() {Timer t;// 调用 LoadPolicy() 函数导入 Selinux 策略LOG(INFO) << "Loading SELinux policy";if (!LoadPolicy()) {LOG(FATAL) << "Unable to load SELinux policy";}bool kernel_enforcing = (security_getenforce() == 1);// 这里判断是否使能 Selinux。可以通过 IsEnforcing() 实现开启或者关闭 Selinux 服务。bool is_enforcing = IsEnforcing();if (kernel_enforcing != is_enforcing) {if (security_setenforce(is_enforcing)) {PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");}}if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();}// init's first stage can't set properties, so pass the time to the second stage.setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 导入 Sepolicy 策略。如果设备中存在 /system/etc/selinux/plat_sepolicy.cil 文件,并且可以访问,则 IsSplitPolicyDevice() 则返回 true,就会调用 LoadSplitPolicy() 函数。在设备中默认有 plat_sepolicy.cil 文件。
bool LoadPolicy() {return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
// 导入 sepolicy 策略。对于拆分策略的读取,由三个策略文件组成。导入的过程是将这三个策略文件编译成单个整体策略文件,然后将这个文件加载到内核中。
// 1、platform 平台 -- 由于包含在系统映像中的逻辑而需要policy
// 2、non-platform 非平台 -- 由于供应商映像中包含的逻辑,需要policy
// 3、mapping -- mapping policy,帮助保持非平台策略与新版本平台策略的前向兼容性
bool LoadSplitPolicy() {// IMPLEMENTATION NOTE: Split policy consists of three CIL files:// * platform -- policy needed due to logic contained in the system image,// * non-platform -- policy needed due to logic contained in the vendor image,// * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy//   with newer versions of platform policy.//// secilc is invoked to compile the above three policy files into a single monolithic policy// file. This file is then loaded into the kernel.// See if we need to load userdebug_plat_sepolicy.cil instead of plat_sepolicy.cil.// 如果设备是 userdebug 版本 + 设备 unlock + 存在 /debug_ramdisk/adb_debug.prop 并且可以访问,则加载 userdebug system sepolicyconst char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");bool use_userdebug_policy =((force_debuggable_env && "true"s == force_debuggable_env) &&AvbHandle::IsDeviceUnlocked() && access(kDebugRamdiskSEPolicy, F_OK) == 0);if (use_userdebug_policy) {LOG(WARNING) << "Using userdebug system sepolicy";}// Load precompiled policy from vendor image, if a matching policy is found there. The policy// must match the platform policy on the system image.std::string precompiled_sepolicy_file;// use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.// Thus it cannot use the precompiled policy from vendor image.// Use_userdebug_policy 需要使用 userdebug_plat_sepolicy.cil 编译 sepolicy。因此,它不能使用来自供应商映像的预编译策略。if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));if (fd != -1) {if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;return false;}return true;}}// No suitable precompiled policy could be loadedLOG(INFO) << "Compiling SELinux policy";// 这里开始读取系统的 SELinux 策略// We store the output of the compilation on /dev because this is the most convenient tmpfs// storage mount available this early in the boot sequence.char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));if (compiled_sepolicy_fd < 0) {PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;return false;}// Determine which mapping file to include// GetVendorMappingVersion() 获取版本号是读取 /vendor/etc/selinux/plat_sepolicy_vers.txt 文件的内容,例如 Android 9 上是 28.0std::string vend_plat_vers;if (!GetVendorMappingVersion(&vend_plat_vers)) {return false;}// plat_mapping_file 文件是在 /system/etc/selinux/mapping 路径下,路径下有以下文件// 26.0.cil 27.0.cil 28.0.cilstd::string plat_mapping_file ("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");// 判断系统中是否存在 /product/etc/selinux/product_sepolicy.cil 文件std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");if (access(product_policy_cil_file.c_str(), F_OK) == -1) {product_policy_cil_file.clear();}// 判断系统中是否存在 /product/etc/selinux/mapping/**.cil 文件std::string product_mapping_file("/product/etc/selinux/mapping/" + vend_plat_vers + ".cil");if (access(product_mapping_file.c_str(), F_OK) == -1) {product_mapping_file.clear();}// vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace// nonplat_sepolicy.cil.std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");// 如果 /vendor/etc/selinux/plat_pub_versioned.cil 存在,那么使用对应的 cil 文件为 /vendor/etc/selinux/nonplat_sepolicy.cil,否则使用 /vendor/etc/selinux/vendor_sepolicy.cilif (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {// For backward compatibility.// TODO: remove this after no device is using nonplat_sepolicy.cil.vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";plat_pub_versioned_cil_file.clear();} else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;return false;}// odm_sepolicy.cil is default but optional.// 判断系统中是否存在 /odm/etc/selinux/odm_sepolicy.cil 文件std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {odm_policy_cil_file.clear();}const std::string version_as_string = std::to_string(SEPOLICY_VERSION);// clang-format off// 使用 compile_args 封装 SELinux 参数std::vector<const char*> compile_args {"/system/bin/secilc",use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,"-m", "-M", "true", "-G", "-N","-c", version_as_string.c_str(),plat_mapping_file.c_str(),"-o", compiled_sepolicy,// We don't care about file_contexts output by the compiler"-f", "/sys/fs/selinux/null",  // /dev/null is not yet available};// clang-format on// 将对应目录下的 cil 文件放入 compile_args 集合中if (!product_policy_cil_file.empty()) {compile_args.push_back(product_policy_cil_file.c_str());}if (!product_mapping_file.empty()) {compile_args.push_back(product_mapping_file.c_str());}if (!plat_pub_versioned_cil_file.empty()) {compile_args.push_back(plat_pub_versioned_cil_file.c_str());}if (!vendor_policy_cil_file.empty()) {compile_args.push_back(vendor_policy_cil_file.c_str());}if (!odm_policy_cil_file.empty()) {compile_args.push_back(odm_policy_cil_file.c_str());}compile_args.push_back(nullptr);// 执行 compile_args 命令if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {unlink(compiled_sepolicy);return false;}unlink(compiled_sepolicy);LOG(INFO) << "Loading compiled SELinux policy";if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;return false;}return true;
}

对于 selinux_setup 总结:

初始化日志

导入系统中的 cil 文件。如果设备是 userdebug 版本,并且设备已经 unlock,系统中存在 /debug_ramdisk/adb_debug.prop 文件,那么就加载 userdebug 对应的系统策略。读取系统路径下的 cil 文件,路径有 /product/etc/selinux/product_sepolicy.cil、/product/etc/selinux/mapping/**.cil、/vendor/etc/selinux/plat_pub_versioned.cil、/vendor/etc/selinux/vendor_sepolicy.cil 或者 /vendor/etc/selinux/nonplat_sepolicy.cil、/odm/etc/selinux/odm_sepolicy.cil 文件。将对应的文件 push 到 compile_args 集合中,使用 compile_args 作为参数执行加载 cil 策略。

判断系统是否开启 SELinux 策略。IsEnforcing() 函数返回系统开启关闭 SELinux 策略的结果。可以通过此处的修改,实现开机时控制 SELinux 功能。

调用 /system/bin/init 进程,传入 second_stage 参数,启动开机第二阶段。

3.2.2.1.5 second_stage

init 启动第二阶段的函数是 SecondStageMain(),实现在 /android/system/core/init/init.cpp 文件中,接下来我们跟踪 init 启动第二阶段的功能。

int SecondStageMain(int argc, char** argv) {// 注册信号处理函数,出现一些异常时可以捕获处理。if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}// 标准输入输出重定向到 /dev/nullSetStdioToDevNull(argv);// 初始化 Kernel 日志,Init 阶段的 log 输出到 /dev/msg LOG PLOGInitKernelLogging(argv);LOG(INFO) << "init second stage started!";// Set init and its forked children's oom_adj.// 设置进程的优先级。oom_adj 表示进程的优先级,值越小,优先级越大if (auto result = WriteFile("/proc/1/oom_score_adj", "-1000"); !result) {LOG(ERROR) << "Unable to write -1000 to /proc/1/oom_score_adj: " << result.error();}// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).// Secommp (SECure COMPuting) 是 Linux 内核 2.6.12 版本引入的安全模块,主要是用来限制某一进程可用的系统调用 (system call),这里开启这个机制GlobalSeccomp();// Set up a session keyring that all processes will have access to. It// will hold things like FBE encryption keys. No process should override// its session keyring.// 这里使用到的是内核提供给用户空间使用的 密钥保留服务 (key retention service),它的主要意图是在 Linux 内核中缓存身份验证数据。远程文件系统和其他内核服务可以使用这个服务来管理密码学、身份验证标记、跨域用户映射和其他安全问题。它还使 Linux 内核能够快速访问所需的密钥,并可以用来将密钥操作(比如添加、更新和删除)委托给用户空间。keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);// Indicate that booting is in progress to background fw loaders, etc.// 创建 /dev/.booting 文件,就是个标记,表示booting进行中close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));// 初始化属性服务。系统中的属性服务是在这里初始化的,系统开辟了属性存储区域,并且提供了访问该区域的接口。property_init();// If arguments are passed both on the command line and in DT,// properties set in DT always have priority over the command-line ones.// 读取特定设备树信息,并设置 ro.boot. 开头的属性。process_kernel_dt();// 将内核中 cmdline 中所有的有 = 号的参数,以及特殊的 androidboot.xx=xxx 的形式设置成相应的属性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.// 设置 init 和 selinux 执行时间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.// 设置 avb 版本const char* avb_version = getenv("INIT_AVB_VERSION");if (avb_version) property_set("ro.boot.avb_version", avb_version);// See if need to load debug props to allow adb root, when the device is unlocked.const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {load_debug_prop = "true"s == force_debuggable_env;}// Clean up our environment.unsetenv("INIT_STARTED_AT");unsetenv("INIT_SELINUX_TOOK");unsetenv("INIT_AVB_VERSION");unsetenv("INIT_FORCE_DEBUGGABLE");// Now set up SELinux for second stage.// 第二阶段设置 Selinux 日志SelinuxSetupKernelLogging();// 第二阶段设置安全上下文SelabelInitialize();// 通过 restorecon 设置各个文件的默认安全上下文SelinuxRestoreContext();// 初始化 epollEpoll epoll;if (auto result = epoll.Open(); !result) {PLOG(FATAL) << result.error();}// 通过 epoll 监控子进程退出时发送 SIGCHLD 信号,并通过 HandleSignalFd() 进行处理InstallSignalFdHandler(&epoll);// 加载各个分区中的属性文件,如 prop.default, build.pro, default.prop 等property_load_boot_defaults(load_debug_prop);// 卸载挂载点 debug_ramdiskUmountDebugRamdisk();// 主要是根据 ro.vndk.version 版本号,将/system/vendor_overlay/ 和 /product/vendor_overlay/ 挂载在 vendor 上fs_mgr_vendor_overlay_mount_all();export_oem_lock_status();// 开启属性服务StartPropertyService(&epoll);MountHandler mount_handler(&epoll);// 读取 USB 设备控制器的节点 /sys/class/udc/xxx,如 fe800000.dwc3,并设置属性 sys.usb.controller=fe800000.dwc3set_usb_controller();// 下面准备解析 rc 文件// 构建内置函数映射表对象,用于处理 rc 文件中 action 的各个命令const BuiltinFunctionMap function_map;Action::set_function_map(&function_map);// 这个主要设置 ./apex 这些分区的挂载信息权限的if (!SetupMountNamespaces()) {PLOG(FATAL) << "SetupMountNamespaces failed";}// 一个容器, 记录 u:r:init:s0 和 u:r:vendor_init:s0 类型的安全上下文// android P 版本以上,给 vendor oem 增加 u:r:vendor_init:s0 权限subcontexts = InitializeSubcontexts();// 构建 ActionManager 对象,用于管理和调度各个 ActionActionManager& am = ActionManager::GetInstance();// 构建 ServiceList 对象,用于管理和调度各个 ServiceServiceList& sm = ServiceList::GetInstance();//wangslParser preParser = CreateParser(am, sm);preParser.ParseConfig("/system/etc/init/pre_uevent.rc");am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");am.ExecuteOneCommand();am.QueueEventTrigger("early-uevent");am.ExecuteOneCommand();//wangsl// 加载各种 rc 文件, 优先加载 bootargs 中 androidboot.init_rc=xxx 指定的 rc 文件// 加载顺序如下:// /init.rc --> /system/etc/init目录rc --> /product/etc/init目录rc --> /product_services/etc/init --> /odm/etc/init --> /vendor/etc/initLoadBootScripts(am, sm);// Turning this on and letting the INFO logging be discarded adds 0.2s to// Nexus 9 boot time, so it's disabled by default.if (false) DumpState();// Make the GSI status available before scripts start running.if (android::gsi::IsGsiRunning()) {property_set("ro.gsid.image_running", "1");} else {property_set("ro.gsid.image_running", "0");}// 构建一个新的 action, 类似 on SetupCgroups, 并执行 SetupCgroupsAction 命令am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");// 添加 erarly-init 到事件队列中,注意此处并没有触发事件。am.QueueEventTrigger("early-init");// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");// ... so that we can start queuing up actions that require stuff from /dev.am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");Keychords keychords;am.QueueBuiltinAction([&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {for (const auto& svc : ServiceList::GetInstance()) {keychords.Register(svc->keycodes());}keychords.Start(&epoll, HandleKeychord);return Success();},"KeychordInit");am.QueueBuiltinAction(console_init_action, "console_init");// Trigger all the boot actions to get us started.// 添加 init 到事件队列中,注意此处并没有触发。am.QueueEventTrigger("init");// Starting the BoringSSL self test, for NIAP certification compliance.am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random// wasn't ready immediately after wait_for_coldboot_doneam.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");// Initialize binder before bringing up other system servicesam.QueueBuiltinAction(InitBinder, "InitBinder");// Don't mount filesystems or start core system services in charger mode.std::string bootmode = GetProperty("ro.bootmode", "");if (bootmode == "charger") {// 当 ro.bootmode 属性为 charger 时添加 charger 到事件队列中,注意此处并没有触发。am.QueueEventTrigger("charger");} else {// 添加 late-init 到事件队列中,注意此处并没有触发。am.QueueEventTrigger("late-init");}// Run all property triggers based on current state of the properties.am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");//wangslsetpriority(PRIO_PROCESS, 0, 0);//wangslwhile (true) {// By default, sleep until something happens.auto epoll_timeout = std::optional<std::chrono::milliseconds>{};if (do_shutdown && !shutting_down) {do_shutdown = false;if (HandlePowerctlMessage(shutdown_command)) {shutting_down = true;}}if (!(waiting_for_prop || Service::is_exec_service_running())) {// 将按照 event_queue_ 的顺序,依次取出 action 链表中与 trigger 匹配的 action。// 每次均执行一个 action 中的一个 command 对应函数(一个 action 可能携带多个 command)。// 当一个 action 所有的 command 均执行完毕后,再执行下一个 action。// 当一个 trigger 对应的 action 均执行完毕后,再执行下一个 trigger 对应 action。am.ExecuteOneCommand();}if (!(waiting_for_prop || Service::is_exec_service_running())) {if (!shutting_down) {auto next_process_action_time = HandleProcessActions();// If there's a process that needs restarting, wake up in time for that.if (next_process_action_time) {epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(*next_process_action_time - boot_clock::now());if (*epoll_timeout < 0ms) epoll_timeout = 0ms;}}// If there's more work to do, wake up again immediately.if (am.HasMoreCommands()) epoll_timeout = 0ms;}if (auto result = epoll.Wait(epoll_timeout); !result) {LOG(ERROR) << result.error();}}return 0;
}

init 启动第二阶段的内容比较多,可以从以下几个点展开说明

  • 开启属性服务
  • rc 文件格式
  • 解析 rc 文件
  • 执行 rc 文件

关于解析 rc 文件,可以从以下几个点展开描述

  • 解析 rc 文件的顺序
  • 从解析功能类和解析函数,描述解析原理
  • rc 文件的内容是如何保存的,trigger 和 service 等数据结构,使用了什么数据结构描述 trigger 和 service 等之间的联系
  • rc 文件的执行是怎么调度的,执行顺序,结合 rc 文件的语法格式描述

在介绍解析 rc 文件之前,我们先来对 rc 文件格式进行描述。

rc 文件是以 **块(section)**为单位的,**块(section)**可以分为两大类:

  • 动作(action)
  • 服务(service)

块(section)以关键字 on 或者 service 开始,直到下一个 on 或者 service 结束,中间所有的行都属于这个块(section)

下面我们来描述下在 rc 文件中,动作和服务的格式。

动作(action)

格式

on  <trigger>         # 触发条件<command>         # 执行命令<command1>        # 可以执行多个命令

动作(action)里面的,on 后面跟着的字符串是触发器(trigger),**触发器(trigger)**是一个用于匹配某种事件类型的字符串,当事件发生时,就会执行 trigger 下面的 command。

**触发器(trigger)**有几种格式,最简单的一种是一个单传的字符串,比如 on boot,表示系统启动时的触发器,还有一种常见的格式是 on property<属性> = <值>。如果属性值在运行时设成了指定的值,则 action 中的命令列表就会执行。

常见的触发器(trigger)有以下几种:

  • on early-init:在初始化早期阶段触发
  • on init:在初始化阶段触发
  • on late-init:在初始化晚期阶段触发
  • on boot/charger:当系统启动/充电时触发
  • on property:当属性值满足条件时触发

command 是 action 的命令列表中的命令,或者是 service 中的 onrestart 选项中的参数命令。

命令将在所属事件发生时被执行,常见的命令有以下这些:

exec <path> [ <argument> ]*:运行指定路径下的程序,并传递参数
export <name> <value>:设置全局环境参数。此参数被设置后对全部进程都有效
ifup <interface>:使指定的网络接口"上线",相当激活指定的网络接口
import <filename>:导入一个额外的 rc 配置文件
hostname <name>:设置主机名
chdir <directory>:改变工作文件夹
chmod <octal-mode> <path>:设置指定文件的读取权限
chown <owner> <group> <path>:设置文件所有者和文件关联组
chroot <directory>:设置根文件夹
class_start <serviceclass>:启动指定类属的全部服务,假设服务已经启动,则不再反复启动
class_stop <serviceclass>:停止指定类属的全部服务
domainname <name>:设置域名
insmod <path>:安装模块到指定路径
mkdir <path> [mode] [owner] [group]:用指定参数创建一个文件夹
mount <type> <device> <dir> [ <mountoption> ]*:类似于linux的mount指令
setprop <name> <value>:设置属性及相应的值
setrlimit <resource> <cur> <max>:设置资源的rlimit
start <service>:假设指定的服务未启动,则启动它
stop <service>:假设指定的服务当前正在执行。则停止它
symlink <target> <path>:创建一个符号链接
sysclktz <mins_west_of_gmt>:设置系统基准时间
trigger <event>:触发另一个时间
write <path> <string> [ <string> ]*:往指定的文件写字符串

服务(service)

**服务(service)**的一般格式如下:

service <name><pathname> [ <argument> ]*<option><option>
  • name:表示此服务的名称
  • pathname:表示可执行文件对应的路径
  • argument:表示执行可执行文件时传入的参数
  • option:表示服务的选项

服务的选项有:

class <name> [ <name>\* ]:为服务指定 class 名字。 同一个 class 名字的服务会被一起启动或退出, 默认值是 defaultconsole [<console>]:这个选项表明服务需要一个控制台。 第二个参数 console 的意思是可以设置你想要的控制台类型,默认控制台是 /dev/console, /dev 这个前缀通常是被省略的, 比如你要设置控制台 /dev/tty0, 那么只需要设置为console tty0 即可。critical:表示服务是严格模式。 如果这个服务在4分钟内或者启动完成前退出超过4次,那么设备将重启进入 bootloader 模式disabled:这个服务不会随着 class 一起启动。只能通过服务名来显式启动。比如 foobar 服务的 class 是 core, 且是 disabled 的,当执行 class_start core 时,foobar 服务是不会被启动的。 foobar 服务只能通过 start foobar 这种方法来启动。file <path> <type> 根据文件路径 path 来打开文件,然后把文件描述符 fd 传递给服务进程。type 表示打工文件的方式,只有三种取值 r, w, rw。对于 native 程序来说,可以通过 libcutils 库提供的 android_get_control_file() 函数来获取传递过来的文件描述符。group <groupname> [ <groupname>\* ]:
在启动 Service 前,将 Service 的用户组改为第一个 groupname, 第一个 groupname 是必须有的, 第二个 groupname 可以不设置,用于追加组(通过setgroups)。目前默认的用户组是 root 组。oneshot:当服务退出的时候,不自动重启。适用于那些开机只运行一次的服务。onrestart:在服务重启的时候执行一个命令seclabel <seclabel>:在启动 Service 前设置指定的 seclabel,默认使用init的安全策略。 主要用于在 rootfs 上启动的 service,比如 ueventd, adbd。 在系统分区上运行的 service 有自己的 SELinux安全策略。setenv <name> <value>:设置进程的环境变量socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]创建一个 unix domain socket, 路径为 /dev/socket/name , 并将 fd 返回给 Service。 type 只能是 dgram, stream or seqpacket。user 和 group 默认值是 0。 seclabel 是这个 socket 的 SELinux security context, 它的默认值是 service 的 security context 或者基于其可执行文件的 security context。user <username> 在启动 Service 前修改进程的所属用户, 默认启动时 user 为 root
second_stage 解析 rc 文件

下面将着重介绍在 init 第二阶段中是如何解析 rc 文件的。second_stage 通过 LoadBootScripts() 函数完成对 rc 文件的解析。

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) 
{/** 函数参数:*  ActionManager& am = ActionManager::GetInstance(); ActionManager 对象,用于管理和调度各个 Action。*  ServiceList& sm = ServiceList::GetInstance(); ServiceList 对象,用于管理和调度各个 Service。*/// 创建解析器 Parser 对象。Parser parser = CreateParser(action_manager, service_list);// 这里通过 ro.boot.init_rc 属性去定义 rc 文件路径,一般不会指定此属性。std::string bootscript = GetProperty("ro.boot.init_rc", "");if (bootscript.empty()) {// 如果没有指定 ro.boot.init_rc 属性路径,那么按照默认顺序去各路径下解析 rc 文件/** 解析 rc 文件路径顺序如下:*  /init.rc*  /system/etc/init*  /product/etc/init*  /odm/etc/init*  /vendor/etc/init*/// ParseConfig() 函数去解析 rc 文件或者目录下的 rc 文件parser.ParseConfig("/init.rc");if (!parser.ParseConfig("/system/etc/init")) {late_import_paths.emplace_back("/system/etc/init");}if (!parser.ParseConfig("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init");}if (!parser.ParseConfig("/product_services/etc/init")) {late_import_paths.emplace_back("/product_services/etc/init");}if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init");}if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init");}} else {// 如果指定了 ro.boot.init_rc 属性,那么按照指定的路径去解析 rc 文件parser.ParseConfig(bootscript);}
}// 创建解析器
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {/** 函数参数:*  ActionManager& am = ActionManager::GetInstance(); ActionManager 对象,用于管理和调度各个 Action。*  ServiceList& sm = ServiceList::GetInstance(); ServiceList 对象,用于管理和调度各个 Service。*/// 初始化解析类对象Parser parser;/** 添加解析器,rc 文件中有多种语句,有 on、service、import 语句代表不同的功能,解析时也有对应的解析器,将不同语句解析成对应的对象* on 关键字解析成 Action 对象* service 关键字解析成 service 对象*/// 添加 Service 解析器,传入 service_list 管理 rc 中定义的 service 服务。parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));// 添加 action 解析器,传入 action_manager 管理 rc 中定义的 action 动作。parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));// 添加 import 解析器,解析 rc 中的 import 功能。parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));return parser;
}

对于 LoadBootScripts() 函数的功能,我们可以分为两个步骤:

步骤一:初始化解析 Parser

对于 rc 文件的解析实际是通过 Parser 类完成的。调用 CreateParser 函数创建了一个 Parser 对象,该对象用于解析我们的 rc 文件;

通过 AddSectionParser 函数给 parser 添加了 ServiceParser、ActionParser、ImportParser 三个成员;

  • ServiceParser 用于解析 init.rc 中定义的 Service
  • ActionParser 用于解析 init.rc 中定义的 Action
  • ImportParser 用于解析 init.rc 中的 import 语句

步骤二:寻找 rc 文件并调用解析类函数进行解析

LoadBootScripts 函数接下来会优先加载 bootargs 中 androidboot.init_rc=xxx 指定的 rc 文件,一般很少指定这个, 所有都是按照如下顺序去加载 rc 文件:

  • /init.rc
  • /system/etc/init 目录
  • /product/etc/init 目录
  • /product_services/etc/init 目录
  • /odm/etc/init 目录
  • /vendor/etc/init 目录

对于 rc 文件的解析有提到,会根据不同的关键字解析成不同的对象,例如 on 关键字解析成 Action 对象,service 关键字解析成 Service 对象。这里先来描述下 Action 和 Service 解析前后的结构。

下面是 init.rc 中 on 的部分内容:

# system/core/rootdir/init.rc
# action
on initsysclktz 0# Mix device-specific information into the entropy poolcopy /proc/cmdline /dev/urandomcopy /system/etc/prop.default /dev/urandom# ......

在经过解析后,会将这部分内容解析成 Action 对象,Action 对象结构如下:

class Action {// 省略成员函数// 属性 triggerstd::map<std::string, std::string> property_triggers_;// 事件 triggerstd::string event_trigger_;// 命令std::vector<Command> commands_;bool oneshot_;Subcontext* subcontext_;std::string filename_;int line_;// 命令与对应函数的映射表static const KeywordFunctionMap* function_map_;
};

下面是 init.rc 中 service 部分内容:

# frameworks/native/cmds/servicemanager/servicemanager.rc
# service
service servicemanager /system/bin/servicemanagerclass core animationuser systemgroup system readproccriticalonrestart restart healthdonrestart restart zygoteonrestart restart audioserveronrestart restart mediaonrestart restart surfaceflingeronrestart restart inputflingeronrestart restart drmonrestart restart cameraserveronrestart restart keystoreonrestart restart gatekeeperdonrestart restart thermalservicewritepid /dev/cpuset/system-background/tasksshutdown critical

经过解析后,会将此部分内容解析成 Service 对象,Service 结构如下:

class Service {// 省略成员函数static unsigned long next_start_order_;static bool is_exec_service_running_;// 服务名std::string name_;// 服务对于的 class 名std::set<std::string> classnames_;// 终端std::string console_;unsigned flags_;pid_t pid_;android::base::boot_clock::time_point time_started_;  // time of last startandroid::base::boot_clock::time_point time_crashed_;  // first crash within inspection windowint crash_count_;                     // number of times crashed within windowuid_t uid_;gid_t gid_;std::vector<gid_t> supp_gids_; std::optional<CapSet> capabilities_;unsigned namespace_flags_;// Pair of namespace type, path to namespace.std::vector<std::pair<int, std::string>> namespaces_to_enter_;// selinux 安全上下文std::string seclabel_;std::vector<std::unique_ptr<DescriptorInfo>> descriptors_;std::vector<std::pair<std::string, std::string>> environment_vars_;Action onrestart_;  // Commands to execute on restart.std::vector<std::string> writepid_files_;std::set<std::string> interfaces_;  // e.g. some.package.foo@1.0::IBaz/instance-name// keycodes for triggering this service via /dev/input/input*std::vector<int> keycodes_;IoSchedClass ioprio_class_;int ioprio_pri_;int priority_;int oom_score_adjust_;int swappiness_ = -1;int soft_limit_in_bytes_ = -1;int limit_in_bytes_ = -1;int limit_percent_ = -1;std::string limit_property_;bool process_cgroup_empty_ = false;bool override_ = false;unsigned long start_order_;std::vector<std::pair<int, rlimit>> rlimits_;bool sigstop_ = false;std::chrono::seconds restart_period_ = 5s;std::optional<std::chrono::seconds> timeout_period_;bool updatable_ = false;std::vector<std::string> args_;std::vector<std::function<void(const siginfo_t& siginfo)>> reap_callbacks_;bool pre_apexd_ = false;bool post_data_ = false;bool running_at_post_data_reset_ = false;
};

在了解解析前后的样子后,我们来看看解析的过程。在对 rc 文件的处理中,有解析和执行两个步骤,这里会对解析和执行进行描述。

rc 解析过程(序列图)

前面我们提到在解析中有三个解析类,ServiceParser、ActionParser、ImportParser 三个解析类对应 rc 文件中的三种内容。

在描述之前,我们先来梳理一下描述结构:

对于 init 进程来说,解析的功能交给了 Parser 类去处理,那么这里需要整理出 Parser 类的类图,表示如何对 init 提供功能的。

Parser 类会使用 ServiceParser、ActionParser、ImportParser 三个解析类去处理 rc 中不同的内容,那这之间是如何联系的。Parser 类持有 SectionParser 对象的引用,而 ServiceParser、ActionParser、ImportParser 三个类均继承自 SectionParser 类,那么对于接口类 SectionParser 类来说,规范好功能接口后,由子类去实现对于不同 rc 内容的解析即可。这里需要说明 SectionParser 类的关系结构。

rc 文件解析类图

请添加图片描述

ActionParser 解析类的函数功能:

ParseSection() 函数是解析第一行的 on 的,作用是创建一个 Action 对象,并且会对 trigger 进行解析,是 property trigger 还是 event trigger,property trigger 表示属性控制的 trigger,event trigger 表示自定义的事件 trigger,例如:

	1. early-boot:系统早期启动时执行的操作,用于初始化系统的基本环境。2. late-init:系统初始化完成后执行的操作,用于启动系统的服务和应用。3. post-fs:文件系统挂载后执行的操作,用于配置文件系统相关的设置。4. boot: post-fs-data, property triggers:引导完成后执行的操作,用于处理数据文件系统和系统属性。5. early-start:启动早期执行的操作,例如初始化虚拟文件系统。6. nonencrypted:非加密设备启动时执行的操作。7. late-fs: late-fs-data triggers:文件系统挂载后执行的操作,用于处理数据文件系统。

ParseLineSection() 函数是解析 命令的。作用是将命令添加到创建的 Action 对象中,当 action 语句解析完毕之后,那么所有的命令都会保存到 Action 对象中。

EndSection() 函数是将解析得到的 Action 对象保存到 action_manager_ 中,当 rc 解析完毕之后,解析的 Action 对象会保存到 ActionManager 中管理。

ServiceParser 解析类的函数功能:

ParseSection() 函数是解析第一行的 service [ ]* 语句的。作用是创建一个 Service 对象,初始化 Service 的 name 变量标识服务的名字。

ParseLineSection() 函数是解析 参数的,具体调用 Service -> ParseLine() 函数完成。Service 对象的 ParseLine() 函数会根据 option 不同的参数使用对应的解析函数去执行解析,具体支持的 option 类型在 option_parsers 中保存:

请添加图片描述

EndSection() 函数是解析得到的 Service 对象保存到 service_list_ 中,如果当前已经有同名的服务存在,那么会在某些情况下,使用新的服务对象替换原有服务对象。当 rc 文件解析完之后,解析的 Service 对象会保存在 ServiceList 中管理。

ImportParser 解析类函数功能:

ParseSection() 函数是将 import 的文件保存到 imports_ 集合中。

ParseLineSection() 函数没有功能逻辑,import 语句是引入新的 rc 文件,没有其他更多的参数。

EndFile() 函数是调用 Parser -> ParseConfig() 函数去解析新的 rc 文件。

解析 rc 序列图

请添加图片描述

在 Parser 解析类中,会通过判断传入参数是文件还是路径,进行不同的逻辑,如果是文件的话,那么就会直接对文件进行解析;如果是路径的话,那么会搜索路径下的 rc 文件进行解析。

解析类的代码路径为:/android/system/core/init/parser.cpp

代码如下:

bool Parser::ParseConfig(const std::string& path) {if (is_dir(path.c_str())) { // 当传入参数是路径,则调用 ParseConfigDir() 函数return ParseConfigDir(path);}return ParseConfigFile(path); // 当传入参数是文件,则调用 ParseConfigFile() 函数
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------bool Parser::ParseConfigFile(const std::string& path) {LOG(INFO) << "Parsing file " << path << "...";android::base::Timer t;auto config_contents = ReadFile(path);if (!config_contents) {LOG(INFO) << "Unable to read config file '" << path << "': " << config_contents.error();return false;}ParseData(path, &config_contents.value());LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";return true;
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------bool Parser::ParseConfigDir(const std::string& path) {LOG(INFO) << "Parsing directory " << path << "...";std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);if (!config_dir) {PLOG(INFO) << "Could not import directory '" << path << "'";return false;}dirent* current_file;std::vector<std::string> files;while ((current_file = readdir(config_dir.get()))) {// Ignore directories and only process regular files.if (current_file->d_type == DT_REG) {std::string current_path =android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);files.emplace_back(current_path);}}// Sort first so we load files in a consistent order (bug 31996208)std::sort(files.begin(), files.end());for (const auto& file : files) {if (!ParseConfigFile(file)) {LOG(ERROR) << "could not import file '" << file << "'";}}return true;
}--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------void Parser::ParseData(const std::string& filename, std::string* data) {/** 参数解析:* filename 表示 rc 文件名;* data 表示读取 rc 的内容;是通过 read(fd, &buf[0], sizeof(buf)) 函数读取的文件内容*/// 这里给读取到的文件内容 String 字符串添加一行内容 "\0" 作为文件结尾data->push_back('\n');  // TODO: fix tokenizerdata->push_back('\0');// 这里初始化解析状态,parse_state 结构体表示解析的状态。// ptr 表示 当前解析中字符的指针,即解析进度// text 表示 当前检出的单词,即参数// line 表示 当前解析中的行号// nexttoken 表示解析状态,有 T_EOF、T_NEWLINE、T_TEXT。parse_state state;state.line = 0;state.ptr = data->data();state.nexttoken = 0;// SectionParser 对于 rc 文件中的 action、service 和 import 语句的解析。SectionParser* section_parser = nullptr;int section_start_line = -1;std::vector<std::string> args;// If we encounter a bad section start, there is no valid parser object to parse the subsequent// sections, so we must suppress errors until the next valid section is found.// 对于没有合适的解析器来解析当前内容,则跳到下一个可以解析的内容部分bool bad_section_found = false;// 此函数是判断解析 section 内容是否解析结束。Lambda表达式,用于结束段落解析和重置解析相关数据auto end_section = [&] {bad_section_found = false;if (section_parser == nullptr) return;if (auto result = section_parser->EndSection(); !result) {parse_error_count_++;LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();}section_parser = nullptr;section_start_line = -1;};// 开启循环解析 rc 文件for (;;) {/** 调用 next_token() 函数开启解析:* 	T_EOF:当解析到 '0' 时,表示解析结束,返回 T_EOF* 	T_NEWLINE:当解析到 '\0' 时,表示解析完一行,返回 T_NEWLINE* 	T_TEXT:当解析到可用内容时,表示解析到一个单词,返回 T_TEXT*/switch (next_token(&state)) {// rc 文件解析到结尾case T_EOF:end_section();for (const auto& [section_name, section_parser] : section_parsers_) {section_parser->EndFile();}return;// rc 文件解析到新的一行case T_NEWLINE: {state.line++;if (args.empty()) break;// If we have a line matching a prefix we recognize, call its callback and unset any// current section parsers.  This is meant for /sys/ and /dev/ line entries for// uevent.// 此处的判断是对于 uevent 的,如果我们有一行匹配我们识别的前缀,调用它的回调并取消设置当前的任何节解析器。这意味着uevent的/sys/和/dev/行条目。auto line_callback = std::find_if(line_callbacks_.begin(), line_callbacks_.end(),[&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });if (line_callback != line_callbacks_.end()) {end_section();if (auto result = line_callback->second(std::move(args)); !result) {parse_error_count_++;LOG(ERROR) << filename << ": " << state.line << ": " << result.error();}} else if (section_parsers_.count(args[0])) { // 这里通过 args 参数选择对应的 section_parser。on -> ActionParser; service -> ServiceParser; import -> HostImportParser;// 在处理新的 section 之前,结束之前的 section。end_section();// 这里获取对应的解析器section_parser = section_parsers_[args[0]].get();section_start_line = state.line; // 保存解析起始行号// 调用 SectionParser->ParseSection() 函数进行解析if (auto result =section_parser->ParseSection(std::move(args), filename, state.line);!result) {parse_error_count_++;LOG(ERROR) << filename << ": " << state.line << ": " << result.error();section_parser = nullptr;bad_section_found = true;}} else if (section_parser) { // 该行的第一个单词不是新的 section 的开头,则认为是上一个 section 的命令,因此直接使用上一个解析器的 ParseLineSection() 函数解析命令。if (auto result = section_parser->ParseLineSection(std::move(args), state.line);!result) {parse_error_count_++;LOG(ERROR) << filename << ": " << state.line << ": " << result.error();}} else if (!bad_section_found) {parse_error_count_++;LOG(ERROR) << filename << ": " << state.line<< ": Invalid section keyword found";}args.clear();break;}// rc 文件解析到可用的文本case T_TEXT:// 如果解析可用文本,例如 on、service、import 等,那么将对应字符串指针保存到 args 中,在 T_NEWLINE 阶段将会处理args.emplace_back(state.text);break;}}
}

这里对于 ParseData 函数需要重要分析,此函数是解析 rc 内容的具体逻辑。

打开 rc 文件 IO 流,开启循环读取文件内容。使用next_token()函数的作用就是寻找单词结束或者行结束标志,如果是单词结束标志就将单词push到args中,如果是行结束标志,则根据第一个单词来判断是否是一个section,section的标志只有三个"on",“service”,“import”,如果是"section",则调用相应的ParseSection()来处理一个新的section,否则把这一行继续作为前“section”所属的行来处理。

ParseSection()被用来解析一个新的section,ParseLineSection()被用来解析该section下的命令行。

那么解析流程图如下所示:
请添加图片描述

对于 Service 的解析会调用到 ServiceParser 对象的 ParseSection、ParseLineSection、EndSection 等函数将 rc 文件中的文本配置信息转换为 Service 对象,并把 Service 对象保存到 ServiceList 中。

对于 Action 的解析会调用 ActionParser 对象的 ParseSection、ParseLineSection、EndSection 等函数将 rc 文件中的文本配置信息转换为 Action 对象,并把 Action 保存到 ActionManager 中。

rc 执行过程

Action 的执行是由 ActionManager 封装管理的。ActionManager 不仅提供对系统的 rc 文件中 action 的解析功能,还有创建 action 的功能,这里怎么理解呢,对于上述的解析过程,rc 文件中的 action 会解析到 ActionManager 中保存,但是除了在 rc 文件中定义的 action 以外,系统还需要一些额外的配置,也会被构造为 action 对象。也需要加入触发条件准备去执行 action。

ActionManager 调用顺序如下:

am.QueueBuiltinAction()

am.QueueEventTrigger()

am.ExecuteOneCommand()

下面对这三个函数进行简要描述:

QueueBuiltinAction() 函数有两个参数,第一个参数 BuiltinFunction 是个函数指针,第二个参数是个字符串。第一个参数表示此 action 要执行的命令,第二个参数是 Trigger。首先根据传进来的参数创建一个 Action 对象;然后调用 Action->AddCommand() 函数将命令写入到 Action 中;完成创建后将 Action 对象保存到 event_queue_ 和 action_ 中。QueueEventTrigger() 函数有一个参数,参数为字符串,表示触发 Action 的 Trigger。此函数用于触发 Trigger,在逻辑中,是将此 Trigger 保存到 event_queue_ 中。ExecuteOneCommand() 函数是执行当前 Action。在 ActionManager 中有变量保存当前要执行的 action 集合 current_executing_actions_,此函数就是执行此集合中的 action 对象命令。当 current_executing_actions_ 有值时,会直接执行 current_executing_actions_ 中的 action;当 current_executing_actions_ 中没有值时,event_queue_ 中会保存 Trigger,会从 action_ 中遍历包含 event_queue_ 中 Trigger 的 action 对象,添加到 current_executing_actions_ 集合中执行。Action 的执行过程中,会调用 Action->ExecuteOneCommand() 函数执行 Action 对象的命令,执行完毕后,在 actions_ 集合中清除已经处理完毕的 action 对象。

从代码可以看出,当 while 循环不断调用 ExecuteOneCommand 函数时,将按照 trigger 表的顺序,依次取出 action 链表中与 trigger 匹配的 action。

每次均执行一个 action 中的一个 command 对应函数(一个 action 可能携带多个 command)。

当一个 action 所有的 command 均执行完毕后,再执行下一个 action。

当一个 trigger 对应的 action 均执行完毕后,再执行下一个 trigger 对应 action。

对于 Command 命令,这里需要说明下,在 /android/system/core/init/builtins.cpp 文件中,定义了 builtin_functions 集合去描述命令集合。集合描述的是命令和执行函数的对应列表。如下所示:

    static const Map builtin_functions = {{"bootchart",               {1,     1,    {false,  do_bootchart}}},{"chmod",                   {2,     2,    {true,   do_chmod}}},{"chown",                   {2,     3,    {true,   do_chown}}},{"class_reset",             {1,     1,    {false,  do_class_reset}}},{"class_reset_post_data",   {1,     1,    {false,  do_class_reset_post_data}}},{"class_restart",           {1,     1,    {false,  do_class_restart}}},{"class_start",             {1,     1,    {false,  do_class_start}}},{"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},{"class_stop",              {1,     1,    {false,  do_class_stop}}},{"copy",                    {2,     2,    {true,   do_copy}}},{"domainname",              {1,     1,    {true,   do_domainname}}},{"enable",                  {1,     1,    {false,  do_enable}}},{"exec",                    {1,     kMax, {false,  do_exec}}},{"exec_background",         {1,     kMax, {false,  do_exec_background}}},{"exec_start",              {1,     1,    {false,  do_exec_start}}},{"export",                  {2,     2,    {false,  do_export}}},{"hostname",                {1,     1,    {true,   do_hostname}}},{"ifup",                    {1,     1,    {true,   do_ifup}}},{"init_user0",              {0,     0,    {false,  do_init_user0}}},{"insmod",                  {1,     kMax, {true,   do_insmod}}},{"installkey",              {1,     1,    {false,  do_installkey}}},{"interface_restart",       {1,     1,    {false,  do_interface_restart}}},{"interface_start",         {1,     1,    {false,  do_interface_start}}},{"interface_stop",          {1,     1,    {false,  do_interface_stop}}},{"load_persist_props",      {0,     0,    {false,  do_load_persist_props}}},{"load_system_props",       {0,     0,    {false,  do_load_system_props}}},{"loglevel",                {1,     1,    {false,  do_loglevel}}},{"mark_post_data",          {0,     0,    {false,  do_mark_post_data}}},{"mkdir",                   {1,     4,    {true,   do_mkdir}}},// TODO: Do mount operations in vendor_init.// mount_all is currently too complex to run in vendor_init as it queues action triggers,// imports rc scripts, etc.  It should be simplified and run in vendor_init context.// mount and umount are run in the same context as mount_all for symmetry.{"mount_all",               {1,     kMax, {false,  do_mount_all}}},{"mount",                   {3,     kMax, {false,  do_mount}}},{"parse_apex_configs",      {0,     0,    {false,  do_parse_apex_configs}}},{"umount",                  {1,     1,    {false,  do_umount}}},{"umount_all",              {1,     1,    {false,  do_umount_all}}},{"readahead",               {1,     2,    {true,   do_readahead}}},{"restart",                 {1,     1,    {false,  do_restart}}},{"restorecon",              {1,     kMax, {true,   do_restorecon}}},{"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},{"rm",                      {1,     1,    {true,   do_rm}}},{"rmdir",                   {1,     1,    {true,   do_rmdir}}},{"setprop",                 {2,     2,    {true,   do_setprop}}},{"setrlimit",               {3,     3,    {false,  do_setrlimit}}},{"start",                   {1,     1,    {false,  do_start}}},{"stop",                    {1,     1,    {false,  do_stop}}},{"swapon_all",              {1,     1,    {false,  do_swapon_all}}},{"enter_default_mount_ns",  {0,     0,    {false,  do_enter_default_mount_ns}}},{"symlink",                 {2,     2,    {true,   do_symlink}}},{"sysclktz",                {1,     1,    {false,  do_sysclktz}}},{"trigger",                 {1,     1,    {false,  do_trigger}}},{"verity_load_state",       {0,     0,    {false,  do_verity_load_state}}},{"verity_update_state",     {0,     0,    {false,  do_verity_update_state}}},{"wait",                    {1,     2,    {true,   do_wait}}},{"wait_for_prop",           {2,     2,    {false,  do_wait_for_prop}}},{"write",                   {2,     2,    {true,   do_write}}},};
Service 的执行

Service 类型的执行依赖 Action 命令。在 Action 命令中,有 class_start 用于启动一类服务。对于 Service 来说,都定义了 class,即使没有定义也会默认分配 class 为 default。对于 class_start 和 start 命令都会执行 Service,通过传入的 service 服务信息在 ServiceList 中找到对应的 Service 对象,调用 Service->start() 函数开启服务。复制服务进程并且执行对应的可执行文件。

总结

本篇文章介绍了 Android 启动时 init 进程的功能,从 init 的启动阶段到 rc 文件的解析,服务的介绍等。

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

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

相关文章

国标GB28181网页直播平台EasyGBS国标GB28181软件的应急管理与安全生产解决方案

在当今社会&#xff0c;安全生产和应急管理已经成为各行各业不可或缺的重要部分。全面提高安全生产管理水平、构建责任全覆盖、监管全过程、监管全方位的综合治理体系已成为社会发展的必然趋势。国标GB28181网页直播平台EasyGBS作为一款基于国标GB28181协议的视频融合管理平台&…

Python | Leetcode Python题解之第523题连续的子数组和

题目&#xff1a; 题解&#xff1a; class Solution:def checkSubarraySum(self, nums, k):d {0: -1}pre 0for index, num in enumerate(nums):pre numrem pre % ki d.get(rem, index)if i index:d[rem] indexelif i < index - 2:return Truereturn False

C++练习题

//C输出 "Hello, World!" #include <iostream> using namespace std; int main() { //printf("Hello World!"); cout<<"Hello World!"<<endl; return 0; } //C输出整数 #include <iostream> using names…

ChatGPT「AI搜索」正式上线:AI搜索要变天了?「速看体验与对比」

随着人工智能的发展&#xff0c;传统搜索引擎难以满足用户日益复杂的信息需求&#xff0c;AI搜索工具就因此诞生。10月31日&#xff0c;OpenAI发布了新的「ChatGPT Search」功能&#xff0c;为其智能聊天系统引入了实时网络搜索。借助这一功能&#xff0c;ChatGPT可以通过自然的…

Docker安装MySQL8.0

文章目录 1、通过Docker运行msyql82、进入容器配置mysql远程连接3、通过Navicat远程访问mysql 1、通过Docker运行msyql8 mkdir -p /home/mysql8/data /home/mysql8/config /home/mysql8/logsdocker run -d \ --name mysql8 \ --privilegedtrue \ --restartalways \ -p 3310…

JAVA基础:数据类型与运算符 (习题笔记)

1.输入自己的名字&#xff0c;年龄和性别&#xff0c;分别用不同的变量接收&#xff0c;并将输入的信息做输出。 public static void main(String[] args) {Scanner input new Scanner(System.in);System.out.println("请输入你的名字&#xff1a;");String name i…

Windows 笔记本WiFi 功能消失解决办法

Windows 笔记本用户在使用 WiFi 时遇到功能突然消失的问题确实比较常见。这通常不是病毒造成的&#xff0c;而是其他几个常见原因所导致的。以下是一些可能的原因及解决方案&#xff1a; 视频教程【win10系统无网络&#xff0c;无wifi解决办法】 https://www.bilibili.com/vid…

opencv学习笔记(6):图像预处理(直方图、图像去噪)

3.直方图 直方图是用来表现图像中亮度分布的&#xff0c;给出的是图像中某个亮度或者某个范围亮度下共有几个像素&#xff0c;即统计一幅图某个亮度像素的数量。 直方图不能反映某一灰度值像素在图像中的位置&#xff0c;失去了图像的空间信息。图像直方图由于其计算代价较小&a…

【面试经典150】day 11

目录 1.无重复字符的最长子串 2.串联所有单词的子串 3.最小覆盖子串 4.有效的数独 ​​​​​​​ 1.无重复字符的最长子串 class Solution {public int lengthOfLongestSubstring(String s) {//定义哈希表Map<Character,Integer> dictnew HashMap<>();int ret…

linux-应用层操作GPIO

GPIO 同样也是通过 sysfs 方式进行操控&#xff0c;进入到/sys/class/gpio 目录下&#xff0c; export&#xff1a;用于将指定编号的 GPIO 引脚导出。在使用 GPIO 引脚之前&#xff0c;需要将其导出&#xff0c;导出成功之后才能使用它。注意 export 文件是只写文件&#xff…

【C++】C/C++内存管理

目录 1. C/C内存分布 2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 3. C内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 3.3new空间申请错误的处理 4. operator new与operator delete函数 4.1 operator new与operator d…

2024年10款专业的PDF合并工具帮你实现高效办公。

合并 PDF 文件还是有很多的优点的&#xff0c;比如能够方便查阅和管理&#xff0c;无需文件之间来回切换&#xff1b;方便系统的整理和分析文件&#xff1b;可以优化阅读体验并且节省存储空间等等。为了帮助大家进行文件合并&#xff0c;我还搜集了一些好用的工具在这里分享。 …

【JavaEE初阶 — 多线程】认识线程

目录 认识线程&#xff08;Thread&#xff09; 1 线程是什么? 2 为什么要有线程 3 进程和线程的区别 区别一 区别二 区别三 区别四 4. Java的线程和操作系统线程的关系 5.创建第一个多线程程序 引入Thread类 重写run() start()与run()区别 降低多线程对CPU的占用…

SpringBoot技术在商场应急管理中的创新应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

第三十四章 Vue路由进阶之声明式导航(导航高亮)

目录 一、导航高亮 1.1. 基于语法 1.2. 主要代码 二、声明式导航的两个类名 2.1. 声明式导航类名匹配方式 2.2. 声明式导航类名样式自定义 ​2.3. 核心代码 一、导航高亮 1.1. 基于语法 在Vue中通过VueRouter插件&#xff0c;我们可以非常简单的实现实现导航高亮效果…

基于Multisim数控直流稳压电源电路(含仿真和报告)

【全套资料.zip】数控直流稳压电源电路设计Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 1.输出直流电压调节范围5-12V。 2.输出电流0-500mA。 3.输出直流电压能步进调节&#xff0c;步…

3D Gaussian Splatting 入门

1 摘要 3D Gaussian Splatting是一种将点云表示为高斯分布&#xff08;Gaussian Distributions&#xff09;的方法&#xff0c;用于3D重建、渲染等领域。这种方法通过在3D空间中对点云进行参数化&#xff0c;使得每个点不仅有位置&#xff08;XYZ坐标&#xff09;&#xff0c;还…

快速遍历包含合并单元格的Word表格

Word中的合并表格如下&#xff0c;现在需要根据子类&#xff08;例如&#xff1a;果汁&#xff09;查找对应的品类&#xff0c;如果这是Excel表格&#xff0c;那么即使包含合并单元格&#xff0c;也很容易处理&#xff0c;但是使用Word VBA进行查找&#xff0c;就需要一些技巧。…

微服务系列四:热更新措施与配置共享

目录 前言 一、基于Nacos的管理中心整体方案 二、配置共享动态维护 2.1 分析哪些配置可拆&#xff0c;需要动态提供哪些参数 2.2 在nacos 分别创建共享配置 创建jdbc相关配置文件 创建日志相关配置文件 创建接口文档配置文件 2.3 拉取本地合并配置文件 2.3.1 拉取出现…

抖音短剧小程序上线:短视频平台的全新娱乐体验

抖音短剧小程序的开发是一个结合了创意与技术的过程&#xff0c;旨在通过简洁而富有吸引力的方式&#xff0c;向用户提供高质量的短剧内容。随着移动互联网的快速发展&#xff0c;短视频平台成为了人们日常生活中不可或缺的一部分&#xff0c;而短剧作为一种新兴的内容形式&…