The 11th holiday is a bit of a depravity, unlimited firepower a bit of an addiction, caution, caution

The Init process is the first user process created after the Linux kernel is started and is very important.

The Init process starts many important daemons during initialization, so understanding the Init process can help us better understand the Android system.

Before introducing the Init process, let’s take a quick look at the Android startup process. From a system perspective, the Android startup process can be divided into three major stages:

  • Bootloader guide
  • Load and start the Linux kernel
  • Start the Android system, can be divided into
    • Start theThe Init process
    • Start theZygote
    • Start theSystemService
    • Start theSystemServer
    • Start theHome
    • And so on…

Let’s take a look at the startup process diagram:

The following is a brief introduction to the startup process:

  1. Bootloaderguide

When you press the power button to start up, the Bootloader runs first

  • BootloaderThe main function is to initialize basic hardware devices (such as CPU, memory, Flash, etc.) and establish memory space mapping for loadingThe Linux kernelPrepare the proper operating environment.
  • Once theThe Linux kernelLoaded,BootloaderIt will be erased from memory
  • If theBootloaderDuring runtime, press the predefined key combination to enter the update module of the system.AndroidThe download update can be opt-inFastboot modeorRecovery mode:
    • FastbootisAndroidDesign a set throughUSBTo update theAndroidA protocol for partitioning images that allows developers to quickly update specified partitions.
    • RecoveryisAndroidUnique upgrade system. usingRecoveryMode can be used to restore factory Settings, or perform OTA, patch, and firmware upgrades. Enter theRecoveryMode actually starts a text modeLinux
  1. Load and startLinuxThe kernel

The Android boot.img file stores the Linux kernel and a root file system

  • Bootloadertheboot.imgThe image loads into memory
  • thenLinuxThe kernel performs initialization of the entire system
  • Then loadingRoot file system
  • Finally startInitprocess
  1. Start theInitprocess

After the Linux kernel is loaded, the Init process is started, which is the first process on the system

  • InitThe process is resolved during startupLinuxConfiguration script ofinit.rcFile. According to theinit.rcThe contents of the document,InitProcess will be:
    • loadingAndroidFile system of
    • Creating a system directory
    • Initialize the property system
    • Start theAndroidSystem important daemons, such asUSB daemon,Adb daemon,Vold daemon,Rild daemonEtc.
  • In the end,InitProcesses also act as daemons to modify properties, restart crashed processes, and so on
  1. Start theServiceManager

ServiceManager is started by the Init process. As mentioned in Binder section, its main function is to manage Binder services and be responsible for the registration and search of Binder services

  1. Start theZygoteprocess

Zygote process is started when Init process initialization is complete. The Zygote process is responsible for forking out application processes and is the parent of all application processes

  • ZygoteCreated during process initializationAndroid virtual machine, preloaded system resource files andJavaclass
  • All fromZygoteprocessforkOut user processes will inherit and share these preloaded resources without wasting time reloading, speeding up the application startup process
  • After the startup,ZygoteThe process also becomes a daemon, responsible for responding to startupAPKThe request of
  1. Start theSystemServer

SystemServer is the Zygote process fork out of the first process, is the core process of the entire Android system

  • SystemServerIn the runningAndroidMost of the systemBinder service
  • SystemServerStart the local service firstSensorManager
  • Then the boot includesActivityManagerService,WindowsManagerService,PackageManagerServiceAll withinJava service
  1. Start theMediaServer

MediaServer is started by the Init process. It includes several multimedia related local Binder services, including CameraService, AudioFlingerService, MediaPlayerService, AudioPolicyService

  1. Start theLauncher
  • SystemServerLoad all of themJava serviceAfter, the final call is madeActivityManagerServicetheSystemReady()methods
  • inSystemReady()Method, will emitIntent<android.intent,category.HOME>
  • Anything that responds to thisIntenttheapkIt’s going to work, generallyLauncherThe application goes back and responds to thisIntent

InitInitialization of a process

The Init process source directory is under system/core/ Init. The program’s entry function main() is in the file init.c

main()Flow of a function

The book is the use of Android 5.0 source code, compared to Android 9.0 this part has a lot of changes, but the direction is consistent, can only be compared to learn…

The main() function is a long one that starts the entire Init process. Because there are many points involved, here we first understand the overall process, details will be added later, bit by bit ha

The structure of the main() function of the Init process looks like this:

int main(int argc, char** argv) {
    // Start parameter judgment section
    
    if (is_first_stage) {
        // Initialize the first phase section
    }
    
    // Initialize the phase 2 part

    while (true) {
        // An infinite loop part
    }
Copy the code

Start program parameter judgment

After entering the main() function, first check the filename of the launcher

Function source code:

    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }
Copy the code
  • If the file name isueventd, the implementation ofueventdThe main function of the daemonueventd_main
  • If the file name iswatchdogd, the implementation ofwatchdogdThe main function of the daemonwatchdogd_main
  • If no, continue

Strange enough to start with, the Init process also includes the start of two other daemons, mainly because the code of these daemons overlaps so much that the developers simply put them together.

Let’s take a look at the snippet from 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/watchdogd
Copy the code
  • At compile time,AndroidTwo Pointers are generatedinitSymbolic links to filesueventdandwatchdogd
  • So, when you start up with these two symlinks,main()The function can determine which one to start based on the name

The first phase of initialization

Set the file property mask

Function source code:

    // Clear the umask.
    umask(0);
Copy the code

By default, a process creates a file folder with the attribute 022. Using umask(0) means that the process creates the attribute 0777

mountCorresponding file system

Function source code:

        // 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");
        mkdir("/dev/pts".0755);
        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));
        / /...
        mount("sysfs"."/sys"."sysfs".0.NULL);
        mount("selinuxfs"."/sys/fs/selinux"."selinuxfs".0.NULL);
        / /...
        // 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);
        InitKernelLogging(argv);
Copy the code

Create some basic directories, such as /dev, /proc, /sys, and mount some file systems, such as TMPFS, devpt, proc, and sysfs, to these directories

  • tmpfsIs a memory-based file system,mountThen you can use it.
    • tmpfsAll files in the file system are stored in the memory. The access speed is fast, but the files are lost during power failure. Therefore, it is suitable for storing some temporary files
    • tmpfsThe size of a file system changes dynamically. It takes up a small amount of space at first and then increases as more files are added
    • AndroidwilltmpfsThe file systemmountto/devDirectory./devThe directory is used to store device nodes created by the system
  • devptsIs a virtual terminal file system, usuallymountto/dev/ptsdirectory
  • procIt is also a memory-based virtual file system that can be thought of as an interface to the kernel’s internal data structures
    • It is used to obtain information about the system
    • You can also modify specific kernel parameters at run time
  • sysfsFile system andprocThe file system is similar, it is inLinux 2.6The kernel is introduced to organize system devices and buses in a hierarchy that allows them to be accessed in user space

Initialize thekerneltheLogsystem

InitKernelLogging() is used for initialization. Since the Android log system has not been started, Init can only use the kernel log system

        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        InitKernelLogging(argv);
Copy the code

Initialize theSELinux

        // Set up SELinux, loading the SELinux policy.
        SelinuxSetupKernelLogging();
        SelinuxInitialize();
Copy the code

SELinux is a secure kernel added to Android 4.3, more on this later

The second phase of initialization

create.bootingEmpty file

Create an empty file in the /dev directory. Booting indicates initialization is in progress

    // At this point we're in the second stage of init.
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
    / /...
    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
Copy the code

Note that we are already in phase two of initialization

  • is_booting()The function will rely on empty files.bootingTo determine if the process is being initialized
  • The file will be deleted after initialization

Initialize theAndroidAttribute system of

    property_init();
Copy the code

The property_init() function creates a shared area to store property values, as described below

parsingkernelParameters and related Settings

    // 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);
Copy the code

This section is about setting properties. Let’s look at some key methods:

  • process_kernel_dt()Function: Read device tree (DT), find system properties, and then passproperty_setSetting System Properties
  • process_kernel_cmdline()Function: parsingkernelthecmdlineFile extraction toandroidboot.A string that starts with a string, passesproperty_setSet the system properties
  • export_kernel_boot_props()Function: sets some additional properties. This function defines a collection of properties defined in the collection fromkernelRead and record

For the second stageSELinuxSet up the

Perform stage 2 SELinux setup and restore some file security contexts

    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();
Copy the code

Initializes the child process termination signal handler

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == - 1) {
        PLOG(FATAL) << "epoll_create1 failed";
    }

    sigchld_handler_init();
Copy the code

In Linux, the parent process signals the end of the child process by catching a SIGCHLD signal, which is emitted when the child process terminates.

  • In order to preventinitBecomes the child process ofzombies(zombie process),initGets the end code of the child process when the child process ends
  • The end code is used to remove the subprocesses from the program table to prevent the zombie subprocesses from occupying the space in the program table
  • When the program table space reaches its maximum, the system can no longer start new processes, which can cause serious system problems

Set system properties and enable the property service

    property_load_boot_defaults();
    export_oem_lock_status();
    start_property_service();
    set_usb_controller();
Copy the code
  • property_load_boot_defaults(),export_oem_lock_status(),set_usb_controller()All three of these functions call and set system properties
  • start_property_service(): Enables the system property service

loadinginit.rcfile

Init. rc is a configurable initialization file that is used in Android as a startup script for a program. It stands for Run Commands run command

Typically, third-party custom vendors can configure an additional initialization configuration: init.%PRODUCT%.rc. The configuration file is parsed during init initialization to complete the customized configuration.

The rules and logic of the init.rc file will be explained in detail below. First, take a look at its flow in the main function.

Function code:

    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);

    subcontexts = InitializeSubcontexts();
    // Create an action object
    ActionManager& am = ActionManager::GetInstance();
    // Create a service object
    ServiceList& sm = ServiceList::GetInstance();
    // Load and parse the init.rc file into the corresponding object
    LoadBootScripts(am, sm);
Copy the code

After parsing is complete, execution is performed

    am.QueueEventTrigger("early-init");

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    // Wait until the initialization of the cold swap device is complete
    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");
    // Initialize the combination key listening module
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    // Display Android Logo on the screen
    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");

    // 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");
Copy the code
  • am.QueueEventTriggerThe function indicates that a desired time condition has been reached
    • Such asam.QueueEventTrigger("early-init")Show thatearly-initConditional trigger, corresponding action can start execution
    • Note that this function only takes the point in time (e.g.early-init) to fill inevent_queue_Run queue
    • At the back of thewhile(true)The loop will actually fetch in order and trigger the corresponding action

Here,

  • init.rcThe relevantactionandserviceParsing completed
  • The corresponding list is ready
  • The correspondingTriggerIt has also been added

Now comes the execution phase:

    while (true) {
        // Execute the Action in the command list
        if(! (waiting_for_prop || Service::is_exec_service_running())) { am.ExecuteOneCommand(); }if(! (waiting_for_prop || Service::is_exec_service_running())) {if(! shutting_down) {// Start the service in the service list
                auto next_process_restart_time = RestartProcesses();
                / /...
            }
            / /...
        }
        
        // Listen for the child's death notification
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == - 1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {((void(*)()) ev.data.ptr)(); }}Copy the code

At this point, the overall flow of the main function is analyzed. We lost a lot of detail in the analysis, and now it’s time to fill in the details.

Start theServiceprocess

RestartProcesses() is called in the while() loop of main to start the service processes in the list of services.

static std::optional<boot_clock::time_point> RestartProcesses(a) {
    / /...
    // Loop through each service
    for (const auto& s : ServiceList::GetInstance()) {
        // Check whether the flag bit is SVC_RESTARTING
        if(! (s->flags() & SVC_RESTARTING))continue;
        / /... Omit time dependent judgments
        // Start the service process
        auto result = s->Start();
    }
    / /...
}
Copy the code

RestartProcesses() checks each service: s->Start() is used to Start services that have the SVC_RESTARTING flag.

S ->Start(); Method, let’s look at it in detail (deleted version) :

Result<Success> Service::Start(a) {

    / /... Omit the part
    // Clear service tags
    // Check the status of the service
    // Check whether the service binary exists
    // Initialize console, SCON (security context), etc
    / /... Omit the part

    pid_t pid = - 1;
    if (namespace_flags_) {/ / when the service defines the namespace assignment for CLONE_NEWPID | CLONE_NEWNS
        pid = clone(nullptr.nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    if (pid == 0) {// The child process was successfully created
        / /... Omit the part
        // Setenv, writepID, redirection standard IO
        / /... Omit the part

        // As requested, set our gid, supplemental gids, uid, context, and
        // priority. Aborts on failure.
        SetProcessAttributes();

        if(! ExpandArgsAndExecv(args_)) {// Parse the parameters and start the service
            PLOG(ERROR) << "cannot execve('" << args_[0] < <"')";
        }
        // Do not understand the purpose of this exit
        _exit(127);
    }

    if (pid < 0) {
        // Failed to create the child process
        pid_ = 0;
        return ErrnoError() << "Failed to fork";
    }

    / /... Omit the part
    // Execute other parameters of service, such as oom_score_adj, create and set parameters related to ProcessGroup
    / /... Omit the part
    NotifyStateChange("running");
    return Success();
}
Copy the code

There are many more details about the startup process of a Service. This section is a brief overview of the process, including scON, PID, SID, PGID, and more.

Lazy! Ability, time is limited, first go down to learn, to be used to gnaw it

Parsing startup scriptsinit.rc

The most important thing to do when the Init process starts is to parse and execute the startup file init.rc. Download link for official documentation

init.rcThe file format

The init.rc file is organized in sections

  • Sections fall into two broad categories:

    • action: by keywordonStart represents a collection of commands
    • service: by keywordserviceStart: Indicates the method and parameters for starting a process
  • Section starts with the keyword on or service and ends with the next ON or service

  • Comments in section begin with #

Give me an example:

import /init.usb.rc
import /init.${ro.hardware}.rc

on early-init
    mkdir /dev/memcg/system 0550 system system
    start ueventd

on init
    symlink /system/bin /bin
    symlink /system/etc /etc

on nonencrypted
    class_start main
    class_start late_start
    
on property:sys.boot_from_charger_mode=1
    class_stop charger
    trigger late-init

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

service flash_recovery /system/bin/install-recovery.sh
    class main
    oneshot

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical
Copy the code

Neither actions nor services are executed in the order written in the file, and it is up to the Init process at runtime to decide if and when they are executed.

For init.rc action:

  • The keywordonThe string that follows is calledtrigger, as aboveearly-init,initAnd so on.
  • triggerThis is followed by a list of commands, each line of which is a command.

For service init.rc:

  • The keywordserviceThe back isThe service name, you can usestartaddThe service nameTo start a service. Such asstart ueventd
  • The service nameThis is followed by the path to the process’s executable and startup parameters
  • serviceThe following lines are calledoption, eachoptionA line
    • Such as:class mainIn theclassIndicates the category to which the service belongsclass_startTo start a set of services likeclass_start main

Want to learn more, you can refer to the README document source, path is the system/core/init/README md

init.rcThe key word

This part is for the system/core/init/README. Md document finishing, pick the key records

The Android RC script contains four types of declarations: Action, Commands, Services, and Options

  • All instructions are in units of action, and symbols are separated by Spaces.
  • The c languageStyle of backslash\Can be used to insert Spaces between symbols
  • Double quotation marks""Can be used to prevent strings from being split into multiple tokens by Spaces
  • Backslash at the end of the line\Can be used for line folding
  • Comments to#At the beginning
  • ActionandServicesUsed to declare a group
    • All of theCommandsandOptionsBelong to the most recently declared group
    • Before the first groupCommandsorOptionsWill be ignored

Actions

Actions is a collection of Commands. Each Action has a trigger that determines when to execute it. When the trigger matches the Action’s trigger, the Action is added to the end of the execution queue

Each Action is fetched from the queue in turn, and each Command of the Action is executed in turn.

The format of Actions is as follows:

on  < trigger >
    < command >
    < command >
    < command >
Copy the code

Services

Programs defined by Services are started in Init and restarted if they exit.

The format of Services is as follows:

service <name> <pathname> [ <argument> ]*
   <option>
   <option>
   ...
Copy the code

Options

Options is a modification of Services. They determine when and how a Service runs.

  • critical: indicates that this is a keyServiceIf theServiceIf you restart the system for more than four times within four minutes, the system automatically restartsrecoverymodel
  • console [<console>]: indicates that the service requires a console
    • The second parameter is used to specify a specific console name, which defaults to/dev/console
    • Omit when specifying/dev/Some, such as/dev/tty0To be writtenconsole tty0
  • disabled: indicates that the service failsclass_startStart. It must be orderedstart service_nameIs specified to start
  • setenv <name> <value>In:ServiceStart up with environment variablesnameSet tovalue
  • socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]: Creates a file named/dev/socket/<name>And passes the file descriptor to the process to start
    • typeMust bedgram,stream,seqpacket
    • userandgroupThe default is 0
    • seclabelIs thissockettheSElinuxSecurity context, which defaults to currentserviceThe context in which the
  • user <username>: Switch the user name before executing the service. The default is root. If the process does not have corresponding permissions, the command cannot be used
  • oneshot:ServiceThe system does not restart after exiting
  • class <name>:ServiceSpecify a name. All services with the same name can be started and stopped simultaneously. If you don’t passclassDisplays the specified, default isdefault
  • onrestart: whenServiceDuring the restart, run a command

There are a lot of other things, I’m not going to go through all of them, like shutdown

, just follow the official instructions

Triggers

A trigger is essentially a string that matches some kind of event containing that string. Trigger is subdivided into event trigger and property trigger.

  • Event triggers can be triggered by the trigger command or by QueueEventTrigger() during initialization

    • Usually simple strings defined in advance, such as:boot.late-initEtc.
  • A property trigger is fired when the variable value of the specified property becomes the specified value

    • The format forproperty:<name>=*

Note that an Action can have multiple property triggers, but at most one event trigger. Take a look at the official example:

on boot && property:a=b
Copy the code

The Action above is triggered only when a boot event occurs and property A is equal to value B.

For the following Action

on property:a=b && property:c=d 
Copy the code

There are three trigger cases:

  • At startup, ifProperty aThe value is equal to thebandProperties of the cThe value is equal to thed
  • inProperties of the cThe value of theta is already zerodIn the case of,Property aThe value of is updated tob
  • inProperty aThe value of theta is already zerobIn the case of,Properties of the cThe value of is updated tod

Event triggers generally include:

type instructions
boot Triggered when init.rc is loaded
device-added-<path> Triggered when a device is added
device-removed-<path> Triggered when the specified device is removed
service-exited-<name> Triggered when a particular service exits
early-init Triggered before initialization
late-init Triggered after initialization
init Triggered during initialization

Commands

Command is the list of commands used for Action or in the Option

of Service.

static const Map builtin_functions = {
        {"chmod",                   {2.2,    {true,   do_chmod}}},
        {"chown",                   {2.3,    {true,   do_chown}}},
        {"class_start",             {1.1,    {false,  do_class_start}}},
        {"class_stop",              {1.1,    {false,  do_class_stop}}},
        ......
    };
Copy the code

Let’s take a look at a few commonly used ones

  1. bootchart [start|stop]: Enables or disables the process startup time recording tool
    //init.rc file
    mkdir /data/bootchart 0755 shell shell
    bootchart start
Copy the code
  • inThe Init processWill start inbootchart, time collection is not performed by default
  • When we need to collect the startup time, we need to create one/data/bootchart/enabledfile
  1. chmod <octal-mode> <path>: Changes file permissions
chmode 0755 /metadata/keystone
Copy the code
  1. chown <owner> <group> <path>: Changes the owner and group of the file
chown system system /metadata/keystone
Copy the code
  1. mkdir <path> [mode] [owner] [group]: Creates a specified directory
mkdir /data/bootchart 0755 shell shell
Copy the code
  1. trigger <event>: Trigger somethingEvent (Action), used to place theThe eventIn a certainThe eventafter
on late-init
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger late-fs
    trigger post-fs-data
    trigger zygote-start
    trigger load_persist_props_action
    trigger early-boot
    trigger boot
Copy the code
  1. class_start <serviceclass>: Starts all specified servicesclassNot running services under
    class_start main
    class_start late_start
Copy the code
  1. class_stop <serviceclass>: Stops all specified servicesclassRunning services under
    class_stop charger
Copy the code
  1. exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]: Passes the given parameterforkAnd start a command.
  • The specific orders are in--After the start
  • Parameters includeseclable(Default-),user,group
  • execIs blocking. No other command is run until the current command is completedInitThe process is suspended.
exec - system system -- /system/bin/tzdatacheck /system/usr/share/zoneinfo /data/misc/zoneinfo
Copy the code

There are a lot of instructions will not be introduced, refer to the official documentation and source code

initScript parsing

In the main function of init. CPP, LoadBootScripts() is used to load the rc script.

/** * after 7.0, init.rc was split, each service has its own rc file * they are basically loaded into /system/etc/init, /vendor/etc/init, /odm/etc/init, etc. * Will to resolve these rc files in the directory, to perform the relevant action * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * for custom service, You can copy the rc file to the partition/etc/init directory */ according to the partition label at compile time
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    // Create a parser
    Parser parser = CreateParser(action_manager, service_list);
    std: :string bootscript = GetProperty("ro.boot.init_rc"."");
    if (bootscript.empty()) {
        // If ro.boot.init_rc is not specially configured, parse the init.rc file first
        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("/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 {
        // Parse data in ro.boot.init_rc directlyparser.ParseConfig(bootscript); }}/** * Create parsers. There are only three sections: service, on, and import *. There are three parsers: ServiceParser, ActionParser, and ImportParser */
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;
    parser.AddSectionParser("service".std::make_unique<ServiceParser>(&service_list, subcontexts));
    parser.AddSectionParser("on".std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import".std::make_unique<ImportParser>(&parser));
    return parser;
}
Copy the code

initDaemon started in

There are many daemons defined in init.rc, 9 let’s look at them:

Import /init.usb.rc # service adbd /system/bin/adbd --root_seclabel=u:r:su:s0 # class core #...... On boot # Start standard binderized HAL daemons class_start HAL # Start all class core services like ADB, console etc class_start core on eraly-init start ueventd on post-fs # Load properties from # /system/build.prop, # /odm/build.prop, # /vendor/build.prop and # /factory/factory.prop load_system_props Service # Servicemanager is used for IPC between framework/application processes by using the LOCAL_INIT_RC command. Use the AIDL interface start Servicemanager # hwServicemanager for IPC between framework/vendor processes. Use the HIDL interface # for IPC between vendor processes. Use HIDL interface start hwServicemanager # vNDServicemanager for IPC between vendor/vendor processes, Start vndServicemanager on post-fs-data # Service ueventd /sbin/ueventd class core...... Service console /system/bin/sh class core...... Import /init.${ro.zygote}. Rc # Zygote starts some related services. # service zygote /system/bin/ app_process64-xzygote /system/bin --zygote --start-system-server --socket-name=zygote # onrestart restart audioserver # onrestart restart cameraserver # onrestart restart media # Onrestart restart netd # onrestart restart wificond # Zygote On zygote-start && Property :ro.crypto. State =unencrypted Start netd start Zygote start zygote_secondaryCopy the code

The startup process and the services that need to be started are basically customized with init.rc.

It feels like the Init process has made development a lot easier by parsing *. Rc. The people who designed the AIL were really fierce…

InitSignal processing by a process

The Init process is the first process in the system. All other processes in the system are descendants of the Init process.

By Linux’s design, the Init process is responsible for cleaning up these offspring when they die to prevent them from becoming zombie processes.

zombiesIntroduction to the

For details on zombie processes, see Wikipedia – Zombie processes

On UniX-like systems, a zombie process is a process that has completed execution (through exit system calls, or by fatal errors or termination signals at runtime) but still has its process control block in the operating system’s process table and is in a terminated state.

This occurs when a child process needs to retain entries to allow its parent to read the exit status of the child process: Once the exit state is read by the wait system call, zombie process entries are removed from the process table, called reaped.

Normally, the process is waited by its parent and reclaimed by the system.

The avoidance of zombie processes

  • The parent processthroughwaitandwaitpidEquifunctional waitThe child processEnd, this will lead toThe parent processhang
  • ifThe parent processI’m busy, so I can use itsignalFunction forSIGCHLDThe installationhandlerBecause theThe child processAfter the end,The parent processWill receive the signal, can be inhandlerIn the callwaitrecycling
  • ifThe parent processDon’t care aboutThe child processWhen does it end, then it can be usedSignal (SIGCHLD, SIG_IGN)Notify the kernel of its own pairThe child processThe end is not interested, thenThe child processWhen finished, the kernel will reclaim and no longer giveThe parent processSend a signal
  • And there are some tricks, justforkTwice,The parent processFirst,forkaThe child processAnd then continue working with the child processforkaSun JinchengAfter the exit, thenSun JinchengbeinitTake over,Sun JinchengAfter the end,InitRecycled. However,The child processDo your own recycling

So what do we do with the Init process

Initialize theSIGCHLDsignal

Sigchld_handler_init () : sigchLD_HANDler_init (); sigchLD_handler_init ();

// file : init.cpp int main(int argc, char** argv) { ...... sigchld_handler_init(); . } // file : Sigchld_handler. CPP void sigchLD_handler_init () {// Create a signalling mechanism for SIGCHLD. Int s[2]; int s[2]; int s[2]; socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s); signal_write_fd = s[0]; signal_read_fd = s[1]; Signal_write_fd if we catch SIGCHLD. Signal_write_fd if we catch SIGCHLD sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = SIGCHLD_handler; act.sa_flags = SA_NOCLDSTOP; // Register SIGCHLD signal SIGAction (SIGCHLD, & ACT, 0); ReapAnyOutstandingChildren(); // register signal_read_fd to epoll_fd register_epoll_handler(signal_read_fd, handle_signal); } // file : CPP /** * SIGchLD_handler. CPP /** * SigchLD_handler Write data to signal_write_fd * At this point the other signal_read_fd in the socket pair becomes readable. */ static void SIGCHLD_handler(int) { if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) { PLOG(ERROR) << "write(signal_write_fd) failed"; } } // file : The sigchLD_handler. CPP /** * register_epoll_handler function registers the socket file descriptor to the polling descriptor epoll_fd */ void register_epoll_handler(int)  fd, void (*fn)()) { epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = reinterpret_cast<void*>(fn); if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { PLOG(ERROR) << "epoll_ctl failed"; }}Copy the code

Initialization of the signal is done through the system call sigAction ()

  • parameteractIn:
    • sa_handlerThe handler used to specify the signal
    • sa_flagsUsed to specify the trigger flag,SA_NOCLDSTOPFlag indicates that it is received when the child process terminatesSIGCHLDsignal

In Linux system, signal is also called soft interrupt, signal arrival will interrupt the process is processing work, so do not call some non-reentrant functions in the signal processing function. And Linux does not queue signals, and no matter how many more signals come in during signal processing, the kernel only sends one more signal to the process after the current signal handler completes, so our signal handler executes as fast as possible in order not to lose the signal

In the case of SIGCHLD signals, the parent process needs to perform wait operations, which can take a long time, so there needs to be a way to resolve this contradiction

  • The above code creates a pair of localssocketUsed for interprocess communication
  • When the signal comes,SIGCHLD_handlerThe handler is just directed tosocketIn thesignal_write_fdJust write data
  • In this way, signal processing shifts tosocketOn the processing of

At this point, we need to listen for signal_read_fd and provide a callback function, which is what register_epoll_handler() does

  • In the functionEPOLLINIndicates that the file descriptor is triggered when it is readable
  • *fnAfter the triggerCallback function pointerPhi is assigned to phiev.data.ptrNote that this pointer variable will be used later
  • The provided callback function ishandle_signal()

Response to the death event of the child process

After the Init process starts, it listens for the created socket. If data arrives, the main thread wakes up and calls handle_signal().

static void handle_signal(a) {
    // Clear outstanding requests.
    // Clear data in signal_read_fd
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));
    
    ReapAnyOutstandingChildren(); 
}
void ReapAnyOutstandingChildren(a) {
    while (ReapOneProcess()) {
    }
}
static bool ReapOneProcess(a) {
    siginfo_t siginfo = {};
    // This returns a zombie pid or informs us that there are no zombies left to be reaped.
    // It does NOT reap the pid; that is done below.
    // Check whether zombie processes exist
    if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) ! =0) {
        PLOG(ERROR) << "waitid failed";
        return false;
    }
    // No zombie process returns directly
    auto pid = siginfo.si_pid;
    if (pid == 0) return false;

    // At this point we know we have a zombie pid, so we use this scopeguard to reap the pid
    // whenever the function returns from this point forward.
    // We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we
    // want the pid to remain valid throughout that (and potentially future) usages.
    // Wait for the child process to terminate
    auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); }); .if(! service)return true;
    // Perform other operations to exitservice->Reap(siginfo); . }Copy the code

After receiving the SIGCHLD signal from the child process, we will find the corresponding Service object of the process, and then call Reap function. Let’s look at the contents of this function:

void Service::Reap(const siginfo_t& siginfo) {
    // If it is not oneshot or a restarted child process
    // Kill the entire process group. On second thought, kill first to prepare for the restart
    // This will not cause errors when the child process already exists
    if(! (flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) { KillProcessGroup(SIGKILL); }// Do some cleanup of the current process.// Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    // Set SVC_DISABLED if it is oneshot or not a restarted child process
    if((flags_ & SVC_ONESHOT) && ! (flags_ & SVC_RESTART)) { flags_ |= SVC_DISABLED; }// Disabled and reset processes do not get restarted automatically.
    // If it is SVC_DISABLED or SVC_RESET
    // Set the process status to stopped and return
    if (flags_ & (SVC_DISABLED | SVC_RESET))  {
        NotifyStateChange("stopped");
        return;
    }

    // If we crash > 4 times in 4 minutes, reboot into recovery.
    // omit crash count detection.// Omit some process state Settings that are reboot dependent
    
    // Execute all onrestart commands for this service.
    // Execute the onrestart command
    onrestart_.ExecuteAllCommands();
    
    // Set the state to Restart
    NotifyStateChange("restarting");
    return;
}
Copy the code

In the Reap() function

  • According to the corresponding processServiceThe object’sflags_Flag bit to determine whether the process can be restarted
  • If you need to reboot, justflags_Flag bit additionSVC_RESTARTINGSign a

At this point, we’re clearhandle_signal()The internal flow of a function, and where is it called from?

Let’s go back to the main() method of init.cpp:

    while (true) {... epoll_event ev;int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == - 1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {((void(*)()) ev.data.ptr)(); }}Copy the code

Note that ev.data.ptr, remember that the register_epoll_handler() function does not

void register_epoll_handler(int fd, void (*fn)(a)) {... ev.data.ptr = reinterpret_cast<void*>(fn); . }Copy the code

When epoll_wait receives data, it executes ((void (*)()) ev.data.ptr)(); , our callback function handle_signal()

Init process death notification to child process logic, source code has been changed, but the core logic has not changed, and take care of it, hahaha ~ ~

Property system

Introduction to the

Properties are used extensively in Android to save system Settings or to pass simple information between processes

  • eachattributebyThe property nameandAttribute valuescomposition
  • The property nameUsually a long string of.The prefix of these names has a specific meaning and cannot be changed arbitrarily
  • Attribute valuesIt can only be a string

The Java layer can get and set properties by:

//class android.os.SystemProperties
    @SystemApi
    public static String get(String key);
    @SystemApi
    public static String get(String key, String def);
    @hide
    public static void set(String key, String val);
Copy the code

Native layer can be used:

android::base::GetProperty(key, "");
android::base::SetProperty(key, val);
Copy the code

For each process in the system:

  • Reading property valuesThere is no restriction on any process and it is read directly from the shared area by the process
  • Modifying property valuesMust passThe Init processAt the same timeThe Init processYou also need to check that the process that initiated the request has the appropriate permissions

After the property value is successfully modified, the Init process checks whether the init.rc file already defines a trigger matching the property value. Execute the command under trigger if defined. Such as:

on property:ro.debuggable=1
    start console
Copy the code

When the property ro.debuggable is set to 1, the start console command is executed to start the console

Android system-level applications and underlying modules rely heavily on the attribute system, often relying on attribute values to determine their behavior.

In the Android system Settings application, many functions are turned on and off by a specific system attribute value. This also means that arbitrarily changing the value of an attribute can seriously affect the operation of the system, so the modification of the value of an attribute must have specific permissions. SELinux now controls the setting of permissions.

The startup process of the property service

Let’s take a look at the overall process of starting a property service:

int main(int argc, char** argv) {...// Attribute service initializationproperty_init(); .// Start the properties servicestart_property_service(); . }Copy the code

In the main() function of init.cpp, property_init(); To initialize the property service

void property_init(a) {
    // create a folder with permission 711. Only owner can set this folder
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    // Read some property files and store the property values in a collection
    CreateSerializedPropertyInfo();
    if (__system_property_area_init()) {// Create properties shared memory space (this function is part of the liBC library)
        LOG(FATAL) << "Failed to initialize property area";
    }
    if(! property_info_area.LoadDefaultPath()) {// Load the properties on the default path to the shared area
        LOG(FATAL) << "Failed to load serialized property info file"; }}Copy the code

Then start the service with the start_property_service() function:

void start_property_service(a) {
    // Omit SELinux operations. property_set("ro.property_service.version"."2");
    // Create the socket corresponding to prop Service and return the socket fd
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false.0666.0.0.nullptr);
    // omit exception judgment for failed creation.// Set maximum number of connections to 8
    listen(property_set_fd, 8);
    // Register epolls event to listen for property_set_fd
    // Call handle_property_set_fd when listening for data changes
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}
Copy the code
  • socketThe descriptorproperty_set_fdAfter being created, useepollTo listen toproperty_set_fd
  • whenproperty_set_fdWhen the data comes in,The init processWill callhandle_property_set_fd()Function to process

Let’s look again at the handle_property_set_fd() function

static void handle_property_set_fd(a) {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
    // Omit some exception judgments.uint32_t cmd = 0;
    // Omit CMD read operations and some exception judgments.switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];
        // omit the character data read assembly operation.uint32_t result =
            HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
        // omit exception handling.break;
      }

    case PROP_MSG_SETPROP2: {
        std: :string name;
        std: :string value;
        // Omit the string data read operation.uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
        // omit exception handling.break;
      }
    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break; }}Copy the code

After receiving CMD to set the properties, the Init process executes the handler HandlePropertySet().

uint32_t HandlePropertySet(const std: :string& name, const std: :string& value,
                           const std: :string& source_context, 
                           const ucred& cr, std: :string* error) {
    // Determine whether the property name to be set is valid
    // this is equivalent to a naming rule check
    if(! IsLegalPropertyName(name)) {// Invalid direct return
        return PROP_ERROR_INVALID_NAME;
    }
    // If it starts with CTL, it is a control class attribute
    if (StartsWith(name, "ctl.")) {
        // Check whether you have the corresponding control permission.// Execute the corresponding control instruction after permission passes
        // Start /stop/start/stop
        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }
    const char* target_context = nullptr;
    const char* type = nullptr;
    // Gets the context and data type of the property to be set
    // Compare target_context with type
    property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);
    // Check if you have the set permission for the current attribute
    if(! CheckMacPerms(name, target_context, source_context.c_str(), cr)) {// There is no direct return
        return PROP_ERROR_PERMISSION_DENIED;
    }
    // Determine the type of the attribute and the type of data to be written
    // The CheckType function has only one string type...
    if (type == nullptr| |! CheckType(type, value)) {// Invalid, return directly
        return PROP_ERROR_INVALID_VALUE;
    }
    // If it is the sys.powerctl attribute, some special processing is required
    if (name == "sys.powerctl") {
        // Add some extra printing. }if (name == "selinux.restorecon_recursive") {
        // Special attributes, special handling
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }
    return PropertySet(name, value, error);
}
Copy the code

With the exception of some special properties, the function that actually sets the property is PropertySet

static uint32_t PropertySet(const std: :string& name, const std: :string& value, std: :string* error) {
    size_t valuelen = value.size();
    // Check whether the attribute name is valid
    if(! IsLegalPropertyName(name)) { *error ="Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }
    // Determine whether the length of the data to be written is valid
    // Check whether the property is read-only (ro)
    if(valuelen >= PROP_VALUE_MAX && ! StartsWith(name,"ro.")) {
        *error = "Property value too long";
        return PROP_ERROR_INVALID_VALUE;
    }
    // Determine the encoding format of the data to be written
    if (mbstowcs(nullptr, value.data(), 0) = =static_cast<std: :size_t> (- 1)) {
        *error = "Value is not a UTF8 encoded string";
        return PROP_ERROR_INVALID_VALUE;
    }
    // Get the property object stored in the system according to the property name
    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    if(pi ! =nullptr) {
        // ro.* properties are actually "write-once".
        // Ro attributes can only be written once
        if (StartsWith(name, "ro.")) {
            *error = "Read-only property was already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }
        // If the property already exists and is not read-only, the property update function is executed
        __system_property_update(pi, value.c_str(), valuelen);
    } else {
        // If no attribute exists in the system, execute the add attribute add function
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            *error = "__system_property_add failed";
            returnPROP_ERROR_SET_FAILED; }}// Don't write properties to disk until after we have read all default
    // properties to prevent them from being overwritten by default values.
    // If it is a persistent attribute, persist it
    if (persistent_properties_loaded && StartsWith(name, "persist.")) {
        WritePersistentProperty(name, value);
    }
    // Add property changes to the Action queue
    property_changed(name, value);
    return PROP_SUCCESS;
}
Copy the code

The Init process waits for and processes requests for the epoll attribute socket.

  • Call if a request comes inhandle_property_set_fdTo process the request
  • inhandle_property_set_fdFunction, first check the requester’suid/gidCheck whether you have the permission, and if so, adjustproperty_service.cppIn thePropertySetFunction.

In the PropertySet function

  • It looks for the property and doesn’t have it, and if it does, it changes the property. If not, add a new attribute.
  • It will also determine if it is correct when it changesroProperty, if it is, it cannot be changed.
  • If it ispersistI’ll write it again/data/property/<name>In the.

Finally, it calls the property_CHANGED function to hang the event to the queue

  • If someone registers this property (e.ginit.rcIn theon property:ro.kernel.qemu=1), will eventually trigger it

ueventdandwatchdogdIntroduction to the

ueventdprocess

The main purpose of the ueventd daemon is to receive uEventD to create and delete device nodes in the dev directory of the device

The ueventd process and Init process are not the same process, but their binaries are the same, with different startup parameters resulting in a different program execution flow.

In the init.rc file

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

The Init process starts ueventD when action eraly-init is executed.

watchdogdprocess

The watchdogd and Ueventd types are separate from the Init process, but the code stays with the Init process. The Watchdogd is designed to work with a hardware watchdog.

When the watchdog function is enabled on a hardware system, the software running on the hardware system must send a signal to the watchdog at a specified interval. This behavior is simply called feed Dog, in case the watchdog times out and causes a system reboot.

The Watchdogd process is rarely seen on today’s systems, but it’s a good model to follow