熊掌号做网站推广的注意事项,wordpress分类排序号,网站建设ppt答辩,电商平台建设实施方案前言
本文将会介绍 Android 启动流程#xff0c;将基于 Android 10 代码逻辑介绍原生启动过程。
bootloader 上电 - 加载 recovery 镜像或者 boot 镜像 - linux kernel 启动 - 加载 init 进程 - 加载 zygote 进程 - systemserver 进程 - 系统启动
…前言
本文将会介绍 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?spm1001.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 were 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 : \libcfs \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_UEVENTS0,-Wall,-Wextra,-Wno-unused-parameter,-Werror,-DALLOW_LOCAL_PROP_OVERRIDE0,-DALLOW_PERMISSIVE_SELINUX0,-DREBOOT_BOOTLOADER_ON_PANIC0,-DWORLD_WRITABLE_KMSG0,-DDUMP_ON_UMOUNT_FAILURE0,-DSHUTDOWN_ZERO_TIMEOUT0,],product_variables: {debuggable: {cppflags: [-UALLOW_LOCAL_PROP_OVERRIDE,-DALLOW_LOCAL_PROP_OVERRIDE1,-UALLOW_PERMISSIVE_SELINUX,-DALLOW_PERMISSIVE_SELINUX1,-UREBOOT_BOOTLOADER_ON_PANIC,-DREBOOT_BOOTLOADER_ON_PANIC1,-UWORLD_WRITABLE_KMSG,-DWORLD_WRITABLE_KMSG1,-UDUMP_ON_UMOUNT_FAILURE,-DDUMP_ON_UMOUNT_FAILURE1,],},eng: {cppflags: [-USHUTDOWN_ZERO_TIMEOUT,-DSHUTDOWN_ZERO_TIMEOUT1,],},uml: {cppflags: [-DUSER_MODE_LINUX],},},static_libs: [libseccomp_policy,libavb,libcfs,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::vectorstd::pairstd::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 well let the rc file figure out the rest.// 设置 linux 最基本的文件系统并且挂载到 / 目录(init ram disk)上,// 并给 0755 权限即用户具有读/写/执行权限组用户和其它用户具有读写权限后续会通过 rc 文件处理一些分区权限和进程CHECKCALL(mount(tmpfs, /dev, tmpfs, MS_NOSUID, mode0755));// /将/dev设置为tmpfs并挂载设置0755权限tmpfs是在内存上建立的文件系统FilesystemCHECKCALL(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, hidepid2,gid MAKE_STR(AID_READPROC)));
#undef MAKE_STR// Dont 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,mode0755,uid0,gid1000));// /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,mode0755,uid0,gid0));// /debug_ramdisk is used to preserve additional files from the debug ramdiskCHECKCALL(mount(tmpfs, /debug_ramdisk, tmpfs, MS_NOEXEC | MS_NOSUID | MS_NODEV,mode0755,uid0,gid0));
#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_ptrDIR, 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 wont 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,// 原理都是对二进制文件进行签名在系统启动时进行认证确保系统运行的是合法的二进制镜像文件。其中认证的范围涵盖bootloaderboot.imgsystem.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_castchar**(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_castchar**(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来创建逻辑分区 systemsystem_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();// Were 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_castchar**(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();}// inits first stage cant 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 trues 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::vectorconst 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 dont 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 childrens 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.xxxxx 的形式设置成相应的属性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 trues 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.controllerfe800000.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_rcxxx 指定的 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 its 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) - ResultSuccess {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// wasnt ready immediately after wait_for_coldboot_doneam.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, MixHwrngIntoLinuxRng);// Initialize binder before bringing up other system servicesam.QueueBuiltinAction(InitBinder, InitBinder);// Dont 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::optionalstd::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 theres a process that needs restarting, wake up in time for that.if (next_process_action_time) {epoll_timeout std::chrono::ceilstd::chrono::milliseconds(*next_process_action_time - boot_clock::now());if (*epoll_timeout 0ms) epoll_timeout 0ms;}}// If theres 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 namepathname [ argument ]*optionoptionname表示此服务的名称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 为 rootsecond_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_uniqueServiceParser(service_list, subcontexts));// 添加 action 解析器传入 action_manager 管理 rc 中定义的 action 动作。parser.AddSectionParser(on, std::make_uniqueActionParser(action_manager, subcontexts));// 添加 import 解析器解析 rc 中的 import 功能。parser.AddSectionParser(import, std::make_uniqueImportParser(parser));return parser;
}
对于 LoadBootScripts() 函数的功能我们可以分为两个步骤
步骤一初始化解析 Parser
对于 rc 文件的解析实际是通过 Parser 类完成的。调用 CreateParser 函数创建了一个 Parser 对象该对象用于解析我们的 rc 文件
通过 AddSectionParser 函数给 parser 添加了 ServiceParser、ActionParser、ImportParser 三个成员
ServiceParser 用于解析 init.rc 中定义的 ServiceActionParser 用于解析 init.rc 中定义的 ActionImportParser 用于解析 init.rc 中的 import 语句
步骤二寻找 rc 文件并调用解析类函数进行解析
LoadBootScripts 函数接下来会优先加载 bootargs 中 androidboot.init_rcxxx 指定的 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::mapstd::string, std::string property_triggers_;// 事件 triggerstd::string event_trigger_;// 命令std::vectorCommand 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::setstd::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::vectorgid_t supp_gids_; std::optionalCapSet capabilities_;unsigned namespace_flags_;// Pair of namespace type, path to namespace.std::vectorstd::pairint, std::string namespaces_to_enter_;// selinux 安全上下文std::string seclabel_;std::vectorstd::unique_ptrDescriptorInfo descriptors_;std::vectorstd::pairstd::string, std::string environment_vars_;Action onrestart_; // Commands to execute on restart.std::vectorstd::string writepid_files_;std::setstd::string interfaces_; // e.g. some.package.foo1.0::IBaz/instance-name// keycodes for triggering this service via /dev/input/input*std::vectorint 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::vectorstd::pairint, rlimit rlimits_;bool sigstop_ false;std::chrono::seconds restart_period_ 5s;std::optionalstd::chrono::seconds timeout_period_;bool updatable_ false;std::vectorstd::string args_;std::vectorstd::functionvoid(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 triggerproperty trigger 表示属性控制的 triggerevent 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_ptrDIR, 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::vectorstd::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::vectorstd::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中如果是行结束标志则根据第一个单词来判断是否是一个sectionsection的标志只有三个on,“service”,“import”如果是section,则调用相应的ParseSection()来处理一个新的section,否则把这一行继续作为前“section”所属的行来处理。
ParseSection()被用来解析一个新的sectionParseLineSection()被用来解析该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 文件的解析服务的介绍等。