Android Init source Code Analysis (1) profiling

Source: Internet
Author: User
Tags null null strcmp dmesg

Features Overview
The init process is the first process launched by the Android kernel, with a process number (PID) of 1, which is the ancestor of all the processes in the Android system, so it is shouldering the important responsibility of system startup. The init source code for Android is located in the system/core/init/directory, with iterations of several versions of the Android system, and the Init source code is also refactored.
Currently Android4.4 source code, the init directory compiled after compiling the following Android system three files, respectively is
    • /init
    • /sbin/ueventd-->/init
    • /sbin/watchdogd-->/init
Among them, ueventd and wathdogd are soft links pointing to/init. (Please read init/android.mk for specific implementation).
In previous versions of Android (prior to 2.2) only the INIT process, Android2.2 created the device-driven node file function independently of the UEVENTD process, and added Watchdogd in Android4.1.

/init mainly completes three main functions:
    1. Parsing init.rc Initializing the Android property system and maintaining the property service
    2. Initialize the Android property system and maintain the property service
    3. Handling child process Start, stop, restart
The/UEVENTD is used to create device-driven nodes. /WATCHDOGD is the watchdog service process.
Code Analysis and Analysis code yourselves grasp the backbone, understand its approximate structure and process, and then block-by-step, analyze its implementation details. This way of getting to the bottom of the big picture allows us to keep our heads up when we read the code, not to go into detail without knowing the whole process, which can easily lead us astray into the code forest.
Next, the main function of INIT.C is analyzed. To facilitate the analysis, the main function code is streamlined, the code is as follows.
int main (int argc, char **argv) {//<part 1> if (!strcmp (basename (argv[0]), "Ueventd")) return Ueventd_m    Ain (argc, argv);       if (!strcmp (basename (argv[0]), "WATCHDOGD")) return Watchdogd_main (argc, argv);    <part2> umask (0);    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);    ..... open_devnull_stdio ();    Klog_init ();    Property_init ();    .....//<part3> INFO ("reading config file\n");    Init_parse_config_file ("/init.rc");    ... action_for_each_trigger ("Early-init", Action_add_queue_tail);       ..... queue_builtin_action (Queue_property_triggers_action, "queue_property_triggers"); <part4> for (;;) {... Execute_one_commanD ();        Restart_processes ();        .... 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_PR                OPERTY_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;}
The main function is divided into the above 4 parts, corresponding to Part1 to PART4, the following respectively to do a specific explanation.
Code <part1> the string content of argv[0] from the command line to distinguish whether the current program is INIT,UEVENTD or WATCHDOGD.
The C program's main function prototype is main (int argc, char* argv[]), Ueventd and WATCHDOGD start are described in Init.rc, executed by the INIT process after the fork, exec start, Therefore, the entry parameters are constructed in the Init code and will be parsed when the init.rc is parsed. At this point we will only need to store the executable's name until Argv[0].
Code <part2>

Umaks (0) is used to set the file model creation mask for the current process (i.e./init), note that the file here is a wide range of files, including plain files, directories, linked files, device nodes, and so on.
PS. The above explanation is excerpted from Umask's Mannual, which performs a man 3 umask view on a Linux system.
The functions of mkdir and open in the Linux C library run as follows.
int mkdir (const char *pathname, mode_t mode);
int open (const char *pathname, int flags, mode_t mode);
Linux kernel to each process has set a mask, when the program calls open, mkdir and other functions to create a file or directory, the incoming open mode will now mask to do the operation, the resulting file mode, the file is the real mode.
For example, to create a directory and set its file permissions to 0777,
mkdir ("TestDir", 0777)
However, the file permissions that are actually written are not necessarily 777 because the mkdir system call TestDir The mask (known as umask) of the current process when it was created, and the actual operation is 0777&~umask as the true permission of the TestDir. Therefore, in the above init, first call Umask (0) to clear the process mask 0, so call Open/mkdir and other functions to create a file or directory, the incoming mode will be written as the actual value of the file system.

Next, create the directory and mount the kernel file system, which are
    • TMPFS, virtual memory file system, the file system is mounted to the/dev directory, the main storage device node file, the user process by accessing the/dev directory under the device node file can interact with the hardware driver.
    • Devpts, a virtual terminal file system
    • Proc, the virtual file system, is mounted to the/proc directory, through which the file system interacts with the kernel data structure to view and set kernel parameters.
    • SYSFS, virtual file system, mounted to the/sys directory, which is similar to Proc, is a newer file system implemented by the 2.6 kernel on the basis of absorbing the design experience and lessons of proc file system, which provides a unified device-driven model for the kernel. (citation: http://www.ibm.com/developerworks/cn/linux/l-cn-sysfs/index.html)
The code <part2> subsequent code is as follows.
 Open_devnull    _stdio ();    Klog_init ();    Property_init ();    Get_hardware_name (Hardware, &revision);    Process_kernel_cmdline ();    Union Selinux_callback CB;    Cb.func_log = Log_callback;    Selinux_set_callback (Selinux_cb_log, CB);    Cb.func_audit = Audit_callback;    Selinux_set_callback (Selinux_cb_audit, CB);    Selinux_initialize (); /* These directories were necessarily created before initial policy load * and therefore need their security context R     Estored to the proper value. * This must happen Before/dev is populatedproperty_init ();     by Ueventd.    */Restorecon ("/dev");    Restorecon ("/dev/socket");    Restorecon ("/dev/__properties__");    Restorecon_recursive ("/sys");    Is_charger =!strcmp (Bootmode, "charger");    INFO ("Property init\n"); Property_load_boot_defaults (); 
Open_devnull_stdio () The function name implies the Stido of the Init process, including stdin (standard input, file descriptor 0), stdout (standard output, file descriptor 1), and stderr (standard error, file descriptor 2), All redirected/dev/null devices, but the attentive reader may be in doubt, in code <part2> although the Tmpfs file system is mounted in the/dev directory, but no device node files are created,/dev/null does not exist at this time. How can I redirect stdio to a null device? With questions we analyze the function implementation.
void Open_devnull_stdio (void) {    int fd;    static const char *name = "/dev/__null__";    if (Mknod (name, S_IFCHR | 0600, (1 << 8) | 3) = = 0) {        fd = open (name, O_RDWR);        unlink (name);        if (FD >= 0) {            dup2 (fd, 0);            Dup2 (FD, 1);            Dup2 (FD, 2);            if (FD > 2) {                close (FD);            }            return;        }    }    Exit (1);}
The function creates the/DEV/__NULL__ device node file through the Mknode function, then opens the file to get the file descriptor fd, and then uses the DUP2 system call to bind the file descriptor 0, 1, 2 to the FD. This/dev/__null__ looks very strange, the Linux system of NULL is not/dev/null, what is the relationship between the two?
In the Linux kernel for the device node files are a primary, secondary device number, the kernel actually use these two device numbers to identify a device driver, and do not use the file name as the identity. The Mknod system call creates a device node file with a high 8-bit main device number for the third parameter, and a low 8-bit device number. Visible/dev/__null__ The primary and secondary equipment numbers are 1, 3 respectively. Is it/dev/null? We need to go deep into the kernel to confirm this.
The following fragment exists in the Kernel/documentation/devices.txt
 1 char Memory Devices 1 =/dev/mem physical memory Access 2 =/dev/kmem Kernel virtual memory Acce  SS 3 =/dev/null Null Device 4 =/dev/port I/o port access 5 =/dev/zero null Byte source 6 =/dev/core obsolete-replaced by/proc/kcore 7 =/dev/full Returns ENOSPC o            N Write 8 =/dev/random nondeterministic random number Gen.           9 =/dev/urandom Faster, less secure random number Gen. Ten =/dev/aio asynchronous I/O notification interface one =/dev/kmsg writes to this come out as PRINTK ' s =/dev/oldmem used by crashdump kernels to access the memory of the kernel that CRAs Hed. 
Visible/dev/__null__ and/dev/null's device number is exactly the same, it is/dev/null vest. So why doesn't the INIT process create/dev/null directly? We are not able to answer this question at present, until we can understand the principle of/sbin/uevnted analysis.
There is also a question, why should stdio redirect the/dev/__null__ device? This is because at this time the anrdoid system is in the early stages of startup and the device node that can be used to receive the INIT process standard output and standard error does not exist yet. So the init process pound, redirecting them directly to/dev/__nulll__.
When we learned C, the first HelloWorld program was printed through printf, and we knew it was printed to the terminal via standard output. printf is also one of the most popular debugging methods for our programmers. Now that the standard output is redirected to a null device, what if we want to add a print statement to init? With this concern, we continue to analyze the code.
Klog_init () then Klog_init () is clearly implying that although the standard output is gone, there are ways to print the log. With a joyful and curious mood, let us see how klog_init is achieved.
void Klog_init (void) {    static const char *name = "/dev/__kmsg__";    if (klog_fd >= 0) return; /* already initialized */    if (Mknod (name, S_IFCHR | 0600, (1 << 8) | one) = = 0) {        klog_fd = open (name, O_wron LY);        if (klog_fd < 0)                return;        Fcntl (KLOG_FD, F_SETFD, fd_cloexec);        unlink (name);}    }
The Klog_init function first checks whether the KLOG_FD has been initialized. On first execution, call Mknod to create a master device number of 1, from the device-Number 11 device node file/dev/__kmsg__, and then open the file to save the file descriptor to the variable klog_fd, and then call Fcntl (KLOG_FD, F_SETFD, Fd_ Cloexec) sentence function is set to close the file descriptor when EXECV is executed. Then call unlink to delete the/dev/__kmsg__ file, here is more special, specific explanation.
When an open file is not close to it, calling unlink cannot delete the file, which is deleted after the call to close. For the kernel, when invoking open opens a file, the kernel maintains the data structure of the file that should be used, where a variable maintains a reference count of the current file, which corresponds to the file descriptor in user space. After the first open, the reference count is 1, the call to open causes the reference count to be 1, and the call to close causes the reference count to be reduced by 1. When invoking the unlink system call, if the file reference count is not 0, the kernel does not immediately delete the file, and the kernel checks the reference count every time the file is close, and the file is actually deleted if it is 0 o'clock.
P.s. According to Unlink's Mannul, (Man 2 unlink), which reads: If the name is the last link to a file and any processes still has the file open The  file  would  remain in existence until the last  file descriptor referring to it is closed.
The/dev/__kmsg__ file is identical to the/DEV/KMSG device node, which is also the latter vest. The device driver node is the kernel log file, the kernel calls the PRINTK function to print the log can be accessed through the device node, writing to the file is equivalent to executing kernel PRINTK. The contents of this file can be read through the Linux system standard program DMESG, and the Android system also provides the DMESG command.

KLOG.C file code is small, this is analyzed in conjunction
static int klog_level = Klog_default_level;int Klog_get_level (void) {    return klog_level;} void Klog_set_level (int level) {    klog_level = level;} #define LOG_BUF_MAX 512void klog_vwrite (int level, const char *FMT, va_list ap) {    char Buf[log_buf_max];    if (Level > Klog_level) return;    if (klog_fd < 0) klog_init ();    if (KLOG_FD < 0) return;    vsnprintf (buf, Log_buf_max, FMT, AP);    Buf[log_buf_max-1] = 0;    Write (KLOG_FD, buf, strlen (BUF));} void Klog_write (int level, const char *FMT, ...) {    va_list ap;    Va_start (AP, FMT);    Klog_vwrite (level, FMT, AP);    Va_end (AP);}
The Klog_write call Klog_vwrite function can be used to write to the/dev/__kmesg__ log, the first parameter is the current log level, if the current levels greater than klog_leve directly return, that is, the log can not be written/dev/__ The kmesg__. In addition, two functions Klog_set_level and klog_get_level are provided for setting and reading the current klog_level, the default level is Klog_default_level, defined in Klog.h.
Klog.h
#define Klog_error_level   3#define klog_warning_level 4#define klog_notice_level  5#define KLOG_INFO_LEVEL    6#define klog_debug_level   7#define klog_error (tag,x ...)   Klog_write (Klog_error_level, "<3>" tag ":" x) #define Klog_warning (Tag,x ...) Klog_write (Klog_warning_level, "<4>" tag ":" x) #define Klog_notice (tag,x ...)  Klog_write (Klog_notice_level, "<5>" tag ":" x) #define KLOG_INFO (tag,x ...)    Klog_write (Klog_info_level, "<6>" tag ":" x) #define KLOG_DEBUG (tag,x ...)   Klog_write (Klog_debug_level, "<7>" tag ":" x) #define KLOG_DEFAULT_LEVEL 3/* Messages <= this level is Logge D */
It is visible that the default level is 3, which is klog_error_level, and only call Klog_error can be exported to/dev/__kmesg__.
Property_init ();

This sentence is used to initialize the Android property system and will be specifically described in the Init property system .

Get_hardware_name

Get_hardware_name (Hardware, &revision) by reading the/proc/cpuinfo file to obtain hardware information, the author of the cottage machine for example, the file content is as follows.

[Email protected]:/$ cat/proc/cpuinfo                                        Processor       : ARMv7 Processor rev 1 (v7l) Processor       : 0BogoMIPS        : 34 8.76processor:       1BogoMIPS:        348.76processor       : 2BogoMIPS        : 348.76processor:       3BogoMIPS        :  348.76Features        : SWP half thumb fastmult vfp edsp thumbee neon Vfpv3 TLS vfpv4cpu IMPLEMENTER:0X41CPU Architecture: 7CPU Variant     : 0x0cpu part        : 0XC05CPU revision    : 1Hardware        : QRD msm8625q skudrevision        : 0000Se Rial          : 0000000000000000
The Get_hardware_name function reads the file, fills the value of the hardware field into the hardware array, and converts the value of the revision field to a 16-digit input into the revision variable.

Process_kernel_cmdline

The INIT program then calls the function Process_kernel_cmdline to parse the kernel boot parameters. The kernel is typically loaded by the bootloader (boot loader), and the currently widely used bootloader is mostly based on u-boot customization. The kernel allows bootloader to pass parameters when it starts itself. After the kernel is booted, the boot parameters can be viewed through/proc/cmdline.

For example, after the android4.4 simulator starts, look at its kernel boot parameters, as follows
[Email protected]:/# Cat/proc/cmdline
Qemu.gles=0 qemu=1 console=ttys0 android.qemud=ttys1 android.checkjni=1 Ndns=1

static void Process_kernel_cmdline (void) {/    * don ' t expose the raw commandline to Nonpriv processes */    chmod ("/pro C/cmdline ", 0440);    /* First pass does the common stuff, and finds if we are in QEMU.     * Second pass is a necessary for QEMU to export all kernel params     * as props.     *    /import_kernel_cmdline (0, Import_kernel_nv);    if (qemu[0])        import_kernel_cmdline (1, Import_kernel_nv);    /* Now propogate the info given on command line to internal variables * used by INIT as well as the current     required Properties     *    /Export_kernel_boot_props ();}
First modify the/proc/cmdline file permissions, 0440 means that only the root user or the root group user can read and write the file, other users cannot access it. The Import_kernel_cmdline function is then called consecutively, and the first parameter identifies whether the current Android device is an emulator, and the second parameter is a function pointer.

The Import_kernel_cmdline function reads the contents of the/proc/cmdline into an internal buffer and splits the contents of the cmdline into small pieces of string, which are then passed to the Import_kernel_nv function for processing. With the output of the previous/proc/cmdline as an example, the string can be split into the following segments

Qemu.gles=0qemu=1console=ttys0android.qemud=ttys1android.checkjni=1ndns=1
As a result, the Import_kernel_nv will be called 6 successive times, followed by the string. The function is implemented as follows:

Import_kernel_nv

static void Import_kernel_nv (char *name, int for_emulator) {char *value = STRCHR (name, ' = ');    int name_len = strlen (name);    if (value = = 0) return;    *value++ = 0;    if (Name_len = = 0) return; if (for_emulator) {/* in the emulator, export any kernel option with the * ro.kernel. Prefix * * CHA        R Buff[prop_name_max];        int len = snprintf (buff, sizeof (buff), "ro.kernel.%s", name);        if (Len < (int.) sizeof (BUFF)) Property_set (buff, value);    Return    } if (!strcmp (name, "Qemu")) {strlcpy (qemu, value, sizeof (QEMU)); } else if (!strncmp (name, "Androidboot.", && Name_len >) {const char *boot_prop_name = name + 12        ;        Char Prop[prop_name_max];        int cnt;        CNT = snprintf (prop, sizeof (prop), "ro.boot.%s", boot_prop_name);    if (CNT < Prop_name_max) Property_set (PROP, value); }}
When Import_kernel_cmdline first executes, the formal parameter passed in to the Import_kernel_nv is for_emulator to 0, so it will match whether name is QEMU, and if so, save its value to the QEMU global static buffer. For Android emulator, there is a "qemu=1" field in the presence/proc/cmdline. If For_emulator is 1, the Ro.kernel is generated. The {Name}={value} property is written to the Android property system.

Now go back to the Process_kernel_cmdline function and continue with the execution

if (qemu[0])     import_kernel_cmdline (1, Import_kernel_nv);
When the system is an emulator, qemu[0] has a value of ' 1 ', the second execution of Import_kernel_cmdline, will call 6 times Import_kernel_nv again, and For_emulator is 1, so 6 properties will be generated, Now to determine our analysis below.

[Email protected]:/# Getprop | grep Ro.kernel.                                     [Ro.kernel.android.checkjni]: [1][ro.kernel.android.qemud]: [Ttys1][ro.kernel.console]: [Ttys0][ro.kernel.ndns]: [ 1][ro.kernel.qemu.gles]: [0][ro.kernel.qemu]: [1]
We can verify that our analysis is correct.

Export_kernel_boot_props ()

Next, proceed to the last sentence of the Process_kernel_cmdline function Export_kernel_boot_props. Because the function implementation is very intuitive, its code is not described in detail. This function is used to set several system properties, including the following:
Reads Ro.boot.serialno, if there is a value written to ro.serialno, otherwise Ro.serialno writes empty. Read Ro.boot.mode, if there is a value write Ro.bootmode, otherwise ro.bootmode write "unkown" read Ro.boot.baseband, if there is a value write Ro.baseband, otherwise ro.baseband write "Unkown"
Read Ro.boot.bootloader, if there is a value written to Ro.bootloader, otherwise ro.bootloader write "Unkown" Read Ro.boot.console, if present, its value is written to the global buffer console read Ro.bootmode, if present, its value is saved to the global buffer Bootmode
Read Ro.boot.hardware, if there is a value written to ro.hardware, otherwise the parsed hardware in/proc/cmdline write Ro.hardware.

SELinux

    Union Selinux_callback CB;    Cb.func_log = Log_callback;    Selinux_set_callback (Selinux_cb_log, CB);    Cb.func_audit = Audit_callback;    Selinux_set_callback (Selinux_cb_audit, CB);    Selinux_initialize ();    /* These directories were necessarily created before initial policy load     * and therefore need their security context R Estored to the proper value.     * This must happen Before/dev are populated by UEVENTD.     *    /Restorecon ("/dev");    Restorecon ("/dev/socket");    Restorecon ("/dev/__properties__");    Restorecon_recursive ("/sys");
This part of the code was added after Android4.1, followed by an iterative iteration of the Android system update. This code mainly involves SELinux initialization. Due to the SELinux and Android system boot shutdown is not small, temporarily not analyzed.

Back to init function <part2> continue analysis

    Is_charger =!strcmp (Bootmode, "charger");    INFO ("Property init\n");    Property_load_boot_defaults ();
The first sentence will use Bootmode with the string "charger" to save it to the Is_charger variable, is_charger non 0 indicates that the previous android was started in charge mode, otherwise normal mode. The normal startup mode is different from the process that the charging mode needs to start, and the difference between the two modes to start the specific program will be described in init.rc parsing.

Next, call the info macro to print a log statement, which is defined in Init/log.h and is implemented as follows

#define ERROR (x ...)   Klog_error ("Init", x) #define NOTICE (x ...)  Klog_notice ("Init", x) #define INFO (x ...)    Klog_info ("Init", X)
This is obviously a log statement with a level of klog_info_level. Whether it can be output to/dev/__kmesg__ is related to the value of the current klog level. By default, Klog level is 3, and this statement will not be output to/dev/__kmsg__.

Here init.c the <part2> Code Analysis of the main function is complete.

The <part3> code then covers the core functions of the INIT process: init.rc parsing. This part of the code logic we will be in the Independent article "Android Init source Code Analysis (2) init.rc parsing" described.

Android Init source Code Analysis (1) profiling

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.