========================================================          ========================================================

=              【原创文章】:参考部分博客内容,学习之余进行了大量的筛减细化分析                        =          =                          【特殊申明】:避讳抄袭侵权之嫌疑,特此说明,欢迎转载!                           =   
========================================================          ========================================================

【前言】

  Android启动篇 — init原理(一)中讲解分init进程分析init创建系统目录并挂在相应系统文件、初始化属性域、设置系统属性、启动配置属性服务端等一系列复杂工作,很多工作和知识点跟Linux关系很大,所以没有作过多介绍,而本此对于init.rc的解析则是重中之重,所以单独拿出来进行详细分析。

int main(int argc, char** argv) {
    /* 01. 创建文件系统目录并挂载相关的文件系统 */
    /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
    /* 03. 初始化属性域 */
    /* 04. 完成SELinux相关工作 */•
    /* 05. 重新设置属性 */
    /* 06. 创建epoll句柄 */
    /* 07. 装载子进程信号处理器 */
    /* 08. 设置默认系统属性 */
    /* 09. 启动配置属性的服务端 */
    /* 10. 匹配命令和函数之间的对应关系 */
------------------------------------------------------------------------------------------- // Android启动篇 — init原理(一)中讲解
/* 11. 解析init.rc */ Parser& parser = Parser::GetInstance(); // 构造解析文件用的parser对象 // 增加ServiceParser为一个section,对应name为service parser.AddSectionParser("service",std::make_unique<ServiceParser>()); // 增加ActionParser为一个section,对应name为action parser.AddSectionParser("on", std::make_unique<ActionParser>()); // 增加ImportParser为一个section,对应name为service parser.AddSectionParser("import", std::make_unique<ImportParser>()); parser.ParseConfig("/init.rc"); // 开始实际的解析过程

【正文】

  init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本,主要包含五种类型语句:Action、Command、Service、Option和Import,在分析代码的过程中我们会详细介绍。

  init.rc的配置代码在:system/core/rootdir/init.rc 中

  init.rc文件是在init进程启动后执行的启动脚本,文件中记录着init进程需执行的操作。

  init.rc文件大致分为两大部分,一部分是以“on”关键字开头的动作列表(action list):

on early-init      // Action类型语句
    # Set init and its forked children's oom_adj.     // #:注释符号
    write /proc//oom_score_adj -
    ... ...
    start ueventd

  Action类型语句格式:

on <trigger> [&& <trigger>]*     // 设置触发器
   <command>
   <command>      // 动作触发之后要执行的命令

  另一部分是以“service”关键字开头的服务列表(service list):  如 Zygote

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0

  Service类型语句格式:

service <name> <pathname> [ <argument> ]*   // <service的名字><执行程序路径><传递参数>
   <option>       // option是service的修饰词,影响什么时候、如何启动services
   <option>
   ...

  借助系统环境变量或Linux命令,动作列表用于创建所需目录,以及为某些特定文件指定权限,而服务列表用来记录init进程需要启动的一些子进程。如上面代码所示,service关键字后的第一个字符串表示服务(子进程)的名称,第二个字符串表示服务的执行路径。

  值得一提的是在Android 7.0中对init.rc文件进行了拆分,每个服务一个rc文件。我们要分析的zygote服务的启动脚本则在init.zygoteXX.rc中定义。

  在init.rc的import段我们看到如下代码:

import /init.${ro.zygote}.rc     // 可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件

  说明:

  从android5.0开始,android开始支持64位的编译,zygote本身也就有了32位和64位的区别,所以在这里用ro.zygote属性来控制启动不同版本的zygote进程。

  init.rc位于/system/core/rootdir下。在这个路径下还包括四个关于zygote的rc文件。分别是Init.zygote32.rc,Init.zygote32_64.rc,Init.zygote64.rc,Init.zygote64_32.rc,由硬件决定调用哪个文件。

  这里拿32位处理器为例,init.zygote32.rc的代码如下所示:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main         # class是一个option,指定zygote服务的类型为main
    socket zygote stream  root system          # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket
    onrestart write /sys/android_power/request_state wake          # onrestart是一个option,说明在zygote重启时需要执行的command
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

  “service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server”

  在Init.zygote32.rc中,定义了一个zygote服务:zygote,由关键字service告诉init进程创建一个名为zygote的进程,这个进程要执行的程序是:/system/bin/app_process,给这个进程四个参数:

    · -Xzygote:该参数将作为虚拟机启动时所需的参数

    · /system/bin:代表虚拟机程序所在目录

    · --zygote:指明以ZygoteInit.java类中的main函数作为虚拟机执行入口

    · --start-system-server:告诉Zygote进程启动SystemServer进程

  接下来,我们回到源码当中,继续分析main函数:

    /* 11. 解析init.rc */
    Parser& parser = Parser::GetInstance();       // 构造解析文件用的parser对象
    // 增加ServiceParser为一个section,对应name为service
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    // 增加ActionParser为一个section,对应name为action
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    // 增加ImportParser为一个section,对应name为service
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    parser.ParseConfig("/init.rc");      // 开始实际的解析过程

  说明:

  上面在解析init.rc文件时使用了Parser类(在init目录下的init_parser.h中定义), 初始化ServiceParser用来解析 “service”块,ActionParser用来解析"on"块,ImportParser用来解析“import”块,“import”是用来引入一个init配置文件,来扩展当前配置的。

  /system/core/init/readme.txt 中对init文件中的所有关键字做了介绍,主要包含了Actions, Commands, Services, Options, and Imports等,可自行学习解读。

  分析init.rc的解析过程:函数定义于system/core/init/ init_parser.cpp中

bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {           // 判断传入参数是否为目录地址
        return ParseConfigDir(path);      // 递归目录,最终还是靠ParseConfigFile来解析实际的文件
    }
    return ParseConfigFile(path);         // 传入传输为文件地址
}

  继续分析ParseConfigFile():

bool Parser::ParseConfigFile(const std::string& path) {
    ... ...
    Timer t;
    std::string data;
    if (!read_file(path.c_str(), &data)) {       // 读取路径指定文件中的内容,保存为字符串形式
        return false;
}
... ...
    ParseData(path, data);        // 解析获取的字符串
    ... ...
}

  跟踪ParseData():

void Parser::ParseData(const std::string& filename, const std::string& data) {
    ... ...
    parse_state state;
    ... ...
    std::vector<std::string> args;

    for (;;) {
        switch (next_token(&state)) {    // next_token以行为单位分割参数传递过来的字符串,最先走到T_TEXT分支
        case T_EOF:
            if (section_parser) {
                section_parser->EndSection();    // 解析结束
            }
            return;
        case T_NEWLINE:
            state.line++;
            if (args.empty()) {
                break;
            }
            // 在前文创建parser时,我们为service,on,import定义了对应的parser
            // 这里就是根据第一个参数,判断是否有对应的parser
            ])) {
                if (section_parser) {
                    // 结束上一个parser的工作,将构造出的对象加入到对应的service_list与action_list中
                    section_parser->EndSection();
                }
                // 获取参数对应的parser
                section_parser = section_parsers_[args[]].get();
                std::string ret_err;
                // 调用实际parser的ParseSection函数
                if (!section_parser->ParseSection(args, &ret_err)) {
                    parse_error(&state, "%s\n", ret_err.c_str());
                    section_parser = nullptr;
                }
            } else if (section_parser) {
                std::string ret_err;
                // 如果第一个参数不是service,on,import
                // 则调用前一个parser的ParseLineSection函数
                // 这里相当于解析一个参数块的子项
                if (!section_parser->ParseLineSection(args, state.filename,
                                                             state.line, &ret_err)) {
                    parse_error(&state, "%s\n", ret_err.c_str());
                }
            }
            args.clear();       // 清空本次解析的数据
            break;
        case T_TEXT:
            args.emplace_back(state.text);     //将本次解析的内容写入到args中
            break;
        }
    }
}

  至此,init.rc解析完,接下来init会执行几个重要的阶段:

int main(int argc, char** argv) {
    /* 01. 创建文件系统目录并挂载相关的文件系统 */
    /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
    /* 03. 初始化属性域 */
    /* 04. 完成SELinux相关工作 */•
    /* 05. 重新设置属性 */
    /* 06. 创建epoll句柄 */
    /* 07. 装载子进程信号处理器 */
    /* 08. 设置默认系统属性 */
    /* 09. 启动配置属性的服务端 */
    /* 10. 匹配命令和函数之间的对应关系 */
/* 11. 解析init.rc*/
----------------------------------------------------------------------------
  /* 12. 向执行队列中添加其他action */
    // 获取ActionManager对象,需要通过am对命令执行顺序进行控制
    ActionManager& am = ActionManager::GetInstance();
    // init执行命令触发器主要分为early-init,init,late-init,boot等
    am.QueueEventTrigger("early-init");    // 添加触发器early-init,执行on early-init内容

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");        // 添加触发器init,执行on init内容,主要包括创建/挂在一些目录,以及symlink等

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    if (bootmode == "charger") {
    am.QueueEventTrigger("charger");     // on charger阶段
    } ) == ) {
    NOTICE("Booting into ffbm mode\n");
    am.QueueEventTrigger("ffbm");
    } else {
    am.QueueEventTrigger("late-init");          // 非充电模式添加触发器last-init
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

  在last-init最后阶段有如下代码:

# Mount filesystems and start core system services.
on late-init
    trigger early-fs

    # Mount fstab in init.{$device}.rc by mount_all command. Optional parameter
    # '--early' can be specified to skip entries with 'latemount'.
    # /system and /vendor must be mounted by the end of the fs stage,
    # while /data is optional.
    trigger fs
    trigger post-fs

    # Load properties from /system/ + /factory after fs mount. Place
    # this in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_system_props_action

    # Mount fstab in init.{$device}.rc by mount_all with '--late' parameter
    # to only mount entries with 'latemount'. This is needed if '--early' is
    # specified in the previous mount_all command on the fs stage.
    # With /system mounted and properties form /system + /factory available,
    # some services can be started.
    trigger late-fs

    # Now we can mount /data. File encryption requires keymaster to decrypt
    # /data, which in turn can only be loaded when system properties are present.
    trigger post-fs-data

    # Load persist properties and override properties (if enabled) from /data.
    trigger load_persist_props_action

    # Remove a file to wake up anything waiting for firmware.
    trigger firmware_mounts_complete

    trigger early-boot
trigger boot

  可见出发了on early-boot和on boot两个Action。

  我们看一下on boot:

on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain
    ... ...
    class_start core

  在on boot 的最后class_start core 会启动class为core的服务,这些服务包括ueventd、logd、healthd、adbd(disabled)、lmkd(LowMemoryKiller)、servicemanager、vold、debuggerd、surfaceflinger、bootanim(disabled)等。

  回到主题,分析trigger触发器的代码,QueueEventTrigger():位于system/core/init/action.cpp

void ActionManager::QueueEventTrigger(const std::string& trigger) {
    trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
}

  此处QueueEventTrigger函数就是利用参数构造EventTrigger,然后加入到trigger_queue_中。后续init进程处理trigger事件时,将会触发相应的操作。

  再看一下QueueBuiltinAction()函数:同样位于system/core/init/action.cpp

void ActionManager::QueueBuiltinAction(BuiltinFunction func,
                                   const std::string& name) {
    // 创建action
    auto action = std::make_unique<Action>(true);
    std::vector<std::string> name_vector{name};

    // 保证唯一性
    if (!action->InitSingleTrigger(name)) {
        return;
    }

    // 创建action的cmd,指定执行函数和参数
    action->AddCommand(func, name_vector);

    trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));
    actions_.emplace_back(std::move(action));
}

  QueueBuiltinAction函数中构造新的action加入到actions_中,第一个参数作为新建action携带cmd的执行函数;第二个参数既作为action的trigger name,也作为action携带cmd的参数。

  接下来继续分析main函数:

int main(int argc, char** argv) {
    /* 01. 创建文件系统目录并挂载相关的文件系统 */
    /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
    /* 03. 初始化属性域 */
    /* 04. 完成SELinux相关工作 */•
    /* 05. 重新设置属性 */
    /* 06. 创建epoll句柄 */
    /* 07. 装载子进程信号处理器 */
    /* 08. 设置默认系统属性 */
    /* 09. 启动配置属性的服务端 */
    /* 10. 匹配命令和函数之间的对应关系 */
/* 11. 解析init.rc*/
/* 12. 向执行队列中添加其他action */
-------------------------------------------------------------------
/* 13. 处理添加到运行队列的事件 */
while (true) {
    // 判断是否有事件需要处理
        if (!waiting_for_exec) {
            // 依次执行每个action中携带command对应的执行函数
am.ExecuteOneCommand();
// 重启一些挂掉的进程
            restart_processes();
        }

        // 以下决定timeout的时间,将影响while循环的间隔
        ;
        // 有进程需要重启时,等待该进程重启
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * ;
            )
                timeout = ;
        }

        // 有action待处理,不等待
        if (am.HasMoreCommands()) {
            timeout = ;
        }

        // bootchart_sample应该是进行性能数据采样
        bootchart_sample(&timeout);

        epoll_event ev;
        // 没有事件到来的话,最多阻塞timeout时间
        , timeout));
        ) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } ) {
            //有事件到来,执行对应处理函数
            //根据上文知道,epoll句柄(即epoll_fd)主要监听子进程结束,及其它进程设置系统属性的请求
            ((void (*)()) ev.data.ptr)();
        }
    }
;
} // end main

  看一下ExecuteOneComand()函数:同样位于system/core/init/action.cpp

void ActionManager::ExecuteOneCommand() {
    // Loop through the trigger queue until we have an action to execute
    // 当前的可执行action队列为空, trigger_queue_队列不为空
    while (current_executing_actions_.empty() && !trigger_queue_.empty()) {
    // 循环遍历action_队列,包含了所有需要执行的命令,解析init.rc获得
        for (const auto& action : actions_) {
            // 获取队头的trigger, 检查actions_列表中的action的trigger,对比是否相同
            if (trigger_queue_.front()->CheckTriggers(*action)) {
                // 将所有具有同一trigger的action加入当前可执行action队列
                current_executing_actions_.emplace(action.get());
            }
        }
        // 将队头trigger出栈
        trigger_queue_.pop();
    }

    if (current_executing_actions_.empty()) {   // 当前可执行的actions队列为空就返回
        return;
    }

    auto action = current_executing_actions_.front(); // 获取当前可执行actions队列的首个action

    ) {
        std::string trigger_name = action->BuildTriggersString();
        INFO("processing action (%s)\n", trigger_name.c_str());
    }

    action->ExecuteOneCommand(current_command_);     // 执行当前的命令

    // If this was the last command in the current action, then remove
    // the action from the executing list.
    // If this action was oneshot, then also remove it from actions_.
    ++current_command_;      // 不断叠加,将action_中的所有命令取出
    if (current_command_ == action->NumCommands()) {
        current_executing_actions_.pop();
        current_command_ = ;
        if (action->oneshot()) {
            auto eraser = [&action] (std::unique_ptr<Action>& a) {
                return a.get() == action;
            };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
        }
    }
}

  我们来观察一下init.rc的开头部分:

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc      // 后面我们即将重点分析zygote进程

  通过ro.zygote的属性import对应的zygote的rc文件。

  

  我们查看init.zygote64_32.rc:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    socket zygote stream  root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    socket zygote_secondary stream  root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks

  可以看到zygote的class是main, 它是在on nonencrypted时被启动的,如下:

on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain
    ... ...
    class_start core

on nonencrypted
    # A/B update verifier that marks a successful boot.
    exec - root cache -- /system/bin/update_verifier nonencrypted
    class_start main
    class_start late_start

  至此,Init.cpp的main函数分析完毕!init进程已经启动完成,一些重要的服务如core服务和main服务也都启动起来,并启动了zygote(/system/bin/app_process64)进程,zygote初始化时会创建虚拟机,启动systemserver等。

Android启动篇 — init原理(二)的更多相关文章

  1. Android启动篇 — init原理(一)

    ========================================================          ================================== ...

  2. Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  3. Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  4. init进程 && 解析Android启动脚本init.rc && 修改它使不启动android && init.rc中启动一个sh文件

    Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径:system/core/init,编译 ...

  5. Android启动脚本init.rc(2)

    在Android中使用启动脚本init.rc,可以在系统的初始化中进行简单的操作. init.rc启动脚本路径:system/core/rootdir/init.rc 内容: Commands:命令 ...

  6. 【朝花夕拾】Android性能篇之(二)Java内存分配

    前言        在内存方面,相比于C/C++程序员,咱们java系程序员算是比较幸运的,因为对于内存的分配和回收,都交给了JVM来处理了,而不需要手动在代码中去完成.有了虚拟机内存管理机制,也就不 ...

  7. Android启动脚本init.rc说明文档readme.txt翻译

    Android Init Language--------------------- Android初始化语言--------------------- The Android Init Langua ...

  8. 【朝花夕拾】Android性能篇之(一)序言及JVM

    序言        笔者从事Anroid开发有些年头了,深知掌握Anroid性能优化方面的知识的必要性,这是一个程序员必须修炼的内功.在面试中,它是面试官的挚爱,在工作中,它是代码质量的拦路虎,其重要 ...

  9. 【朝花夕拾】Android性能篇之(三)Java内存回收

    在上一篇日志([朝花夕拾]Android性能篇之(二)Java内存分配)中有讲到,JVM内存由程序计数器.虚拟机栈.本地方法栈.GC堆,方法区五个部分组成.其中GC堆是一块多线程的共享区域,它存在的作 ...

随机推荐

  1. Debian安装Oracle Java步骤

    在Debian下安装OpenJDK使用apt命令非常方便的安装,但安装Oracle就需要手动了,这里需了解ln和update-alternatvies命令. ln链接 首先我们来说说linux的链接, ...

  2. wemall app商城源码中基于JAVA的Android异步加载图片管理器代码

    wemall doraemon是Android客户端程序,服务端采用wemall微信商城,不对原商城做任何修改,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可随意定制修改.本文分享其中 ...

  3. Kubernetes 1.5安装

    Kubernetes从1.3开始引入kubeadm来试图简化其复杂的安装.但kubeadm至今仍不稳定,而且我个人觉得kubeadm反而麻烦,还不如直接用脚本或者其他自动化工具来安装来的利索.关于ku ...

  4. 求int型正整数在内存中存储时1的个数

    题目描述: 输入一个int型的正整数,计算出该int型数据在内存中存储时1的个数. 输入描述: 输入一个整数(int类型) 输出描述: 这个数转换成2进制后,输出1的个数 输入例子: 5 输出例子: ...

  5. 3385: [Usaco2004 Nov]Lake Counting 数池塘

    3385: [Usaco2004 Nov]Lake Counting 数池塘 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 22  Solved: 21 ...

  6. 1212: [HNOI2004]L语言

    1212: [HNOI2004]L语言 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 643  Solved: 252[Submit][Status] ...

  7. MyBastis初次环境配置讲解

    MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .20 ...

  8. 使用js实现ajax的get请求步骤

    (以下内容非原创,视频整合得来的) 1.创建XMLHttpRequest对象 2.浏览器与服务器建立连接 3.浏览器向服务器发送请求 4.服务器向浏览器响应请求 下面给出一个实例 1.创建一个test ...

  9. Solr field alias

    Field alias Any field, function, or transformer can be displayed with a different name in the output ...

  10. 介绍Office 365 中文用户社区 4.0

    本文于2017年3月18日首发于LinkedIn,原文链接在这里 为了给广大用户提供一个可以自由交流.切磋技术的平台,微软和其他一些国际知名的大型软件公司一样,都有创建用户社区(Community,或 ...