Android init 进程部分理论
1. Android 系统的启动流程
Android init 进程是系统开机启动后执行的用户空间的第一个进程,它是从 Linux 内核启动的第一个进程,并在整个系统生命周期内始终存在。Init 进程的主要职责是加载系统的配置,启动系统服务,初始化系统。并且管理系统的状态。它在系统启动过程中起着关键作用,是其它进程的父进程。
1.1 init 进程的作用
- 启动系统的各个服务:通过启动 Zygote ⇒ SystemServer ⇒ 系统各种 Services(AMS、PMS、WMS、IMS 等)
- 配置系统环境变量和属性
- 管理用户空间进程的启动
- 初始化硬件设备和挂载文件系统
- 处理系统的各种启动和运行时事件
2. init 进程流程
2.1 第一阶段
init 进程的第一阶段由 kernel 拉起。这里还是 vendor 域的。
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)__hwasan_set_error_report_callback(AsanReportCallback);
#endif// Boost prio which will be restored latersetpriority(PRIO_PROCESS, 0, -20);if (!strcmp(basename(argv[0]), "ueventd")) {return ueventd_main(argc, argv);}if (argc > 1) {if (!strcmp(argv[1], "subcontext")) {android::base::InitLogging(argv, &android::base::KernelLogger);const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();return SubcontextMain(argc, argv, &function_map);}if (!strcmp(argv[1], "selinux_setup")) {return SetupSelinux(argv);}if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}return FirstStageMain(argc, argv);
}
init 启动第一阶段,会创建一些目录。然后执行 /system/bin/init selinux_setup
。又回到开始的 main 入口,参数是 selinux_setup。
int FirstStageMain(int argc, char** argv) {......const char* path = "/system/bin/init";const char* args[] = {path, "selinux_setup", nullptr};auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);close(fd);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;
}
- 创建目录
- 创建设备节点
- 挂载虚拟文件系统
- 安装 Linux 驱动 ko 模块
- 初始化并启动 console
- 读取 fstab 分区信息并解析
- 根据 fstab 分区信息创建设备,创建逻辑分区
- 挂载 system 分区
2.2 SELinux 相关初始化
int main(int argc, char** argv) {......if (!strcmp(argv[1], "selinux_setup")) {return SetupSelinux(argv);}......
}
int SetupSelinux(char** argv) {SetStdioToDevNull(argv);InitKernelLogging(argv);if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();MountMissingSystemPartitions();SelinuxSetupKernelLogging();LOG(INFO) << "Opening SELinux policy";PrepareApexSepolicy();// Read the policy before potentially killing snapuserd.std::string policy;ReadPolicy(&policy);CleanupApexSepolicy();auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();if (snapuserd_helper) {// Kill the old snapused to avoid audit messages. After this we cannot// read from /system (or other dynamic partitions) until we call// FinishTransition().snapuserd_helper->StartTransition();}LoadSelinuxPolicy(policy);if (snapuserd_helper) {// Before enforcing, finish the pending snapuserd transition.snapuserd_helper->FinishTransition();snapuserd_helper = nullptr;}// This restorecon is intentionally done before SelinuxSetEnforcement because the permissions// needed to transition files from tmpfs to *_contexts_file context should not be granted to// any process after selinux is set into enforcing mode.if (selinux_android_restorecon("/dev/selinux/", SELINUX_ANDROID_RESTORECON_RECURSE) == -1) {PLOG(FATAL) << "restorecon failed of /dev/selinux failed";}SelinuxSetEnforcement();// 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";}setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);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 日志接口
- 设置系统 selinux 的工作模式加载 selinux 安全上下文策略
- 进入第二阶段
2.3 第二阶段
- 准备阶段,重定向,消息捕获等准备工作
- Property 属性服务初始化,加载系统中的各种属性文件及上下文
- 挂载文件系统
- 关键路径的 selinux 上下文恢复
- Property 属性服务的启动
- 加载和解析系统启动脚本 init.rc
- 主任务阶段 while(true)
2.3.1 PropertyInit 解读
int SecondStageMain(int argc, char** argv) {......PropertyInit();......StartPropertyService(&property_fd);......
}
void PropertyInit() {selinux_callback cb;cb.func_audit = PropertyAuditCallback;selinux_set_callback(SELINUX_CB_AUDIT, cb);mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);CreateSerializedPropertyInfo();if (__system_property_area_init()) {LOG(FATAL) << "Failed to initialize property area";}if (!property_info_area.LoadDefaultPath()) {LOG(FATAL) << "Failed to load serialized property info file";}// If arguments are passed both on the command line and in DT,// properties set in DT always have priority over the command-line ones.ProcessKernelDt();ProcessKernelCmdline();ProcessBootconfig();// Propagate the kernel variables to internal variables// used by init as well as the current required properties.ExportKernelBootProps();PropertyLoadBootDefaults();
}
2.3.2 属性服务端的启动 StartPropertyService 以及通信架构梳理
int SecondStageMain(int argc, char** argv) {......PropertyInit();......StartPropertyService(&property_fd);......
}
void StartPropertyService(int* epoll_socket) {InitPropertySet("ro.property_service.version", "2");int sockets[2];if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {PLOG(FATAL) << "Failed to socketpair() between property_service and init";}*epoll_socket = from_init_socket = sockets[0];init_socket = sockets[1];StartSendingMessages();if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,/*passcred=*/false, /*should_listen=*/false, 0666, /*uid=*/0,/*gid=*/0, /*socketcon=*/{});result.ok()) {property_set_fd = *result;} else {LOG(FATAL) << "start_property_service socket creation failed: " << result.error();}listen(property_set_fd, 8);auto new_thread = std::thread{PropertyServiceThread};property_service_thread.swap(new_thread);auto async_persist_writes =android::base::GetBoolProperty("ro.property_service.async_persist_writes", false);if (async_persist_writes) {persist_write_thread = std::make_unique<PersistWriteThread>();}
}
2.3.3 init 进程启动处理内核 uevent 事件
- ueventd_main
int main(int argc, char** argv) {......if (!strcmp(basename(argv[0]), "ueventd")) {return ueventd_main(argc, argv);}......
}
- GetConfiguration 获取设备上的 ueventd.rc 文件并解析
- 创建用于处理设备节点权限配置的 DeviceHandler,并入列到 uevent_handlers
- 创建用于固件加载路径的 FirmwareHandler,并入列到 uevent_handlers
- 创建用于处理设备模块相关的事件 ModaliasHandler,并入列到 uevent_handlers
- 冷启动相关处理
- 监听内核 uevent 事件,遍历调用上述 handler 进行处理
示例场景
假设系统中有一个新接入的设备,例如一个 USB 网卡。网卡驱动需要动态加载一个内核模块。以下是具体流程。
- 第一步:系统接入新设备
当新设备接入时,内核会通过 uevent 生成一个热插拔事件,事件内容示例如下:
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1d.7/usb1/1-1
SUBSYSTEM=usb
DEVTYPE=usb_device
MODALIAS=usb:v0BDAp8176d0200dc00dsc00dp00icFFiscFFipFF
其中,MODALIAS 字段值为:usb:v0BDAp8176d0200dc00dsc00dp00icFFiscFFipFF。这个 MODALIAS 描述了 USB 设备的厂商 ID(v0BDA)、产品 ID(p8176)等信息,用于匹配内核模块。
- 第二步:ueventd 捕获事件
ueventd 监听到来自内核的这个 uevent,并将事件传递给所有注册的处理器(包括 ModaliasHandler)。 - 第三步:ModaliasHandler 处理 MODALIAS
ModaliasHandler 解析 MODALIAS 字段的值,并根据其内容决定加载哪个内核模块。它会在配置的 base_paths 路径查找对应的模块文件。例如
/odm/lib/modules/usb:v0BDAp8176d0200dc00dsc00dp00icFFiscFFipFF.ko
/vendor/lib/modules/usb:v0BDAp8176d0200dc00dsc00dp00icFFiscFFipFF.ko
- 第四步:加载内核模块
如果找到匹配的模块文件(例如 /vendor/lib/modules/rtl8188eu.ko),ModaliasHandler 调用内核接口加载改模块。成功加载后,设备驱动可以使用模块提供的功能。
内核 (kernel)│设备发现/热插拔 (uevent)│▼┌───────────────────────┐│ Uevent Daemon ││ (ueventd / init 等) │└───────────────────────┘│▼┌───────────────────────────┐│ ModaliasHandler ││ HandleUevent(uevent) │└───────────────────────────┘│if (uevent.modalias != "")│▼┌───────────────────────────┐│ modprobe_.LoadWithAliases │└───────────────────────────┘│查找 modules.alias 映射│▼┌───────────────────────────┐│ insmod / modprobe 执行 ││ 加载对应内核模块 .ko │└───────────────────────────┘│▼驱动生效 (device ready)
3. init.rc 程序运行配置脚本
3.1 init.rc 的组成及存储位置
- Android Init 语言由以下五大类语句组成:动作(Actions)、命令(Commands)、服务(Services)、选项(Options)和导入(Imports)。这些语句以行为单位,由通过空格分隔的标记(tokens)组成。前三个最为重要,需要掌握。(这里跳过语句的讲解,请自行搜索学习)
- init 语言用于普通文本文件,这些文件通常以 .rc 为后缀。系统中通常会有多个此类文件,存储在不同的位置。
- /system/etc/init/hw/init.rc 是主要的 .rc 文件,在 init 可执行文件启动时首先加载。它负责系统的初始设置。init 在加载主文件 /system/etc/init/hw/init.rc 后,会立即加载以下目录中的所有 rc 文件:/{system,system_ext,vendor,odm,product}/etc/init/*.rc。
3.2 init.rc 文件常见指令
常见 on 触发器关键字及其含义
on boot
含义:系统启动时触发。
用途:通常用于执行系统启动时的初始化任务,例如挂载文件系统、设置环境变量、启动守护进程等。
on early-init
含义:init 进程早期初始化阶段触发。
用途:用于设置早期启动环境,通常执行对系统至关重要的初始化命令,例如 SELinux 设置。
on init
含义:init 进程初始化完成后触发。
用途:执行系统基础配置任务,例如挂载根文件系统。
on post-fs
含义:文件系统挂载后触发。
用途:用于处理需要访问文件系统的初始化任务。
on post-fs-data
含义:文件系统挂载并解密数据分区后触发。
用途:执行依赖于数据分区的初始化任务。
on late-init
含义:init 初始化的后期阶段触发。
用途:执行非关键性任务,例如初始化辅助服务或启动子系统。
on property:<property>=<value>
含义:当指定的属性设置为特定值时触发。
用途:用于属性驱动的任务,例如当某个系统属性发生变化时启动或停止某些服务。
on fs
含义:挂载文件系统时触发。
用途:处理特定文件系统的挂载配置。
on charger
含义:设备进入充电模式时触发。
用途:执行与充电模式相关的初始化任务。
on userdata
含义:用户数据分区已挂载且可用时触发。
用途:执行依赖用户数据的初始化操作。
on boot_completed
含义:系统启动完成后触发。
用途:执行用户空间服务初始化或其他非关键任务。
on restart
含义:当某个服务重启时触发。
用途:执行与服务重启相关的任务。
on shutdown
含义:系统关闭时触发。
用途:处理系统关闭时的清理任务。
on power
含义:设备的电源事件(如插入或拔出充电器)触发。
用途:处理与电源相关的任务。
on <event>(自定义事件)
含义:用户可以定义其他自定义触发器。
用途:允许灵活地定义触发条件,响应特定的系统状态或事件。service 服务名称 对应服务可执行程序路径
3.3 init.rc 的解析器及执行流程
int SecondStageMain(int argc, char** argv) {.......ActionManager& am = ActionManager::GetInstance();ServiceList& sm = ServiceList::GetInstance();LoadBootScripts(am, sm);......
}
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {Parser parser = CreateParser(action_manager, service_list);std::string bootscript = GetProperty("ro.boot.init_rc", "");if (bootscript.empty()) {parser.ParseConfig("/system/etc/init/hw/init.rc");if (!parser.ParseConfig("/system/etc/init")) {late_import_paths.emplace_back("/system/etc/init");}// late_import is available only in Q and earlier release. As we don't// have system_ext in those versions, skip late_import for system_ext.parser.ParseConfig("/system_ext/etc/init");if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init");}if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init");}if (!parser.ParseConfig("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init");}} else {parser.ParseConfig(bootscript);}
}