Related articles Android system architecture and system source directory

preface

As the first in a series on the Android Framework layer, we’re going to look at the Android startup process. There are a lot of important things that are involved in this process. In this series, we’re going to look at the Init process.

1. Introduction of init

The Init process is the first user space process in The Android system, and as the first process, it is given a number of very important responsibilities, such as creating zygote(incubator) and properties services. The init process consists of multiple source files located in the source directory system/core/init. This article will analyze the Init process based on Android7.0 source code.

2. Introduce the init process

The init process starts with the first steps of the Android startup process: 1. Boot power and system boot When the power is pressed boot chip code starts executing from a predefined location (solidified in ROM). Load the boot program Bootloader into RAM and execute. Bootloader is a small program before the Android operating system starts to run. Its main function is to pull the system OS up and run. 3. Linux kernel startup During kernel startup, set cache, protected storage, schedule list, and load driver. When the kernel is done setting up the system, it first looks for the “init” file in the system files and then starts the root process or the first process on the system. 4. The init process starts

Step 4 is where we find the init process that we’re going to talk about in this video. All the steps of the Android startup process will be covered in the final article in this series.

Init entry function

The entry function for init is main, as shown below. 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); } umask(0); add_environment("PATH", _PATH_DEFPATH); bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") ! = 0); If (is_first_stage) {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); } open_devnull_stdio(); klog_init(); klog_set_level(KLOG_NOTICE_LEVEL); NOTICE("init %s started! \n", is_first_stage ? "first stage" : "second stage"); if (! is_first_stage) { // Indicate that booting is in progress to background fw loaders, etc. close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); // Initializes the property-related resource property_init(); //1 process_kernel_dt(); process_kernel_cmdline(); export_kernel_boot_props(); }... // Start the property service start_property_service(); //2 const BuiltinFunctionMap function_map; Action::set_function_map(&function_map); Parser& parser = Parser::GetInstance(); parser.AddSectionParser("service",std::make_unique<ServiceParser>()); parser.AddSectionParser("on", std::make_unique<ActionParser>()); parser.AddSectionParser("import", std::make_unique<ImportParser>()); ParseConfig("/init.rc"); / / 3... while (true) { if (! waiting_for_exec) { am.ExecuteOneCommand(); restart_processes(); } int timeout = -1; if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * 1000; if (timeout < 0) timeout = 0; } if (am.HasMoreCommands()) { timeout = 0; } bootchart_sample(&timeout); epoll_event ev; int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout)); if (nr == -1) { ERROR("epoll_wait failed: %s\n", strerror(errno)); } else if (nr == 1) { ((void (*)()) ev.data.ptr)(); } } return 0; }Copy the code

The main method of init does a lot of things, but let’s just focus on the main points, calling property_init at comment 1 to initialize the property and start_property_service at comment 2 to start the property service, which we’ll talk about later. Parser. ParseConfig(“/init.rc”) parses init.rc. Rc is a system/core/init/init_parse. CPP file.

4.init.rc

Init. rc is a configuration file that contains scripts written by the Android Init Language. It contains five types of statements: Action, Commands, Services, Options, and Import. The configuration code for init.rc 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
    # set RLIMIT_NICE to allow priorities from 19 to -20
    setrlimit 13 40 40
...Copy the code

This is just a snippet of code, where # is the comment symbol. On init and on boot are Action statements in the following format:

On <trigger> [&& <trigger>]* // Sets the trigger <command> <command> // The command to execute after the action is triggeredCopy the code

To analyze how to create a Zygote, we’ll focus on the Services type statement, which looks like this:

Service <name> <pathname> [<argument>]* //<service name> < execution path >< pass argument> <option> Affects when and how to start services <option>...Copy the code

Note that in Android 7.0 the init.rc file was split, one rc file per service. The zygote service startup script is defined in init.zygotexx. rc. For a 64-bit processor, init.zygote64.rc shows the code below. system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    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
    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasksCopy the code

Service is used to tell the init process to create a process named zygote. The zygote process execution path is /system/bin/app_process64, followed by the parameters to be passed to app_process64. Class main refers to zygote’s class name as main, which will be used later.

5. Analytical service

Zygote64. rc. The ParseSection function is used to construct a framework for the service. The other is ParseLineSection, which is used to parse child items. The code is shown below. system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                 std::string* err) {
    if (args.size() < 3) {
        *err = "services must have a name and a program";
        return false;
    }
    const std::string& name = args[1];
    if(! IsValidName(name)) { *err = StringPrintf("invalid service name '%s'", name.c_str());
        return false;
    }
    std::vector<std::string> str_args(args.begin() + 2, args.end());
    service_ = std::make_unique<Service>(name, "default", str_args);/ / 1
    return true;
}

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
                                     const std::string& filename, int line,
                                     std::string* err) const {
    return service_ ? service_->HandleLine(args, err) : false;
}Copy the code

In comment 1, we construct a service object with a classname of “default” based on the parameters. EndSection is called when parsing is complete:

void ServiceParser::EndSection() {
    if (service_) {
        ServiceManager::GetInstance().AddService(std::move(service_)); }}Copy the code

Then look at what AddService does:

void ServiceManager::AddService(std::unique_ptr<Service> service) {
    Service* old_service = FindServiceByName(service->name());
    if (old_service) {
        ERROR("ignored duplicate definition of service '%s'",
              service->name().c_str());
        return;
    }
    services_.emplace_back(std::move(service));/ / 1
}Copy the code

The code at comment 1 adds the Service object to the Services list. The general process is to create a service object based on the parameters, populate the service object based on the options field, and add the service object to the list of services in a vector. .

6. The init start the zygote

Init: Zygote: zygote: zygote: zygote: zygote The zygote startup script tells us that the class name of Zygote is main. In the init. Rc has the following configuration code: the system/core/rootdir/init. Rc

. on nonencrypted # A/B update verifier that marks a successful boot. exec - root -- /system/bin/update_verifier nonencrypted class_start main class_start late_start ...Copy the code

Where class_start is a COMMAND, the corresponding function is do_class_start. We know that main refers to zygote, so class_start main is used to start zygote. The do_class_start function is defined 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;
}Copy the code

To view your StartIfNotDisabled done: the system/core/init/service. The CPP

bool Service::StartIfNotDisabled() {
    if(! (flags_ & SVC_DISABLED)) {return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}Copy the code

Then look at the Start method, as shown below.

bool Service::Start() {
    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
    time_started_ = 0;
    if (flags_ & SVC_RUNNING) {// If the Service is already running, it is not started
        return false;
    }
    bool needs_console = (flags_ & SVC_CONSOLE);
    if(needs_console && ! have_console) { ERROR("service '%s' requires console\n", name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }
  // Check whether the execution file of the Service to be started exists. If no, do not start the Service
    struct stat sb;
    if (stat(args_[0].c_str(), &sb) == - 1) {
        ERROR("cannot find '%s' (%s), disabling '%s'\n",
              args_[0].c_str(), strerror(errno), name_.c_str());
        flags_ |= SVC_DISABLED;
        return false; }... pid_t pid = fork();//1.fork creates a child process
    if (pid == 0) {// Run in a child process
        umask(077);
        for (const auto& ei : envvars_) {
            add_environment(ei.name.c_str(), ei.value.c_str());
        }
        for (const auto& si : sockets_) {
            int socket_type = ((si.type == "stream" ? SOCK_STREAM :
                                (si.type == "dgram" ? SOCK_DGRAM :
                                 SOCK_SEQPACKET)));
            constchar* socketcon = ! si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str(); int s = create_socket(si.name.c_str(), socket_type, si.perm, si.uid, si.gid, socketcon);if (s >= 0) { PublishSocket(si.name, s); }}...//2. Execute the program using execve
        if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
            ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));
        }

        _exit(127); }... returntrue;
}Copy the code

From the code in comments 1 and 2, we know that the fork function is called in the Start method to create the child process, and execve is called in the child process to execute system/bin/app_process. This will enter the main function of framework/ CMDS /app_process/app_main.cpp, as shown below. 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.");
        return 10; }}Copy the code

You can see from the code in comment 1 that zygote is started by calling start runtime(AppRuntime).

7. Attribute services

Windows platform has a registry manager, the contents of the registry in the form of key-value pairs to record some use information of users, software. Even if the system or software is restarted, it can still perform initialization based on previous records in the registry. Android provides a similar mechanism called properties services. In the beginning of this article, we mentioned in the init. CPP code and attribute code related services include: system/core/init/init. CPP

  property_init();
  start_property_service();Copy the code

These two lines of code initialize the property service configuration and start the property service. First let’s learn about initialization and startup of the service configuration.

The code for initializing the property service and starting the property_init function is shown below. system/core/init/property_service.cpp

void property_init() {
    if (__system_property_area_init()) {
        ERROR("Failed to initialize property area\n");
        exit(1); }}Copy the code

The __system_property_area_init function is used to initialize the property memory region. Now look at the code for the start_property_service function:

void start_property_service() {
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666.0.0, NULL);/ / 1
    if (property_set_fd == - 1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
        exit(1);
    }
    listen(property_set_fd, 8);/ / 2
    register_epoll_handler(property_set_fd, handle_property_set_fd);/ / 3
}Copy the code

Comment 1 is used to create a non-blocking socket. Note 2 calls the listen function to listen on property_set_fd, so that the socket created becomes the server, that is, the property service. The second parameter setting of listen, 8, means that the properties service can serve up to eight users simultaneously trying to set the properties. The code at comment 3 puts property_set_fd into the epoll handle and uses epoll to listen for property_set_fd: When data arrives in property_set_fd, the init process uses the handle_property_set_fd function to process it. In the new Linux kernel, epoll is used to replace SELECT. The biggest advantage of epoll is that it does not lose efficiency as the number of FDS listening increases. Because the kernel’s SELECT implementation uses polling, the more FDS polling, the more time it takes.

Property services processing the request We’ve learned from the above, property services to receive the client’s request, will call handle_property_set_fd function for processing: the system/core/init/property_service CPP

static void handle_property_set_fd()
{  
...

        if(memcmp(msg.name,"ctl.".4) = =0) {
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid); }}else {
            // Check client process permissions
            if (check_mac_perms(msg.name, source_ctx, &cr)) {/ / 1
                property_set((char*) msg.name, (char*) msg.value);/ / 2
            } else {
                ERROR("sys_prop: permission denied uid:%d name:%s\n",
                      cr.uid, msg.name);
            }
            close(s);
        }
        freecon(source_ctx);
        break;
    default:
        close(s);
        break; }}Copy the code

The code in comment 1 checks the client process permissions, and the property_set function is called in comment 2 to modify the properties, as shown below.

int property_set(const char* name, const char* value) {
    int rc = property_set_impl(name, value);
    if (rc == - 1) {
        ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
    }
    return rc;
}Copy the code

The property_set function calls the property_set_impl function:

static int property_set_impl(const char* name, const char* value) {
    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);
    if(! is_legal_property_name(name, namelen))return - 1;
    if (valuelen >= PROP_VALUE_MAX) return - 1;
    if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
        if(selinux_reload_policy() ! =0) {
            ERROR("Failed to reload policy\n"); }}else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
        if(restorecon_recursive(value) ! =0) {
            ERROR("Failed to restorecon_recursive %s\n", value); }}// Find the property from the property storage space
    prop_info* pi = (prop_info*) __system_property_find(name);
    // If the attribute exists
    if(pi ! =0) {
       // If the property starts with "ro.", it is read-only and cannot be modified
        if(! strncmp(name,"ro.".3)) return - 1;
       // Update the property value
        __system_property_update(pi, value, valuelen);
    } else {
       // Add the attribute if it does not exist
        int rc = __system_property_add(name, namelen, value, valuelen);
        if (rc < 0) {
            returnrc; }}/* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) = =0)  {
        if (strcmp("net.change", name) == 0) {
            return 0;
        }
      // After the property name starting with net. is updated, the property name needs to be written to net.change
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) = =0) {
        /* * Don't write properties to disk until after we have read all default properties * to prevent them from being overwritten by default values. */
        write_persistent_property(name, value);
    }
    property_changed(name, value);
    return 0;
}Copy the code

The property_set_impl function modifies properties and processes properties starting with ro, net, and persist. So much for the source code for the property service to handle requests.

With that said, the init process does three things: 1. Initialize and start the properties service. 3. Parse the init.rc configuration file and start zygote

References: Android7.0 init. Rc: Android7.0 init. Rc: Android7.0 init. Rc: Android7.0 init Android7.0 init process source analysis Android scenario analysis attribute services


Welcome to pay attention to my wechat public number, the first time to get blog updates, as well as more system of Android related original technology dry goods. Scan the qr code below or long press to identify the QR code, you can follow.