目录
- 按下电源键时,android做了啥?
- init进程解析
- FirstStageMain
- SetupSelinux
- SecondStageMain
- init.rc 解析
按下电源键时,android做了啥?
当我们按下电源键时,手机开始上电,并从地址0x00000000处开始执行,而这个地址通常是Bootloader程序的首地址。
bootloader是一段裸机程序,是直接与硬件打交道的,其最终目的是“初始化并检测硬件设备,准备好软件环境,最后调用操作系统内核”。除此之外,bootloader还有保护功能,部分品牌的手机对bootloader做了加锁操作,防止boot分区和recovery分区被写入。
或许有人会问了,什么是boot分区,什么又是recovery分区?
我们先来认识一下Android系统的常见分区:
/boot
这个分区上有Android的引导程序,包括内核和内存操作程序。没有这个分区设备就不能被引导。恢复系统的时候会擦除这个分区,并且必须重新安装引导程序和ROM才能重启系统。
/recovery
recovery分区被认为是另一个启动分区,你可以启动设备进入recovery控制台去执行高级的系统恢复和管理操作。
/data
这个分区保存着用户数据。通讯录、短信、设置和你安装的apps都在这个分区上。擦除这个分区相当于恢复出厂设置,当你第一次启动设备的时候或者在安装了官方或者客户的ROM之后系统会自动重建这个分区。当你执行恢复出厂设置时,就是在擦除这个分区。
/cache
这个分区是Android系统存储频繁访问的数据和app的地方。擦除这个分区不影响你的个人数据,当你继续使用设备时,被擦除的数据就会自动被创建。
/apex
Android Q新增特性,将系统功能模块化,允许系统按模块来独立升级。此分区用于存放apex 相关的内容。
为什么需要bootloader去拉起linux内核,而不把bootloader这些功能直接内置在linux内核中呢?这个问题不在此做出回答,留给大家自行去思考。
bootloader完成初始化工作后,会载入 /boot 目录下面的 kernel,此时控制权转交给操作系统。操作系统将要完成的存储管理、设备管理、文件管理、进程管理、加载驱动等任务的初始化工作,以便进入用户态。
内核启动完成后,将会寻找init文件(init文件位于/system/bin/init),启动init进程,也就是android的第一个进程。
我们来关注一下内核的common/init/main.c中的kernel_init方法。
static int __ref kernel_init(void *unused) { ... if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; } if (CONFIG_DEFAULT_INIT[0] != '\0') { ret = run_init_process(CONFIG_DEFAULT_INIT); if (ret) pr_err("Default init %s failed (error %d)\n",CONFIG_DEFAULT_INIT, ret); else return 0; } 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; }
可以看到,在init_kernel的最后,会调用run_init_process方法来启动init进程。
static int run_init_process(const char *init_filename){ const char *const *p; argv_init[0] = init_filename; return kernel_execve(init_filename, argv_init, envp_init); }
kernel_execve是内核空间调用用户空间的应用程序的函数。
接下来我们来重点分析init进程。
init进程解析
我们从system/core/init/main.cpp 这个文件开始看起。
int main(int argc, char** argv) { #if __has_feature(address_sanitizer) __asan_set_error_report_callback(AsanReportCallback); #endif 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); } if (!strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); } if (!strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); } } return FirstStageMain(argc, argv); }
第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数。
main函数有四个参数入口:
- 一是参数中有ueventd,进入ueventd_main
- 二是参数中有subcontext,进入InitLogging 和SubcontextMain
- 三是参数中有selinux_setup,进入SetupSelinux
- 四是参数中有second_stage,进入SecondStageMain
main的执行顺序如下:
- FirstStageMain 启动第一阶段
- SetupSelinux 加载selinux规则,并设置selinux日志,完成SELinux相关工作
- SecondStageMain 启动第二阶段
- ueventd_main init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
FirstStageMain
我们来从FirstStageMain的源码看起,源码位于/system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) { boot_clock::time_point start_time = boot_clock::now(); #define CHECKCALL(x) \ if (x != 0) errors.emplace_back(#x " failed", errno); // Clear the umask. umask(0); //初始化系统环境变量 CHECKCALL(clearenv()); CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1)); // 挂载及创建基本的文件系统,并设置合适的访问权限 CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); CHECKCALL(mkdir("/dev/pts", 0755)); CHECKCALL(mkdir("/dev/socket", 0755)); CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL)); #define MAKE_STR(x) __STRING(x) CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC))); #undef MAKE_STR // 不要将原始命令行公开给非特权进程 CHECKCALL(chmod("/proc/cmdline", 0440)); gid_t groups[] = {AID_READPROC}; CHECKCALL(setgroups(arraysize(groups), groups)); 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))); } //创建linux随机伪设备文件 CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8))); CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))); //log wrapper所必须的,需要在ueventd运行之前被调用 CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2))); CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))); ... //将内核的stdin/stdout/stderr 全都重定向/dev/null,关闭默认控制台输出 SetStdioToDevNull(argv); // tmpfs已经挂载到/dev上,同时我们也挂载了/dev/kmsg,我们能够与外界开始沟通了 //初始化内核log 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!"; auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir}; if (!old_root_dir) { PLOG(ERROR) << "Could not opendir("/"), not freeing ramdisk"; } struct stat old_root_info; ... //挂载 system、cache、data 等系统分区 if (!DoFirstStageMount()) { LOG(FATAL) << "Failed to mount required partitions early ..."; } ... //进入下一步,SetupSelinux const char* path = "/system/bin/init"; const char* args[] = {path, "selinux_setup", nullptr}; execv(path, const_cast<char**>(args)); return 1; }
我们来总结一下,FirstStageMain到底做了哪些重要的事情:
- 挂载及创建基本的文件系统,并设置合适的访问权限
- 关闭默认控制台输出,并初始化内核级log。
- 挂载 system、cache、data 等系统分区
SetupSelinux
这个模块主要的工作是设置SELinux安全策略,本章内容主要聚焦于android的启动流程,selinux的内容在此不做展开。
int SetupSelinux(char** argv) { ... const char* path = "/system/bin/init"; const char* args[] = {path, "second_stage", nullptr}; execv(path, const_cast<char**>(args)); return 1; }
SetupSelinux的最后,进入了init的第二阶段SecondStageMain。
SecondStageMain
不多说,先上代码。
int SecondStageMain(int argc, char** argv) { // 禁止OOM killer 结束该进程以及它的子进程 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(); } // 启用全局Seccomp,Seccomp是什么请自行查阅资料 GlobalSeccomp(); // 设置所有进程都能访问的会话密钥 keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); // 创建 /dev/.booting 文件,就是个标记,表示booting进行中 close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); //初始化属性的服务,并从指定文件读取属性 property_init(); ... // 进行SELinux第二阶段并恢复一些文件安全上下文 SelinuxSetupKernelLogging(); SelabelInitialize(); SelinuxRestoreContext(); //初始化Epoll,android这里对epoll做了一层封装 Epoll epoll; if (auto result = epoll.Open(); !result) { PLOG(FATAL) << result.error(); } //epoll 中注册signalfd,主要是为了创建handler处理子进程终止信号 InstallSignalFdHandler(&epoll); ... //epoll 中注册property_set_fd,设置其他系统属性并开启系统属性的服务 StartPropertyService(&epoll); MountHandler mount_handler(&epoll); ... ActionManager& am = ActionManager::GetInstance(); ServiceList& sm = ServiceList::GetInstance(); //解析init.rc等文件,建立rc文件的action 、service,启动其他进程,十分关键的一步 LoadBootScripts(am, sm); ... am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups"); //执行rc文件中触发器为 on early-init 的语句 am.QueueEventTrigger("early-init"); // 等冷插拔设备初始化完成 am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng"); am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits"); am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict"); // 设备组合键的初始化操作 Keychords keychords; am.QueueBuiltinAction( [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> { for (const auto& svc : ServiceList::GetInstance()) { keychords.Register(svc->keycodes()); } keychords.Start(&epoll, HandleKeychord); return Success(); }, "KeychordInit"); am.QueueBuiltinAction(console_init_action, "console_init"); // 执行rc文件中触发器为on 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 // wasn't ready immediately after wait_for_coldboot_done am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng"); am.QueueBuiltinAction(InitBinder, "InitBinder"); // 当设备处于充电模式时,不需要mount文件系统或者启动系统服务,充电模式下,将charger设为执行队列,否则把late-init设为执行队列 std::string bootmode = GetProperty("ro.bootmode", ""); if (bootmode == "charger") { am.QueueEventTrigger("charger"); } else { am.QueueEventTrigger("late-init"); } // 基于属性当前状态 运行所有的属性触发器. am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers"); while (true) { //开始进入死循环状态 auto epoll_timeout = std::optional<std::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())) { am.ExecuteOneCommand(); } if (!(waiting_for_prop || Service::is_exec_service_running())) { if (!shutting_down) { auto next_process_action_time = HandleProcessActions(); // If there's a process that needs restarting, wake up in time for that. if (next_process_action_time) { epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>( *next_process_action_time - boot_clock::now()); if (*epoll_timeout < 0ms) epoll_timeout = 0ms; } } // If there's 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; }
总结一下,第二阶段做了以下这些比较重要的事情:
- 初始化属性的服务,并从指定文件读取属性
- 初始化epoll,并注册signalfd和property_set_fd,建立和init的子进程以及部分服务的通讯桥梁
- 初始化设备组合键,使系统能够对组合键信号做出响应
- 解析init.rc文件,并按rc里的定义去启动服务
- 开启死循环,用于接收epoll的事件
在第二阶段,我们需要重点关注以下问题:
init进程是如何通过init.rc配置文件去启动其他的进程的呢?
init.rc 解析
我们从 LoadBootScripts(am, sm)这个方法开始看起,一步一部来挖掘init.rc 的解析流程。
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { //初始化ServiceParse、ActionParser、ImportParser三个解析器 Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { //bootscript为空,进入此分支 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 { parser.ParseConfig(bootscript); } }
我们可以看到这句话,Parse开始解析init.rc文件,在深入下去之前,让我们先来认识一下init.rc。
parser.ParseConfig("/init.rc")
init.rc是一个可配置的初始化文件,负责系统的初步建立。它的源文件的路径为 /system/core/rootdir/init.rc
。
init.rc文件有着固定的语法,由于内容过多,限制于篇幅的原因,在此另外单独开了一篇文章进行讲解:
Android 10 启动分析之init语法
了解了init.rc的语法后,我们来看看init.rc文件里的内容。
import /init.environ.rc //导入全局环境变量 import /init.usb.rc //adb 服务、USB相关内容的定义 import /init.${ro.hardware}.rc //硬件相关的初始化,一般是厂商定制 import /vendor/etc/init/hw/init.${ro.hardware}.rc import /init.usb.configfs.rc import /init.${ro.zygote}.rc //定义Zygote服务
我们可以看到,在/system/core/init目录下,存在以下四个zygote相关的文件
怎样才能知道我们当前的手机用的是哪个配置文件呢?
答案是通过adb shell getprop | findstr ro.zygote
命令,看看${ro.zygote}
这个环境变量具体的值是什么,笔者所使用的华为手机的ro.zygote
值如下所示:
什么是Zygote,Zygote的启动过程是怎样的,它的启动配置文件里又做了啥,在这里我们不再做进一步探讨, 只需要知道init在一开始在这个文件中对Zygote服务做了定义,而上述的这些问题将留到 启动分析之Zygote篇
再去说明。
on early-init # Disable sysrq from keyboard write /proc/sys/kernel/sysrq 0 # Set the security context of /adb_keys if present. restorecon /adb_keys # Set the security context of /postinstall if present. restorecon /postinstall mkdir /acct/uid # memory.pressure_level used by lmkd chown root system /dev/memcg/memory.pressure_level chmod 0040 /dev/memcg/memory.pressure_level # app mem cgroups, used by activity manager, lmkd and zygote mkdir /dev/memcg/apps/ 0755 system system # cgroup for system_server and surfaceflinger mkdir /dev/memcg/system 0550 system system start ueventd # Run apexd-bootstrap so that APEXes that provide critical libraries # become available. Note that this is executed as exec_start to ensure that # the libraries are available to the processes started after this statement. exec_start apexd-bootstrap
紧接着是一个Action,Action的Trigger 为early-init,在这个 Action中,我们需要关注最后两行,它启动了ueventd服务和apex相关服务。还记得什么是ueventd和apex吗?不记得的读者请往上翻越再自行回顾一下。
ueventd服务的定义也可以在init.rc文件的结尾找到,具体代码及含义如下:
service ueventd //ueventd服务的可执行文件的路径为 /system/bin/ueventd class core //ueventd 归属于 core class,同样归属于core class的还有adbd 、console等服务 critical //表明这个Service对设备至关重要,如果Service在四分钟内退出超过4次,则设备将重启进入恢复模式。 seclabel u:r:ueventd:s0 //selinux相关的配置 shutdown critical //ueventd服务关闭行为
然而,early-init 这个Trigger到底什么时候触发呢?
答案是通过init.cpp代码调用触发。
我们可以在init.cpp 代码中找到如下代码片段:
am.QueueEventTrigger("early-init");
QueueEventTrigger这个方法的实现机制我们稍后再进行探讨,目前我们只需要了解, ActionManager
这个类中的 QueueEventTrigger
方法,负责触发init.rc中的Action。
我们继续往下看init.rc的内容。
on init ... # Start logd before any other services run to ensure we capture all of their logs. start logd # Start essential services. start servicemanager ...
在Trigger 为init的Action中,我们只需要关注以上的关键内容。在init的action中启动了一些核心的系统服务,这些服务具体的含义为 :
接下来是late-init Action:
on late-init //启动vold服务(管理和控制Android平台外部存储设备,包括SD插拨、挂载、卸载、格式化等) trigger early-fs trigger fs trigger post-fs trigger late-fs //挂载/data , 启动 apexd 服务 trigger post-fs-data # 读取持久化属性或者从/data 中读取并覆盖属性 trigger load_persist_props_action //启动zygote服务!!在启动zygote服务前会先启动netd服务(专门负责网络管理和控制的后台守护进程) trigger zygote-start //移除/dev/.booting 文件 trigger firmware_mounts_complete trigger early-boot trigger boot //初始化网络环境,设置系统环境和守护进程的权限
最后,我们用流程图来总结一下上述的启动过程:
以上就是Android 10 启动Init进程解析的详细内容,更多关于Android 10 启动Init进程的资料请关注自由互联其它相关文章!