Boot Overview#
The first boot process of Android, with init's PID being 1, parses init.rc to construct the system's initial operating state, after which other systems will start sequentially.
Power On & System Boot
When the power is turned on, it will load the program (BootLoader) from a predetermined location (PC reads ROM) into RAM and begin execution.
BootLoader
The BootLoader is a small program that runs before the Android system starts, primarily responsible for bringing up and running the system OS.
Linux Kernel Boot
When the kernel starts, it sets up caches, protects storage, schedules lists, loads drivers, etc. After the kernel completes system setup, it will look for the init.rc file in the system and start the init process.
Init Process Startup
This process mainly initializes and starts the system property service and is also used to start the Zygote process.
Launcher App (Desktop Application)
Init Process Overview#
As the first process to start the Android system, init parses init.rc to sequentially start key system service processes, which include three key service processes:
ServiceManager (similar to DNS, used to find service handles in SystemServer)
Zygote (initial process, loads necessary Android resources, starts the virtual machine, etc.)
SystemServer (starts system-level services, such as AMS, WMS, etc.)
The init process will start the Zygote and SystemServer processes, while Zygote listens for AMS via Socket, and ServiceManager communicates using Binder.
Creates and mounts the necessary file directories for startup.
Initializes and starts essential services.
Parses the init.rc configuration file and starts the Zygote process.
Init Process - main.cpp Analysis#
// /init/main.cpp
#include "builtins.h"
#include "first_stage_init.h"
#include "init.h"
#include "selinux.h"
#include "subcontext.h"
#include "ueventd.h"
using namespace android::init;
... omitted parts
int main(int argc, char** argv) {
... omitted parts
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20);
if (!strcmp(basename(argv[0]), "ueventd")) { // ueventd is responsible for creating device nodes, permissions, anonymous shared memory, etc.
return ueventd_main(argc, argv);
}
if (argc > 1) {
if (!strcmp(argv[1], "subcontext")) {
// Initialize Logger
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
return SubcontextMain(argc, argv, &function_map);
}
// SELinux security-related
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// Init process (second stage)
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// Start init process (first stage)
return FirstStageMain(argc, argv);
}
First Stage of Init Process - first_stage_init#FirstStageMain
File Mounting#
The first stage mainly involves 1. creating and mounting related files, listing several important file systems here, detailed functions can be found in comments, 2. setting input parameters, 3. executing execv function (reloading main.cpp with different parameters).
The mounted files are all in memory (RAM) and are physical files.
Command Used | Mount Directory/File Name, File Management | Features | Description |
---|---|---|---|
mount | tmpfs | Exists in RAM and is not persistent (named tmp) | A virtual memory file system that stores all files in virtual memory; once tmpfs is unmounted, all internal data will disappear. |
mount | /dev/devpts | Dynamically mounts devpts | Provides a standard for pseudo-terminals; whenever the pty master device /dev/ptmx is opened, a new pty device file will be dynamically created under /dev/pts . |
mount | proc | Can modify kernel parameters at runtime | A virtual file system that can be seen as an interface to the kernel's internal data structures. |
mount | sysfs | Introduced in Linux 2.6 kernel (mainly for hardware-related parameters, which relate to devpts devices) | A virtual file system typically mounted at /sys. |
// first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers(); // Restart Boot Loader on init error
}
// Record boot time
boot_clock::time_point start_time = boot_clock::now();
// Record list of errors
std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x) \
if ((x) != 0) errors.emplace_back(#x " failed", errno);
// Clear umask, files and folders created afterwards will have full permissions
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.
// mount is used to mount the tmpfs file system
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
// Create folders and assign permissions
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)
// Mount proc
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// Don't expose the raw command line 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);
// 8.0 added user groups
gid_t groups[] = {AID_READPROC};
// Add groups to the current process's device
CHECKCALL(setgroups(arraysize(groups), groups));
// Mount sys, using sysfs to manage documents
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
// Create nodes (mknod) for output Log
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)));
}
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
// This is needed for log wrapper, which gets called before ueventd runs.
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
// See storage config details at http://source.android.com/devices/storage/
// Mount /mnt/{vendor,product}, using tmpfs to manage
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000"));
// /mnt/vendor is used to mount vendor
CHECKCALL(mkdir("/mnt/vendor", 0755));
// /mnt/product is used to mount product-specific partitions that cannot be
// part of the product partition, e.g., because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /second_stage_resources is used to preserve files from first to second
// stage init
CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"))
#undef CHECKCALL
SetStdioToDevNull(argv);
InitKernelLogging(argv);
// List error messages
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";
}
... omitted parts
return 1;
}
Mount Specific Partition Devices
// first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
... omitted parts
// Mount specific partition devices
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
... omitted parts
}
SELinux Related Work
// first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
... omitted parts
struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}
// Initialize security framework: Android Verified Boot & AVB mainly used to prevent system files from being tampered with
// Also prevents system rollback functionality
SetInitAvbVersionInRecovery();
setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
1);
... omitted parts
}
Starting the Init Process via execv's SecondStageMain Method
// first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
... omitted parts
// Set init process parameters
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
// Binder open (log message)
auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
// Pass the above args
// Start the init process, passing the parameter "selinux_setup"
execv(path, const_cast<char**>(args)); // Start the init process's SecondStageMain method via execv
}
Init Process Interlude SE#
selinux.cpp#SetupSelinux
Before the second stage, SE settings will be performed.
After the first_stage_init#FirstStageMain
file mounting, the execv function will restart the main.cpp process, entering the main.cpp#main
method again.
// /init/main.cpp file
#include "builtins.h"
#include "first_stage_init.h"
#include "init.h"
#include "selinux.h"
#include "subcontext.h"
#include "ueventd.h"
using namespace android::init;
... omitted parts
// Upon re-entry, argv contains the string selinux_setup
int main(int argc, char** argv) {
... omitted parts
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20);
// Check input parameters
if (!strcmp(basename(argv[0]), "ueventd")) { // ueventd is responsible for creating device nodes, permissions, anonymous shared memory, etc.
return ueventd_main(argc, argv);
}
// When the number of parameters > 1
if (argc > 1) {
if (!strcmp(argv[1], "subcontext")) {
// Initialize Log
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
return SubcontextMain(argc, argv, &function_map);
}
// SELinux security-related
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// Init process (second stage)
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// Start init process (first stage)
return FirstStageMain(argc, argv);
}
SetupSelinux mainly loads the SE policy, and after loading SE is complete, it will set new parameters and restart main.cpp via execv (as explained in the previous section).
// /init/selinux.cpp
int SetupSelinux(char** argv) {
SetStdioToDevNull(argv);
// 1. Initialize core Log
InitKernelLogging(argv);
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
boot_clock::time_point start_time = boot_clock::now();
// 2. Mount missing system blocks
MountMissingSystemPartitions();
SelinuxSetupKernelLogging();
LOG(INFO) << "Opening SELinux policy";
PrepareApexSepolicy();
// Read the policy before potentially killing snapuserd.
std::string policy;
// Read 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();
}
// Load SE policy
LoadSelinuxPolicy(policy);
... omitted parts
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
// Restart the process
execv(path, const_cast<char**>(args));
return 1;
}
Second Stage of Init Process#
init.cpp#SecondStageMain Parsing init.rc
After the initialization and mounting of devices in first_stage_init, the main.cpp class's SecondStageMain function will be executed (which can be seen as the second stage of the init process).
// /init/main.cpp file
#include "builtins.h"
#include "first_stage_init.h"
#include "init.h"
#include "selinux.h"
#include "subcontext.h"
#include "ueventd.h"
using namespace android::init;
... omitted parts
// Upon re-entry, argv contains the string second_stage
int main(int argc, char** argv) {
... omitted parts
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20);
// Check input parameters
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (argc > 1) {
if (!strcmp(argv[1], "subcontext")) {
// Initialize Log
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
return SubcontextMain(argc, argv, &function_map);
}
// SELinux security-related
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// Init process (second stage)
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// Start init process (first stage)
return FirstStageMain(argc, argv);
}
Begin analyzing init.cpp#SecondStageMain
, divided into 10 stages, detailed in comments.
Initialize property domains, mainly performing the PropertyInit function.
// init/init.cpp
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers(); // Restart Boot Loader on init failure
}
// Boot time
boot_clock::time_point start_time = boot_clock::now();
trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
// Redirect standard input, output, and error to the null device file `/dev/null`
SetStdioToDevNull(argv);
// Initialize kernel log system
InitKernelLogging(argv);
// Initialize system $PATH
if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";
}
// The init process does not depend on other processes, so it should not crash,
// thus the function to handle signals is set to null here.
{
// sigaction is for signals
struct sigaction action = {.sa_flags = SA_RESTART};
action.sa_handler = [](int) {};
// Ignore SIGPIPE signal
sigaction(SIGPIPE, &action, nullptr); // Set SIGPIPE to null
}
// Set init and its forked children's oom_adj.
if (auto result =
WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
!result.ok()) {
LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
<< " to /proc/1/oom_score_adj: " << result.error();
}
// Set up a session keyring that all processes will have access to. It
// will hold things like FBE encryption keys. No process should override
// its session keyring.
// Call syscall to set related parameters
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
// Indicate that booting is in progress to background fw loaders, etc.
// Create /dev/.booting file, marking that booting is in progress
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
... omitted parts
PropertyInit(); // Implemented in init/property_service.cpp
... omitted parts
return 0;
}
Clear environment variables: Clear previously written environment variables from system properties.
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
// Clear previously written environment variables from system properties
// Clean up our environment.
unsetenv("INIT_AVB_VERSION");
unsetenv("INIT_FORCE_DEBUGGABLE");
... omitted parts
}
SELinux related work
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
// Now set up SELinux for the second stage.
SelinuxSetupKernelLogging(); // Complete SELinux work for the second stage
SelabelInitialize(); // Register some processors
SelinuxRestoreContext(); // SELinux stored Context
... omitted parts
}
Create epoll event notifications
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
Epoll epoll;
// epoll.Open() creates child process I/O listener
if (auto result = epoll.Open(); !result.ok()) {
PLOG(FATAL) << result.error();
}
... omitted parts
}
Load child process signal handlers: init is a daemon process (can be understood as a daemon thread), with this process as the main one; if the init process ends, all other child processes will end.
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
// Create handler to process child process termination signals, register a signal to epoll listener (if there are changes, epoll will notify)
// epoll will receive notifications first, then read data using socket
// To prevent child processes of the init process from becoming zombie processes
InstallSignalFdHandler(&epoll);
InstallInitNotifier(&epoll);
... omitted parts
}
Start property service: the main function StartPropertyService starts a socket listening for property changes.
// /init/init.cpp
static int property_fd = -1;
int SecondStageMain(int argc, char** argv) {
... omitted parts
// Start other property services
StartPropertyService(&property_fd);
unsetenv("INIT_AVB_VERSION"); // Remove environment variable INIT_AVB_VERSION
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status(); // Ultimately decides the value of "ro.boot.flash.locked"
MountHandler mount_handler(&epoll);
// Set UDC Controller for USB storage (sys/class/udc)
SetUsbController();
... omitted parts
}
Match command & function correspondence: Cmd & Function Map, from this we can see which cmd instructions the system supports and how the instructions actually operate.
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
// Create Function & Cmd Map
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
// Save function_map object in Action
// Action::set_function_map, `::` is equivalent to static Function
Action::set_function_map(&function_map);
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
... omitted parts
}
LoadBootScripts: parse init.rc files.
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
// The object will be used to obtain analysis results
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// Parse scripts
LoadBootScripts(am, sm);
... omitted parts
}
// Parse .rc scripts
static void LoadBootScripts(ActionManager& action_manager,
ServiceList& service_list) {
// Create parser
Parser parser = CreateParser(action_manager, service_list);
// Pre-set to parse ro.boot.init_rc
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
// Parse specified init.rc file
parser.ParseConfig("/system/etc/init/hw/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
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);
}
}
Add trigger events: Here we can see some settings in the rc file, where common commands include 1 early-init, 2 init, 3 late-init.
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
// ActionManager parsing completed
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
// Trigger label early-init
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return {};
},
"KeychordInit");
// Trigger all the boot actions to get us started.
am.QueueEventTrigger("init");
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
... omitted parts
}
Enter a loop: Continuously process received commands & execute Services.
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... omitted parts
while(true) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
// Determine if a shutdown command has been received
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
HandlePowerctlMessage(*shutdown_command);
shutdown_state.set_do_shutdown(false);
}
... omitted parts
// No need to wait && Service is not running || Service has completed running
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
am.ExecuteOneCommand(); // Execute one cmd
}
... omitted parts
}
}