AOSP 8.0 系统启动之二init启动(三)

init.rc是Android系统初始化的关键配置脚本,用于启动系统服务。文章详细介绍了init.rc的四种类型:动作(Actions)、命令(Commands)、服务(Services)和选项(Options),并列举了各种触发器和命令的实例。此外,文章还探讨了源码解析过程,特别是如何通过class_start启动服务。最后,提到了监听服务的机制,确保系统能响应新事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、前言

二、 init.rc 语法

2.1 init.rc脚本由4种类型

2.2  Actions(动作)

2.2 .Commands(命令)

2.4 Services(服务)

2.5 Options(选项)

2.6 实列解释

三、源码分析

四、监听服务


一、前言

init经过前两个阶段后,已经建立了属性系统和SELinux系统,设置子进程的死亡监听,但是最关键的部分,就是init进程还需要执行启动许多关键的系统服务,但是如果都是像属性系统和SELinux系统那样一行行代码去做,显得有点杂乱繁琐,而且不容易扩展,所以Android系统引入了init.rc

init.rc是init进程启动的配置脚本,这个脚本是用一种叫Android Init Language(Android初始化语言)的语言写的,
在7.0以前,init进程只解析根目录下的init.rc文件,但是随着版本的迭代,init.rc越来越臃肿,
所以在7.0以后,init.rc一些业务被分拆到/system/etc/init,/vendor/etc/init,/odm/etc/init三个目录下,
在本篇文章中,我将讲解init.rc的一些语法,然后一步步分析init进程是如何去解析init.rc文件的

本文主要讲解以下内容

  • Android Init Language语法
  • 解析.rc文件
  • 加入一些事件和一些Action
  • 触发所有事件并不断监听新的事件

本文涉及到的文件

platform/system/core/init/README.md
platform/system/core/init/init.cpp
platform/system/core/init/init_parser.cpp
platform/system/core/init/action.cpp
platform/system/core/init/action.h
platform/system/core/init/keyword_map.h
platform/system/core/init/builtins.cpp
platform/system/core/init/service.cpp
platform/system/core/init/service.h
platform/system/core/init/import_parser.cpp
platform/system/core/init/util.cpp

二、 init.rc 语法

2.1 init.rc脚本由4种类型

  • Actions(行为)
  • Commands(命令)
  • Services( 服务)
  • Options(选项)

2.2  Actions(动作)

1)动作的基本格式(可以执行多个命令)

on <trigger> ##出发条件
   <command1> ##命令1
   <command2> ##命令2
   <command3> ##命令3
   ...

那到底​​​​​​定义了哪些<trigger>呢 ? 请看下面

on boot                     #系统启动触发
on early-init               #在初始化之前触发
on init                     #在初始化时触发(在启动配置文件/init.conf被装载之后)
on late-init                #在初始化晚期阶段触发
on charger                  #当充电时触发
on property:<key>=<value>   #当属性值满足条件时触发
on post-fs                  #挂载文件系统
on post-fs-data             #挂载data
on device-added-<path>      #在指定设备被添加时触发
on device-removed-<path>    #在指定设备被移除时触发
on service-exited-<name>    #在指定service退出时触发
on <name>=<value>           #当属性<name>等于<value>时触发

trigger的执行顺序

on early-init  //代码触发的第一个action
    ... 初始化操作,创建一些属性文件、目录、设定目录权限、启动ueventd守护进程等

on init //第二个
    ... 创建一些属性文件、目录、设定目录权限、启动服务等

on late-init //第三个
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger post-fs-data
    trigger load_all_props_action
    trigger firmware_mounts_complete
    trigger early-boot
    trigger boot //最后一个,在此之前的有些trigger 还会触发其他trigger,按照链式管理, 也会比boot先执行

2.2 .Commands(命令)

1)命令将在所属事件发生(也就是上面 <trigger>发生)时一个一个顺序执行

2)常见的Command请看下面

1.exec<path>[ <argument> ]*	fork并执行一个程序,其路径为<path>,这条命令将阻塞直到该程序启动完成,因此它有可能造成init程序在某个节点不停地等待
2.export<name><value>	设置某个环境变量<name>的值为<value>,这是对全局有效的,即其后所有进程都将继承这个变量
3.ifup<interface>	使网络接口<interface>成功连接
4.import<filename>	引入一个名为<filename>的文件
5.hostname<name>	设置主机名
6.chdir<dirc>	更改工作目录为<dirc>
7.chmod<octal-mode><path>	更改文件访问权限
8.chown<owner><group><path>	更改文件所有者和组群
9.chroot<direc>	更改根目录位置
10.class_start<serviceclass>	如果它们不在运行状态的话,启动由<serviceclass>类名指定的所有相关服务
11.class_stop<serviceclass>	如果它们在运行状态的话,停止domainname	设置域名
12.insmod	在<path>路径上安装一个模块
13.mkdir<path>[mode][owner][group]	在<path>路径上新建一个目录
14.mount<type><device><dir>[<mountoption>]*	尝试在指定路径上挂载一个设备
15.setkey	目前未定义
16.setprop<name><value>	设置系统属性<name>的值为<value>
17.setrlinit<resource><cur><max>	设置一种资源的使用限制。这个概念亦存在于Linux系统中,<cur>表示软限制,<max>表示硬限制
18.start<service>	启动一个服务
19.stop<service>	停止一个服务
20.symlink<target><path>	创建一个<path>路径的软链接,目标为<target>
21.sysclk<mins_west_of_gmt>	设置基准时间,如果当前时间时GMT,这个值是0
22.trigger<event>	触发一个事件
23.write<path><string>[<string>]*	打开一个文件,并写入字符串

其中最重要的是start <service> 

2.4 Services(服务)

1)基本格式:

service <name><pathname> [ <argument> ]*
    <option1>
    <option2>
    ...
 
<name> 此service的名字
<pathname> 此service的路径,因为是可执行文件,所以一定有存储路径
<argument> 启动service所带的参数
<option> 对此service的约束选项,具体有哪些参考下面的Options

备注:service其实是一个可执行程序,它们在特定选项的约束下会被init程序运行或者重启(service可以在配置中指定是否需要退出时重启,这样当service出现异常或者程序crash时候就有机会复原)

2.5 Options(选项)

1.critical	表示这是一个对设备至关重要的一个服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式
2.disabled	此服务不会自动启动,而是需要通过显式调用服务名来启动
3.setenv<name><value>	设置环境变量<name>为某个值<value>
4.socket<name><type><perm>[ <user>[<group>]]	创建一个名为/dev/socket/<name>的unix domain socket,然后将它的fd值传给启动它的进程,有效的<type>值包括dgram,stream和seqacket,而user和group的默认值是0
5.user<username>	在启动服务前将用户组切换为<username>,默认情况下用户都是root
6.group<groupname>[<groupname>]*	在启动服务前将用户组切换为<groupname>
7.oneshot	只启动一次,当此服务退出时,不要主动去重启它
8.class<name>	为该服务指定一个class名,同一个class的所有服务必须同时启动或者停止。默认情况下服务的class名是“default”
9.onrestart	当此服务重启时,执行某些命令

2.6 实列解释

启动zygote service流程 ,也可参考zygote启动流程

//init.rc

import /init.${ro.zygote}.rc //系统 64&32 位 zygote进程

on late-init //中间trigger, 从上倒下的执行(按解析顺序)trigger
    trigger early-fs
    ....

    # Now we can start zygote for devices with file based encryption
    trigger zygote-start // 启动zygote的trigger
    ....

on zygote-start && property:ro.crypto.state=unencrypted
    # A/B update verifier that marks a successful boot.
    exec_start update_verifier_nonencrypted
    start netd
    start zygote   //启动zygote 服务(具体服务定义参考下面),zygote进程启动
    start zygote_secondary



//一般是定义在init.zygote.{ro.zygote}.rc  //和上面的import对应
service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main  //同一个class的所有服务必须同时启动或者停止。默认情况下服务的class名“default”
    priority -20 //优先级-20, 进程最高优先级
    user root
    group root readproc
    socket zygote stream 660 root system //创建socket节点,名称是zygote
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media //如果zygote死亡,会重启系统核心进程服务,如media/cameraserver/netd等
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks


//依然还是init.rc 之中
on nonencrypted
    class_start main // 这里会遍历启动所有定义为main的服务,因为zygote也是属于main,只是上面已启动过,不需要重复启动,直接跳过,内部会调用fork函数
    class_start late_start

启动ueventd service流程

on boot  //最后一个trigger 
    ...
    class_start core


service ueventd /sbin/ueventd 
    class core
    critical  //重要服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式
    seclabel u:r:ueventd:s0
    shutdown critical

最终执行的源码是在定义在builtins.cpp 之中

const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const Map builtin_functions = {
        {"bootchart",               {1,     1,    do_bootchart}},
        .....
        {"class_start",             {1,     1,    do_class_start}},
        {"class_stop",              {1,     1,    do_class_stop}},
        ......
        {"start",                   {1,     1,    do_start}},
        {"stop",                    {1,     1,    do_stop}},
        
    };
    // clang-format on
    return builtin_functions;
}

三、源码分析

int main(int argc, char** argv) {

    ...

    const BuiltinFunctionMap function_map;
    /*
     * 1.C++中::表示静态方法调用,相当于java中static的方法
     */
    Action::set_function_map(&function_map); //将function_map存放到Action中作为成员属性


    Parser& parser = Parser::GetInstance();//单例模式,得到Parser对象
 /*
     * 1.C++中std::make_unique相当于new,它会返回一个std::unique_ptr,即智能指针,可以自动管理内存
     * 2.unique_ptr持有对对象的独有权,两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作
     * 3.移动操作的函数是 p1=std::move(p) ,这样指针p指向的对象就移动到p1上了
     * 4.接下来的这三句代码都是new一个Parser(解析器),然后将它们放到一个map里存起来
     * 5.ServiceParser、ActionParser、ImportParser分别对应service action import的解析
     */
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {//如果ro.boot.init_rc没有对应的值,则解析/init.rc以及/system/etc/init、/vendor/etc/init、/odm/etc/init这三个目录下的.rc文件
        parser.ParseConfig("/init.rc");
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {//如果ro.boot.init_rc属性有值就解析属性值
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }
...

am.QueueEventTrigger("early-init");  //启动就决定了early-init先执行;

四、监听服务

之前的所有工作都是往各种数组、队列里面存入信息,并没有真正去触发,而接下来的工作就是真正去触发这些事件,以及用epoll不断监听新的事件

while (true) {
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1; //epoll超时时间,相当于阻塞时间

         /*
          * 1.waiting_for_prop和IsWaitingForExec都是判断一个Timer为不为空,相当于一个标志位
          * 2.waiting_for_prop负责属性设置,IsWaitingForExe负责service运行
          * 3.当有属性设置或Service开始运行时,这两个值就不为空,直到执行完毕才置为空
          * 4.其实这两个判断条件主要作用就是保证属性设置和service启动的完整性,也可以说是为了同步
          */
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            am.ExecuteOneCommand(); //执行一个command
        }
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            restart_processes(); //重启服务

            // If there's a process that needs restarting, wake up in time for that.
            if (process_needs_restart_at != 0) { //当有进程需要重启时,设置epoll_timeout_ms为重启等待时间
                epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
                if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
            }

            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout_ms = 0; //当还有命令要执行时,将epoll_timeout_ms设置为0
        }

        epoll_event ev;
        /*
         * 1.epoll_wait与上一篇中讲的epoll_create1、epoll_ctl是一起使用的
         * 2.epoll_create1用于创建epoll的文件描述符,epoll_ctl、epoll_wait都把它创建的fd作为第一个参数传入
         * 3.epoll_ctl用于操作epoll,EPOLL_CTL_ADD:注册新的fd到epfd中,EPOLL_CTL_MOD:修改已经注册的fd的监听事件,EPOLL_CTL_DEL:从epfd中删除一个fd;
         * 4.epoll_wait用于等待事件的产生,epoll_ctl调用EPOLL_CTL_ADD时会传入需要监听什么类型的事件,
         *   比如EPOLLIN表示监听fd可读,当该fd有可读的数据时,调用epoll_wait经过epoll_timeout_ms时间就会把该事件的信息返回给&ev
         */
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();//当有event返回时,取出ev.data.ptr(之前epoll_ctl注册时的回调函数),直接执行
            //上一篇中在signal_handler_init和start_property_service有注册两个fd的监听,一个用于监听SIGCHLD(子进程结束信号),一个用于监听属性设置
        }
    }

return 0;  //进程退出,不过看上面while处于死循环,也证明init 进程不会主动退出程序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值