PC启动一般会通过BIOS或者EFI引导程序启动,Android一般作为移动设备,没有PC的BIOS或者EFI,取而代之的是BootLoader。
按下电源键CPU上电完成后,会从固定地址加载一段程序,就是BootLoader,不通的CPU可能地址段会有差异,BootLoader是一段引导程序,常见的就是U-boot。
U-boot程序启动后,一般会先检测是否同时按下了触发U-boot功能的按键,比如进入Recovery模式等, 这些按键一般是在U-boot中事先编码好的。
如果没有按下这些按键,U-boot会从nand flash中装载linux内核,装载的地址也是在编译uboot时预定好的。linux内核启动后,会创建第一个进程:idle。
idle进程是linux进程启动后创建的第一个进程,其pid=0,idle进程被创建后会创建init进程和kthreadd进程,然后idle会在cpu没有任务时进行空转。
kthreadd被创建后,其pid=2,运行在内核空间,负责所有内核线程的调度和管理。
init进程是linux系统的第一个用户进程,其pid=1,是系统所有用户进程的根进程,其源码在/system/core/init/main.cpp中,以main方法为入口:
using namespace android::init;
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#endif
// Boost prio which will be restored later
setpriority(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的第一阶段,会调用FirstStageMain函数,通过命令搜索FirstStageMain函数所在文件:
//输入以下搜索命令,然后点击Enter
grep FirstStageMain ./ -rn
搜索结果如下:
./first_stage_main.cpp:20: return android::init::FirstStageMain(argc, argv);
./first_stage_init.h:22:int FirstStageMain(int argc, char** argv);
./main.cpp:78: return FirstStageMain(argc, argv);
./first_stage_init.cpp:183:int FirstStageMain(int argc, char** argv) {
可以知道是在./first_stage_init.cpp第183行,输入如下命令定位到函数所在行
vi ./first_stage_init.cpp +183
FirstStageMain函数中可以看到,会先对文件系统进行一系列的权限设定,然后通过执行execv再次调用init,传入参数selinux_setup,进入到init的第二阶段
int FirstStageMain(int argc, char** argv) {
...
// Clear the umask.
//权限掩码设置为0,则都是111,其他授权时最终的规则就是:(xxx & ~掩码 = 最终权限)
umask(0);
//文件系统授权
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'll let the rc file figure out the rest.
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mkdir("/dev/dm-user", 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
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
// Don't expose the raw bootconfig to unprivileged processes.
chmod("/proc/bootconfig", 0440);
std::string bootconfig;
android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
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));
...
//通过execv再次执行init,传入参数selinux_setup,进入到init的第二阶段
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(args));
}
再次回看/system/core/init/main.cpp源码,由于本次传入的参数是selinux_setup,结合代码可以看到,会进入到SetupSelinux这个函数中
using namespace android::init;
int main(int argc, char** 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);
}
}
...
}
通过命令搜索
grep SetupSelinux ./ -rn
搜索结果如下
./selinux.cpp:730:int SetupSelinux(char** argv) {
./main.cpp:70: return SetupSelinux(argv);
./selinux.h:23:int SetupSelinux(char** argv);
使用命令定位到行
vi ./selinux.cpp +730
代码如下,此阶段主要是启动Selinux安全机制,初始化selinux,加载SELinux规则和配置SELinux相关log输出,然后通过执行execv再次调用init,传入参数selinux_setup,进入到init的第三阶段
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";
...
LoadSelinuxPolicy(policy);
if (snapuserd_helper) {
snapuserd_helper->FinishTransition();
snapuserd_helper = nullptr;
}
SelinuxSetEnforcement();
setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);
//通过execv再次执行init,传入参数second_stage,进入到init的第三阶段
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast(args));
...
return 1;
}
再次回看/system/core/init/main.cpp源码,本次传入的参数是second_stage,结合代码可以看到,会进入到SecondStageMain这个函数中,同样的方式,搜索
grep SecondStageMain ./ -rn
搜索结果
./init.h:47:int SecondStageMain(int argc, char** argv);
./main.cpp:74: return SecondStageMain(argc, argv);
./init.cpp:742:int SecondStageMain(int argc, char** argv) {
使用命令定位到行
vi ./init.cpp +742
代码如下,此阶段主要是初始化了一些属性和解析init.rc,以及守护服务
int SecondStageMain(int argc, char** argv) {
...
// Indicate that booting is in progress to background fw loaders, etc.
//设置启动中标记
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
...
//初始化系统属性
PropertyInit();
...
// Mount extra filesystems required during second stage init
//挂载额外的文件系统
MountExtraFilesystems();
// Now set up SELinux for second stage.
//设置SElinux相关
SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();
//使用epoll注册信号处理函数,守护进程服务
Epoll epoll;
if (auto result = epoll.Open(); !result.ok()) {
PLOG(FATAL) << result.error();
}
InstallSignalFdHandler(&epoll);
InstallInitNotifier(&epoll);
//启动系统属性服务
StartPropertyService(&property_fd);
...
//设置commands指令对应的函数map,用于在后面解析init.rc文件中的指令查找
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);
...
//解析init.rc脚本
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
...
while (true) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional{};
...
//执行从init.rc脚本解析出来的每条指令
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
}
...
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout = 0ms;
}
auto pending_functions = epoll.Wait(epoll_timeout);
if (!pending_functions.ok()) {
LOG(ERROR) << pending_functions.error();
} else if (!pending_functions->empty()) {
// We always reap children before responding to the other pending functions. This is to
// prevent a race where other daemons see that a service has exited and ask init to
// start it again via ctl.start before init has reaped it.
//子进程退出后的处理
ReapAnyOutstandingChildren();
for (const auto& function : *pending_functions) {
(*function)();
}
}
...
}
return 0;
}
其他的可先不做分析,重点分析一下两点:
1.先看一下GetBuiltinFunctionMap函数做了什么,源码在/system/core/init/builtins.cpp,这个函数保存了指令与函数的对应关系,当发起key的调用时,会调用value对应的函数。
// Builtin-function-map start
const BuiltinFunctionMap& GetBuiltinFunctionMap() {
constexpr std::size_t kMax = std::numeric_limits::max();
// clang-format off
static const BuiltinFunctionMap builtin_functions = {
{"bootchart", {1, 1, {false, do_bootchart}}},
{"chmod", {2, 2, {true, do_chmod}}},
{"chown", {2, 3, {true, do_chown}}},
{"class_reset", {1, 1, {false, do_class_reset}}},
{"class_reset_post_data", {1, 1, {false, do_class_reset_post_data}}},
{"class_restart", {1, 1, {false, do_class_restart}}},
{"class_start", {1, 1, {false, do_class_start}}},
{"class_start_post_data", {1, 1, {false, do_class_start_post_data}}},
{"class_stop", {1, 1, {false, do_class_stop}}},
{"copy", {2, 2, {true, do_copy}}},
{"copy_per_line", {2, 2, {true, do_copy_per_line}}},
{"domainname", {1, 1, {true, do_domainname}}},
{"enable", {1, 1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
{"exec_start", {1, 1, {false, do_exec_start}}},
{"export", {2, 2, {false, do_export}}},
{"hostname", {1, 1, {true, do_hostname}}},
{"ifup", {1, 1, {true, do_ifup}}},
{"init_user0", {0, 0, {false, do_init_user0}}},
{"insmod", {1, kMax, {true, do_insmod}}},
{"installkey", {1, 1, {false, do_installkey}}},
{"interface_restart", {1, 1, {false, do_interface_restart}}},
{"interface_start", {1, 1, {false, do_interface_start}}},
{"interface_stop", {1, 1, {false, do_interface_stop}}},
{"load_exports", {1, 1, {false, do_load_exports}}},
{"load_persist_props", {0, 0, {false, do_load_persist_props}}},
{"load_system_props", {0, 0, {false, do_load_system_props}}},
{"loglevel", {1, 1, {false, do_loglevel}}},
{"mark_post_data", {0, 0, {false, do_mark_post_data}}},
{"mkdir", {1, 6, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {0, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
{"perform_apex_config", {0, 0, {false, do_perform_apex_config}}},
{"umount", {1, 1, {false, do_umount}}},
{"umount_all", {0, 1, {false, do_umount_all}}},
{"update_linker_config", {0, 0, {false, do_update_linker_config}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"remount_userdata", {0, 0, {false, do_remount_userdata}}},
{"restart", {1, 1, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
{"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}},
{"rm", {1, 1, {true, do_rm}}},
{"rmdir", {1, 1, {true, do_rmdir}}},
{"setprop", {2, 2, {true, do_setprop}}},
{"setrlimit", {3, 3, {false, do_setrlimit}}},
{"start", {1, 1, {false, do_start}}},
{"stop", {1, 1, {false, do_stop}}},
{"swapon_all", {0, 1, {false, do_swapon_all}}},
{"enter_default_mount_ns", {0, 0, {false, do_enter_default_mount_ns}}},
{"symlink", {2, 2, {true, do_symlink}}},
{"sysclktz", {1, 1, {false, do_sysclktz}}},
{"trigger", {1, 1, {false, do_trigger}}},
{"verity_update_state", {0, 0, {false, do_verity_update_state}}},
{"wait", {1, 2, {true, do_wait}}},
{"wait_for_prop", {2, 2, {false, do_wait_for_prop}}},
{"write", {2, 2, {true, do_write}}},
};
// clang-format on
return builtin_functions;
}
// Builtin-function-map end
2.init.rc中的服务是如何被启动的
首先以ActionManager和ServiceList通过GetInstance分别创建单例,然后调用LoadBootScripts函数传入参数,LoadBootScripts代码如下,可以看到会先去ro.boot.init_rc中读取配置,如果为空,会进入解析init.rc的流程,否则解析配置
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
//创建解析器
Parser parser = CreateParser(action_manager, service_list);
//获取ro.boot.init_rc配置
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
//解析init.rc
parser.ParseConfig("/system/etc/init/hw/init.rc");
//如果init分区没有挂载,添加到late_import_paths
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");
//如果vender分区没有挂载,添加到late_import_paths
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
//如果odm分区没有挂载,添加到late_import_paths
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
//如果product分区没有挂载,添加到late_import_paths
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
} else {
//解析ro.boot.init_rc
parser.ParseConfig(bootscript);
}
}
init.rc文件是用Android Init Language编写的特殊文件,使用这种语法编写的文件,统一用.rc作为文件后缀,其语法指令规则可参考源码system/core/init/README.md文档,其中包含Commands、Options等,
Commands
定义在system/core/init/builtins.cpp
中
Options
定义在system/core/init/service_parser.cpp
中
后续会单独篇幅说明这个语法规则,本篇暂不展开说明。
init.rc文件在源码中的位置:system/core/rootdir/init.rc
回到SecondStageMain方法中,am.ExecuteOneCommand()函数会执行从.rc文件中解析出来的指令,跟一下这个函数,函数在./action_manager.cpp第67行:
void ActionManager::ExecuteOneCommand() {
{
auto lock = std::lock_guard{event_queue_lock_};
// Loop through the event queue until we have an action to execute
while (current_executing_actions_.empty() && !event_queue_.empty()) {
for (const auto& action : actions_) {
if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
event_queue_.front())) {
current_executing_actions_.emplace(action.get());
}
}
event_queue_.pop();
}
}
if (current_executing_actions_.empty()) {
return;
}
//从队列头获得一个action
auto action = current_executing_actions_.front();
if (current_command_ == 0) {
std::string trigger_name = action->BuildTriggersString();
LOG(INFO) << "processing action (" << trigger_name << ") from (" << action->filename()
<< ":" << action->line() << ")";
}
//调用到action的ExecuteOneCommand中,current_command_代表行数
action->ExecuteOneCommand(current_command_);
// If this was the last command in the current action, then remove
// the action from the executing list.
// If this action was oneshot, then also remove it from actions_.
++current_command_;
if (current_command_ == action->NumCommands()) {
current_executing_actions_.pop();
current_command_ = 0;
if (action->oneshot()) {
auto eraser = [&action](std::unique_ptr& a) { return a.get() == action; };
actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser),
actions_.end());
}
}
}
会调用到action->ExecuteOneCommand()函数,继续跟./action.cpp:146行:
void Action::ExecuteOneCommand(std::size_t command) const {
// We need a copy here since some Command execution may result in
// changing commands_ vector by importing .rc files through parser
Command cmd = commands_[command];
//继续跟
ExecuteCommand(cmd);
}
void Action::ExecuteCommand(const Command& command) const {
android::base::Timer t;
//执行了InvokeFunc
auto result = command.InvokeFunc(subcontext_);
auto duration = t.duration();
// Any action longer than 50ms will be warned to user as slow operation
if (!result.has_value() || duration > 50ms ||
android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
std::string trigger_name = BuildTriggersString();
std::string cmd_str = command.BuildCommandString();
LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name << " (" << filename_
<< ":" << command.line() << ") took " << duration.count() << "ms and "
<< (result.ok() ? "succeeded" : "failed: " + result.error().message());
}
}
调用到command.InvokeFunc()函数中,看看做了什么:
Result Command::InvokeFunc(Subcontext* subcontext) const {
//从vendor 或 oem 解析出来的rc文件都会走这里,会有一定限制
if (subcontext) {
if (execute_in_subcontext_) {
return subcontext->Execute(args_);
}
auto expanded_args = subcontext->ExpandArgs(args_);
if (!expanded_args.ok()) {
return expanded_args.error();
}
return RunBuiltinFunction(func_, *expanded_args, subcontext->context());
}
//原生init.rc文件命令会走这里
return RunBuiltinFunction(func_, args_, kInitContext);
}
Result RunBuiltinFunction(const BuiltinFunction& function,
const std::vector& args, const std::string& context) {
auto builtin_arguments = BuiltinArguments(context);
builtin_arguments.args.resize(args.size());
builtin_arguments.args[0] = args[0];
for (std::size_t i = 1; i < args.size(); ++i) {
auto expanded_arg = ExpandProps(args[i]);
if (!expanded_arg.ok()) {
return expanded_arg.error();
}
builtin_arguments.args[i] = std::move(*expanded_arg);
}
//执行这个函数
return function(builtin_arguments);
}
function就是上面第一点提到的,当触发map中元素key调用时,会对应调用value对应的函数,这里以init.rc中一段脚本举例,看一下服务是如何启动的:
on zygote-start && ...
...
start statsd
start netd
start zygote
start zygote_secondary
结合上面map中的key-value对应关系,发现start对应着do_start
函数,源码位置/system/core/init/builtins.cpp:
static Result do_start(const BuiltinArguments& args) {
//通过传入的arg,查找对应服务
Service* svc = ServiceList::GetInstance().FindService(args[1]);
if (!svc) return Error() << "service " << args[1] << " not found";
//通过调用start执行
if (auto result = svc->Start(); !result.ok()) {
return ErrorIgnoreEnoent() << "Could not start service: " << result.error();
}
return {};
}
通过命令搜索
grep "::Start()" ./ -rn
搜索结果
./service.cpp:397:Result Service::Start() {
函数如下
Result Service::Start() {
...
pid_t pid = -1;
//通过克隆或者fork子进程
if (namespaces_.flags) {
pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
} else {
pid = fork();
}
//子进程创建成功
if (pid == 0) {
//设置权限掩码
umask(077);
if (auto result = EnterNamespaces(namespaces_, name_, override_mount_namespace);
!result.ok()) {
LOG(FATAL) << "Service '" << name_
<< "' failed to set up namespaces: " << result.error();
}
for (const auto& [key, value] : environment_vars_) {
setenv(key.c_str(), value.c_str(), 1);
}
for (const auto& descriptor : descriptors) {
descriptor.Publish();
}
if (auto result = WritePidToFiles(&writepid_files_); !result.ok()) {
LOG(ERROR) << "failed to write pid to files: " << result.error();
}
if (task_profiles_.size() > 0 && !SetTaskProfiles(getpid(), task_profiles_)) {
LOG(ERROR) << "failed to set task profiles";
}
// As requested, set our gid, supplemental gids, uid, context, and
// priority. Aborts on failure.
SetProcessAttributesAndCaps();
//内部调用的execv来启动的程序
if (!ExpandArgsAndExecv(args_, sigstop_)) {
PLOG(ERROR) << "cannot execv('" << args_[0]
<< "'). See the 'Debugging init' section of init's README.md for tips";
}
_exit(127);
}
if (pid < 0) {
pid_ = 0;
return ErrnoError() << "Failed to fork";
}
...
return {};
}
追一下ExpandArgsAndExecv函数:
static bool ExpandArgsAndExecv(const std::vector& args, bool sigstop) {
std::vector expanded_args;
std::vector c_strings;
expanded_args.resize(args.size());
c_strings.push_back(const_cast(args[0].data()));
for (std::size_t i = 1; i < args.size(); ++i) {
auto expanded_arg = ExpandProps(args[i]);
if (!expanded_arg.ok()) {
LOG(FATAL) << args[0] << ": cannot expand arguments': " << expanded_arg.error();
}
expanded_args[i] = *expanded_arg;
c_strings.push_back(expanded_args[i].data());
}
c_strings.push_back(nullptr);
if (sigstop) {
kill(getpid(), SIGSTOP);
}
//有没有很熟悉,最终还是通过执行execv来执行程序文件的
return execv(c_strings[0], c_strings.data()) == 0;
}
先通过克隆或者fork子进程,然后调用execv函数启动程序文件,由此一个服务就启动起来了。
至此一个完整的init进程启动服务就分析完成了,下一篇分析一下zygote被启动起来时做了哪些事情!