Android INIT process (2); Initialization language (init. Rc) Parsing
The software version used in this article
Android: 4.2.2
Linux Kernel: 3.1.10
This article and several subsequent articles will analyze the initialization (init) process of Android in detail, and introduce a lot of knowledge in it, I hope to help readers understand the android startup process. This chapter mainly describes how to determine the initialization file names related to hardware and how property services work.
Android is essentially a Linux kernel-based operating system. Similar to Ubuntu Linux and Fedora Linux. Android only adds some special support for mobile devices at the application layer. Since Android is a Linux kernel system, the basic startup process should also comply with Linux rules. If you have studied other Linux systems, you should understand that a complete Linux system will first load a Linux kernel to the memory, that is, compile the bzimage file generated by the Linux kernel source code, the zimage file is generated for the Linux kernel source code optimized for Android. This file is the binary version of the Linux kernel. Because zimage runs in the kernel space, the software we usually use runs in the Application Space (for details about the kernel space and application space, refer to Android Deep Exploration (Volume 1 ): hal and driver development will comprehensively analyze the overall Android system in subsequent volumes ). Kernel space and application space cannot be accessed directly through the memory address level, so a communication mechanism needs to be established.
Currently, Linux has many communication mechanisms that can interact between user space and kernel space, such as device driver files (in the/dev directory), memory files (/proc,/sys directory, and so on ). Everyone who understands Linux should know that one of the important features of Linux is that everything exists in the form of files. For example, a device usually corresponds to one or more device files. These files that interact with the kernel space are in the user space. Therefore, after loading the Linux kernel, you must first create the directory where these files are located. The program that completes these tasks is the init described in this article. Init is a command line program. One of its main tasks is to create the directory where these files interact with the kernel space. After the Linux kernel is loaded, the first thing to do is to call the INIT program, that is, init is the first program executed in the user space.
Before analyzing the core code of init, you also need to know that init has done the following work in addition to creating some directories.
1. initialize attributes
2. commands for processing configuration files (mainly init. RC files), including processing various actions.
3. Performance analysis (using the bootchart tool ).
4. Execute Command in an infinite loop (start other processes ).
Although init does not do a lot of work, the code is still very complicated. The INIT program is not composed of a source code file, but a link to the target file of a group of source code files. These files are located in the following directory.
<Android source code directory>/system/CORE/init
Among them, init. c is the main file of init. open the file and check the content. As init is a command line program, the analysis of init. C should start with the main function. The Code is as follows:
Int main (INT argc, char ** argv) {int fd_count = 0; struct pollfd ufds [4]; char * tmpdev; char * debuggable; char TMP [32]; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; If (! Strcmp (basename (argv [0]), "ueventd") return ueventd_main (argc, argv); If (! Strcmp (basename (argv [0]), "watchdogd") return watchdogd_main (argc, argv);/* clear the umask */umask (0 ); // The following code starts to create directories for various user spaces, such as/dev,/proc,/sys, and mkdir ("/dev", 0755); mkdir ("/proc ", 0755); mkdir ("/sys", 0755); mount ("tmpfs", "/dev", "tmpfs", ms_nosuid, "mode = 0755 "); mkdir ("/dev/PTS", 0755); mkdir ("/dev/socket", 0755); mount ("devpts", "/dev/PTS ", "devpts", 0, null); mount ("proc", "/proc", "proc", 0, Null); mount ("sysfs", "/sys", "sysfs", 0, null);/* detect/dev /. whether the booting file can be read/written and created */close (open ("/dev /. booting ", o_wronly | o_creat, 0000); open_devnull_stdio (); klog_init (); // initialize the property_init (); get_hardware_name (hardware, & revision ); // process the kernel command line process_kernel_cmdline ();...... Is_charger =! Strcmp (bootmode, "charger"); Info ("Property init \ n"); If (! Is_charger) property_load_boot_defaults (); Info ("reading config file \ n"); // analyze the content of the/init. RC file init_parse_config_file ("/init. RC ");...... // Execute the action action_for_each_trigger ("init", action_add_queue_tail) in the initialization file; // If (! Is_charger) {detail ("early-Fs", action_add_queue_tail); detail ("FS", action_add_queue_tail); action_for_each_trigger ("post-Fs", action_add_queue_tail ); struct ("post-FS-Data", struct);} queue_builtin_action (struct, "property_service_init"); queue_builtin_action (signal_init_action, "signal_init"); queue_builtin _ Action (check_startup_action, "check_startup"); If (is_charger) {lower ("charger", action_add_queue_tail);} else {lower ("early-Boot", action_add_queue_tail ); action_for_each_trigger ("Boot", action_add_queue_tail);}/* Run all property triggers based on current state of the properties */queue_builtin_action (actions, "queue_property_trigg ERS "); # If bootchart queue_builtin_action (bootchart_init_action," bootchart_init "); # endif // enter the infinite loop and create the init sub-process (init is the parent process of all processes) for (;) {int NR, I, timeout =-1; // execute the command (the command corresponding to the sub-process) execute_one_command (); restart_processes (); If (! Property_set_fd_init & get_property_set_fd ()> 0) {ufds [fd_count]. FD = get_property_set_fd (); ufds [fd_count]. events = Pollin; ufds [fd_count]. revents = 0; fd_count ++; property_set_fd_init = 1;} If (! Signal_fd_init & get_signal_fd ()> 0) {ufds [fd_count]. FD = get_signal_fd (); ufds [fd_count]. events = Pollin; ufds [fd_count]. revents = 0; fd_count ++; signal_fd_init = 1;} If (! Keychord_fd_init & get_keychord_fd ()> 0) {ufds [fd_count]. FD = get_keychord_fd (); ufds [fd_count]. events = Pollin; ufds [fd_count]. revents = 0; fd_count ++; keychord_fd_init = 1;} If (process_needs_restart) {timeout = (process_needs_restart-gettime () * 1000; If (timeout <0) timeout = 0;} If (! Action_queue_empty () | cur_action) Timeout = 0; // bootchart is a performance statistics tool used to collect hardware and system information and write it to the disk, so that the program can use # If bootchart if (bootchart_count> 0) {If (timeout <0 | timeout> bootchart_polling_ms) Timeout = bootchart_polling_ms; If (bootchart_step () <0 | -- bootchart_count = 0) {bootchart_finish (); bootchart_count = 0 ;}# endif // wait for the next command to submit Nr = poll (ufds, fd_count, timeout); If (NR <= 0) continue; for (I = 0; I <fd_count; I ++) {If (ufds [I]. revents = Pollin) {If (ufds [I]. FD = get_property_set_fd () handle_property_set_fd (); else if (ufds [I]. FD = get_keychord_fd () handle_keychord (); else if (ufds [I]. FD = get_signal_fd () handle_signal () ;}} return 0 ;}
We can see that the main function is very complex, but we do not need to make every statement very clear (because it is very difficult ), generally, you only need to know the main line of init. In fact, we can see from the main function of init. Init is actually divided into the following two parts.
1. Initialization (including creating directories such as/Dev and/proc, initializing attributes, and executing actions in initialization files such as init. Rc ).
2. Create a sub-process using an infinite loop of the for loop.
The first job is easy to understand. The second task is the core of init. In Linux, init is the parent process of all application space processes. Therefore, we usually run commands on Linux terminals and create processes. In fact, they are all done in this infinite for loop. That is to say, after executing the PS-e command on the Linux terminal, all the processes except init are created by init. In addition, init will also be resident in the content. Of course, if init crashes, the Linux system will basically crash.
Because init is complex, this article only analyzes part of it. In subsequent articles, we will analyze the core components of init in detail.
It is relatively simple to create a directory after the main function is started, and there is nothing to analyze in this part. That is, call some common APIs (mkdir) to create some directories. Now let's talk about some other things. Because the underlying Android source code (including init) is actually in the field of Linux application programming, to fully understand the android source code, we need to understand the basic structure of Linux, you must be familiar with the Linux Application Layer APIs. To meet the needs of these readers, I will write some articles about Linux Application Programming in the future. OK. Now let's get down to the point. Next, we will analyze the parsing of the configuration file.
The configuration file here mainly refers to init. RC. You can go to the android shell to see an init. RC file in the root directory. The file is read-only. Even if you have the root permission, you cannot modify the file. Because the files we see in the root directory are only images of memory files. That is to say, after Android is started, it loads the init. RC file to the memory. The content of the init. RC file is actually only the content of the init. RC file in the memory. Once you restart Android, the content of the init. RC file will be restored to the initial load. The only way to thoroughly modify the content of the init. RC file is to modify the kernel image (boot. IMG) in the android Rom ). In fact, the boot. IMG name is the kernel image. However, this file not only contains the complete Linux Kernel File (zimage), but also contains another image file (ramdisk. IMG ). Ramdisk. IMG contains the init. RC file and the init command. Therefore, only by modifying the init. RC file in the ramdisk. imgfile, re-packaging the boot. imgfile, and flashing the file can the init. RC file be completely modified. If the reader has the android source code, after compilation, a root directory is generated in the related sub-directories in the out Directory, which is actually the content after ramdisk. IMG is decompressed. The init command and init. RC file are displayed. In subsequent articles, we will discuss how to modify the init. RC file and how to fl it. However, these contents have little to do with this article, so we will not discuss them in detail.
Return to the main function. After the directory is created, the following three functions are executed.
Property_init ();
Get_hardware_name (hardware, & revision );
Process_kernel_cmdline ();
Property_init mainly allocates some storage space for the attribute, and this function is not the core. However, when viewing the init. RC file, we will find that other configuration files, such as/init. USB. RC, are imported using some import statements at the beginning of the file. Most configuration files use a specific file name. Only the following code uses a variable ($ {Ro. Hardware}) to execute part of the configuration file name. Where does the value of this variable come from?
Import/init. $ {Ro. Hardware}. RC
First, you must understand that the content of the init. $ {Ro. Hardware}. RC configuration file is usually related to the current hardware. Now let's take a look at the get_hardware_name function. The Code is as follows:
Void get_hardware_name (char * hardware, unsigned int * Revision) {char data [1024]; int FD, N; char * X, * HW, * Rev; /* If the value of hardware already exists, it indicates that hardware is provided through the kernel command line and returns */If (hardware [0]) return directly; // open the/proc/cpuinfo file FD = open ("/proc/cpuinfo", o_rdonly); If (FD <0) return; // read the content of the/proc/cpuinfo file n = read (FD, Data, 1023); close (FD); If (n <0) return; data [N] = 0; // obtain the value of the hardware field from the/proc/cpuinfo file hW = strst R (data, "\ nhardware"); REV = strstr (data, "\ nrevision"); // The value of the hardware field is obtained successfully if (HW) {x = strstr (HW, ":"); If (x) {x + = 2; n = 0; while (* X & * X! = '\ N') {If (! Isspace (* X) // converts the value of the hardware field to lowercase, and updates the value of the hardware parameter. // hardware, that is, in init. the hardware array defined in the C file hardware [n ++] = tolower (* X); X ++; If (n = 31) break ;} hardware [N] = 0 ;}} if (REV) {x = strstr (Rev, ":"); If (x) {* revision = strtoul (x + 2, 0, 16 );}}}
From the code of the get_hardware_name method, we can know that this method is mainly used to determine the values of the hardware and revision variables. Revision is not discussed here, as long as you study hardware. The source for obtaining hardware is from the Linux kernel command line or the content in the/proc/cpuinfo file. Linux Kernel command line is not discussed at the moment (because this value is rarely passed). First, check/proc/cpuinfo. This file is a virtual file (memory file ), run the CAT/proc/cpuinfo command to view the content in the file, as shown in 1. In the white box, it is the value of the hardware field. The value is grouper because the device is Nexus 7. If the program is in this position, the hardware-related configuration file name is init. Grouper. RC. Readers with Nexus 7 will see that there is indeed an init. Grouper. RC file in the root directory. Note that the native ROM of Nexus 7 does not set the configuration file name elsewhere, so the configuration file name is the value obtained from the hardware field of the/proc/cpuinfo file.
Figure 1
Now let's look at the process_kernel_cmdline function called after the get_hardware_name function. The Code is as follows:
Static void process_kernel_cmdline (void) {/* don't expose the raw CommandLine to nonpriv processes */chmod ("/proc/cmdline", 0440 ); // import the kernel command line parameter import_kernel_cmdline (0, import_kernel_nv); If (qemu [0]) import_kernel_cmdline (1, import_kernel_nv); // use the attribute value to set kernel variable handler ();}
In the process_kernel_cmdline function, apart from using the import_kernel_cmdline function to import kernel variables, the main function is to call the export_kernel_boot_props function to set kernel variables through attributes, for example, through Ro. boot. set the hardware variable in the hardware attribute, that is, you can use Ro. boot. you can modify the value of the hardware attribute in the get_hardware_name function from the/proc/cpuinfo file. Next, let's take a look at the code of the export_kernel_boot_props function.
Static void Merge (void) {char TMP [prop_value_max]; const char * pval; unsigned I; struct {const char * src_prop; const char * dest_prop; const char * def_val ;} prop_map [] = {"Ro. boot. serialno "," Ro. serialno "," ",}, {" Ro. boot. mode "," Ro. bootmode "," unknown ", },{" Ro. boot. baseband "," Ro. baseband "," unknown ", },{" Ro. boot. bootloader "," Ro. bootloader "," unknown ",},}; // indicates the owner of the kernel. For (I = 0; I <array_size (prop_map); I ++) {pval = property_get (prop_map [I]. src_prop); property_set (prop_map [I]. dest_prop, pval?: Prop_map [I]. def_val);} // according to ro. boot. set the console variable pval = property_get ("Ro. boot. console "); If (pval) strlcpy (console, pval, sizeof (console);/* save a copy for init's usage during boot */strlcpy (bootmode, property_get ("Ro. bootmode "), sizeof (bootmode);/* If this was given on Kernel command line, override what we read * Before (e.g. from/proc/cpuinfo), if anything * // get Ro. boot. hard Ware property value pval = property_get ("Ro. boot. hardware "); If (pval) // here via Ro. boot. the hardware attribute changes the value of the hardware variable strlcpy (hardware, pval, sizeof (hardware) again; // you can set Ro by setting the value of the hardware variable. the hardware property // This property is the property of setting the initialization file name mentioned earlier. It is actually set through the property_set ("Ro. hardware ", hardware); snprintf (TMP, prop_value_max," % d ", revision); property_set (" Ro. revision ", TMP);/* todo: these are obsolete. we shoshould delete them */ If (! Strcmp (bootmode, "Factory") property_set ("Ro. factorytest", "1"); else if (! Strcmp (bootmode, "factory2") property_set ("Ro. factorytest", "2"); else property_set ("Ro. factorytest", "0 ");}
From the code of the export_kernel_boot_props function, we can see that this function is actually to set some attribute values back and forth, and use some attribute values to modify variables such as console and hardware. The hardware variable (which is an array of 32 characters) has obtained a value from the/proc/cpuinfo file in the get_hardware_name function and passed Ro in the export_kernel_boot_props function. boot. the value of the hardware attribute is set once, but it is not set in Nexus 7, so the value of the hardware is still grouper. At last, use the hardware variable to set the Ro. Hardware attribute, so the final initialization file name is init. Grouper. RC.
There is another question: What are the attributes or attribute files mentioned previously? Is init. RC? Of course not. In fact, these attribute files are configuration files that are read by the system in different directories.
Property Service)
Before studying these configuration files, you should first understand how init handles these attributes. Readers who have compiled Windows Local applications should understand that there is a registry mechanism in Windows that provides a large number of properties in the registry. There is a similar mechanism in Linux, Which is the property service. During the startup process, init starts the property service (socket service) and creates a storage area in the memory to store these attributes. When reading these attributes, you can directly read them from this memory area. If you modify the attribute value, you must use the socket connection Attribute Service. In init. in the c file, an action function calls the start_property_service function to start the property service. The action is init. RC and similar files in an execution mechanism, because the content is relatively large, so the init. the execution mechanism in the RC file will be discussed in detail in the next article.
Now, find the start_property_service function, which is in the property_service.c file and the same directory as the init. c file.
Void start_property_service (void) {int FD; // load different attribute files (prop_path_system_build); load_properties_from_file (prop_path_system_default); load_override_properties (); /* read persistent properties after all default values have been loaded. */load_persistent_properties (); // create a socket Service (Attribute Service) FD = create_socket (prop_service_name, sock_stream, 0666, 0, 0); If (FD <0) return; fcntl (FD, f_setfd, fd_cloexec); fcntl (FD, f_setfl, o_nonblock); // start service listening listen (FD, 8); property_set_fd = FD ;}
Now that we know how to start the property service, the following two macros are involved in the start_property_service function.
Prop_path_system_build
Prop_path_system_default
Both macros are the paths of predefined property file names. To get the definition of these macros, we first analyze another function.
A property_get function was used to read attribute values. The function is implemented in property_service.c. The Code is as follows:
const char* property_get(const char *name){ prop_info *pi; if(strlen(name) >= PROP_NAME_MAX) return 0; pi = (prop_info*) __system_property_find(name); if(pi != 0) { return pi->value; } else { return 0; }}
We can see that a core function _ system_property_find is called in the property_get function. This function actually implements the function of obtaining attribute values. This function is a library of bionic. It is implemented in the system_properties.c file and can be found in the following directory.
<Android source code root directory>/bionic/libc/bionic
The code for the _ system_property_find function is as follows:
Const prop_info * _ system_property_find (const char * Name) {// obtain the first address prop_area * pA = _ system_property_area __; unsigned COUNT = pa-> count of the attribute storage memory area; unsigned * TOC = pa-> TOC; unsigned Len = strlen (name); prop_info * PI; while (count --) {unsigned entry = * TOC ++; if (toc_name_len (entry )! = Len) continue; Pi = toc_to_info (Pa, entry); If (memcmp (name, Pi-> name, Len) continue; return PI;} return 0 ;}
From the code of the _ system_property_find function, we can easily see that the first line uses the _ system_property_area _ variable, which is global. A property_init function is involved in the analysis of the main function. This function calls the init_property_area function, which is used to initialize the attribute memory area, that is, the _ system_property_area _ variable.
Static int init_property_area (void) {prop_area * pA; If (pa_info_array) Return-1; if (init_workspace (& pa_workspace, pa_size) Return-1; fcntl (pa_workspace.fd, f_setfd, fd_cloexec); 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;/* initialize the attribute memory area, which will be used by the Attribute Service */_ system_property_area _ = PA; property_area_inited = 1; return 0 ;}
In the header file system_properties.c that corresponds to the system_properties.h file, two macros that represent the path of the property file are defined. In fact, there are two other macros that represent the path, a total of four property files. The system_properties.h file can be found in the <Android source code root directory>/bionic/libc/include/sys directory. The four macros are defined as follows:
#define PROP_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"
Now, you can access the corresponding directory of the Android device. Generally, you can find the above four files. Normally, you will find a default file in the root directory. prop file, cat default. prop will see the content of this file. The property service loads all the properties in all the four property files and the properties set using property_set. On the android terminal, you can directly use the getprop command to obtain all attribute values from the property service. 2. The getprop command can also get the specific property value by using the root property name, for example, getprop Ro. Build. Product.
Figure 2
If you are interested, you can see how getprop reads and writes properties through the property service. The source code file of the getprop command is getprop. C. You can find this file in the <Android source code root directory>/system/CORE/toolbox directory. In fact, the getprop function is used to obtain the property value. After analyzing this function, we actually call the _ system_property_find function to obtain the attribute value from the memory region specified by the _ system_property_area _ variable.
In addition, the system_properties.c file contains the following two functions used to modify or add an attribute value through the Attribute Service.
Static int send_prop_msg (prop_msg * MSG) {struct pollfd pollfds [1]; struct sockaddr_un ADDR; socklen_t Alen; size_t namelen; int s; int R; int result =-1; // create a socket S = socket (af_local, sock_stream, 0) for connecting to the property service; if (S <0) {return result;} memset (& ADDR, 0, sizeof (ADDR); // property_service_socket is the socket device file name namelen = strlen (property_service_socket); strlcpy (ADDR. sun_path, property_service_soc Ket, sizeof ADDR. sun_path); ADDR. sun_family = af_local; Alen = namelen + offsetof (struct sockaddr_un, sun_path) + 1; if (temp_failure_retry (connect (S, (struct sockaddr *) & ADDR, Alen) <0) {close (s); return result;} r = temp_failure_retry (send (S, MSG, sizeof (prop_msg), 0); If (r = sizeof (prop_msg )) {pollfds [0]. FD = s; pollfds [0]. events = 0; r = temp_failure_retry (poll (pollfds, 1,250/* MS */) ); If (r = 1 & (pollfds [0]. revents & pollhup )! = 0) {result = 0;} else {result = 0 ;}} close (s); return result ;} // you can directly call this function to set the attribute value int _ system_property_set (const char * Key, const char * value) {int err; int tries = 0; int update_seen = 0; prop_msg MSG; If (Key = 0) Return-1; if (value = 0) value = ""; if (strlen (key)> = prop_name_max) Return-1; if (strlen (value)> = prop_value_max) Return-1; memset (& MSG, 0, sizeof MSG); MSG. cmd = prop_msg_setprop; strlcpy (MSG. name, key, sizeof MSG. name); strlcpy (MSG. value, value, sizeof MSG. value); // set the attribute value err = send_prop_msg (& MSG); If (ERR <0) {return err;} return 0 ;}
The send_prop_msg function involves a property_service_socket variable, which is defined as follows:
static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
In fact, send_prop_msg communicates with the property service through this device file. You can enter the/dev/socket directory on the android terminal. A property_service file is usually displayed, which is the device file mapped to the property service.
Now we have analyzed how init determines the hardware-related initialization file name (init. Grouper. Rc), discussed four Property Files, their loading process, and the basic principles of implementing the property service. In the next article, we will discuss more in-depth content. For example, the init. RC file provides many actions, so what is aciton? How does init parse the init. RC file? These contents will be announced in the next article.