fwrite

fwrite

好好生活
twitter
github
email

Init process

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 starts loading the program (BootLoader) into RAM from a predetermined location (PC reads ROM) and begins 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 boots, it sets up caches, protects storage, schedules lists, loads drivers... After the kernel completes system setup, it looks for the init.rc file in the system and starts the init process.
Init Process Startup
This process is mainly for initializing and starting system property services 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 sequentially starts key system service processes by parsing init.rc, focusing on 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 using 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 seen in comments, 2. setting incoming parameters, 3. executing the execv function (loading main.cpp again but with different parameters).
The mounted files are all in memory (RAM) and are physical files.

CommandMount Directory/File Name, File ManagementFeaturesDescription
mounttmpfsExists in RAM and is not persistent (tmp naming)A virtual memory file system that stores all files in virtual memory; once tmpfs is unmounted, all internal data will disappear.
mount/dev/devptsDynamically mounts devptsProvides a standard for pseudo-terminals; as long as the pty master device /dev/ptmx is opened, a new pty device file will be dynamically created under /dev/pts.
mountprocCan modify kernel parameters at runtimeA virtual file system that can be seen as an interface to the kernel's internal data structures.
mountsysfsIntroduced in Linux 2.6 kernel (mainly for hardware-related parameters, which relates to devpts devices)A virtual file system, usually mounted under the /sys directory.
  // 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 error list
      std::vector<std::pair<std::string, int>> errors;
  
      
  #define CHECKCALL(x) \
      if ((x) != 0) errors.emplace_back(#x " failed", errno);
  
      // Clear umask, files and directories created afterwards will have full permissions
      umask(0);
      CHECKCALL(clearenv());
      CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
      // Get the basic filesystem setup we need to 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 directories 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 boot config 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 the group 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 management
      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 mounts, it will restart the main.cpp process via the execv function, re-entering the main.cpp#main method.

  // /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
  
  // After re-entering, 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 incoming 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, it sets new parameters and restarts 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 snapuserd 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 again
      execv(path, const_cast<char**>(args));
  
      return 1;
  }

Second Stage of Init Process#

init.cpp#SecondStageMain Parses init.rc
After the initialization and mounting of devices in first_stage_init, it will execute the SecondStageMain function in main.cpp (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
  
  // After re-entering, 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 incoming 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);
  }

Start analyzing init.cpp#SecondStageMain, divided into 10 stages, detailed comments can be seen.
Initializing property domains mainly involves 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 will not crash, and should not receive signals
      // So here we reset the signal handling function to null
      {
          // sigaction is a signal
          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 to mark 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;
  }

Clearing environment variables: clearing previously written environment variables in system properties.

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
      ... omitted parts
  
      // Clear previously written environment variables in 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
  }

Creating epoll event notifications

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... omitted parts
  
      Epoll epoll;
      // epoll.Open() creates child process I/O listening
      if (auto result = epoll.Open(); !result.ok()) {
          PLOG(FATAL) << result.error();
      }
  
      ... omitted parts
  
  }

Loading child process signal handlers: init is a daemon process (can be understood as a daemon thread), with this process as the main one; if this 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 be notified)
      // 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
  
  }

Starting 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 determines the value of "ro.boot.flash.locked"
  
      MountHandler mount_handler(&epoll);
  
      // Set UDC Controller for USB storage (sys/class/udc)
      SetUsbController();
  
      ... omitted parts
  }

Matching commands & 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: parses the init.rc file.

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... omitted parts
      
      // The object will be used to obtain analysis results later
      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);
      }
  }

Adding 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 the current state of the properties.
      am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
  
      ... omitted parts
  }

Entering a loop: continuously processing received commands & executing 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
      }
  
  }
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.