Based on 9.0 source code parsing, the code path is commented in the first line of the code. This article is for the usual notes, there are inevitable mistakes, welcome to correct, if there is any mistake to bring you confusion and loss, please forgive me.

An overview of the

The init process is the first user Android system process, the init process initiated by the Linux kernel, entrance to/system/core/init/init. The main method of CPP. Its execution consists of two main parts: first_stage and second_stage. These two phases do a lot of initialization, and the next two articles will explore this process.

Init entrance

/ * system/core/init/init. CPP * / / * * * the main function: the first parameter to the number of parameters, including the name of the executable. The second argument is an array of strings, * being the concrete passed argument, where args[0] is the name of the executable program. */ int main(int argc, char** argv) {/** * 1. When a pathname argument is passed, the string after the last '/' is returned, or the string before the penultimate '/' and penultimate '/' if the last '/' is empty. For example: * "/data/data/com/xray", the result is returned as xray. * 2. STRCMP: compares two strings passed in, equal if the return value is 0, unequal if the return value is not 0. But in C, 0 is false and non-0 is true. */ if (! STRCMP (basename(argv[0]), "ueventd")) {argv[0] = argv[0], "ueventd") Return ueventD_main (argc, argv); } if (! STRCMP (basename(argv[0]), "Watchdogd ") {// As with ueventd, the watchdogd process entry is used to restart the system if the program goes wrong. return watchdogd_main(argc, argv); } if (argc > 1 && ! STRCMP (argv[1], "subContext ") {// SubContext is new in Android9, which is used to isolate system and Vendor InitKernelLogging(argv); // const BuiltinFunctionMap function_map; return SubcontextMain(argc, argv, &function_map); } the if (REBOOT_BOOTLOADER_ON_PANIC) {/ / initializes the restart signal monitoring, when listening to the reset signal, restart the system InstallRebootSignalHandlers (); }... return 0; }Copy the code

Ueventd start

/* /system/core/rootdir/init.rc */
on early-init
  ...
    start ueventd
## Daemon processes to be run by init.
service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical
Copy the code

We know from rc that the ueventd process depends on the /sbin/ueventd binary, but where is the code implementation? In the Android.mk file, we can see that /sbin/ueventd is just a soft link to init binary. So ueventd real entrance is our top/system/core/init/init. The main method of CPP.

/* /system/core/init/Android.mk */ # Create symlinks. LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \ ln -sf .. /init $(TARGET_ROOT_OUT)/sbin/ueventd; \ ln -sf .. /init $(TARGET_ROOT_OUT)/sbin/watchdogdCopy the code

Let’s look at the implementation of UeventD

/* /system/core/init/ueventd.cpp */ int ueventd_main(int argc, char** argv) { /* * init sets the umask to 077 for forked processes. We need to * create files with exact permissions, without modification by * the umask. */ umask(000); InitKernelLogging(argv); LOG(INFO) << "ueventd started!" ; SelinuxSetupKernelLogging(); SelabelInitialize(); DeviceHandler device_handler = CreateDeviceHandler(); // parse the rc file UeventListener uevent_listener; if (access(COLDBOOT_DONE, F_OK) ! = 0) { ColdBoot cold_boot(uevent_listener, device_handler); cold_boot.Run(); // Go through /sys and write "add" to uevent, } // We use waitpid() in ColdBoot, so We can't ignore SIGCHLD until now. signal(SIGCHLD, SIG_IGN); // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN // for SIGCHLD above. While (waitpid(-1, nullptr, WNOHANG) > 0) {} uevent_listener.poll ([&device_handler](const Uevent& Uevent) { Obtain the kernel uEvent event and dynamically create the device node HandleFirmwareEvent(uEvent). device_handler.HandleDeviceEvent(uevent); return ListenerAction::kContinue; }); return 0; }Copy the code

Ueventd starts in two parts:

  • Cold Boot

It iterates over all devices registered with /sys and writes ‘add’ to every ‘uevent’ file it finds, which causes the kernel to generate and resend uEvent messages for all currently registered devices. The reason for this is that when these devices are registered with SYSFS, UEventD is not yet up and running to receive their UEvent messages and properly process them, and the Android system will not run properly, so it needs to regenerate its UEvents so that UEventD can process them. The simple way to summarize this process is to create static device files.

  • Hot Plug

When a new device is connected, such as a USB flash drive on a TV, uEventD receives uEvent events sent by the kernel and dynamically creates device files for the device.

The watchdog launched

Like ueventd, Watchdogd is the other side of init. In this case, it is used as the interface of the hardware watchdog timer (/dev/watchdog, if any) in the user state. Set a timeout variable that sends a Keepalive signal (one “” byte) at intervals. If the watchdogd does not send keepalive in time, the watchdog timer will issue an interrupt that requires the kernel to restart.

/* system/core/init/watchdogd.cpp */ int watchdogd_main(int argc, char **argv) { InitKernelLogging(argv); int interval = 10; if (argc >= 2) interval = atoi(argv[1]); // the atoi function converts a string to an integer int margin = 10; if (argc >= 3) margin = atoi(argv[2]); LOG(INFO) << "watchdogd started (interval " << interval << ", margin " << margin << ")!" ; int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC); if (fd == -1) { PLOG(ERROR) << "Failed to open " << DEV_NAME; return 1; } int timeout = interval + margin; int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout); Ioctl is a device driver function that manages the device's I/O channel. This parameter is the file descriptor returned by the open function. The second parameter is the user program's command to control the device. If (ret) {PLOG(ERROR) << "Failed to set timeout to "<< timeout; ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout); if (ret) { PLOG(ERROR) << "Failed to get timeout"; } else { if (timeout > margin) { interval = timeout - margin; } else { interval = 1; } LOG(WARNING) << "Adjusted interval to timeout returned by driver: " << "timeout " << timeout << ", interval " << interval << ", margin " << margin; } } while (true) { write(fd, "", 1); // Write the "" string to tell the watchdog timer (/dev/watchdog) that everything is ok. If the system fails, // Watchdogd does not send the "" string, the hardware watchdog timer will issue an interrupt, Sleep (interval) is required to restart the kernel. The subContext Init process has almost unlimited permissions and can initialize the system during startup using input scripts from the system partition and verdor partition (vendor partition). This access can lead to a huge vulnerability in Treble system/vendor split, because vendor scripts can instruct init to access files, properties, and so on that are not part of the stable system-vendor ABI (Application binary Interface). Vendor init has been designed to fill this hole by using a separate security-enhanced Linux (SELinux) domain, vendor_init, to take advantage of vendor-specific permissions to run commands in /vendor. So if you want to run a script in init you need to add it to vendorCopy the code

InstallRebootSignalHandlers

When the signal is registered internally through SIGAction, the bootloader restarts when the signal is heard.

/* /system/core/init/init.cpp */ static void InstallRebootSignalHandlers() { // Instead of panic'ing the kernel as is the default behavior when init crashes, // we prefer to reboot to bootloader on development builds, as this will prevent // boot looping bad configurations and allow both developers and test farms to easily // recover. // Restart the bootloader at init crashes instead of kernel panic as the default behavior. Struct sigAction action (struct sigAction); memset(&action, 0, sizeof(action)); sigfillset(&action.sa_mask); action.sa_handler = [](int signal) { // These signal handlers are also caught for processes forked from init, however we do not // want them to trigger reboot, so we directly call _exit() for children processes here. if (getpid() ! = 1) {// exit(signal); } // Calling DoReboot() or LOG(FATAL) is not a good option as this is a signal handler. // RebootSystem uses syscall() which isn't actually async-signal-safe, but our only option // and probably good enough given this is already an error case and only enabled for // development builds. RebootSystem(ANDROID_RB_RESTART2, "bootloader"); }; action.sa_flags = SA_RESTART; sigaction(SIGABRT, &action, nullptr); sigaction(SIGBUS, &action, nullptr); sigaction(SIGFPE, &action, nullptr); sigaction(SIGILL, &action, nullptr); sigaction(SIGSEGV, &action, nullptr); #if defined(SIGSTKFLT) sigaction(SIGSTKFLT, &action, nullptr); #endif sigaction(SIGSYS, &action, nullptr); sigaction(SIGTRAP, &action, nullptr); }Copy the code

first stage

The init process is executed in two phases: first stage and second stage

int main(int argc, char** argv) { ... bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr); If (is_first_stage) {// If (is_first_stage) {boot_clock::time_point start_time = boot_clock::now(); // Clear the umask. umask(0); If umask is 0, the user can create a directory with the maximum permission on each directory minus the umaks value. If umask is 0, the user can create a directory with the maximum permission on each directory minus umaks value. So the default permission for the directory created here is 666. If umask is 0022, the default permission for the directory is 644 clearenv(); setenv("PATH", _PATH_DEFPATH, 1); / / set an environment variable/sbin/system/sbin, / system/bin: / system/xbin: / odm/bin: / vendor/bin: / vendor/xbin / / 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("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); // Mount temporary file system TMPFS to /dev directory mkdir("/dev/ PTS ", 0755); // create directory mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); #define MAKE_STR(x) __STRING(x) mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)); // Don't expose the raw commandline to unprivileged processes. chmod("/proc/cmdline", 0440); gid_t groups[] = { AID_READPROC }; setgroups(arraysize(groups), groups); mount("sysfs", "/sys", "sysfs", 0, NULL); mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL); mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)); //mknod creates a special device file, / dev/KMSG output kernel log if constexpr (WORLD_WRITABLE_KMSG) {mknod ("/dev/kmsg_debug S_IFCHR | 0622, makedev (1, 11)); } mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)); mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)); // Mount staging areas for devices managed by vold // See storage config details at http://source.android.com/devices/storage/ mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=1000"); // /mnt/vendor is used to mount vendor-specific partitions that can not be // part of the vendor partition, e.g. because they are mounted read-write. mkdir("/mnt/vendor", 0755); // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually // talk to the outside world... // TMPFS has been mounted to /dev and now has the /dev/kmsg file, it is time to initialize the kernel logging system with /dev/kmsg. InitKernelLogging(argv); LOG(INFO) << "init first stage started!" ; if (! DoFirstStageMount()) { LOG(FATAL) << "Failed to mount required partitions early ..." ; } /** * Initializes validation, which ensures that all executed code comes from trusted sources to prevent attacks and corruption. It can establish a complete trust chain from the hardware protected * trust root to the boot loader, and then to the boot partition and its verified partition (including system, vendor). During the process of device startup, the complete line and authenticity of the next stage will be verified before * enters the next stage. */ SetInitAvbVersionInRecovery(); // Enable seccomp if global boot option was passed (otherwise it is enabled in zygote). Android has a new feature called SecComp, which filters System calls and prevents them from being called directly by applications. global_seccomp(); // Set up SELinux, loading the SELinux policy. SelinuxSetupKernelLogging(); SelinuxInitialize(); // We're in the kernel domain, so re-exec init to transition to the init domain now // that the SELinux policy has been loaded. if (selinux_android_restorecon("/init", 0) == -1) { PLOG(FATAL) << "restorecon failed of /init failed"; } setenv("INIT_SECOND_STAGE", "true", 1); / / set an environment variable, mark has completed the first phase of the static constexpr uint32_t kNanosecondsPerMillisecond = 1 e6. uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond; setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1); char* path = argv[0]; char* args[] = { path, nullptr }; execv(path, args); // The path is /system/bin/init, which is basically calling main again, Execv () only returns if an error Happened, in which case we // panic and never fall through this conditional. PLOG(FATAL) << "execv(\"" << path << "\") failed"; }... return 0; }Copy the code

The file system

The file system describe
tmpfs Virtual memory file system, which stores all files in virtual memory. If you unmount the TMPFS file system, all the contents under the TMPFS file system will no longer exist
devpts A standard interface is provided for pseudo-terminals, whose standard mount point is /dev/pts. As soon as the primary pTY composite device /dev/ptmx is opened, a new PTY device file is dynamically created under /dev/pts
proc A very important virtual file system, which can be seen as an interface to the kernel’s internal data structure, through which we can obtain information about the system and modify specific kernel parameters at run time
sysfs Like the Proc file system, it is a virtual file system that does not occupy any disk space. It is usually mounted in the /sys directory. The sysfs file system, introduced by the Linux2.6 kernel, organizes devices and buses connected to the system into a hierarchical file that can be accessed in user space

InitKernelLogging

InitKernelLogging redirects standard input, standard output, and standard error to /sys/fs/selinux/null

/* /system/core/init/log.cpp */ void InitKernelLogging(char* argv[]) { // Make stdin/stdout/stderr all point to /dev/null. int fd = open("/sys/fs/selinux/null", O_RDWR); if (fd == -1) { int saved_errno = errno; android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter); errno = saved_errno; PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null"; } /** *dup2(int fd1, int fd2) copy fd1 to fd2. dup2(fd, 1); dup2(fd, 2); if (fd > 2) close(fd); android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter); }Copy the code

We see the last line of code, this line of code paths in the system/core/base/logging. The CPP, it basically do is set the log level, etc. We look at the second parameter emphatically android: : base: : KernelLogger, its code is as follows:

/* system/core/base/logging.cpp */ void KernelLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag, const char*, unsigned int, const char* msg) { // clang-format off static constexpr int kLogSeverityToKernelLogLevel[] = { [android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log // level) [android::base::DEBUG] = 7, // KERN_DEBUG [android::base::INFO] = 6, // KERN_INFO [android::base::WARNING] = 4, // KERN_WARNING [android::base::ERROR] = 3, // KERN_ERROR [android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT [android::base::FATAL] = 2, // KERN_CRIT }; // clang-format on static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1, "Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity"); static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC)); if (klog_fd == -1) return; int level = kLogSeverityToKernelLogLevel[severity]; // The kernel's printk buffer is only 1024 bytes. // TODO: should we automatically break up long lines into multiple lines? // Or we could log but with something like "..." at the end? char buf[1024]; size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg); if (size > sizeof(buf)) { size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n", level, tag, size); } iovec iov[1]; iov[0].iov_base = buf; iov[0].iov_len = size; TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1)); // write the log to /dev/kmsg}Copy the code

So if we want to view the kernel logs, we can do this:

cat /dev/kmsg
Copy the code

There are two other ways to view the kernel log:

cat /proc/kmsg
Copy the code
dmesg
Copy the code

conclusion

This is the first phase of init. It can be hard to figure out every detail, sometimes a single C function takes half a day, but I believe it’s worth it. The next article will start with the second phase, which will include parsing rc files, selinux-related issues (which are a huge headache in Android P), and more. Well, that’s all for this article, and I’ll see you next.

The resources

  • Look at init another way
  • Understanding Android in Depth: Volume 1
  • Android Authentication enabled
  • Seccomp filter in Android O
  • ueventd

About me

  • Public id: CodingDev