Android Startup Process Analysis (8) parsing the init. rc action, androidinit. rc
######################################## #####
This article is original in extreme cold ice. For more information, see the source.
######################################## #####
The previous chapter describes how to parse init. rc after it is loaded during android startup.
Parsing implements different initialization files based on three different sections: parse_action, parse_service, and parse_import.
In this section, we will explain from parse_action how to parse the on keyword under init. rc and the corresponding action and command.
By convention, Let's first look at the implementation of this function:
Static void * parse_action (struct parse_state * state, int nargs, char ** args) {struct action * act; if (nargs <2) {parse_error (state, "actions must have a trigger \ n"); return 0 ;}if (nargs> 2) {parse_error (state, "actions may not have extra parameters \ n "); return 0;} act = calloc (1, sizeof (* act); // initialize the struct action act-> name = args [1]; // assign the list_init (& act-> commands) value to the action name; // initialize the internal linked list list_init (& act-> qlist) of the commands struct of the action ); // initialize the qlist structure and extract the internal linked list list_add_tail (& action_list, & act-> alist); // use listnode alist, add the current structure to the linked list with action_list as the sentinel node,/* XXX add to hash */return act ;}
All the contents initialized here are linked list operations.
For better understanding, let's take a look at the action body:
Struct action {/* node in list of all actions */struct listnode alist; // use this listnode to add it to action_list/* node in the queue of pending actions */struct listnode qlist;/* node in list of actions for a trigger */struct listnode tlist; unsigned hash; const char * name; struct listnode commands; struct command * current ;};
Okay. To sum up, after parse_action, the current action will be added to the linked list where action_list is the sentinel node.
The linked lists of commands and qlist struct are initialized.
In parse_new_section, we can see that after initialization, The parse_line of the current state is set to parse_line_action
case K_on: state->context = parse_action(state, nargs, args); if (state->context) { state->parse_line = parse_line_action; return; } break;
When executing the command of the next action, the parse_line_action () function is executed.
The details are as follows:
If (kw_is (kw, SECTION) {state. parse_line (& state, 0, 0); parse_new_section (& state, kw, nargs, args);} else {state. parse_line (& state, nargs, args); (essentially parse_line_action (& state, nargs, args ))}}
Next we will look at the implementation of parse_line_action:
Static void parse_line_action (struct parse_state * state, int nargs, char ** args) {struct command * cmd; struct action * act = state-> context; // use state-> context to obtain the parsed action int (* func) (int nargs, char ** args); int kw, n; if (nargs = 0) {// determines whether it is null. if there is no command to execute, return;} kw = lookup_keyword (args [0]); // obtain the kw. The principle is the same as that of the SECTION. if (! Kw_is (kw, COMMAND) {// if this command is not a COMMAND, an error is returned. Parse_error (state, "invalid command '% s' \ n", args [0]); return ;}// only execute command under action, and these commands are the n = kw_nargs (kw) defined in keywords; // several parameters are required to obtain the command from keywords, which is the third item in the array initialization, nargs if (nargs <n) {// if the required parameter is not met, the system returns the error parse_error (state, "% s requires % d % s \ n ", args [0], n-1, n> 2? "Arguments": "argument"); return;} cmd = malloc (sizeof (* cmd) + sizeof (char *) * nargs ); // initialize the command struct in the action struct cmd-> func = kw_func (kw); // obtain the function to be executed by this command, and put it in the pointer of func cmd-> line = state-> line; // obtain the command line in the file cmd-> filename = state-> filename; // which file is commands cmd-> nargs = nargs; // The commands parameters include several memcpy (cmd-> args, args, sizeof (char *) * nargs ); // copy these parameters to the commands array. list_add_tail (& act-> commands, & cmd-> clist); // copy the commands to be executed, added to the structure of the action. The listnode is in the commands linked list}
I have a question. In the structure of the action, a qlist is initialized, but it does not seem to be used?
Why?
Let's go back to the main function of init. c and take a look:
init_parse_config_file("/init.rc"); ERROR("action for each trigger <==== chao"); action_for_each_trigger("early-init", action_add_queue_tail);
After the init. rc file is parsed, The action_for_each_trigger function is executed.
Void action_for_each_trigger (const char * trigger, void (* func) (struct action * act) {struct listnode * node; struct action * act; list_for_each (node, & action_list) {// traverse the action_list (all actions. Act = node_to_item (node, struct action, alist); if (! Strcmp (act-> name, trigger) {// if the name is the same as "trigger", the action_add_queue_tail function func (act) will be executed ); // The actual execution is the action_add_queue_tail (act) function }}}
Let's take a look at what this function is doing:
void action_add_queue_tail(struct action *act){ if (list_empty(&act->qlist)) { list_add_tail(&action_queue, &act->qlist); }}
Because each action is initialized with a qlist, it must not be empty.
This action struct will be added to the action_queue linked list. This is strange. Why should an action be added to two linked lists?
Return to the main function of init. c,
As you can see, after action_for_each_trigger is complete, many operations are performed.
action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); ERROR("action for each trigger init <==== chao"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail);
What does queue_builtin_action do? Is there any relationship with our previous questions?
Let's take a look at its implementation:
Void queue_builtin_action (int (* func) (int nargs, char ** args), char * name) // take the first function as an example. The func here is wait_for_coldboot_done_action, name: wait_for_coldboot_done {struct action * act; struct command * cmd; act = calloc (1, sizeof (* act); // initialize the action struct act-> name = name; // assign the name list_init (& act-> commands) to the structure of the action; // initialize the list_init (& act-> qlist) linked list of the command ); // initialize the qlist linked list cmd = calloc (1, sizeof (* cmd); // The structure of the malloc command cmd-> func = func; // assign the function to cmd-> func cmd-> args [0] = name; // set the first parameter of cmd to cmd-> nargs = 1; // for such functions, args and nargs are useless. They are only initialization, so that they are not null list_add_tail (& act-> commands, & cmd-> clist ); // The listnode using command is a clist node, <span style = "font-size: 13.63636302948px; line-height: 25.9943180084229px; font-family: Arial; "> Add the commands to be executed to the action struct. The listnode is a commands linked list. </span> list_add_tail (& action_list, & act-> alist ); // Add this action to the action_list linked list. action_add_queue_tail (act); // Add this action to the qlist node}
In this way, we can clearly see the differences between the two linked lists.
For the linked list of action_list, if the actions in init. rc are action1, action2, action3..., the linked list of our action_list is:
Action_list-> action1-> action2-> action3-> actoin4...-> wait_for_coldboot_done-> mix_hwrng_into_rng .....
The linked list corresponding to action_qlist corresponds:
Action_queue-> early-init-> wait_for_coldboot_done-> mix_hwrng_into_rng->...-> init-> callback-> property_service_init-> ....
That is, action_list is arranged according to the parsing order of init. rc and the declared actions to be added in init. c.
Action_queue is arranged in the order of processes declared and executed in the main function of init. c.
These finally figured out the data structure after action resolution, so another big challenge is the service data structure.
Let's take a look at it in the next article.