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.