3. In depth understanding of init

Copyright notice: This is the original article of CSDN blogger "Allah Shennong", which follows the CC 4.0 BY-SA copyright agreement
Original link: https://blog.csdn.net/Innost/article/details/47204675

Chapter 3 in depth understanding of init

3.1 general

Init is a process. Specifically, it is the first process in user space in Linux system. Because Android is based on the Linux kernel, init is also the first process in the user space of the Android system, and its process number is 1. As the process of Tianzi No.1, init has been given many extremely important responsibilities. This chapter will focus on two more important responsibilities:

  • Init process is responsible for creating several key processes in the system, especially Zygote, which will be introduced in the next chapter. It is the founder of the Java world. So how does the init process create Zygote?
  • The Android system has many properties, so init provides a property service to manage them. So how does this property service work?

As mentioned above, this chapter will analyze init through the following two aspects:

  • How init creates zygote.
  • How the init attribute service works.

3.2 init analysis

The entry function of init process is main, and its code is as follows:

init.c

int main(int argc, char **argv)
{
    intdevice_fd = -1;
    intproperty_set_fd = -1;
    intsignal_recv_fd = -1;
    intkeychord_fd = -1;
    int fd_count;
    ints[2];
    intfd;
    structsigaction act;
    chartmp[PROP_VALUE_MAX];
    structpollfd ufds[4];
    char*tmpdev;
    char*debuggable;
   
	//Set the signal processing function for subprocess exit. This function is sigchld_handler. 
   	act.sa_handler = sigchld_handler;
    act.sa_flags= SA_NOCLDSTOP;
   	act.sa_mask = 0;
   	act.sa_restorer = NULL;
   	sigaction(SIGCHLD, &act, 0);
  
  	......//Create some folders and mount devices. These are related to Linux and will not be discussed too much.
   	mkdir("/dev/socket", 0755);
   	mount("devpts", "/dev/pts", "devpts", 0,NULL);
   	mount("proc", "/proc", "proc", 0, NULL);
   	mount("sysfs", "/sys", "sysfs", 0, NULL);

    //Redirect standard I / O / error output to / dev/_null_. 
	open_devnull_stdio();
	/*
	Set the log output device of init to / dev/__kmsg__, However, after the file is opened, it will be unlink ed immediately,
	In this way, other processes cannot open this file to read log information.
	*/
   	log_init();

   	//The above involves a lot of knowledge related to Linux system. Unfamiliar readers can study it by themselves. They will not affect our analysis
   	//Parse init RC configuration file
   	parse_config_file("/init.rc");
    
    ......
    //The following function obtains the Hardware name of the machine by reading / proc/cpuinfo. My HTCG7 mobile phone is bravo.
   	get_hardware_name();
	snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);
	//Parse the configuration file related to the machine. The corresponding file of my G7 mobile phone is init bravo. rc. 
   	parse_config_file(tmp);

	/*
	After parsing the above two configuration files, you will get a series of actions. The following two sentences of code will execute those in
	early-init Action of the phase. Init divides the action execution time into four stages: early init, init
	early-boot,boot. Because some actions can only be executed after other actions are completed, there is a priority. Which?
	The stage to which the action belongs is determined by the configuration file. The related knowledge of configuration files will be introduced later.
	*/
   	action_for_each_trigger("early-init", action_add_queue_tail);
   	drain_action_queue();

	/*
	Create a socket that uses Uevent to interact with the Linux kernel. For the knowledge of Uevent, in Chapter 9
	Vold Introduction will be made during analysis.
    */
   	device_fd = device_init();
    //Initializes resources related to properties
	property_init();
	//Initialize the / dev/keychord device, which is related to debugging. Its usage is not discussed in this book. Readers can study it by themselves,
	//The content is relatively simple.
   	keychord_fd = open_keychord();

    ......
		/*
	  	INIT_IMAGE_FILE Defined as "/ initlogo RLE ", the following function will load this file as the boot of the system
	 	Note that it is not the boot animation file loaded by the boot animation control program bootanimation.
		*/
		if(load_565rle_image(INIT_IMAGE_FILE) ) {
	   		/*
			If initlogo. Is loaded If the RLE file fails (possibly without this file), the / dev/ty0 device will be opened and
			Output the words "ANDROID" as the boot screen. The boot screen seen on the simulator is it.
			*/
      	......
      	}
   }
    if(qemu[0])
       import_kernel_cmdline(1);
   ......
	//Call property_ The set function sets an attribute item. An attribute item includes an attribute name and an attribute value.
   	property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");

    ......//Perform the action in the init phase
   	action_for_each_trigger("init", action_add_queue_tail);
   	drain_action_queue();

    //Start property service
   	property_set_fd = start_property_service();

	/*
	Call the socketpair function to create two connect ed sockets. Socketpair is a Linux system call,
	Unfamiliar readers can use man socketpair to query relevant information. You'll know their usefulness later.
	*/
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
       	signal_fd = s[0];
       	signal_recv_fd = s[1];
        ......
    }

    ......
    //Execute the actions of early boot and boot phases in the configuration file.
   	action_for_each_trigger("early-boot", action_add_queue_tail);
   	action_for_each_trigger("boot", action_add_queue_tail);
	drain_action_queue();

	......    
	//init focuses on things from four aspects.
    ufds[0].fd= device_fd;//device_fd is used to listen for Uevent events from the kernel
   	ufds[0].events = POLLIN;
   	ufds[1].fd = property_set_fd;//property_set_fd is used to listen for events from the property server
	ufds[1].events= POLLIN;
	//signal_recv_fd is created by socketpair, and its events come from another socket.
	ufds[2].fd = signal_recv_fd;
   	ufds[2].events = POLLIN;
   	fd_count = 3;
	if(keychord_fd > 0) {
   		//If the keychord device is initialized successfully, init will also pay attention to the events from the device.
       	ufds[3].fd = keychord_fd;
       	ufds[3].events = POLLIN;
       	fd_count++;
	}
	......

#if BOOTCHART
    ......//It is related to Boot char and will not be discussed.
	/*
	Boot chart Is a small tool that can analyze the performance of the system and generate a chart of the system startup process,
	To provide some valuable information, and the greatest use of this information is to help improve the startup speed of the system.
    */
#endif
	for(;;) {
	    //From then on, init will enter an infinite loop.
       	int nr, i, timeout = -1;

       	for (i = 0; i < fd_count; i++)
        	ufds[i].revents = 0;
        
        //Perform actions in a loop
       	drain_action_queue();
       	restart_processes(); //Restart those processes that have died
......
#if BOOTCHART
        ...... // Boot Chart correlation
#endif
        //Call poll and wait for something to happen
        nr= poll(ufds, fd_count, timeout);
        ......
		//ufds[2] saves signal_recv_fd, used to receive messages from socket.
        if(ufds[2].revents == POLLIN) {
        	//A child process dies, and init has to deal with it
            read(signal_recv_fd, tmp, sizeof(tmp));
           	while (!wait_for_one_process(0))
               ;
           	continue;
        }
 
        if(ufds[0].revents == POLLIN)
           handle_device_fd(device_fd);//Handling Uevent events
        if(ufds[1].revents == POLLIN)
           handle_property_set_fd(property_set_fd);//Handles events for the property service.
        if(ufds[3].revents == POLLIN)
           handle_keychord(keychord_fd);//Handle the keychord event.
    }
    return 0;
}

From the above code, we can see that the task of init is still very heavy. Although many lines have been omitted from the above code, the result is still very long. However, from the two knowledge points to be analyzed in this chapter, the workflow of init can be simplified into the following four points:

  • Parse two configuration files, where the pair init Parsing of RC file.
  • Execute the actions of each stage, and the work of creating Zygote is completed in one of these stages.
  • Call property_init initializes the resources related to the property and uses the property_start_service starts the property service.
  • Init enters an infinite loop and waits for something to happen. Focus on how init handles things from the socket and from the property server.

Streamlining workflow is a common method for analyzing code in the future. Readers can also use this method in the process of analyzing code.

3.2.1 parsing configuration files

According to the above code, two configuration files will be parsed in init, one of which is the system configuration file init RC, the other is the configuration file related to the hardware platform. Take the HTC G7 mobile phone as an example. This configuration file is called init bravo. RC, where Bravo is the name of the hardware platform. Parse the two configuration files and call the same parse_config_file function. Let's take a look at this function. In the analysis process, use init RC dominated.

parser.c

int parse_config_file(const char *fn)
{
	char *data;
	data = read_file(fn, 0);//Read the contents of the configuration file, which is init rc. 
	if (!data) return -1;
	parse_config(fn,data); //Call parse_config for real parsing
	return 0;
}

After reading the contents of the file, parse is called_ Config. The code of this function is as follows:

parser.c

static void parse_config(const char *fn, char*s)
{
struct parse_state state;
char *args[SVC_MAXARGS];
int nargs;

nargs = 0;
state.filename = fn;
state.line = 1;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_no_op; //Set the parsing function. Different parsing functions are used for different contents
for (;;) {
    switch(next_token(&state)) {
      case T_EOF:
           state.parse_line(&state, 0, 0);
           return;
      caseT_NEWLINE:
           if (nargs) {
              //Get the type of keyword
               int kw = lookup_keyword(args[0]);
               if (kw_is(kw, SECTION)) { //Judge whether the keyword type is SECTION.
                    state.parse_line(&state,0, 0);
                   parse_new_section(&state,kw, nargs, args);//Resolve this SECTION.
               } else {
                   state.parse_line(&state, nargs, args);
               }
               nargs = 0;
           }
           break;
       case T_TEXT:
          ......
           break;
        }
    }
}

Above is parse_config function, although the code is short, it is actually more complex. On the whole, parse_config will first find a section of the configuration file, and then use different parsing functions for different sections. So, what is a section? This is the same as init RC file is related to the organizational structure. Don't rush to see init RC, or go to the code to find the answer first.

Keyword definition

keywords.h this file defines the keywords used in init. Its usage is very interesting. Let's look at this file first. The code is as follows:

keywords.h

#ifndef KEYWORD / / if no KEYWORD macro is defined, go to the following branch
......//Declare some functions, which are the execution functions of the Action mentioned above.
int do_class_start(int nargs, char **args);
int do_class_stop(int nargs, char **args);
......
int do_restart(int nargs, char **args);
......
#define __MAKE_KEYWORD_ENUM__  // Define a macro

/*
Define the KEYWORD macro. Although there are four parameters, only the first one is used here, where K_## Symbols in ## represent connections
 The final value is K_symbol.  Symbol is actually init Keywords in RC
*/
#define KEYWORD(symbol, flags, nargs, func)K_##symbol,
enum { //Define an enumeration that defines the enumeration value of each keyword.
   K_UNKNOWN,
#endif
	......
	//According to the definition of KEYWORD above, an enumeration value K will be obtained here_ class,
   	KEYWORD(class,       OPTION,  0, 0)
   	KEYWORD(class_start, COMMAND, 1, do_class_start)//K_class_start,
	KEYWORD(class_stop,  COMMAND, 1, do_class_stop)//K_class_stop,
	KEYWORD(on,          SECTION, 0, 0)//K_on,
   	KEYWORD(oneshot,     OPTION,  0, 0)
   	KEYWORD(onrestart,   OPTION,  0, 0)
   	KEYWORD(restart,     COMMAND, 1,do_restart)
   	KEYWORD(service,     SECTION, 0,0)
    ......
   	KEYWORD(socket,      OPTION,  0, 0)
   	KEYWORD(start,       COMMAND, 1,do_start)
   	KEYWORD(stop,        COMMAND, 1,do_stop)
    ......
#ifdef __MAKE_KEYWORD_ENUM__
   	KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD / / cancel KEYWORD macro definition
#endif

keywords.h doesn't seem strange, but it's a simple header file. Why is its usage very interesting? See how it is used in the code, as follows:

parser.c

......//parser.c will contain keywords H header file, and more than once!!
//Include keywords for the first time h. According to keywords H code, we will first get an enumeration definition
#include "keywords.h"
/*
Redefine the KEYWORD macro. This time, all four parameters are used. It looks like a structure. Where #symbol represents
 A string whose value is "symbol".
*/
#define KEYWORD(symbol, flags, nargs, func) \
    [K_##symbol ] = { #symbol, func, nargs + 1, flags, },

//Define a structure keyword_info array, which is used to describe some attributes of keywords. Please pay attention to the comments inside.
struct {
    constchar *name;  //The name of the keyword.
    int(*func)(int nargs, char **args);//The processing function corresponding to the keyword.
	unsignedchar nargs;//Number of parameters. The number of parameters for each keyword is fixed.
	//Keyword has three attributes: COMMAND, OPTION and SECTION. COMMAND has corresponding processing function
   	unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = { "unknown", 0, 0, 0},

/*
The second contains keywords h. Since the KEYWORD macro has been redefined, the keywords previously used as enumeration values
 Now it becomes a keyword_ The index of info array.
*/
#include "keywords.h"   
};
#undef KEYWORD
 
//Some auxiliary macros help us operate the keyword quickly_ Info.
#define kw_is(kw, type) (keyword_info[kw].flags& (type))
#define kw_name(kw) (keyword_info[kw].name)
#define kw_func(kw) (keyword_info[kw].func)
#define kw_nargs(kw) (keyword_info[kw].nargs)

Now I have a taste of keywords The magic of H? It did two things:

  • Include KeyWorks for the first time H, it declares something like do_ Functions such as classstart also define an enumeration with an enumeration value of K_class,K_ Keywords such as MKDIR.
  • The second contains keywords After h, you get a keyword_info structure array, this keyword_ The info structure array takes the enumeration value defined above as the index and stores the corresponding keyword information, including keyword name, processing function, number of parameters of processing function, and attributes.

At present, the most important keyword information is symbol and flags. What keywords are considered to be sections? According to keywords H, symbol is the following two keywords to represent section:

KEYWORD(on,          SECTION, 0, 0)
KEYWORD(service,     SECTION, 0, 0)

With the above knowledge, let's look at the configuration file init RC content.

init. Analysis of RC

init. The contents of RC are as follows: (we intercepted some contents. Note that the annotation symbol is #)

init.rc

on init  #According to the above analysis, the on keyword indicates a section, and the corresponding name is "init"
 ......  #All the following contents belong to this section until the beginning of the next section.
 exportPATH /sbin:/system/sbin:/system/bin:/system/xbin
 exportLD_LIBRARY_PATH /system/lib
 exportANDROID_BOOTLOGO 1 #According to keywords H, export represents a COMMAND
 export ANDROID_ROOT /system
 exportANDROID_ASSETS /system/app
...... #Omit some contents
on boot  #This is a new section called "boot"
   ifup lo#This is a COMMAND
   hostname localhost
   domainname localdomain
    ......
   #class_start is also a COMMAND, and the corresponding function is do_class_start, very important, remember.
    class_startdefault 
    ......
	#The following section means: the attribute to be persistent service. adb. When the value of enable becomes 1,
	#You need to execute the corresponding COMMAND, which is start ADB
     onproperty:persist.service.adb.enable=1
         start adbd //start is a COMMAND
     on property:persist.service.adb.enable=0
         stopadbd
    ......
#service is also the identification of a section, and the corresponding section is named "zygote"“
service zygote /system/bin/app_process -Xzygote/system/bin –zygote        \
 --start-system-server
	socketzygote stream 666  #socket keyword indicates OPTION
   	onrestart write /sys/android_power/request_state wake #onrestart is also an OPTION
   	onrestart write /sys/power/state on
   	onrestart restart media
#A section named "media"
service media /system/bin/mediaserver
    usermedia
    groupsystem audio camera graphics inet net_bt net_bt_admin net_raw
iopriort 4

Check init. From above According to the analysis of RC:

  • The content of a section starts with the keyword indicating the section and ends at the next place indicating the section.
  • init. A section named boot and init appears in RC. Boot and init here are boot and init in the four stages of action execution described earlier. In other words, the actions executed in the boot phase are defined by the boot section.

In addition, it can be found that zygote is placed in a service section. Next, take zygote this section as an example to introduce how a service is parsed.

3.2.2 parsing service

The corresponding service section of zygote is:

init.rc::zygote

service zygote /system/bin/app_process -Xzygote/system/bin –zygote \
--start-system-server
	socketzygote stream 666  #socket is OPTION
	#The following onrestart is OPTION, and write and restart are commands
    onrestart write /sys/android_power/request_state wake
   	onrestart write /sys/power/state on
	onrestart restart media

The entry function for parsing a section is parse_new_section, its code is as follows:

parser.c

void parse_new_section(struct parse_state*state, int kw,
                       int nargs, char **args)
{
	switch(kw) {
    case K_service:  //Parse the service with parse_service and parse_line_service
    	state->context = parse_service(state, nargs, args);
        if(state->context) {
           	state->parse_line = parse_line_service;
            return;
        }
       	break;
    case K_on: //Parse on section
        ......//Readers can study it by themselves
       	break;
    }
   	state->parse_line = parse_line_no_op;
}

Parse is used in service parsing_ Service and parse_ line_ Before introducing the two functions of service, first look at how init organizes the service.

service structure

A structure called service is used in init to store information related to service section. You can take a look at this structure. The code is as follows:

init.h::service structure definition

struct service {
  //listnode is a special structure, which is widely used in kernel code. It is mainly used to link the structure into a
  //Two way linked list. There is a global service in init_ List, which is specially used to save the service obtained after parsing the configuration file.
   	struct listnode slist; 
    constchar *name; //The name of the service, corresponding to our example, is "zygote".
    constchar *classname; //The name of the class to which the service belongs. The default is "default"
   	unsigned flags;//Properties of service
    pid_t pid;    //Process number
    time_t time_started;   //Last start time
    time_t time_crashed;  //Time of last death
    int nr_crashed;        //Number of deaths
    uid_t uid;     //uid,gid related
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;
   	/*
	Some service s need to use sockets. The following socketinfo is used to describe socket related information.
	Our zygote also uses socket. The content in the configuration file is socket zygote stream 666.
	It indicates that an AF will be created_ Stream type socket (actually TCP socket), whose name is "zygote",
	The read-write permission is 666.
   	*/
	struct socketinfo *sockets; 
	//service generally runs in a separate process. envvars is used to describe the environment variable information required to create this process.
    struct svcenvinfo *envvars; 
   	/*
  	Although the keyword onrestart indicates an OPTION, this OPTION is usually followed by a COMMAND,
 	The following action structure can be used to store command information, which will be analyzed immediately.
	*/
    struct action onrestart;

    //Content related to keychord
    int *keycodes;
    int nkeycodes;
    int keychord_id;
    //io priority setting
    int ioprio_class;
    int ioprio_pri;
    //Number of parameters
    int nargs;
    //Used to store parameters
    char *args[1];
}; 

The structure of the service we know now is relatively clear and easy to understand. How to express the three onrestart in zygote? See the action structure used in the service:

init.h::action structure definition

struct action {
	/*
	An action structure can be stored in three two-way linked lists, of which alist is used to store all actions,
	qlist It is used to link the actions waiting to be executed, and tlist is used to link the actions waiting to be executed after certain conditions are met
	The action to be executed.
	*/
    struct listnode alist;
   	struct listnode qlist;
    struct listnode tlist;

   	unsigned hash;
    const char *name;
   
   	//The COMMAND linked list corresponding to this OPTION, taking zygote as an example, has three onrestart option s, so
  	//It creates three command structures.
    struct listnode commands;
    struct command *current;
};

After knowing the above knowledge, can you guess parse_service and parse_ line_ What about the role of service? Come and see them right away.

parse_service

parse_ The code of service is as follows:

parser.c

static void *parse_service(struct parse_state*state, int nargs, char **args)
{
    struct service *svc; //Declare a service structure
    ......
    //init maintains a global service linked list. First judge whether there is a service with the same name.
    svc =service_find_by_name(args[1]);
    if(svc) {
       ......  //If there is a service with the same name, subsequent operations cannot be continued.
       return 0;
    }
   
	nargs-= 2;
    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    ......
   	svc->name = args[1];
	svc->classname = "default";//Setting classname to "default" is crucial!
   	memcpy(svc->args, args + 2, sizeof(char*) * nargs);
   	svc->args[nargs] = 0;
   	svc->nargs = nargs;
	svc->onrestart.name = "onrestart";
 
   	list_init(&svc->onrestart.commands);
    //Add zygote this service to the global linked list service_list.
   	list_add_tail(&service_list, &svc->slist);
    return svc;
}

parse_ The service function just builds a service shelf, and the specific contents need to be filled in by the following parsing functions. Let's look at another parsing function parse of service_ line_ service.

Understand parse_line_service

parse_ line_ The code of service is as follows:

parser.c

static void parse_line_service(structparse_state *state, int nargs, char **args)
{
    struct service *svc = state->context;
    struct command *cmd;
    int i,kw, kw_nargs;
    ......
   	svc->ioprio_class = IoSchedClass_NONE;
    //In fact, we still do all kinds of processing according to keywords.
    kw =lookup_keyword(args[0]);
    switch(kw) {
    case K_capability:
    	break;
    case K_class:
        if(nargs != 2) {
           	......
        }else {
           	svc->classname = args[1];
        }
       	break;
    ......
	case K_oneshot:
   		/*
		This is the attribute of service. It has five attributes in total, namely:
		SVC_DISABLED: Does not start automatically with class. You will see the role of class below.
		SVC_ONESHOT: There is no need to restart after exiting, that is, the service can be started only once.
		SVC_RUNNING: Running, this is the state of the service.
		SVC_RESTARTING: Wait for restart, which is also the state of the service.
		SVC_CONSOLE: The service requires a console.
		SVC_CRITICAL: If the service is restarted continuously within the specified time, the system will restart and enter the recovery mode.
		zygote No attribute is used, which indicates that it will start automatically with the processing of class;
		init will restart after exiting; Do not use the console; Even continuous restart will not cause the system to enter recovery mode.
       	*/
       	svc->flags |= SVC_ONESHOT;
       	break;
	case K_onrestart: //Fill in the content of the action structure according to the content of onrestart
       	nargs--;
       	args++;
        kw= lookup_keyword(args[0]);
        ......
        //Create command structure
       	cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
       	cmd->func = kw_func(kw);
       	cmd->nargs = nargs;
       	memcpy(cmd->args, args, sizeof(char*) * nargs);
        //Add the new command to the two-way linked list.
       	list_add_tail(&svc->onrestart.commands, &cmd->clist);
       	break;
    ......
    case K_socket: { //Create socket related information
       	struct socketinfo *si;
        ......
        si= calloc(1, sizeof(*si));
        if(!si) {
           	parse_error(state, "out of memory\n");
           	break;
        }
       	si->name = args[1]; //socket name
       	si->type = args[2]; //Type of socket
       	si->perm = strtoul(args[3], 0, 8); //Read and write permissions of socket
        if(nargs > 4)
           	si->uid = decode_uid(args[4]);
        if(nargs > 5)
           	si->gid = decode_uid(args[5]);
       	si->next = svc->sockets;
       	svc->sockets = si;
       	break;
    }
    ......
   	default:
       	parse_error(state, "invalid option '%s'\n", args[0]);
    }
}

parse_line_service will fill the service structure according to the content of the configuration file. What will zygote get after parsing? Figure 3-1 shows the results of zygote analysis:

Figure 3-1 schematic diagram of zygote analysis results

As can be seen from the above figure:

  • service_ The list linked list links all the parsed services together and is a two-way linked list. The forward node is represented by prev and the backward node is represented by next.
  • socketinfo is also a two-way linked list. Because zygote has only one socket, a virtual box socket is drawn as a demonstration of the linked list.
  • onrestart points to a commands linked list through commands. zygote has three commands.

After the zygote service is parsed, it is "everything is ready, only the east wind is owed". The next step is to understand how init controls the service.

3.2.3 init control service

Let's first look at how the service is started.

Start zygote

init.rc has such a sentence:

#class_start is a COMMAND, and the corresponding function is do_class_start, very important, remember.
class_start default

class_start indicates a COMMAND, and the corresponding processing function is do_class_start, which is within the boot section. Why is it important?

Remember the four execution stages in the init process? When the init process executes to the following sentences, do_class_start will be executed.

//Add the command of the bootsection section to the execution queue
action_for_each_trigger("boot",action_add_queue_tail);
//Execute the COMMAND in the queue. Class is a COMMAND, so it corresponds to do_class_start will be executed.
drain_action_queue();

Let's look at do_class_start function:

builtins.c

int do_class_start(int nargs, char **args)
{
	/*
	args For do_ class_ Parameter of start, init There is only one parameter in RC, which is default.
	The following function will start from the service_ Find the service whose classname is "default" in the list, and then
	Call service_start_if_not_disabled function. Now the reader understands the
	classname Is the role of?
	*/
	service_for_each_class(args[1],service_start_if_not_disabled);
	return 0;
}

We already know that zygote the value of the service's classname is "default", so we will call the service for this service_start_if_not_disabled, the code of this function is:

parser.c

static void service_start_if_not_disabled(struct service *svc)
{
	if (!(svc->flags & SVC_DISABLED)) {
	     service_start(svc,NULL); //zygote does not have SVC set_ DISABLED
	}
}

service_ The code of the start function is as follows:

init.c

void service_start(struct service *svc, constchar *dynamic_args)
{
    struct stat s;
    pid_t pid;
    int needs_console;
    int n;
 
   	svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING));
   	svc->time_started = 0;
   
    if(svc->flags & SVC_RUNNING) {
       return;//If the service is already running, it does not need to be processed

    }
	/*
	service It usually runs in another process, which is also a child process of init, so you need to judge before starting the service
	Whether the corresponding executable exists. zygote's corresponding executable is / system/bin/app_process
	*/
    if(stat(svc->args[0], &s) != 0) {
      	svc->flags |= SVC_DISABLED;
       	return;
    }
    ......
   	pid =fork(); //Call fork to create a child process
	if(pid == 0) {
    	//pid is zero, we are in the child process
       struct socketinfo *si;
       struct svcenvinfo *ei;
       char tmp[32];
       int fd, sz;

		//Get the information of the attribute storage space and add it to the environment variable. You will see where to use it in the attribute service section later.
       	get_property_workspace(&fd, &sz);
       	add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
        //Add environment variable information
       	for (ei = svc->envvars; ei; ei = ei->next)
           	add_environment(ei->name, ei->value);
        	//Create socket according to socketinfo
       		for (si = svc->sockets; si; si = si->next) {
           		int s = create_socket(si->name,
                                  !strcmp(si->type,"dgram") ?
                                  SOCK_DGRAM :SOCK_STREAM,
                                  si->perm,si->uid, si->gid);
	           if (s >= 0) {
	               //Add socket information to the environment variable.
	                publish_socket(si->name, s);
	           }
        }
       ......//Set uid, gid, etc
     	setpgid(0, getpid());
       if(!dynamic_args) {
        /*
		Execute / system/bin/app_process, and then enter the app_ In the main function of process.
		fork,execve These two functions are common system calls on Linux systems.
        */
            if (execve(svc->args[0], (char**)svc->args, (char**) ENV) < 0) {
              	......
           	}
        } else {
          ......
    }
   	......//The processing of the parent process init and setting the information of the service, such as startup time, process number, status, etc.
   	svc->time_started = gettime();
   	svc->pid = pid;
   	svc->flags |= SVC_RUNNING;
	//Each service has an attribute. The attribute of zygote is init svc. Zygote, now set its value to running
   	notify_service_state(svc->name, "running");
}

Originally, zygote was created through fork and execv! But the onrestart in the service structure seems to be useless. Why?

Restart zygote

According to the name, you can guess that onrestart should be used when zygote restarts. Let's first look at the actions of its parent process init after zygote dies:

init.c

static void sigchld_handler(int s)
{  //When the child process exits, this signal handler function of init will be called
   write(signal_fd, &s, 1); //To signal_fd write data
}

signal_fd is one of the two sockets created through socket pair in init, since it will go to this signal_ If you send data in FD, another socket will receive it. This will cause init to return from the poll function:

init. Code snippet of RC:: main function

 nr =poll(ufds, fd_count, timeout);
 ......
 if(ufds[2].revents == POLLIN) {
   read(signal_recv_fd, tmp, sizeof(tmp));
       while (!wait_for_one_process(0))//Call wait_for_one_process function processing
         ;
       continue;
 }
 ......
//Look at this wait directly_ for_ one_ Process function:
static int wait_for_one_process(int block)
{
    pid_t pid;
    int status;
    struct service *svc;
    struct socketinfo *si;
    time_t now;
    struct listnode *node;
    struct command *cmd;

	while( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
    if(pid <= 0) return -1;
    //Find the dead service. Now you should find the service representing zygote.
	svc = service_find_by_pid(pid);
   	......

	if(!(svc->flags & SVC_ONESHOT)) {
    	//Kill all the child processes created by zygote, which is why the Java World crashes after zygote dies.
       	kill(-pid, SIGKILL);
   	}
 
    //Clean up the socket information. Unclear readers can use the command man 7 AF_UNIX query related knowledge.
    for(si = svc->sockets; si; si = si->next) {
       	char tmp[128];
       	snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s",si->name);
        unlink(tmp);
    }
 
   	svc->pid = 0;
   	svc->flags &= (~SVC_RUNNING);
 
   	if(svc->flags & SVC_ONESHOT) {
       svc->flags |= SVC_DISABLED;
    }
   	......
	now = gettime();
	/*
	If SVC is set_ If it is marked as critical, the service cannot be restarted more than 4 times within 4 minutes, otherwise
	The machine will restart and enter recovery mode. According to init RC configuration, only servicemanager process
	Enjoy such treatment.
	*/
    if(svc->flags & SVC_CRITICAL) {
        if(svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
           if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
              ......
               sync();
               __reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,
                        LINUX_REBOOT_CMD_RESTART2, "recovery");
               return 0;
           }
        } else {
           svc->time_crashed = now;
           svc->nr_crashed = 1;
        }
    }

   	svc->flags |= SVC_RESTARTING;
	//Set marked as SVC_ Restart, and then execute the COMMAND in the service onrestart. These contents are
	//It's very simple. Readers can learn by themselves.
   	list_for_each(node, &svc->onrestart.commands) {
       	cmd = node_to_item(node, struct command, clist);
       	cmd->func(cmd->nargs, cmd->args);
	}
	//Set init svc. The value of zygote is restarting.
   	notify_service_state(svc->name, "restarting");
    return 0;
}

Through the above code, you can know the function of onrestart, but where did zygote restart itself? The answer is in the following code:

init. c: : mainfunction code snippet

for(;;) {
       int nr, i, timeout = -1;
       for (i = 0; i < fd_count; i++)
           ufds[i].revents = 0;
       drain_action_queue(); //After the poll function returns, it will enter the next cycle
       restart_processes(); //All flags will be restarted here, and the flag is SVC_ The service of restarting.
       ......
}

In this way, zygote is back!

3.2.4 Attribute Service

We know that there is a thing called registry on Windows platform. The registry can store some key value pairs similar to key/value. Generally speaking, the system or some applications will store some of their own properties in the registry. Even if the next system restart or application restart, it can also carry out corresponding initialization according to the properties previously set in the registry. The Android platform also provides a type mechanism, which can be called property service. Applications can query or set properties through this property mechanism. Readers can log in to the real machine or simulator with the adb shell, and then use the getprop command to view the properties in the current system. That is, the HTC G7 test results are shown in Figure 3-2: (only some attributes are shown in the figure)

Figure 3-2 schematic diagram of HTC G7 attributes

How is this attribute service implemented? Let's look at the code, which is related to init C the code related to attribute service includes the following two lines:

property_init();
property_set_fd = start_property_service();

Look at them separately.

Property service initialization

(1) Create storage space
Look at property first_ Init function, the code is as follows:

property_service.c

void property_init(void)
{
	init_property_area();//Initialize attribute storage area
	//Load default Prop file
   	load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}

In propertyty_ In the init function, call init first_ property_ Area function, create a storage area for storing attributes, and then load default The contents of the prop file. Look at init again_ property_ How does area work? Its code is as follows:

property_service.c

static int init_property_area(void)
{
   	prop_area *pa;

   	if(pa_info_array)
       	return -1;
		/*
		Initialize storage space, PA_SIZE is the total size of this storage space, 32768 bytes, pa_workspace
		It is a structure of workspace type. The following is its definition:
		typedef struct {
		    void *data;   //Starting address of storage space
		    size_tsize;  //Size of storage space
		    int fd;   //Shared memory file descriptor
		} workspace;
		init_workspace Function call ashmem provided by Android system_ create_ The region function creates a block
		Shared memory. We will touch on the knowledge of shared memory in Chapter 7. Here, just treat it as an ordinary memory
		All right.
    	*/
   		if(init_workspace(&pa_workspace, PA_SIZE))
       		return -1;
 
   		fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

		//In 32768 bytes of storage space, there is PA_INFO_START (1024) bytes are used to store header information
   		pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
    	pa = pa_workspace.data;
   		memset(pa, 0, PA_SIZE);
   		pa->magic = PROP_AREA_MAGIC;
   		pa->version = PROP_AREA_VERSION;
			//__ system_property_area__ This variable is output by the bionic libc library. What's the use?
       		__system_property_area__ = pa;

    	return0;
}

The above content is relatively simple, but the final assignment statement has a lot to come from__ system_property_area__ Is a variable output from the bionic libc library. Why assign a value to it here?

Originally, although the attribute area is created by the init process, the Android system hopes that other processes can also read things in this memory. To achieve this, it has done the following two things:

  • Create attribute areas on shared memory, which can span processes. This has been seen in the above code, init_ This shared memory will be created internally in the workspace function.
  • How to let other processes know the shared memory? Android makes use of gcc's constructor attribute, which indicates a__ libc_ The prenit function will be called automatically when the bionic libc library is loaded__ libc_prenit, this function will internally complete the mapping of shared memory to local processes.

(2) Client process gets storage space
For the above contents, see the relevant codes:

libc_init_dynamic.c

//The constructor property instructs the loader to call first after loading the library__ libc_ The prenit function. This is the same as on Windows
//The DllMain function of the dynamic library is similar
void __attribute__((constructor))__libc_prenit(void);
void __libc_prenit(void)
{
    ......
     __libc_init_common(elfdata); //Call this function
    ......
}

__ libc_ init_ The common function is:

libc_init_common.c

void __libc_init_common(uintptr_t *elfdata)
{
   ......
   __system_properties_init();//Initializes the client's property storage area
}

system_properties.c

int __system_properties_init(void)
{
   	prop_area *pa;
    int s,fd;
   	unsigned sz;
    char *env;

	.....
	//Remember where you added environment variables in the startup zygote section? Property stores information about the area
	//It was added there. It needs to be taken out and used here.
    env = getenv("ANDROID_PROPERTY_WORKSPACE");
    //Fetch the file descriptor of the attribute storage area. The knowledge of shared memory will be introduced in Chapter 7.
    fd = atoi(env);
    env = strchr(env, ',');
    if (!env) {
      	return -1;
    }
    sz = atoi(env + 1);
	//Map the memory created by init to the local process space so that the local process can use this shared memory.
	//Note that prot is specified during mapping_ Read property, so the client process can only read the property, not set the property.
    pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
   
    if (pa == MAP_FAILED) {
       return -1;
    }
   if((pa->magic != PROP_AREA_MAGIC) || (pa->version !=PROP_AREA_VERSION)) {
       munmap(pa, sz);
       return -1;
    }
   __system_property_area__ = pa;
    return 0;
}

Many parts of the above code are related to shared memory. Problems related to shared memory will be introduced in Chapter 7. Readers can also learn about shared memory first.

In short, in this way, the client process can directly read the attribute space, but does not have permission to set attributes. How do client processes set properties?

Start property server

(1) Start property server
The init process starts a property server, and the client can set properties only by interacting with the property server. Let's first look at the content of the property server, which consists of start_ property_ The service function starts, and the code is as follows:

Property_servie.c

int start_property_service(void)

{
    int fd;

   	/*
    Loading property files is actually parsing the properties in these files and setting them into the property space. Android system
    There are four files for storing attributes, which are:
    #definePROP_PATH_RAMDISK_DEFAULT 	"/default.prop"
	#define PROP_PATH_SYSTEM_BUILD     	"/system/build.prop"
	#define PROP_PATH_SYSTEM_DEFAULT   	"/system/default.prop"
	#define PROP_PATH_LOCAL_OVERRIDE   	"/data/local.prop"
	*/
  
   	load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
   	load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
	load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
	//Some attributes need to be saved to permanent media. These attribute files are loaded by the following function
	//It is stored in the / data/property directory, and the file names of these files must be with persist start. This function
	//Very simple, readers can study it by themselves.
    load_persistent_properties();
   //Create a socket for IPC communication.
    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    if( fd< 0) return -1;
   	fcntl(fd, F_SETFD, FD_CLOEXEC);
   	fcntl(fd, F_SETFL, O_NONBLOCK);
   	listen(fd, 8);
    return fd;

}

The attribute service creates a socket to receive the request, but where is the request processed? In fact, the relevant processing has been carried out in the for loop in init.

(2) Processing set property requests
The place where the request is received is in the init process. The code is as follows:

init. c: : mainfunction fragment

if (ufds[1].revents == POLLIN)
           handle_property_set_fd(property_set_fd);

When the property server receives a client request, init calls handle_property_set_fd for processing. The code of this function is as follows:

property_service.c

void handle_property_set_fd(int fd)
{
   	prop_msg msg;
    int s;
    int r;
    int res;
    struct ucred cr;
    struct sockaddr_un addr;
   	socklen_t addr_size = sizeof(addr);
   	socklen_t cr_size = sizeof(cr);
    //Receive TCP connection first
    if ((s= accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
       return;
    }

    //Get the permissions and other attributes of the client process.
    if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        ......
       	return;
    }
   	//Receive request data
    r = recv(s,&msg, sizeof(msg), 0);
   	close(s);
    ......
   	switch(msg.cmd) {
    case PROP_MSG_SETPROP:
       msg.name[PROP_NAME_MAX-1] = 0;
       msg.value[PROP_VALUE_MAX-1] = 0;
        /*
		If the message starts with ctl, it is considered as a control message. The control message is used to execute some commands, such as
		adb shell After logging in, enter setprop CTL Start bootanim to view the boot animation,
		To close, enter setpropctl Stop bootanim, isn't it interesting?
        */
       if(memcmp(msg.name,"ctl.",4) == 0) {
           if (check_control_perms(msg.value, cr.uid, cr.gid)) {
               handle_control_message((char*) msg.name + 4, (char*) msg.value);
           }
           ......
        }else {
           //Check whether the client process has sufficient permissions
           if (check_perms(msg.name, cr.uid, cr.gid)) {
               //Then call property_. Set settings.
               property_set((char*) msg.name, (char*) msg.value);
           }
           ......
        }
       break;
   default:
       break;
    }
}

When the permissions of the client meet the requirements, init calls property_set performs related processing. This function is relatively simple, and the code is as follows:

property_service.c

int property_set(const char *name, const char*value)
{
   	prop_area *pa;
   	prop_info *pi;

    int namelen = strlen(name);
    int valuelen = strlen(value);
    ......
    //Find out whether the attribute already exists from the attribute storage space
    pi = (prop_info*) __system_property_find(name);
 
	if(pi!= 0) {
    	//If the attribute name is ro At the beginning, it means that it is read-only and cannot be set, so it is returned directly.
       	if(!strncmp(name, "ro.", 3)) return -1;
        pa = __system_property_area__;
        //Update the value of this property
       	update_prop_info(pi, value, valuelen);
       	pa->serial++;
       	__futex_wake(&pa->serial, INT32_MAX);
	}else {
   		//If the corresponding attribute is not found, it is considered to be added, so a new item needs to be created. Note that Android supports
   		//There are 247 attributes at most. If there are 247 attributes in the storage space of the current attribute, it will be returned directly.
        pa = __system_property_area__;
       	if(pa->count == PA_COUNT_MAX) return -1;

        pi = pa_info_array + pa->count;
       	pi->serial = (valuelen << 24);
       	memcpy(pi->name, name, namelen + 1);
        memcpy(pi->value, value, valuelen +1);

       	pa->toc[pa->count] =
           (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));

       pa->count++;
       pa->serial++;
       __futex_wake(&pa->serial, INT32_MAX);
    }
    //There are some special properties that need special processing. Here, it is mainly based on net Property starting with change.
    if(strncmp("net.", name, strlen("net.")) == 0)  {
    	if(strcmp("net.change", name) == 0) {
           	return 0;
        }
       	property_set("net.change", name);
    } elseif (persistent_properties_loaded &&
       	strncmp("persist.", name,strlen("persist.")) == 0) {
       	//If the attribute name is in persist At the beginning, you need to write these values to the corresponding file.
      	write_persistent_property(name, value);
	}
	/*
	Remember init The following sentence in RC?
	on property:persist.service.adb.enable=1
         startadbd
	When persist service. adb. When the enable attribute is set to 1, the command start ADB will be executed,
	This is through property_ The changed function is very simple, and readers can read it by themselves.
	*/
   	property_changed(name, value);
    return 0;
}

OK, the work of the attribute server has been understood. Let's see how the client sets the attribute.

(3) Client send request
Client through property_set send request, property_set is provided by libcutils library. The code is as follows:

properties.c

int property_set(const char *key, const char*value)
{
   	prop_msg msg;
   	unsigned resp;

   	......
   	msg.cmd = PROP_MSG_SETPROP;//Set the message code to PROP_MSG_SETPROP. 
   	strcpy((char*) msg.name, key);
   	strcpy((char*) msg.value, value);
    //Send request
    returnsend_prop_msg(&msg);
}

static int send_prop_msg(prop_msg *msg)
{
    int s;
    int r;
    //Establish socket connection with property server
    s = socket_local_client(PROP_SERVICE_NAME,
                           ANDROID_SOCKET_NAMESPACE_RESERVED,
                            SOCK_STREAM);
    if(s < 0) return -1;
    //Send it through socket
   	while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
       	if((errno == EINTR) || (errno == EAGAIN)) continue;
       	break;
    }

    if(r == sizeof(prop_msg)) {
        r = 0;
    } else{
        r = -1;
    }

   	close(s);
    return r;
}

So far, the introduction of property server is finished. Overall, it's relatively simple.

3.3 summary of this chapter

This chapter explains how the init process parses zygote and the working principle of the attribute server, in order to help readers understand the first process of Tianzi. On the whole, init RC is the most difficult to parse. I believe that readers have understood init through the above example analysis Analytical principle of RC. In addition, inti involves a lot of knowledge related to Linux system, and interested readers can study it by themselves.

Keywords: Android

Added by JackW on Sun, 23 Jan 2022 05:10:26 +0200