Android system startup process analysis init process startup process

Posted by dusty on Thu, 11 Jun 2020 10:19:31 +0200

preface

This series of articles briefly introduces the starting process of Android system, which will not stick to the details of the source code, so as to let readers understand the general process. In addition, we need to remind you that you need to have some C/C + + foundation to read this chapter. In this process, there will be many important knowledge points. In this series, we will explain them one by one. In this article, we will learn the init process. In this series, we will analyze the startup process of Android system based on the Android 8.1 source code.

1 init process startup process

The init process is the first process in the user space of Android system. The process number is 1, which is a key step in the startup process of Android system. As the first process, it is given many extremely important work responsibilities, such as creating Zygote (incubator) and attribute service. The init process is composed of multiple source files, which are located in the source directory system/core/init.

1.1 introduction of init process

In order to explain the init process, we first need to understand the first few steps of the Android system startup process to introduce the init process.

1. Power on and system start
When the power key is pressed, the boot chip code is executed from a predefined place (solidified in ROM). Load the BootLoader into the team and execute.
2. Bootloader
BootLoader is a small program before the Android operating system starts to run. Its main function is to pull up and run the system OS.
3. Linux kernel startup
When the kernel starts, set cache, protected memory, schedule list, and load driver. After the kernel finishes setting up the system, it first looks in the system file init.rc File, the well starts the init process.
4. init process start
The init process does a lot of work. It is mainly used to initialize and start property services, and also to start the Zygote process.
 
As can be seen from the above steps, when we press the power key, the system will load the boot program after starting, and the boot program will start the Linux kernel. After the Linux kernel is loaded, the first thing is to start the init process.

1.2 entry function of init process

After the Linux kernel is loaded, it first looks for the init.rc File, and start the init process, and then view the entry function main of the init process. The code is as follows:

system/core/init/init.cpp

int main(int argc, char** argv) {
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    add_environment("PATH", _PATH_DEFPATH);

    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    //Create a file and mount it
    if (is_first_stage) {
        boot_clock::time_point start_time = boot_clock::now();

        // Clear the umask.
        umask(0);

        // 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.
        // Create and mount the file directory required for startup
        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));
        // 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("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        // Initialize the Kernel Log, so that the Kernel Log can be obtained from the outside world
        InitKernelLogging(argv);

        LOG(INFO) << "init first stage started!";

        if (!DoFirstStageMount()) {
            LOG(ERROR) << "Failed to mount required partitions early ...";
            panic();
        }

        SetInitAvbVersionInRecovery();

        // Set up SELinux, loading the SELinux policy.
        selinux_initialize(true);

        // 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(ERROR) << "restorecon failed";
            security_failure();
        }

        setenv("INIT_SECOND_STAGE", "true", 1);

        static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
        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);

        // execv() only returns if an error happened, in which case we
        // panic and never fall through this conditional.
        PLOG(ERROR) << "execv(\"" << path << "\") failed";
        security_failure();
    }

    // At this point we're in the second stage of init.
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";

    // 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.
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);

    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

    // Initialize property service
    property_init();   //  .......  1

    // 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);

    // Clean up our environment.
    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

    // Now set up SELinux for second stage.
    selinux_initialize(false);
    selinux_restore_context();


    // Create epoll handle
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }

    // It is used to set the subprocess signal processing function. If the subprocess (Zygote process) exits abnormally, the init process will call the function
    // Set signal processing function to process
    signal_handler_init();   // ...... 2

    // Import default environment variables
    property_load_boot_defaults();
    export_oem_lock_status();

    // Start property service
    start_property_service();  // ...... 3

    set_usb_controller();

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

    ActionManager& am = ActionManager::GetInstance();
    ServiceManager& sm = ServiceManager::GetInstance();
    Parser& parser = Parser::GetInstance();

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        // analysis init.rc configuration file
        parser.ParseConfig("/init.rc"); // ...... 4
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }

    // Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();

    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(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
    am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

    // 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(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

    while (true) {
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1;

        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }

        if (!(waiting_for_prop || sm.IsWaitingForExec())) {
            // Traverse the execution function corresponding to the command carried in each action
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || sm.IsWaitingForExec())) {
            if (!shutting_down) 
                // Restart the dead process 
                restart_processes(); // ...... 5

            // If there's a process that needs restarting, wake up in time for that.
            if (process_needs_restart_at != 0) {
                epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
                if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
            }

            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout_ms = 0;
        }

        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)();
        }
    }

    return 0;
}

In the main function of init, we only need to pay attention to the main points. At the beginning, the umask will be cleaned up, and then the file directories needed for startup will be created and mounted. Among them, tmpfs, devpts, proc, sysfs and selinuxfs are mounted. These are the system runtime directories. These directories only exist when the system is running, and disappear when the system is stopped.

Call property at comment 1_ The init function initializes the property and calls start at note 3_ property_ The service function starts the property service, which will be explained later.

Call signal at comment 2_ handler_ The init function is used to set the subprocess signal processing function, which is defined in system/core/init/ signal_handler.cpp It is mainly used to prevent the sub processes out of the init process fork from becoming Zombie process (the zombie process is when the child process ends before the parent process, and the parent process does not recycle the child process, releasing the resources occupied by the child process, the child process will become a zombie process). In order to prevent the occurrence of the zombie process, the system will issue SIGCHLD when the child process is suspended and terminated, and signal_ handler_ The init function is used to receive SIGCHLD signals (only SIGCHLD signals terminated by the process are processed internally).

Suppose the subprocess Zygote of init process fork is terminated, signal_ handler_ Handle is called inside the init function_ The signal function finally finds out the Zygote process and removes all the information of the Zygote process, and then restarts the startup script of the Zygote service (for example init.zygote64.rc), about init.Zygote64 As we will see later in. Re, the Zygote process itself will be restarted in note 5. Here is just an example of Zygote process. The principle of other init process subprocesses is similar.

Note 4 for parsing init.rc Files, parsing init.rc The file of is system/core/init/init_parse.cpp File, let's look at init.rc What has been done in.

1.3 analysis init.rc

        init.rc It is a very important configuration file. It is a script written by Android Init Language. It mainly contains five types of statements: Action, Commands, Services, Options and Import. init.rc The configuration code for is shown below.

system/core/rootdir/init. rc

on init
    sysclktz 0

    # Mix device-specific information into the entropy pool
    copy /proc/cmdline /dev/urandom
    copy /default.prop /dev/urandom
    
    ...

on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain

    ...

Only a part of the code is intercepted here, where ා is the annotation symbol. on init and on boot are Action type statements. Their formats are:

on <trgger> [&& <trigger>]*
   <command>
   <command>
   <command>
   ...

Action starts with on, then triggers and commands are defined. Action statements are parsed by ActionParser.

In order to analyze how to create a Zygote process, first understand the Service type statements. Services are some programs that need to be restarted when the system starts or exits during initialization. The format is as follows:

service <name> <pathname> [ <argument> ]*
        <option>
        <option>
        ...

First, you need to define the name of the Service and specify the program path. Then you need to decorate the Service through option (which determines when and how the Service runs). The Service statement uses ServiceParser to parse

Note that Android 8.1 is right init.rc The file is split, and each service corresponds to a re file. The Zygote startup script we want to analyze is init.zygoteXX.rc As defined in, take 64 bit processor as an example, init.Zygote64 The code for. RC is as follows:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

The service is used to inform the init process to create a process named zygote. The path of the zygote process execution program is / system/bin/app_process64, the latter is to pass to app_ Parameters for process64. class main means that zygote's class name is main, which will be used later.

1.4 parsing Service type statements

Next, analyze the service. As mentioned before, analyze the service statement by using ServiceParse. The implementation code of ServiceParser is in system/core/init/service.cpp It uses two functions: ParseSection, which parses the. rc file of the service, as mentioned above init.zygote64.rc, ParseSection function is mainly used to build Service; the other is ParseLineSection, which resolves children, service.cpp The code for is as follows:
system/core/init/service.cpp
bool ServiceParser::ParseSection(std::vector<std::string>&& args, const std::string& filename,
                                 int line, std::string* err) {
    if (args.size() < 3) {
        // Determine whether the service has name and program (executable program)
        *err = "services must have a name and a program";
        return false;
    }

    const std::string& name = args[1];
    if (!IsValidName(name)) {
        // Check if the name of the Service is valid
        *err = StringPrintf("invalid service name '%s'", name.c_str());
        return false;
    }

    // In Service_ Find if there is a Service named name in Manager
    Service* old_service = service_manager_->FindServiceByName(name);
    if (old_service) {
        // Determine whether there is a duplicate Service
        *err = "ignored duplicate definition of service '" + name + "'";
        return false;
    }

    std::vector<std::string> str_args(args.begin() + 2, args.end());
    service_ = std::make_unique<Service>(name, str_args); // ... 1
    return true;
}


bool ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) {
    return service_ ? service_->ParseLine(std::move(args), err) : false;
}
In note 1, a Service object is constructed according to the parameter. Its classname is the value of variable name. After all data has been parsed, the EndSection function is called.
system/core/init/service.cpp
void ServiceParser::EndSection() {
    if (service_) {
        service_manager_->AddService(std::move(service_));
    }
}
In the EndSection function, the AddService function of ServiceManage will be called, and then you can see what the AddService function has done. The code is as follows:
system/core/init/service.cpp
void ServiceManager::AddService(std::unique_ptr<Service> service) {
    services_.emplace_back(std::move(service)); // ... 1
}
The code at note 1 adds the Service object to the Service list. In general, the above Service parsing process is to create a Service object according to the parameters, then fill in the Service object according to the content of the option field, and finally add the Service object to the Service chain list of vector type.   

1.5 init start Zygote

After analyzing the Service, I will talk about how init starts the Service. Here, I mainly talk about starting the Zygote Service. In the startup script of Zygote, we know that Zygote class name is main. At init.rc There are the following configuration codes in:
system/core/rootd ir/init. re
on nonencrypted
    class_start main
    class_start late_start

Of which class_start is a command, and the corresponding function is do_class_start. We know that main means Zygote, so class_start main is used to start Zygote. do_class_start function in builtins.cpp , as shown below.

system/core/init/builtins.cpp

static int do_class_start(const std::vector<std::string>& args) {
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    ServiceManager::GetInstance().
        ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
    return 0;
}

The ForEachServicelnClass function will traverse the Service list, find Zygote with class name as main, and execute the StartlfNotDisabled function, as shown below:

system/core/init/service.cpp

bool Service::StartIfNotDisabled() {
    if (!(flags_ & SVC_DISABLED)) { // ... 1
        return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}
Note 1: if the Service does not set the disabled option in its corresponding RC file, the Start function will be called to Start the Service. The INI t.Zygote64.rc corresponding to Zygote does not set the disabled option, so let's look at the Start function next, as shown below

system/core/init/service.cpp

bool Service::Start() {
    // Starting a service removes it from the disabled or reset state and
    // immediately takes it out of the restarting state if it was in there.
    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));

    // Running processes require no additional work --- if they're in the
    // process of exiting, we've ensured that they will immediately restart
    // on exit, unless they are ONESHOT.

    // Determine whether the Service is running. If so, return directly to avoid repeated startup.
    if (flags_ & SVC_RUNNING) {
        return false;
    }

    // Console environment required
    bool needs_console = (flags_ & SVC_CONSOLE);
    if (needs_console) {
        if (console_.empty()) {
            console_ = default_console;
        }

        // Make sure that open call succeeds to ensure a console driver is
        // properly registered for the device node
        int console_fd = open(console_.c_str(), O_RDWR | O_CLOEXEC);
        if (console_fd < 0) {
            PLOG(ERROR) << "service '" << name_ << "' couldn't open console '" << console_ << "'";
            flags_ |= SVC_DISABLED;
            return false;
        }
        close(console_fd);
    }

    //Judge whether the corresponding execution file of the Service to be started exists. If not, the Service will not be started
    struct stat sb;
    if (stat(args_[0].c_str(), &sb) == -1) {
        PLOG(ERROR) << "cannot find '" << args_[0] << "', disabling '" << name_ << "'";
        flags_ |= SVC_DISABLED;
        return false;
    }

    std::string scon;
    if (!seclabel_.empty()) {
        scon = seclabel_;
    } else {
        scon = ComputeContextFromExecutable(name_, args_[0]);
        if (scon == "") {
            return false;
        }
    }

    LOG(INFO) << "starting service '" << name_ << "'...";

    //1 if the subprocess is not started, the fork function is called to create the subprocess
    pid_t pid = -1;
    if (namespace_flags_) {
        pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork(); //Focus on creating subprocesses through fock
    }

    if (pid == 0) {  //2 running in a subprocess
        umask(077);

        if (namespace_flags_ & CLONE_NEWPID) {
            // This will fork again to run an init process inside the PID
            // namespace.
            SetUpPidNamespace(name_);
        }

        for (const auto& ei : envvars_) {
            add_environment(ei.name.c_str(), ei.value.c_str());
        }

        std::for_each(descriptors_.begin(), descriptors_.end(),
                      std::bind(&DescriptorInfo::CreateAndPublish, std::placeholders::_1, scon));

        // See if there were "writepid" instructions to write to files under /dev/cpuset/.
        auto cpuset_predicate = [](const std::string& path) {
            return StartsWith(path, "/dev/cpuset/");
        };
        auto iter = std::find_if(writepid_files_.begin(), writepid_files_.end(), cpuset_predicate);
        if (iter == writepid_files_.end()) {
            // There were no "writepid" instructions for cpusets, check if the system default
            // cpuset is specified to be used for the process.
            std::string default_cpuset = GetProperty("ro.cpuset.default", "");
            if (!default_cpuset.empty()) {
                // Make sure the cpuset name starts and ends with '/'.
                // A single '/' means the 'root' cpuset.
                if (default_cpuset.front() != '/') {
                    default_cpuset.insert(0, 1, '/');
                }
                if (default_cpuset.back() != '/') {
                    default_cpuset.push_back('/');
                }
                writepid_files_.push_back(
                    StringPrintf("/dev/cpuset%stasks", default_cpuset.c_str()));
            }
        }
        std::string pid_str = std::to_string(getpid());
        for (const auto& file : writepid_files_) {
            if (!WriteStringToFile(pid_str, file)) {
                PLOG(ERROR) << "couldn't write " << pid_str << " to " << file;
            }
        }

        if (ioprio_class_ != IoSchedClass_NONE) {
            if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) {
                PLOG(ERROR) << "failed to set pid " << getpid()
                            << " ioprio=" << ioprio_class_ << "," << ioprio_pri_;
            }
        }

        if (needs_console) {
            setsid();
            OpenConsole();
        } else {
            ZapStdio();
        }

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

        if (!ExpandArgsAndExecve(args_)) {  //3 key points: start sub process execution.
            PLOG(ERROR) << "cannot execve('" << args_[0] << "')";
        }

        _exit(127);
    }

    if (pid < 0) {
        PLOG(ERROR) << "failed to fork for '" << name_ << "'";
        pid_ = 0;
        return false;
    }

    if (oom_score_adjust_ != -1000) {
        std::string oom_str = std::to_string(oom_score_adjust_);
        std::string oom_file = StringPrintf("/proc/%d/oom_score_adj", pid);
        if (!WriteStringToFile(oom_str, oom_file)) {
            PLOG(ERROR) << "couldn't write oom_score_adj: " << strerror(errno);
        }
    }

    time_started_ = boot_clock::now();
    pid_ = pid;
    flags_ |= SVC_RUNNING;
    process_cgroup_empty_ = false;

    errno = -createProcessGroup(uid_, pid_);
    if (errno != 0) {
        PLOG(ERROR) << "createProcessGroup(" << uid_ << ", " << pid_ << ") failed for service '"
                    << name_ << "'";
    } else {
        if (swappiness_ != -1) {
            if (!setProcessGroupSwappiness(uid_, pid_, swappiness_)) {
                PLOG(ERROR) << "setProcessGroupSwappiness failed";
            }
        }

        if (soft_limit_in_bytes_ != -1) {
            if (!setProcessGroupSoftLimit(uid_, pid_, soft_limit_in_bytes_)) {
                PLOG(ERROR) << "setProcessGroupSoftLimit failed";
            }
        }

        if (limit_in_bytes_ != -1) {
            if (!setProcessGroupLimit(uid_, pid_, limit_in_bytes_)) {
                PLOG(ERROR) << "setProcessGroupLimit failed";
            }
        }
    }

    if ((flags_ & SVC_EXEC) != 0) {
        LOG(INFO) << "SVC_EXEC pid " << pid_ << " (uid " << uid_ << " gid " << gid_ << "+"
                  << supp_gids_.size() << " context "
                  << (!seclabel_.empty() ? seclabel_ : "default") << ") started; waiting...";
    }

    NotifyStateChange("running");
    return true;
}

In the Start function, first determine whether the service has been run. If it has been run, it will not Start again, and return false directly. When the program executes to the annotation 1, it shows that the sub process has not been started, calling the fork function to create the child process, and returning the PID value. If pid=0 is in the annotation 2, it shows that the current code logic is executed in the sub process, and the annotation 3 is calling the ExpandArgsAndExecve function in the sub process, the Service sub process will be activated and enter the main function of the Service, if Service It's Zygote. As mentioned above, the execution path of Zygote is system/bin/app_process64, which will enter frameworks/base/cmds/app_ process/app _ Main. The main function of CPP, that is, the main function of Zygote, is as follows:

frameworks/base/cmds/app_process/app _main. cpp
int main(int argc, char* const argv[])
{
    ....
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote); // ... 1
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}
As you can see from the code in note 1, call the start function of runtime to start Zygote, and then Zygote starts.

1.6 Attribute Service

When the init process starts, it will start the attribute service and allocate storage for it to store these attributes. If you need to read these attributes directly, you can. In the beginning of section 1.2, we mentioned init.cpp In the main function, there are two lines of code related to property affairs:
system/core/init/init.cpp
    // Initialize property service
    property_init(); 

    // Start property service
    start_property_service();  

These two lines of code are used to initialize the property service configuration and start the property service. First, let's learn the initialization and startup of property service configuration.

1. Property service initialization and startup

       property_ The specific implementation code of init function is as follows:
system/core/init/Property_service. cpp
void property_init() {
    if (__system_property_area_init()) {
        LOG(ERROR) << "Failed to initialize property area";
        exit(1);
    }
}

Wei system_ property_ area_ The init function is used to initialize the attribute memory area. Next, look at start_ property_ Specific code of service function:

system/core/init/Property_service. cpp
void start_property_service() {
    property_set("ro.property_service.version", "2");

    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr, sehandle); // ... 1
    if (property_set_fd == -1) {
        PLOG(ERROR) << "start_property_service socket creation failed";
        exit(1);
    }

    listen(property_set_fd, 8); // ... 2

    register_epoll_handler(property_set_fd, handle_property_set_fd); // ... 3
}

Create a non blocking Socket at comment 1. Call the listen number pair property at comment 2_ set_ FD listens, so the created Socket becomes a server, that is, a property service. The second parameter of the listen function is set to 8, which means that the property service can provide services for up to eight users trying to set properties at the same time. The code at comment 3 will_ set_ FD is put into epoll, and epoll is used to monitor property_set_fd: when property_ set_ When data arrives in FD, the init process will call handle_property_set_fd function.

In the new Linux kernel, epoll is used to replace select. Epoll is the improved poll of Linux kernel for processing large batch of file descriptors. It is an enhanced version of select/poll of multiplexing I/0 interface under Linux. It can significantly improve the utilization of CPU resources of the system when there are only a few active programs in a large number of concurrent connections. The data type used to save events in epoll is red black tree, which is fast to find. The array used to save information in select is slow to find. Only when waiting for a small number of file descriptors, the efficiency of epoll and select will be about the same.

2. Service processing client request

From the above we know that when the property service receives a client request, it will call handle_property_set_fd function:

system/core/init/Property_service.cpp

static void handle_property_set_fd() {
    
    ...

    switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];
        // If the socket cannot read the property data, it will return directly.
        if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
            !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }

        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;

        handle_property_set(socket, prop_value, prop_value, true); // ... 1
        break;
      }

      ...     

    }
}

Describe handle in note 1_ property_ set_ FD function calls handle_property_set function to process the client request. The code is as follows:

system/core/init/Property_service.cpp

static void handle_property_set(SocketConnection& socket,
                                const std::string& name,
                                const std::string& value,
                                bool legacy_protocol) {
  ...

  // Start with "ctl.", indicating control attribute
  if (android::base::StartsWith(name, "ctl.")) { // ... 1
    // Check client permissions
    if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
      // Set control properties
      handle_control_message(name.c_str() + 4, value.c_str()); // ... 2
      if (!legacy_protocol) {
        socket.SendUint32(PROP_SUCCESS);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
                 << " service ctl [" << value << "]"
                 << " uid:" << cr.uid
                 << " gid:" << cr.gid
                 << " pid:" << cr.pid;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
      }
    }
  } else {
    // Non control attribute, common attribute,
    // Check client permissions
    if (check_mac_perms(name, source_ctx, &cr)) {
      uint32_t result = property_set(name, value); // ... 3
      if (!legacy_protocol) {
        socket.SendUint32(result);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
      }
    }
  }

  freecon(source_ctx);
}

As can be seen from the above code, system attributes can be divided into two types: one is common attribute, and the other is control attribute. Control attribute is used to execute some commands, such as boot animation. The property name in note 1 starts with "ctl.", which indicates that it is control property and then calls handle_. control_ Message function to modify the control properties. If it is a normal property, call the property at annotation 3_ Set function to modify common attributes as follows:

system/core/init/Property_service.cpp

uint32_t property_set(const std::string& name, const std::string& value) {
    if (name == "selinux.restorecon_recursive") {
        return PropertySetAsync(name, value, RestoreconRecursiveAsync);
    }

    return PropertySetImpl(name, value); // ... 1
}

In property_ In the set method, first determine whether the name of the attribute is“ selinux.restorecon_recursive ", if yes, use the PropertySetAsync function to complete the setting, if not, use the PropertySetImpl function to complete the setting, here we analyze the PropertySetImpl function to complete the property setting, the code is as follows:

system/core/init/Property_service.cpp

static uint32_t PropertySetImpl(const std::string& name, const std::string& value) {
    size_t valuelen = value.size();
    // Determine whether the name of the attribute is legal
    if (!is_legal_property_name(name)) {
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: bad name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (valuelen >= PROP_VALUE_MAX) {
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                   << "value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

    // Find the attribute from the attribute store
    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    // Judge whether the attribute exists
    if (pi != nullptr) {
        // ro.* properties are actually "write-once".
        // If the property name starts with "ro.", it means read-only property. It can't be modified and will be returned directly.
        if (android::base::StartsWith(name, "ro.")) {
            LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                       << "property already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }
        // If the property exists, update the property value.
        __system_property_update(pi, value.c_str(), valuelen);
    } else {
        // If the property does not exist, add it
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                       << "__system_property_add failed";
            return PROP_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.
    // Process properties beginning with "persist."
    if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) {
        write_persistent_property(name.c_str(), value.c_str());
    }
    property_changed(name, value);
    return PROP_SUCCESS;
}
The PropertySetImpl function is mainly used to modify ordinary properties. First, it is necessary to determine whether the property is legal. If it is legal, look up the property from the property storage control. If the property exists, update the property value. Otherwise, add the property. In addition, the attributes with names beginning with "ro." "persist." are processed accordingly.

1.7 init process startup summary

init program startup has done a lot of work, mainly including the following three things:
(1) create and mount the file directory required for startup.
(2) initialize and start the attribute service.
(3) analysis init.rc Configure the file and start the Zygote process.

Topics: Attribute socket Android SELinux