Andrid Init 分析-- 基于 4.4

前言

学习笔记

主题

init 进程是 Android 内核启动的第一个进程,其进程号(pid)为1,是 Android 系统所有进程的祖先,因此它肩负着系统启动的重要责任。
Android 的 init 源代码位于 system/core/init/ 目录下,伴随 Android 系统多个版本的迭代,init 源代码也几经重构。目前 Android4.4 源代码中,
init 目录编译后生成如下 Android 系统的三个文件,分别是
        /init
        /sbin/ueventd-->/init
        /sbin/watchdogd-->/init
    其中 ueventd 与 wathdogd 均是指向 /init 的软链接。(具体实现请阅读init/Android.mk)。在 Android 系统早期版本(2.2之前)只有 init
进程,Android2.2 中将创建设备驱动节点文件功能独立到 ueventd 进程完成,在 Android4.1 中则添加了 watchdogd。

进程功能:

    /init主要完成三大功能:
        解析init.rc初始化Android属性系统,并维护属性服务
        初始化Android属性系统,并维护属性服务
        处理子进程启动、停止、重启动
    /ueventd用于创建设备驱动节点。
    /watchdogd 是看门狗服务进程。

在这里插入图片描述
在这里插入图片描述

流程总结:

    1. 若通过 ueventd 执行 init 可执行文件,则执行 ueventd_main() (ueventd.c)
    2. 创建文件目录,并将它们作为虚拟文件系统的挂载点进行挂载,还有设置启动标志。
    3. 改变 log 的输入输出。
    4. 函数 property_init() 用户初始化属性区域,该属性区域来自设备文件 /dev/__properties__ 映射(mmap)到 init 
        进程空间后的虚拟地址。
    5. 获得硬件平台型号名称。
    6. 通过 process_kernel_cmdline() 将内核启动参数从 /proc/cmdline 中的配置项中读出,并赋值给相应的属性。
    7. 若是安全 Linux(SELinux), 则加载安全策略。
    8. 判断设备是否处于充电模式。
    9. 遍历解析 Android 初始化语言脚本语言文件 init.rc 
    10. 将 trigger 为 early-init 的 action 添加到待执行队列 action_queue 中 
    11. 将一些内建的 action 添加到 action_list 列表中和待执行队列中 
    12. 将 .rc 文件中的 init 部分的 action 添加到待执行队列 action_queue 尾部 
    13. 在充电模式时,跳过加载文件系统的操作。在正常的非充电模式下,将 .rc 文件中的 early-fs/fs/post-fs/post-fs-data 
        四个部分的 section 中的 action 添加到待执行队列 action_queue 尾部 
    14. 将内建的 action 添加到 action_list 和 action_queue 中 
            property_service_init_action() 主要用于将下面三个宏定义的文件中的属性装载进来 
                    #defind PROP_PATH_SYSTEM_BUILD      "/system/build.prop"
                    #defind PROP_PATH_SYSTEM_DEFAULT    "/system/default.prop"
                    #defind PROP_PATH_LOCAL_OVERRIDE    "/data/local.prop"
                如果下面的宏定义还有属性文件,亦进行装载(可能覆盖上面的属性值)
                    #define PERSISTENT_PROPERTY_DIR     "/data/property"
                最后他创建一个 UNIX 本地 socket,监听属性值的变动 

            signal_init_action 用于处理子进程(各个 .rc 中的 service 进程)的退出、重启、停止等操作
                见 signal_handler.c 中的 wait_for_one_process()

            check_startup_action 检查前面的启动是否顺序完成,并删除前面创建的标识文件 /dev/.booting
                
    15. 区分充电械和非充电模式,从而执行不同操作。充电模式是将 .rc 文件中的 charger 部分的 action 添加到 
        待执行队列尾部,否则添加 early-boot 和 boot 中的 action 
    16. 将内建的 action 添加到 action_list 和 action_queue 中
            queue_property_triggers_action 
                遍历所有的 action_list 中的所有 action,检查是否有关于
                property 的 action,有则添加其到 action_queue 队列中,这意味着 .rc 文件中的关于属性改变
                而触发引起的执行操作在这儿都会被触发执行一次 
    17. 若定义的 BOOTCHART 为真,则添加内置的 bootchart 初始化操作 
            bootchart_init_action 将调用 bootchart_init() 进行初始化操作,如准备记录文件,获取系统版本信息等
    18. 进入无限循环,先执行上面的添加到待执行队列 action_queue 中的各个 action 中的 command,然后检查
        是否有 service 需要重启的操作 
    19. 接着,在无限循环中的中间及后面部分,继续完成 bootchart 操作,记录统计信息,写入文件等。
    20. 在无限循环的最后面,用于检查是否有属性值改变而触发 action 的 command 操作,是否有组合键 keychord 
        写入而启动 service 的操作,是否有 service 等子进程退出需要处理的操作。

解析内核参数

解析内核启动参数,并根据启动参数判断系统类型,将生成 ro.kernel.{name} = {value} 写入 Android 属性系统, 如当
    系统以模拟器启动时,启动参数如下:
        root@generic:/ # cat /proc/cmdline                                             
        qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1
    会在代码中分成如下几段,以 import_kernel_nv() 处理:
        qemu.gles=0  
        qemu=1  
        console=ttyS0  
        android.qemud=ttyS1  
        android.checkjni=1  
        ndns=1 
    然后根据解析结果通过 property_set() 写入 Android 属性系统中,写入如下内容:
        root@generic:/ # 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] 
    函数最后通过 export_kernel_boot_props() 读取 Android 属性系统,根据 ro.boot.xxx 来设置系统属性
    其代码不在详细描述。该函数用于设置几个系统属性,具体包括如下:
        读取ro.boot.serialno,若存在其值写入ro.serialno,否则ro.serialno写入空。
        读取ro.boot.mode,若存在其值写入ro.bootmode,否则ro.bootmode写入"unkown"
        读取ro.boot.baseband,若存在其值写入ro.baseband,否则ro.baseband写入"unkown"
        读取ro.boot.bootloader,若存在其值写入ro.bootloader,否则ro.bootloader写入"unkown"
        读取ro.boot.console,若存在,其值写入全局缓冲区console中
        读取ro.bootmode,若存在,其值保存到全局缓冲区bootmode中
        读取ro.boot.hardware,若存在其值写入ro.hardware,否则将/proc/cmdline中解析出来的hardware写入ro.hardware中。

解析配置文件 init.rc 系列文件

		init.rc是一个文本文件,可认为它是Android系统启动脚本。init.rc文件中定义了环境变量配置、系统进程启动,分区挂载,属性配置
    等诸多内容。init.rc具有特殊的语法。init源码目录下的readme.txt中详细的描述了init启动脚本的语法规则,是试图定制 init.rc 的开发
    者的必读资料。
        Android启动脚本包括一组文件,包括:
            init.rc  
            init.usb.rc  
            init.trace.rc  
            init.{hardware}.rc  
            init.environ.rc  
            init.zygote32.rc 
        这些文件可能分布于如下目录中:
            system/core/rootdir
            device/{vendor}/{hardware}/
        除 init.rc 外,其他文件都由 init.rc 中以 import 语句导入,一般来说 init.rc 文件存放通用配置,而其他特定模块以及特定硬件的配置
    则放置在独立的文件中,这样设计可以使 init.rc 脚本简洁,方便系统维护和升级。
        一个简单的init.rc语法如下。(基于system/core/rootdir/init.rc裁剪):
            # Copyright (C) 2012 The Android Open Source Project  
            #  
            # IMPORTANT: Do not create world writable files or directories.  
            # This is a common source of Android security bugs.  
            #  
              
            import /init.environ.rc  
            import /init.usb.rc  
            import /init.${ro.hardware}.rc  
            import /init.${ro.zygote}.rc  
            import /init.trace.rc  
            on early-init  
                start ueventd  
              
            on init  
                sysclktz 0  
                loglevel 3  
                mkdir /system  
                mkdir /data 0771 system system  
                write /proc/sys/kernel/panic_on_oops 1  
            # Load properties from /system/ + /factory after fs mount.  
            on load_all_props_action  
                load_all_props  
            on post-fs  
                # once everything is setup, no need to modify /  
                mount rootfs rootfs / ro remount  
            on boot  
                # basic network init  
                ifup lo  
                class_start core  
                class_start main  
            on property:vold.decrypt=trigger_reset_main  
                class_reset main  
            service ueventd /sbin/ueventd  
                class core  
                critical  
            on property:ro.debuggable=1  
                start console  
            service debuggerd /system/bin/debuggerd  
                class main  
            service bootanim /system/bin/bootanimation  
                class main  
                user graphics  
                group graphics  
                disabled  
                oneshot
        为了行文方便,下文提及init.rc,通常泛指Android启动脚本。

    init.rc 可以定义两类结构:Actions 与 Services
        【Actions】
            Actions是一组命令的集合,定义一个Actions如下,每个Actions都可以定义一个触发器(trigger),Actions格式如下:
                on <trigger>  
                   <command>        //其中<command>类似于shell命令,command对应一个函数,通常执行一条动作,例如创建一个文件夹等。
                   <command>  
                   <command>
                    
            触发器(Triggers):
                触发器只是一段字符串罢了,PS:在android/system/core/rootdir/下执行
                    grep -h "^on" --include="*.rc" -r .
                可以当前init启动脚本所含有的trigger,如下
                    on early-init  
                    on init  
                    on property:sys.boot_from_charger_mode=1  
                    on load_all_props_action  
                    on firmware_mounts_complete  
                    on late-init  
                    on post-fs  
                    on post-fs-data  
                    on boot  
                    on nonencrypted  
                    on property:sys.init_log_level=*  
                    on charger  
                    on property:vold.decrypt=trigger_reset_main  
                    on property:vold.decrypt=trigger_load_persist_props  
                    on property:vold.decrypt=trigger_post_fs_data  
                    on property:vold.decrypt=trigger_restart_min_framework  
                    on property:vold.decrypt=trigger_restart_framework  
                    on property:vold.decrypt=trigger_shutdown_framework  
                    on property:sys.powerctl=*  
                    on property:sys.sysctl.extra_free_kbytes=*  
                    on property:sys.sysctl.tcp_def_init_rwnd=*  
                    on property:ro.debuggable=1  
                    on property:ro.kernel.qemu=1  
                    on boot  
                    on post-fs-data  
                    on property:sys.usb.config=none  
                    on property:sys.usb.config=adb  
                    on property:sys.usb.config=accessory  
                    on property:sys.usb.config=accessory,adb  
                    on property:sys.usb.config=audio_source  
                    on property:sys.usb.config=audio_source,adb  
                    on property:sys.usb.config=accessory,audio_source  
                    on property:sys.usb.config=accessory,audio_source,adb  
                    on property:persist.sys.usb.config=*

                根据 trigger 的不同,可以将 Actions 大致分为两类:
                (1) 普通型
                        这类trigger的的作用仅仅是用于给一个Actions命名,方便查找和引用。如 early-init、init、late-init、early-fs、fs、post-fs、
                    post-fs-data、early-boot、boot、charger等, 一般来说,这类 Actions 将在 Android 启动时执行,其 trigger 暗示了执行对应 Actions 执
                    行的时机。具体的执行流程将在本文最后介绍。
                        此外,根据 readme.txt 描述,还有其他几种 trigger,但在 init.rc 以及 init 源代码中却没有找到相关代码,如下所示,
                            device-added-<path>             // 设备节点被添加或移除时调用,。
                            device-removed-<path>
                        
                            service-exited-<name>
                       这类trigger将在某 service 退出时执行。关于什么是 service 稍后介绍。

                (2) 属性型
                    其 trigger 为 property:<name>=<value>
                    其 trigger 不仅唯一标识了这个 Actions,同时也设定了这类 Actons 执行的条件,当 property <name> 的值为 <value> 时才会被执行。

            Commands:
                command的格式如下:
                    command-name <parament1> [parament2...]             // <>表示必须存在的参数,[]表示可选参数

                说明:readme.txt中虽然有大部分commands的介绍,但并不完整。init.rc 中所有 commands 都在 keywords.h 中定义,可使用如下命令提取。
                    sed -n "s/KEYWORD([,]\+,[ \t]\+COMMAND.*/\1/p" keywords.h      //  增加用于补全高亮"/* 

                目前 Android4.4 支持的 Commands 如下:Keywords.h (s:\i841\system\core\init)
                    chdir <direcotory> 改变工作目录  
                    chroot <directory> 改变当前进程的root目录  
                    class_start <serviceclass> 如果serviceclass内所有services尚未启动,则启动它  
                    class_stop  <serviceclass> 停止serviceclass内所有services  
                    class_reset <serviceclass> 重启serviceclass内所有services  
                    domainname <name>  设置domain名称  
                    enable  <servicename>  
                    exec <path> [ <argument> ]*  fork后执行path所执行的程序,该语句会阻塞直到path指定的程序执行完毕。  
                    export <name> <value> 设置全局环境变量,将会被该命令后所启动的进程继承。  
                    hostname <name> 设置主机名  
                    ifup  <interface> 启动interface所指定的网络接口  
                    insmod <path> 安装path所指定的内核模块  
                    mkdir <path> [mode] [owner] [group] 创建path制定的目录,并根据可选的mode、owner、group设定参数。如果未指定可选参数,则创建的文件夹权限将会设置为0755,而owner与group都为root  
                    mount_all  
                    mount  <type> <device> <dir> [ <mountoption] *  
                    powerctl  
                    restart <service>  
                    restorecon  
                    restorecon_recursive  
                    rm <path> 删除path指定的文件  
                    rmdir <path> 删除path指定的目录(目录为空才能删除)  
                    setcon  
                    setenforce  
                    setkey  
                    setprop  
                    setrlimit  
                    setsebool  
                    start <service>  
                    stop <service>  
                    swapon_all  
                    trigger  
                    symlink  
                    sysclktz  
                    wait  
                    write  
                    copy  
                    chown  
                    chmod  
                    loglevel  
                    load_persist_props  
                    load_all_props  

        【Actions】
                这里的 service 仅仅是 init.rc 中的概念,与通常意义上的“服务”概念无关。一个 Service 对应一个可执行程序,并且可以设定该程序
            的一些执行性质,如仅仅执行一次、或退出时自动重启。当service所代表的可执行程序在退出时自动重启时,该service通常意味着这是一个守护进程。
            
            service 字段由如下格式定义:
                service <name> <pathname> [ <argument> ]*       // <name>字段为service的名字
                                                                // <pathname>为该service对应的二进制程序的路径,随后是该程序的参数列表
                   <option>                                     // <option>是该service的属性
                   <option>  
                   ... 
            
            目前 Android4.4 中所支持的 option 如下所示(ps,由 sed -n "s/KEYWORD([,]\+,[ \t]\+OPTION.*/\1/p" keywords.h 命令生成)  //  增加用于补全高亮"/* 
            文件路径: system/core/init/keywords.h 

                capability  
                class <name> 设定service的class  
                console  
                critical                                            说明该服务是个对于设备很关键的服务,如果4分钟内退出大于4次,则系统将重启并进入recovery恢复模式
                disabled                                            该服务不能通过启动一类服务来启动,比如 class_start core来启动,只能以单独的名字来启动 start adbd.
                group <groupname> [<groupname>]* 设定进程  
                keycodes                                            用于特殊按键启动服务,一般调试用
                oneshot                                             该服务只启动一次,退出后不再运行  
                onrestart                                           当 service 重启时,自动执行后面的命令  
                seclabeli  
                setenv  
                socket                                              语法:socket <name> <type> <perm> <user> <group>, 创建一个名字为vold<name>,类别为stream<type> //访问权限为0660<perm> 用户为root,用户组为mount
                user <username>      
                ioprio  
            
                为了方便管理多个 service,可为 service 设定 class 属性,具有同样 class 的多个 service 构成一个组,可以在 Actions 中通过 
            class_start、class_stop、class_reset等命令启动、停止、重启动。

        【此外init.rc中还有其他规则】
            以#号开头的行为注释行
            import语句导入其他init脚本文件,
            \可用于转义换行符
            空格与Tab字符都可用作空白字符

    Android中的实现非常的简洁。
        【解析设计思路】如下:
            1. 引入 section 的概念。一个 Actions、Service、import 是一个section,分别实现不同的 section 解析代码。
            2. 基于行解析,行解析函数与当前所在的 section 有关,使用函数指针实现。
            3. 利用空白字符(一个或多个空行)实现分词,当检测到新的一行时,识别关键词为 on、service、import,若是则认为一个 section 开
                始了,同时也意味着上一个 section 终结了。import使用递归实现
            4. 每个 Actions 创建一个 struct action 数据结构,每个 command 创建一个 struct command 数据结构,action 中有一个 command 的链表;
                每个 service 创建一个 struct service 数据结构。
            5. 创建全局 Actions 链表,将识别到 Actions 都加入全局链表中,创建全局 Service 链表,将识别到的 Service 加入到全局链表中

        【数据关系图】全局数据结构组合关系类似如下图:

            【actions 表】
            struct action --->    ...  ---> struct action 
                |                                                                   // struct action : 代表 init.rc 中的一个 Actions                                      
                |- struct command                                                   //      struct command: 代表 init.rc 中 Actions 下的命令                                                     
                |- struct command                                                   // struct service: 代表 init.rc 中的一个 Service                                          
                ...
            
            【service 表】
            struct service ---> ....   ---> struct service 


        【执行思路】如下:
            创建一个额外的链表充当 Actions 执行队列 【action-queue】。将一些 Actions 链表加入到执行队列中。
            然后一次从队列中取出一个 Actions,依次执行其中每一条 command,当该 Actions 执行完毕后则从 
            actions-queue 取出下一个 Actions 继续执行。


    有关 service start 的流程: 
        (1)init_parse_config_file("/init.rc")  读取并分析 init.rc 文件,将里面的 action 和 service 分别添加到 action_list 和 service_list 
        (2)调用 action_for_each_trigger,queue_builtin_action,直接或间接调用 action_add_queue_tail 将 action_list 里面存在的 action 和单
            独指定的"builtin action” 添加到action queue。
        (3)在 for 循环里面调用 execute_one_command 执行 action_queue 里面添加的各个 action 里面的各个 command,当然也包括 "on" section 对
            应的 "boot "action。
        (4)"boot "action 里面的 class start 这个 command 的执行,其实对应的就是 do_class_start 函数执行,最终 service_start() 被调用来启动各个 service。

猜你喜欢

转载自blog.csdn.net/wangjun7121/article/details/88135515
4.4