Android8.0.0-r4 init.rc文件解析

一、简述

    Android init.rc文件由系统第一个启动的init程序解析,此文件由语句组成,主要包含了四种类型的语句:Action, Commands,Services, Options. 在init.rc 文件中一条语句通常是占据一行.单词之间是通过空格符来相隔的,如果需要在单词内使用空格,那么得使用转义字符"\", 如果在一行的末尾有一个反斜杠,那么是换行折叠符号,应该和下一行合并成一起来处理,这样做主要是为了避免一行的字符太长,与C语言中的含义是一致的。注释是以#号开头。 Action和 services显式声明了一个语句块,而commands和options属于最近声明的语句块。在第一个语句块之前 的commands和options会被忽略.

二、关键字

  token:  计算机语言中的一个单词,就跟英文中的单词差不多一人概念.
  Section: 语句块,相当于C语言中大括号内的一个块。一个Section以Service或On开头的语句块.以Service开头的Section叫做服务,而以On开头的叫做动作(Action).
  services: 服务.
  Action: 动作
  commands:命令.
  options:选项.
  trigger:触发器,或者叫做触发条件.

  class: 类属,即可以为多个service指定一个相同的类属,方便操作同时启动或停止.

三、语句解析

1. 动作(Action)
    动作表示了一组命令(commands)组成.动作包含一个触发器,决定了何时执行这个动作。当触发器的条件满足时,这个动作会被加入到已被执行的队列尾。如果此动作在队列中已经存在,那么它将不会执行.一个动作所包含的命令将被依次执行。动作的语法如下所示:
Actions组织形式位:
        on   
            
            
         
动作的使用示例如下:
    on init
    export PATH /sbin:/system/sbin:/system/bin:/system/xbin
    mkdir /system
init表示一个触发条件,这个触发事件发生后,进行设置环境变量和建立目录的操作称为一个“动作”。

2. 服务(services)
服务是指那些需要在系统初始化时就启动或退出时自动重启的程序.
它的语法结构如下所示:
service [ ]*  
 

[ ]*
    类似于linux的mount指令
setkey
    TBD(To Be Determined),待定.
setprop
    设置属性及对应的值.
setrlimit
    设置资源的rlimit(资源限制),不懂就百度一下rlimit
start
    如果指定的服务未启动,则启动它.
stop
    如果指定的服务当前正在运行,则停止它.
symlink
    创建一个符号链接.
sysclktz
    设置系统基准时间.
trigger
    Trigger an event.  Used to queue an actionfrom another action.这名话没有理解,望高手指点.
write [ ]*

    往指定的文件写字符串.

6 属性(Properties)
init程序在运行时会更新属性系统的一些属性,提供程序内部正在执行的信息.
属性名
描述
init.action
    当前正在执行的动作,如果没有则为空字符串""
init.command
    当前正在执行的命令.没有则为空字符串.
init.svc.
    当前某个服务的状态,可为"stopped","running", "restarting"
setprop 用于设置属性,on property可以用于判断属性,这里的属性在整个Android系统运行中都是一致的。init脚本的关键字可以参考init进程的system/core/init/keyword.h文件。init.rc的使用方法,可以参考说明文件system/core/init/readme.txt。如果想要修改启动过程只需要修改init.c(system/core/init)或者init.rc里的内容即可

四、init.rc 文件解析过程

文件结构
    init.rc 基本单位是 section。 
        section 有三种类型: 
            1. on 
            2. service 
            3. import

解析 init.rc 的过程就是识别一个个 section 的过程。 

在 init.c 中的 main() 中去执行一个个命令。 
(android采用双向链表来存储section的信息,解析完成之后,会得到三个双向链表action_list、service_list、import_list来分别存储三种section的信息上。)
  1. system/core/init/init.c 
    在 init.c 中调用
init_parse_config_file("/init.rc");

其代码实现如下:

int init_parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);        //read_file()调用open\lseek\read 将init.rc读出来
    if (!data) return -1;

    parse_config(fn, data);        //调用parse_config开始解析
    DUMP();
    return 0;
}
  1. parse_config() 代码:
static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn;    //文件名
    state.line = 0;
    state.ptr = s;          //文字指针
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;

    list_init(&import_list);
    state.priv = &import_list;

    for (;;) {
        switch (next_token(&state)) {                   //next_token()根据从state.ptr开始遍历
        case T_EOF:                                     //遍历到文件结尾,然后goto解析import的.rc文件
            state.parse_line(&state, 0, 0);
            goto parser_done;
        case T_NEWLINE:                                 //到了一行结束
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);      //找到这一行的关键字
                if (kw_is(kw, SECTION)) {                        //如果这是一个section的第一行                                            
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {                                         //如果这不是一个section的第一行
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:                                            //遇到普通字符
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
parser_done:
    list_for_each(node, &import_list) {
         struct import *import = node_to_item(node, struct import, list);
         int ret;

         INFO("importing '%s'", import->filename);
         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

next_token() 解析完 init.rc 中一行之后, 
会返回T_NEWLINE,这时调用 lookup_keyword 函数来找出这一行的关键字, lookup_keyword返回的是一个整型值,对应keyword_info[]数组的下标,keyword_info[]存放的是keyword_info结构体类型的数据,

struct {
    const char *name;                      //关键字的名称
    int (*func)(int nargs, char **args);   //对应的处理函数
    unsigned char nargs;                   //参数个数
    unsigned char flags;                   //flag标识关键字的类型,包括COMMAND、OPTION、SECTION
} keyword_info

因此keyword_info[]中存放的是所有关键字的信息,每一项对应一个关键字。 
根据每一项的flags就可以判断出关键字的类型,如新的一行是SECTION,就调用parse_new_section()来解析这一行, 
如新的一行不是一个SECTION的第一行,那么调用state.parseline()来解析(state.parseline所对应的函数会根据section类型的不同而不同),在parse_new_section()中进行动态设置。

三种类型的section: service、on、import, 
service对应的state.parseline为parse_line_service, 
on对应的state.parseline为parse_line_action, 
import section中只有一行所以没有对应的state.parseline。

五. 总结

5.1 服务名的命名规则

service 的名字长度不能超过16个字节

service 的名字只能是字母,数字和'-','_'

5.2 系统组

system/core/include/private/android_filesystem_config.h中的android_ids定义了系统中所有进程的gid值

"root" AID_ROOT

"system" AID_SYSTEM

5.3 系统中propertey属性

可以在makefile中用宏PRODUCT_DEFAULT_PROPERTY_OVERRIDES定义缺省的property

系统中有PROP_PATH_SYSTEM_BUILD和PROP_PATH_SYSTEM_DEFAULT

system/build.prop;

system/default.prop

system.prop

并且init 进程创建了/dev/socket/property_service socket去监听 设置prop的请求.

5.4 service的启动

执行完on boot以后,就会依次启动core,main,defualt类中的服务

最终init进程通过执行fork创建进程

5.5 init进程

所有的service都是init进程的子进程,当init进程执行完init.rc以后,就会监听所有service的退出状态,propertery的设置。

六. 其他工具

Bootcharting

init的这个版本包含运行”bootcharting”的代码:生成一个日志文件,后期能够被 www.bootchart.org 
提供的工具处理。

在虚拟机中,使用-bootchart 选项来使启动的时候带有bootcharting持续秒。

在一个设备中,使用命令创建 /data/bootchart/start: 
  adb shell 'echo $TIMEOUT > /data/bootchart/start'

$TIMEOUT的值对应着期望bootchart持续的秒数。当这些时间过后,Bootcharting将会停止。

你可以通过下面的命令在任何时候停bootcharting: 
  adb shell 'echo 1 > /data/bootchart/stop'

注意,/data/bootchart/stop会在bootcharting最后被init自动删除。对于/data/bootchart/start 
情况并非如此,所以当你收集完数据之后,不要忘记删除他。

日志文件被写入/data/bootchart中。一个脚本被提供去恢复他们,并且创建一个bootchart.tgz文件 
,这个文件可以被bootchart命令行工具使用:

      sudo apt-get install pybootchartgui
      # grab-bootchart.sh uses $ANDROID_SERIAL.
      $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

一个需要注意的事情就是,bootchart将会显示init好像是他从0s的时候开始运行。当内核 
开始init的时候,你必须查看dmesg的工作。

Debugging init

默认的,由init执行的程序将会把标准输出和标准错误丢入到/dev/null。为了帮助调试, 
你可以通过安卓程序日志封装程序运行你的程序。这个将会重定向标准输出/标准错误到 
安卓日志系统中。

例如 
service akmd /system/bin/logwrapper /sbin/akmd

当在init中自己运行的时候,为了快速的转变,使用:

      mm -j
      m ramdisk-nodeps
      m bootimage-nodeps
      adb reboot bootloader
      fastboot boot $ANDROID_PRODUCT_OUT/boot.img

可选的,使用虚拟机:

emulator -partition-size 1024 -verbose -show-kernel -no-window

在klog_init()调用之后,你可能想要调用klog_set_level(6),所以你需要在 
dmesg或者是虚拟机输出中查看内核日志。

你可能感兴趣的:(Android代码经验)