Android system startup process (1) parsing init process

Keywords: Android Attribute socket Linux

Related articles
Android System Architecture and System Source Directory

Preface

As the first series in the "Android Framework Layer" series, we first need to understand the Android system startup process, which involves a lot of important knowledge points. In this series, we will explain them one by one. In this article, we will learn about the init process.

1.init introduction

Init process is the first process of user space in Android system. As the first process, it has been entrusted with many extremely important work responsibilities, such as creating zygote (incubator) and attribute services. The init process is composed of multiple source files located in the source directory system/core/init. This article will analyze the Init process based on Android 7.0 source code.

2. Introducing init process

Speaking of the init process, first of all, I want to mention the first steps of the Android system startup process:
1. Start-up power supply and system start-up
When the power is pressed, the boot chip code starts to execute from a predefined place (solidified in ROM). Load Bootloader to RAM, and then execute.
2. Bootloader
Bootloader is a small program before the Android operating system starts running. Its main function is to pull up the OS and run it.
3.linux Kernel Startup
When the kernel starts up, the cache, the protected memory, the schedule list are set, and the driver is loaded. When the kernel completes the system setup, it first looks for the "init" file in the system file, and then starts the root process or the first process of the system.
4.init process startup

The fourth step reveals the init process we're going to talk about in this section. All steps in the Android system startup process will be explained in the last article in this series.

3.init entry function

The entry function of init is main, and 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);
    }
    umask(0);
    add_environment("PATH", _PATH_DEFPATH);
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
    //Create files and mount them
    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));
        //Initialization of property-related resources
        property_init();//1
        process_kernel_dt();
        process_kernel_cmdline();
        export_kernel_boot_props();
    }
 ...
    //Start Property Services
    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>());
    //Parsing init.rc configuration file
    parser.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;
}

The main method of init does a lot of things. We only need to pay attention to the main points. We call property_init at Annotation 1 to initialize the property and call start_property_service at Annotation 2 to start the property service. We will talk about the property service later. Note 3 parser.ParseConfig("/init.rc") is used to parse init.rc. Parse the init.rc file as the system/core/init/init_parse.cpp file. Next, let's look at what's done in init.rc.

4.init.rc

init.rc is a configuration file, which is a script written by 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
...    

Only a part of the code is intercepted here, where # is the comment symbol. on init and on boot are Action-type statements in the form of:

on <trigger> [&& <trigger>]*     //set trigger  
   <command>  
   <command>      //Commands to be executed after action triggers  

To analyze how to create zygote, we mainly look at the Services type statement in the following format:

service <name> <pathname> [ <argument> ]*   //< service name > < execution path > < delivery parameters >  
   <option>       //option is a service modifier that affects when and how services are started  
   <option>  
   ...  

Note that in Android 7.0, init.rc files are split, one RC file per service. The startup script for the zygoteXX. RC service we want to analyze is defined in init.zygoteXX.rc. Take a 64-bit processor as an example. The code for init.zygote64.rc is shown 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/tasks

The service is used to notify the init process to create a process named zygote whose path is / system/bin/app_process64, followed by parameters to be passed to app_process64. class main refers to zygo's class name as main, which will be used later.

5. Analytical service

Next, we will use two functions to parse service. One is ParseSection, which parses the rc file of service, such as init.zygote64.rc mentioned above. ParseSection function is mainly used to build the shelf of service. The other is ParseLineSection, which parses subitems. 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;
}

At Note 1, a service object is constructed according to its parameters, and its classname is "default". EndSection is called when the parsing is complete:

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

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
}

The code at Note 1 adds service objects to the services list. In general, the above parsing process is to create a service object based on parameters, then fill the service object according to the content of the option field, and finally add the service object to the service list of vector type. ,

6.init starts zygote

After explaining the service, let's talk about how init starts the service. Here we will mainly explain how to start the zygote service. In the zygote startup script, we know that the class name of zygote is main. In init.rc, there are the following configuration codes:
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 
...    

Where class_start is a COMMAND, the corresponding function is do_class_start. We know that main means 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;
}

To see what StartIfNotDisabled did:
system/core/init/service.cpp

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

Next, 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 Service is already running, it does not start
        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;
    }
  //Determine whether the corresponding execution file of Service needs to be started exists or not, and if it does not exist, it does 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 Function creation subprocess
    if (pid == 0) {//Running in sub-processes
        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)));
            const char* 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.adopt execve Execution procedure
        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);
    }
...
    return true;
}

By annotating the code in Notes 1 and 2, we know that in Start method, we call the fork function to create the sub-process, and call execve to execute system/bin/app_process in the sub-process, which 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;
    }
}

From the code at Note 1, you can see that the start of runtime(AppRuntime) is called to start zygote.

7. Attribute Services

There is a registry manager on Windows platform. The contents of the registry record some usage information of users and software in the form of key-value pairs. Even if the system or software is restarted, it can initialize according to previous records in the registry. Android also provides a similar mechanism, called attribute services.
At the beginning of this article, we mentioned that the code related to attribute services in init.cpp code is:
system/core/init/init.cpp

  property_init();
  start_property_service();

These two codes are used to initialize the property service configuration and start the property service. First, let's learn the initialization and start-up of service configuration.

Initialization and Startup of Property Services

The code for 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);
    }
}

_ The system_property_area_init function is used to initialize the property memory area. Next, look at the specific 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
}

Note 1 is used to create a non-blocking socket. Note 2 calls the listen function to listen on property_set_fd, so the socket created becomes the server, that is, the attribute service; the second parameter of the listen function is set 8, which means that the attribute service can provide services for up to eight users who try to set the attribute at the same time. The code at Note 3 places property_set_fd in the epoll handle and uses epoll to listen for property_set_fd: When data arrives in property_set_fd, the init process is processed with handle_property_set_fd function.
In the new linux kernel, epoll is used to replace select. The biggest advantage of epoll is that it does not reduce efficiency as the number of FDS monitored increases. Because the select implementation in the kernel is handled by polling, the more FDS polled, the more time-consuming it will be.

Attribute Service Processing Request
From the above, we know that when the attribute service receives the client's request, it will call handle_property_set_fd function to process:
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;
    }
}

The code at Note 1 checks the client process permissions, and at Note 2 calls the property_set function 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;
}

The property_set function mainly 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 attributes exist
    if(pi != 0) {
       //If the attribute begins with "ro.", it means that it is read-only, cannot be modified, and returns directly.
        if(!strncmp(name, "ro.", 3)) return -1;
       //Update attribute values
        __system_property_update(pi, value, valuelen);
    } else {
       //Add an attribute if it does not exist
        int rc = __system_property_add(name, namelen, value, valuelen);
        if (rc < 0) {
            return rc;
        }
    }
    /* 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 updating the attribute name starting with net. you need to write the attribute name into 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;
}

property_set_impl function is mainly used to modify attributes, and to deal with attributes starting with ro, net and persist. This is where the source code for the attribute service processing requests comes in.

8. Summary of init process

In summary, the init process has done three main things:
1. Create files and mount them
2. Initialize and start attribute services
3. Parse the init.rc configuration file and start the Zygo process

Reference material:
Deep Understanding of Android Systems
Deep Understanding of Android Volume I
Android's init process in detail (I)
In-depth analysis of Android startup process
Android 7.0 parses Init.rc files
A change in Android 7.0 init.rc
Source code analysis of Android 7.0 init process
Attribute Services for Android Scenario Analysis

Welcome to pay attention to my Wechat Public Number, get blog update reminders for the first time, and more systematic Android related technology dry goods.
Sweep down the two-dimensional code to pay attention to:

Posted by Buchead on Sun, 24 Mar 2019 03:12:27 -0700