当前位置: 首页 > ops >正文

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;	
}
  1. 创建目录
  2. 创建设备节点
  3. 挂载虚拟文件系统
  4. 安装 Linux 驱动 ko 模块
  5. 初始化并启动 console
  6. 读取 fstab 分区信息并解析
  7. 根据 fstab 分区信息创建设备,创建逻辑分区
  8. 挂载 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;
}
  1. 重定向操作
  2. 捕获异常信号
  3. 设置 selinux 日志接口
  4. 设置系统 selinux 的工作模式加载 selinux 安全上下文策略
  5. 进入第二阶段

2.3 第二阶段

  1. 准备阶段,重定向,消息捕获等准备工作
  2. Property 属性服务初始化,加载系统中的各种属性文件及上下文
  3. 挂载文件系统
  4. 关键路径的 selinux 上下文恢复
  5. Property 属性服务的启动
  6. 加载和解析系统启动脚本 init.rc
  7. 主任务阶段 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);}......
}
  1. GetConfiguration 获取设备上的 ueventd.rc 文件并解析
  2. 创建用于处理设备节点权限配置的 DeviceHandler,并入列到 uevent_handlers
  3. 创建用于固件加载路径的 FirmwareHandler,并入列到 uevent_handlers
  4. 创建用于处理设备模块相关的事件 ModaliasHandler,并入列到 uevent_handlers
  5. 冷启动相关处理
  6. 监听内核 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);}
}
http://www.xdnf.cn/news/19060.html

相关文章:

  • 解决使用OSS的multipartUpload方法上传大文件导致内存溢出的问题
  • 设计模式-行为型模式-命令模式
  • 【编号513】2025年全国地铁矢量数据
  • 从混乱到高效:ITSM软件如何重塑企业IT管理的新格局
  • 淘宝四个月造了一个超越美团的“美团”
  • 对接印度股票市场 数据源API
  • 逻辑漏洞 跨站脚本漏洞(xss)
  • 滚珠导轨如何赋能精密制造?
  • 【技术分享】系统崩溃后产生的CHK文件如何恢复?完整图文教程(附工具推荐)
  • 采用机器学习的苗期棉株点云器官分割与表型信息提取
  • DINOv3:自监督视觉模型的新里程碑!
  • Matlab实现基于CPO-QRCNN-BiGRU-Attention注意力多变量时间序列区间预测
  • Rust:所有权
  • 音视频学习(五十九):H264中的SPS
  • 使用STM32CubeMX使用CAN驱动无刷电机DJI3508
  • VisualStudio 将xlsx文件嵌入到资源中访问时变String?
  • HTML 和 JavaScript 关联的基础教程
  • LeetCode 刷题【56. 合并区间】
  • Linux - 中文显示乱码问题解决方法(编码查看及转换)- 学习/实践
  • 【Spring Cloud微服务】6.通信的利刃:深入浅出 Spring Cloud Feign 实战与原理
  • 智能体开发:学习与实验 ReAct
  • web端播放flv视频流demo(flv.js的使用)
  • API 月度更新汇总:ONLYOFFICE 协作空间文档
  • 【RAG Agent实战】告别“单线程”RAG:用查询理解与LangGraph构建能处理复杂意图的高级代理
  • WPF+IOC学习记录
  • 学习Java30天(tcp的多开客户端和bs架构以及java高级)
  • 群核科技--SpatialGen
  • 毕马威 —— 公众对人工智能的信任、态度及使用情况调查
  • OpenHarmony设备使用统计深度实战:从数据埋点到信息采集的全链路方案
  • matlab利用模糊算法控制PID参数实现模糊控制