Android is based on Linux, so Android startup is started by the Linux Kernel and init process is created. This process is the granddaddy of all user Spaces.
During the init process, servicemanager(binder servicemanager) and Zygote (Java process) are started successively. Zygote creates the system_server and app processes.
So you’ve probably heard the phrase: App processes are created by the Zygote process by fork.
Let me try to analyze the creation of the init process during Android startup.
The analysis was based on Android 10.0
init
The init process is the first process in Linux user space during Android startup. The init startup entry is in its SecondStageMain method. But the SecondStageMain method that calls init is done through the main method in main.cpp.
So we’ll start with the main method of main. CPP.
main
system/core/init/main.cpp
int main(int argc, char** argv) { #if __has_feature(address_sanitizer) __asan_set_error_report_callback(AsanReportCallback); #endif // create device nodes, permissions, etc. strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (argc > 1) {// Initialize the log system if (! strcmp(argv[1], "subcontext")) { android::base::InitLogging(argv, &android::base::KernelLogger); const BuiltinFunctionMap function_map; return SubcontextMain(argc, argv, &function_map); } // 2. Create an enhanced Linux if (! strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); } // 3. Parse init.rc file, provide service, create epoll and handle termination of child process, etc. strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); Return FirstStageMain(argc, argv); }Copy the code
In the main method of main. CPP, there are three main steps:
- FirstStageMain
- SetupSelinux
- SecondStageMain
FirstStageMain
system/core/init/first_stage_init.cpp
It is the first step to start the init process and its main task is to mount the associated file system
int FirstStageMain(int argc, char** argv) { if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } boot_clock::time_point start_time = boot_clock::now(); std::vector<std::pair<std::string, int>> errors; #define CHECKCALL(x) \ if (x ! = 0) errors.emplace_back(#x " failed", errno); // Clear the umask. umask(0); // create file system 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(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)); 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)); . . // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually // talk to the outside world... // Initialize the logging system InitKernelLogging(argv); . . Const char* path = "/system/bin/init"; const char* args[] = {path, "selinux_setup", nullptr}; execv(path, const_cast<char**>(args)); // execv() only returns if an error happened, in which case we // panic and never fall through this conditional. PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }Copy the code
Use mount to mount the file system, mkdir to create the corresponding file directory, and configure the corresponding access permission.
Note that these files exist only while the application is running and disappear with the application once the application is finished.
There are four types of mounted file systems:
tmpfs
: a virtual memory file system that stores all files in virtual memory. Due to thetmpfs
Is residing in theRAM
Therefore, its content is not durable. After power off,tmpfs
The content disappears, which is also known astmpfs
The root cause of…devpts
: provides a standard interface for the pseudo-terminal, and its standard hook point is/dev/pts
. As long aspty
The main composite equipment/dev/ptmx
When it’s opened, it’s in/dev/pts
Create a new one dynamicallypty
Device file.proc
: is also a virtual file system, which can be viewed as an interface to the kernel’s internal data structure, through which we can obtain system information and also modify specific kernel parameters at run time.sysfs
And:proc
Similarly, a file system is a virtual file system that does not occupy any disk space. It is usually hooked up to/sys
Directory.
The log logging system is also initialized at FirstStageMain via InitKernelLogging(argv). At this time, Android does not have its own system log, but uses the kernel log system. If the device node /dev/kmsg is opened, you can run cat /dev/kmsg to obtain the kernel log.
Finally, the corresponding path and the parameter selinux_setup for the next stage are passed through the execv method.
SetupSelinux
system/core/init/selinux.cpp
// This function initializes SELinux then execs init to run in the init SELinux context. int SetupSelinux(char** argv) { InitKernelLogging(argv); if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } // Set up SELinux, loading the SELinux policy. SelinuxSetupKernelLogging(); SelinuxInitialize(); // We're in the kernel domain and want to transition to the init domain. File systems that // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here, // but other file systems do. In particular, this is needed for ramdisks such as the // recovery image for A/B devices. if (selinux_android_restorecon("/system/bin/init", 0) == -1) { PLOG(FATAL) << "restorecon failed of /system/bin/init failed"; } const char* path = "/system/bin/init"; const char* args[] = {path, "second_stage", nullptr}; execv(path, const_cast<char**>(args)); // execv() only returns if an error happened, in which case we // panic and never return from this function. PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }Copy the code
It is used to improve Linux security and further restrict access permissions.
Finally, SecondStageMain, the core stage of init startup through execv.
SecondStageMain
system/core/init/init.cpp
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
...
...
// 初始化属性服务
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
process_kernel_cmdline();
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
// Make the time that init started available for bootstat to log.
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
// Set libavb version for Framework-only OTA match in Treble build.
const char* avb_version = getenv("INIT_AVB_VERSION");
if (avb_version) property_set("ro.boot.avb_version", avb_version);
// See if need to load debug props to allow adb root, when the device is unlocked.
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
// Clean up our environment.
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
unsetenv("INIT_FORCE_DEBUGGABLE");
// Now set up SELinux for second stage.
SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();
Epoll epoll;
if (auto result = epoll.Open(); !result) {
PLOG(FATAL) << result.error();
}
// 初始化single句柄
InstallSignalFdHandler(&epoll);
property_load_boot_defaults(load_debug_prop);
UmountDebugRamdisk();
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
// 开启属性服务
StartPropertyService(&epoll);
MountHandler mount_handler(&epoll);
set_usb_controller();
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
subcontexts = InitializeSubcontexts();
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 解析init.rc等相关文件
LoadBootScripts(am, sm);
// Turning this on and letting the INFO logging be discarded adds 0.2s to
// Nexus 9 boot time, so it's disabled by default.
if (false) DumpState();
// Make the GSI status available before scripts start running.
if (android::gsi::IsGsiRunning()) {
property_set("ro.gsid.image_running", "1");
} else {
property_set("ro.gsid.image_running", "0");
}
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
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(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");
// Trigger all the boot actions to get us started.
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");
// Initialize binder before bringing up other system services
am.QueueBuiltinAction(InitBinder, "InitBinder");
// 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");
while (true) {
// By default, sleep until something happens.
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;
}
Copy the code
There are four main steps in SecondStageMain
- Initialize the property service
- Initialize the single handle
- Enabling the Property Service
- Parse the. Rc file
Initialize the property service
system/core/init/property_service.cpp
void property_init() {
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
Copy the code
The main method is __system_property_area_init(), which is used to create cross-process memory
- perform
open
Open,dev/properities
Shared memory file, size is128KB
- perform
mmap
, maps memory toinit
process - Store the head address of this memory in a global variable
__system_property_area__
, subsequent additions or modifications to attributes are based on this variable.
Initialize the single handle
system/core/init/init.cpp
static void InstallSignalFdHandler(Epoll* epoll) { // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving // SIGCHLD when a child process stops or continues (b/77867680#comment9). const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP }; sigaction(SIGCHLD, &act, nullptr); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); if (! IsRebootCapable()) { // If init does not have the CAP_SYS_BOOT capability, it is running in a container. // In that case, receiving SIGTERM will cause the system to shut down. sigaddset(&mask, SIGTERM); } if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) { PLOG(FATAL) << "failed to block signals"; } // Register a handler to unblock signals in the child processes. const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals); if (result ! = 0) { LOG(FATAL) << "Failed to register a fork handler: " << strerror(result); } signal_fd = signalfd(-1, &mask, SFD_CLOEXEC); if (signal_fd == -1) { PLOG(FATAL) << "failed to create signalfd"; } if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); ! result) { LOG(FATAL) << result.error(); }}Copy the code
The init process is the parent process of all user-space processes and generates a signal when its child process terminates. For the parent process to process.
The main purpose is to prevent child processes from becoming zombie processes.
What is zombie progression?
Parent use the fork to create the child, the child process terminates, if the parent process does not know line process has been terminated, then the child has already been out, but in the system the process still retains some information for it (such as process, operation time, exit status, etc.), the child process is what is called a zombie process. The system process table is a limited resource. If it is exhausted by zombie processes, the system may not be able to create new processes.
The handle is registered with epoll and is finally called back by HandleSignalFd.
What is epoll?
In the new kernel of Linux, epoll is used to replace SELECT /poll, which is an improved version of the Linux kernel for handling mass file descriptors, and an enhanced version of the multiplex I/O interface select/poll under Linux. It can significantly improve system CPU utilization for applications with only a small amount of activity in a large number of concurrent connections.
Epoll uses red-black trees internally, so lookups are faster than select using arrays.
Enabling the Property Service
system/core/init/property_service.cpp
void StartPropertyService(Epoll* epoll) {
selinux_callback cb;
cb.func_audit = SelinuxAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
property_set("ro.property_service.version", "2");
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr);
if (property_set_fd == -1) {
PLOG(FATAL) << "start_property_service socket creation failed";
}
listen(property_set_fd, 8);
if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
PLOG(FATAL) << result.error();
}
}
Copy the code
The main tasks are:
- Create non-blocking
Socket
And returnproperty_set_fd
File descriptor. - use
listen()
Function to listenproperty_set_fd
At this time,Socket
That is, the attribute server, and it can be at most8
The user who tries to set the property provides the service. - use
epoll
To register, whenproperty_set_fd
When the data comes in,init
The process will callhandle_property_set_fd()
Function.
Parse the. Rc file
system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { 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); }}Copy the code
ParseConfig parses the init.rc configuration file.
The.rc file syntax is end-of-line, space-separated syntax, starting with # to represent comment lines. The. Rc file contains Action, Service, Command, Options, and Import files. The Action and Service names are unique and duplicate names are invalid.
The Action and Service statements in init.rc have corresponding classes to parse, namely ActionParser and ServiceParser.
Start Zygote by parsing the configuration in init.rc.
Follow-up analysis of Zygote startup.
Today I will try to analyze the main process involved in Android init startup under Linux system.
Init startup involves the following tasks:
- Create and mount the file system required for startup
- Initialize the property service
- create
single
Handle to listen for child processes to prevent zombie processes - Enabling the Property Service
- parsing
.rc
File and startZygote
process
project
Android_startup: provides a simple and efficient way to initialize components during application startup, optimizing startup speed. Not only does it support all the features of Jetpack App Startup, but it also provides additional synchronous and asynchronous waiting, thread control, and multi-process support.
AwesomeGithub: Based on Github client, pure exercise project, support componentized development, support account password and authentication login. Kotlin language for development, the project architecture is based on Jetpack&DataBinding MVVM; Popular open source technologies such as Arouter, Retrofit, Coroutine, Glide, Dagger and Hilt are used in the project.
Flutter_github: a cross-platform Github client based on Flutter, corresponding to AwesomeGithub.
Android-api-analysis: A comprehensive analysis of Knowledge points related to Android with detailed Demo to help readers quickly grasp and understand the main points explained.
Daily_algorithm: an algorithm of the day, from shallow to deep, welcome to join us.