启动过程
老是有在群里看到大佬们讨论Android系统的第一个始祖进程是init进程,对于有过多年开发经验的我,应用程序开发跟init进程是扯不上关系的,为了跟上大佬的脚步,不得不花时间看了一些列Android启动流程的文章,认认真真的跟着源码走了一遍,虽然对于实际开发应用没有什么提升,感觉也没啥鸟用,但走一遍确实对于整个Android的分层结构有了更深刻的认识,可以跟上群里大佬吹逼的脚步了,接下来就把我梳理的流程记录一下
从按开机键上电开始,Android在上电后会通过汇编指令去加载uboot引导程序,然后由uboot从分区中加载内核镜像,并启动内核,Android的内核使用的是Linux内核,内核这部分对于应用开发涉及不多,但是需要知道的是,Linux内核启动主要涉及到3个特殊的进程
idle进程(pid=0)->Linux系统第一个进程,是init进程和kthread进程的父进程
init进程(pid=1)-> 正式进入用户态的第一个用户进程,也是Android系统应用程序的第一个进程
kthreadd进程(pid=2)->这个还是Linux的内核态进程,系统内核的管家
Init(pid=1)用户空间的始祖进程,孵化出了zygote进程(zygote进程有兼容的64位和32位,所以当前手机是2个zygote进程),然后所有的用户的apps(类似微信,系统相册,照相机等)都是由zygote进程孵化产生的。
init进程
接下来分析init进程的启动流程
内核代码初始化会执行到kernel/common/init/main.c ->kernel_init()
static int __ref kernel_init(void *unused){
...
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") || //后续流程传入参数是"/bin/init"
!try_to_run_init_process("/bin/sh"))
return 0;
}
static int try_to_run_init_process(const char *init_filename)
{
int ret;
ret = run_init_process(init_filename); //执行run_init_process方法
return ret;
}
static int run_init_process(const char *init_filename)
{
//执行kernel_execve指令,这个指令就是内核空间去执行用户空间的程序
return kernel_execve(init_filename, argv_init, envp_init);
}
Android.bp
/system/core/init
执行用户空间的init,会执行init文件夹下面的Android.bp脚本文件,里面可以可以看到init的执行入口函数在main.cpp代码中
cc_binary {
name: "init_second_stage",
recovery_available: true,
stem: "init",
defaults: ["init_defaults"],
static_libs: ["libinit"],
required: [
"e2fsdroid",
"init.rc",
"mke2fs",
"sload_f2fs",
"make_f2fs",
"ueventd.rc",
],
srcs: ["main.cpp"],
symlinks: ["ueventd"],
target: {
recovery: {
cflags: ["-DRECOVERY"],
exclude_shared_libs: [
"libbinder",
"libutils",
],
},
},
}
main.cpp
init的代码位于/system/core/init 下面,找到main函数的执行逻辑
//C++主函数有两个参数,argc表示参数的个数,第二个是参数的列表,具体的参数
int main(int argc, char** argv) {
//strcmp是是String的函数,比较字符串
//basename是C的一个函数,得到特定路径中最后一个/后的内容
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);
}
从这段代码看,主要执行的流程就是FirstStageMain/SetupSelinux/SecondStageMain/SubcontextMain
- FirstStageMain
第一次执行main函数的时候,没有携带任何参数,所以会最先执行FirstStageMain方法
int FirstStageMain(int argc, char** argv) {
//挂载 创建 文件
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
...
//重定向 输入输出
SetStdioToDevNull(argv);
//初始化内核的日志打印
InitKernelLogging(argv);
//启动 selinux_setup
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));
return 1;
}
- SetupSelinux
接下来再次执行main.cpp中的main函数,传入了selinux_setup参数
int SetupSelinux(char** argv) {
//重复第一步里面的重定向输入输出和日志打印
SetStdioToDevNull(argv);
InitKernelLogging(argv);
......
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
return 1;
}
这部分的作用主要是Linux的安全策略,管理Android系统的权限,保证系统内核代码运行的稳定
- SecondStageMain
接下来再次执行main.cpp中的main函数,传入了second_stage参数
//$init.cpp
int SecondStageMain(int argc, char** argv) {
//再一次出现了这两个
SetStdioToDevNull(argv);
InitKernelLogging(argv);
......
//创建一块共享的内存空间,初始化属性域
PropertyInit();
......
Epoll epoll;
//处理子进程终止信号,杀死僵尸进程
//初始化子进程退出信号处理函数,并调用epoll_ctl设置 property fd可读的回调函数
InstallSignalFdHandler(&epoll);
InstallInitNotifier(&epoll);
//启动属性服务,调用epoll_ctl设置 property fd 可读的回调函数
StartPropertyService(&property_fd);
......
//匹配命令和函数之间的关系
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);
......
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
//解析init.rc
LoadBootScripts(am, sm);
......
//类似Looper循环,死循环进入等待
while (true) {
auto pending_functions = epoll.Wait(epoll_timeout);
}
return 0;
}
//解析init.rc文件
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
//创建解析器
Parser parser = CreateParser(action_manager, service_list);
......
parser.ParseConfig("/system/etc/init/hw/init.rc");
}
//.rc文件的语法里面就有service on import等关键字,创造一个解析器
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
parser.AddSectionParser("service", std::make_unique<ServiceParser>(
&service_list, GetSubcontext(), std::nullopt));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
//parse.cpp
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) {
return ParseConfigDir(path);
}
return ParseConfigFile(path);
}
bool Parser::ParseConfigDir(const std::string& path) {
for (const auto& file : files) {
if (!ParseConfigFile(file)) {
}
}
return true;
}
bool Parser::ParseConfigFile(const std::string& path) {
ParseData(path, &config_contents.value());
return true;
}
//解析init文件
void Parser::ParseData(const std::string& filename, std::string* data) {
for (;;) {
switch (next_token(&state)) {
case T_EOF:
return;
case T_NEWLINE: {
break;
}
case T_TEXT:
break;
}
}
}
//init.rc
import /system/etc/init/hw/init.${ro.zygote}.rc //${ro.zygote}由厂商定义,与平台相关
on late-init
# Now we can start zygote for devices with file based encryption
trigger zygote-start
on zygote-start && property:ro.crypto.state=unencrypted
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd //start 对应的映射关系定义于 /system/core/init/builtins.cpp中
start zygote //调用 start 对应的处理函数,启动名为 zygote 的服务 (传入init.zygote.rc中定义的参数)
start zygote_secondary
小结
init进程会去解析android.bp文件,从会找到执行入口是main.cpp的main()函数,而这一块会根据不同的入参分步进行初始化过程
第一步会创建挂载文件,重定向输入输出,初始化内容日志打印等
第二步会启动selinux安全策略
第三步会初始化一些属性域,处理僵尸进程,匹配native函数和内核命令的关系,解析init.rc文件,也会有一个类似Looper的epoll循环机制
解析init.rc文件里面会有start zygote命令,然后就会去执行zygote进程的启动流程,这部分在下一篇进行整理