宿觞博客 宿觞博客

Android系统init进程启动及init.rc全解析

in 杂文 read (36) 文章转载请注明来源!

服务启动机制

  1. system/core/init/init.c文件main函数中parse_config_file(init.rc)读取并解析init.rc文件内容。将service信息放置到system/core/init/init_parser.cpp的service_list中
  2. system/core/init/init.c文件main函数继续执行restart_servie_if_needed(…) -> service_start(…) -> Execve(…)建立service进程;

为了让大伙看得更明白,上个图先《总体启动框架图》:
20160427180741205.png

init.rc 简介

目前Linux有很多通讯机制可以在用户空间和内核空间之间交互,例如设备驱动文件(位于/dev目录中)、内存文件(/proc、/sys目录等)。了解Linux的同学都应该知道Linux的重要特征之一就是一切都是以文件的形式存在的,例如,一个设备通常与一个或多个设备文件对应。这些与内核空间交互的文件都在用户空间,所以在Linux内核装载完,需要首先建立这些文件所在的目录。而完成这些工作的程序就是本文要介绍的init。Init是一个命令行程序。其主要工作之一就是建立这些与内核空间交互的文件所在的目录。当Linux内核加载完后,要做的第一件事就是调用init程序,也就是说,init是用户空间执行的第一个程序。

尽管init完成的工作不算很多,不过代码还是非常复杂的。Init程序并不是由一个源代码文件组成的,而是由一组源代码文件的目标文件链接而成的。这些文件位于如下的目录。

需要明白的是,这些init.rc只是语法文件,并不是程序,真正的入口则是上面提到的system/core/init/init.c
因为init.c文件比较大,在文章的第二部分我会简要的通过main函数分析init启动流程;

init.rc有两个,分别位于:
./system/core/rootdir/init.rc
./bootable/recovery/etc/init.rc
从目录上大致可以猜测,这两个init.rc使用场景不一样,一个是刷机用到的,也就是进入recorvery模式,一个是正常启动用到的;我们这里重点分析的是上面那个,也是init.c关联的那个;

init.rc语法结构解析

要了解init.rc是怎么解析的,我们需要先看看说明文档,说明文档在,当然也可以看下热心网友的中文对照版本;
init.rc位于/bootable/recovery/etc/init.rc
Android初始化语言包含了四种类型的声明:
Actions(行为)、Commands(命令)、Services(服务)和Options(选项)

所有这些都是以行为单位的,各种记号由空格来隔开。
C语言风格的反斜杠号可用于在记号间插入空格。
双引号也可用于防止字符串被空格分割成多个记号。
行末的反斜杠用于折行,注释行以井号(#)开头(允许以空格开头)。

需要注意的是,这个只是一个语法文件,就像一个xml文件一样,没有执行顺序的,解析器通过读这个文件获取想要的数据,包括service,action等

Actions和Services声明一个新的分组Section。所有的命令或选项都属于最近声明的分组。位于第一个分组之前的命令或选项将会被忽略。
Actions和Services有唯一的名字。如果有重名的情况,第二个申明的将会被作为错误忽略。

Actions

Actions(行为)是一系列命令的开始
Actions代表一些Action.Action代表一组命令(Commands),Actions都有一个trigger(触发器),该触发器决定了何时执行这个Action,即在什么情况下才能执行该Action中的定义命令.当一些条件满足触发器的条件时,该Action中定义的命令会被添加到要执行命令队列的尾部(如果这组命令已经在队列中,则不会再次添加).

队列中的每一个action都被依次提取出,而这个action中的每个command(命令)在一个Action从队列移除时,该Action定义的命令会依次被执行.

Action的格式如下:

on <trgger> [&& <trigger>]*
   <command1>
   <command2>
   <command3>
   ...

on后面跟着一个触发器,当trigger被触发时,command1,command2,command3,会依次执行,直到下一个Action或下一个Service。

简单来说,Actions就是Android在启动时定义的一个启动脚本,当条件满足时,会执行该脚本,脚本里都是一些命令commands,不同的脚本用on来区分。

Triggers(触发器)

trigger即我们上面所说的触发器,本质上是一个字符串,能够匹配某种包含该字符串的事件.
trigger又被细分为事件触发器(event trigger)和属性触发器(property trigger).
Triggers(触发器)是一个用于匹配特定事件类型的字符串,用于使Actions发生。

事件触发器可由"trigger"命令或初始化过程中通过QueueEventTrigger()触发,通常是一些事先定义的简单字符串,例如:boot,late-init
属性触发器是当指定属性的变量值变成指定值时触发,其格式为property:=*

一个Action可以有多个属性触发器,但是最多有一个事件触发器.下面我们看两个例子:

on boot && property:a=b

该Action只有在boot事件发生时,并且属性a和b相等的情况下才会被触发.

on property:a=b && property:c=d

该Action会在以下三种情况被触发:

  • 在启动时,如果属性a的值等于b并且属性c的值等于d
  • 在属性c的值已经是d的情况下,属性a的值被更新为b
  • 在属性a的值已经是b的情况下,属性c的值被更新为d

当前AIL中常用的有以下几种事件触发器:

类型                        说明
-------------------------------------------------
boot                    init.rc被装载后触发
device-added-<path>        指定设备被添加时触发
device-removed-<path>    指定设备被移除时触发
service-exited-<name>    在特定服务(service)退出时触发
early-init                初始化之前触发
late-init                初始化之后触发
init                    初始化时触发(在 /init.conf (启动配置文件)被装载之后)

Init的触发是由init.c里的函数action_for_each_trigger来决定的(在main函数中被调用)。

Services

Services(服务)是一个程序,以 service开头,由init进程启动,一般运行于另外一个init的子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service,在启动时会通过fork方式生成子进程。Services(服务)的形式如下:

service <name> <pathname> [ <argument> ]*
    <option>
    <option>
    ...

其中:

  • name:服务名
  • pathname:当前服务对应的程序位置
  • option:当前服务设置的选项
  • argument 可选参数

init.rc文件详解

为了方便理解,我把整个init.rc解析一边,便于大家了解整个流程;如果想要了解recovery下的init语法解析,参考这篇文章《recovery下的init.rc语法解析》
代码量比较大,如果觉得看起来费劲,可以挑绿色部分看:

int main( int argc, char **argv )
{
    #创 建一些linux根文件系统中的目录
    mkdir( "/dev", 0755 );
    mkdir( "/proc", 0755 );
    mkdir( "/sys", 0755 );

    mount( "tmpfs", "/dev", "tmpfs", 0, "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 );
    #init的 标准输入,标准输出,标准错误文件描述符定向到__null__,意味着没有输入和输出,它的输入和输出全部写入到Log中
    open_devnull_stdio();
    #初始化 log 写入init进 信息
    log_init();
    #读取并 且解析init.rc文件(这个文件在根目录下)
    parse_config_file( "/init.rc" );
    #取得硬件 为打印我们的设备名fs100
    get_hardware_name();
    snprintf( tmp, sizeof(tmp), "/init.%s.rc", hardware );
    #读取并 且解析硬件相关的init脚本文件,
    parse_config_file( tmp );
    #触发在init脚本文件中名字为early-init的action,并且执行其commands,其实是: on early-init
    action_for_each_trigger( "early-init", action_add_queue_tail );
    drain_action_queue();
    #初始化动态设备管理,设备文件有变化时反应给内核,后面具体解释
    device_fd = device_init(); # 初 始 化 设 备 管 理 务
    #加载启动动画,如果动画打开失败,则在屏幕上打印: A N D R O I D字样。
    if ( load_565rle_image( INIT_IMAGE_FILE ) )
    {
        fd = open( "/dev/tty0", O_WRONLY );
        if ( fd >= 0 )
        {
            const char *msg;
            msg = "\n"
                  "\n"
                  "\n"
                  879         "\n"
                  "\n"
                  "\n"
                  "\n" /* console is 40 cols x 30 lines */
                  "\n"
                  "\n"
                  "\n"
                  "\n"
                  "\n"
                  "\n"
                  "\n"
                  /* "             A N D R O I D ";开机动画 */
                  write( fd, msg, strlen( msg ) );
            close( fd );
        }
    }

    #触发 在init脚本文件中名字为init的action,并且执行其commands,其实是:on init
    action_for_each_trigger( "init", action_add_queue_tail );
    drain_action_queue();
    #启动系统属性服务: system property service
    property_set_fd = start_property_service();
    #创建socket用来处理孤儿进程信号
    if ( socketpair( AF_UNIX, SOCK_STREAM, 0, s ) == 0 )
    {
        signal_fd    = s[0];
        signal_recv_fd    = s[1];
        fcntl( s[0], F_SETFD, FD_CLOEXEC );
        fcntl( s[0], F_SETFL, O_NONBLOCK );
        fcntl( s[1], F_SETFD, FD_CLOEXEC );
        fcntl( s[1], F_SETFL, O_NONBLOCK );
    }
    #触发 在init脚本文件中名字为early-boot和boot的action,并且执行其commands,其实是:on early-boot和on boot
    action_for_each_trigger( "early-boot", action_add_queue_tail );
    action_for_each_trigger( "boot", action_add_queue_tail );
    drain_action_queue();
    #启动所有属性变化触发命令,其实是: on property:ro.xx.xx=xx
    queue_all_property_triggers();
    drain_action_queue();
    #进入 死循环()
    for (;; )
    {
    #启 动所有init脚本中声明的service,
    #如 :266 service servicemanager /system/bin/servicemanager
    #user system
    #critical
    #onrestart restart zygote
    #onrestart restart media
    restart_processes();
    #多路监听设备管理,子进程运行状态,属性服务
        nr = poll( ufds, fd_count, timeout );
        if ( nr <= 0 )
            continue;
        if ( ufds[2].revents == POLLIN )
        {
            read( signal_recv_fd, tmp, sizeof(tmp) );
            while ( !wait_for_one_process( 0 ) )
                ;
            continue;
        }

        if ( ufds[0].revents == POLLIN )
            handle_device_fd( device_fd );

        if ( ufds[1].revents == POLLIN )
            handle_property_set_fd( property_set_fd );
        if ( ufds[3].revents == POLLIN )
            handle_keychord( keychord_fd );
    }

    return(0);
}
jrotty WeChat Pay

微信、支付宝、QQ

jrotty Alipay

三合一打赏

文章二维码

扫描二维码,在手机上阅读!

本文基于《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权
文章链接:https://blog.2dph.com/archives/217/ (转载时请注明本文出处及文章链接)

编程教程rootlinux代码
发表新评论
博客已萌萌哒运行
© 2019 宿觞
目前距离2020年高考还有
百度统计 站点地图
所有者:段鹏辉
湘ICP备06010528号
前篇 后篇
雷姆
拉姆
0:00