第4章:init进程的诞生——内核启动后第一个用户空间进程
大家好,我是你们的老朋友。今天我们来聊聊Android启动流程中一个关键节点——init进程的诞生。
说实话,我刚开始研究Android源码时,对init进程的理解也就停留在「它是第一个用户空间进程」这个层面。直到有一次我在调试一个开机异常问题,发现init进程启动后卡在了某个服务上,整个系统都起不来。那次经历让我意识到,init进程远不止「第一个进程」这么简单。
4.1 内核如何启动init进程
先说说背景。当Linux内核完成初始化后,它会做一件很重要的事——启动用户空间的第一个进程。这个进程就是init。
内核是怎么找到init的呢?其实很简单。内核在启动的最后阶段,会调用run_init_process()函数。它会按顺序尝试以下几个路径:
/sbin/init
/etc/init
/bin/init
/bin/sh
如果都找不到,内核就会panic,系统直接崩溃。嗯,这里要注意,Android系统用的是/init,而不是传统的/sbin/init。
内核启动init的代码在init/main.c中,核心逻辑大概是这样的:
static int __ref kernel_init(void *unused)
{
// ... 其他初始化工作 ...
// 尝试启动init进程
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
// 默认路径
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
// 尝试多个路径
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel.");
}
我个人习惯把这段代码称为「init进程的出生证明」。它清晰地展示了内核是如何一步步寻找init进程的。
4.2 init进程的启动流程
init进程启动后,它会做以下几件大事:
- 挂载文件系统:挂载proc、sysfs、devtmpfs等虚拟文件系统
- 创建设备节点:在/dev下创建必要的设备节点
- 解析init.rc:读取并执行init.rc配置文件
- 启动核心服务:如servicemanager、zygote等
- 进入事件循环:监听并处理各种事件
init进程的入口函数是main(),在system/core/init/init.cpp中。我截取一段关键代码:
int main(int argc, char** argv) {
// ... 参数解析 ...
// 挂载文件系统
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
// 初始化属性系统
property_init();
// 解析init.rc
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
Parser parser;
parser.ParseConfig("/init.rc");
// 执行early-init阶段
am.QueueEventTrigger("early-init");
// 进入事件循环
while (true) {
// 处理事件
am.ExecuteOneCommand();
// 重启死掉的进程
restart_processes();
// 等待事件
epoll::poll();
}
return 0;
}
你看,init进程的核心就是一个无限循环。它不断处理事件、重启服务、等待新事件。这就是Android系统能持续运行的基础。
核心要点:init进程本质上是一个事件驱动的守护进程。它不只是一个启动器,更是整个Android系统的「管家」。
4.3 init.rc配置文件的作用
init.rc是Android系统的「启动脚本」。它用Android特有的语言(.rc语言)描述了系统启动时需要做什么。
init.rc文件位于/init.rc,但实际内容是通过多个.rc文件拼接而成的。在Android 10之后,这些文件分布在/system/etc/init/、/vendor/etc/init/、/odm/etc/init/等目录下。
init.rc的核心语法包括:
| 语法元素 | 说明 | 示例 |
|---|---|---|
| Action | 定义在特定条件下执行的动作 | on boot |
| Service | 定义需要启动的服务 | service servicemanager |
| Command | 具体的执行命令 | start, mkdir, chmod |
| Option | 服务的配置选项 | class core, oneshot |
我举个例子,这是init.rc中启动servicemanager的部分:
# 在boot阶段启动核心服务
on boot
# 启动servicemanager
start servicemanager
start hwservicemanager
start vndservicemanager
# 定义servicemanager服务
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote
onrestart restart audioserver
onrestart restart media
这里有个细节值得注意:critical选项表示这个服务是关键的,如果它挂了,系统会重启。而onrestart则定义了当这个服务重启时,需要连带重启哪些服务。
避坑指南:我曾经在项目中遇到一个问题,修改了init.rc后系统起不来。排查了半天,发现是语法错误——少写了一个空格。init.rc对语法非常敏感,一个空格、一个换行都可能让整个系统崩溃。所以修改init.rc时,一定要用语法检查工具验证。
4.4 init进程的事件驱动模型
init进程采用epoll事件驱动模型。它会监听多种事件:
- 属性变化事件:当系统属性被设置时触发
- 信号事件:子进程退出时触发
- socket事件:其他进程通过socket通信时触发
- 定时器事件:定时执行某些操作
事件处理的流程是这样的:
// 伪代码,展示事件处理流程
while (true) {
// 1. 等待事件
int event_fd = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
// 2. 处理事件
for (int i = 0; i < event_fd; i++) {
if (events[i].data.fd == property_fd) {
// 处理属性变化
handle_property_set_fd();
} else if (events[i].data.fd == signal_fd) {
// 处理子进程退出
handle_signal();
} else if (events[i].data.fd == keychord_fd) {
// 处理按键事件
handle_keychord();
}
}
// 3. 执行待处理的action
am.ExecuteOneCommand();
// 4. 重启需要重启的服务
restart_processes_if_needed();
}
这种设计的好处是:init进程不会忙等,而是让CPU在空闲时进入休眠状态。当有事件发生时,epoll会立即唤醒init进程处理。
4.5 实际项目中的经验
我在做系统优化时,遇到过几个和init进程相关的坑:
- 服务启动顺序问题:有些服务依赖其他服务先启动。如果顺序不对,服务会启动失败。解决办法是在init.rc中显式声明依赖关系。
- oneshot服务重启:有些服务设置了
oneshot,意味着它只运行一次。如果它意外退出,init不会自动重启它。这在某些场景下会导致功能缺失。 - 属性系统阻塞:属性设置操作是同步的,如果某个属性设置卡住了,整个init进程都会阻塞。我曾经遇到过因为属性设置超时导致开机慢的问题。
警告:千万不要在init.rc中启动重量级应用或服务。init进程是单线程的,如果某个服务启动耗时太长,会阻塞后续所有服务的启动。我曾经见过有人把浏览器启动写进init.rc,结果开机时间从30秒变成了3分钟。
4.6 小结
init进程的诞生,标志着Android系统从内核态进入了用户态。它通过解析init.rc配置文件,构建了整个Android系统的运行框架。
说白了,init进程就是Android系统的「总指挥」。它负责拉起所有核心服务,监控它们的运行状态,并在必要时重启它们。没有init进程,Android系统就是一盘散沙。
下一章,我们会深入分析init.rc的解析过程,看看init是如何把那些配置指令变成实际操作的。到时候我会分享一个我调试init.rc解析bug的真实案例,很有意思。
公众号:蓝海资料掘金营,微信deep3321