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: