源码分析之init.rc脚本

安卓系统的开机,是由kernel内核引导系统的第一个init进程开始的,而控制init进程的执行则是由脚本文件inti.rc来控制的,为什么还要对init进程进行控制了,下面我们来具体了解一下

概述

init.rc文件是安卓系统内核kernel启动后,进入第一个程序init(进程id始终为1)需要解析的文件。

  • 此文件由语句组成,主要包括了四种类型的语句:

    Action,
    Commands,
    Services,
    Options.
    
  • rc文件语法是以行尾单位,以空格间隔的语法,以#开始代表注释行

1.Action

动作表示了一组命令(commands)组成。动作包括一个触发器,决定了何时运行这个动作。
当触发器的条件满足时,这个动作会被增加到已被运行的队列尾。
假设此动作在队列中已经存在,那么它将不会运行。
通过trigger,即以 on 开头的语句,决定何时执行相应的service。

  • on early-init; 在初始化早期阶段触发;
  • on init; 在初始化阶段触发;
  • on late-init; 在初始化晚期阶段触发;
  • on boot/charger: 当系统启动/充电时触发,还包含其他情况;
  • on property:=: 当属性值满足条件时触发;

  • 示例:

    on early-init
        # Set init and its forked children's oom_adj.
        write /proc/1/oom_score_adj -1000
    
        # Set the security context of /adb_keys if present.
        restorecon /adb_keys
    
        start ueventd
    

触发器(trigger):用来描写叙述一个触发条件,当这个触发条件满足时能够运行动作。

  • boot 当init程序运行,并加载/init.conf文件时触发.
  • = 当属性名相应的值设置为指定值时触发.
  • device-added- 当加入设备时触发.
  • device-removed- 当设备移除时触发.
  • service-exited- 当指定的服务退出时触发.

系统是如何调用这个触发器的呢?
在/system/core/init/init.cpp文件的main方法中有如下语句:

...
//解析init.rc
init_parse_config_file("/init.rc");

//执行rc文件中触发器为 on early-init 的语句执行
action_for_each_trigger("early-init", action_add_queue_tail);
...

2.Command

常用命令如下:

  • class_start <service_class_name>: 启动属于同一个class的所有服务;
  • start <service_name>: 启动指定的服务,若已启动则跳过;
  • stop <service_name>: 停止正在运行的服务
  • setprop :设置属性值
  • mkdir :创建指定目录
  • symlink <sym_link>: 创建连接到的<sym_link>符号链接;
  • write : 向文件path中写入字符串;
  • exec: fork并执行,会阻塞init进程直到程序完毕;
  • exprot :设定环境变量;
  • loglevel :设置log级别;
  • ifup :使指定的网络接口”上线”,相当激活指定的网络接口;
  • hostname :设置主机名;
  • insmod :安装模块到指定路径;
  • trigger :触发事件或服务

示例:

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

    # set RLIMIT_NICE to allow priorities from 19 to -20
    setrlimit 13 40 40

    ...
    write /proc/sys/vm/overcommit_memory 1
    write /proc/sys/vm/min_free_order_shift 4
    chown root system /sys/module/lowmemorykiller/parameters/adj
    ...

    # Tweak background writeout
    write /proc/sys/vm/dirty_expire_centisecs 200
    write /proc/sys/vm/dirty_background_ratio  5

    # Permissions for System Server and daemons.
    chown radio system /sys/android_power/state
    ...

    chown root radio /proc/cmdline

    # Define default initial receive window size in segments.
    setprop net.tcp.default_init_rwnd 60

    class_start core    //启动core class

3.Service

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

示例如下:

service bootanim /system/bin/bootanimation
class core
user graphics
group graphics audio
disabled
oneshot

service bootanim /system/bin/bootanimation代表的是服务名为bootanim,服务的路径,也就是服务执行操作时运行/system/bin/bootanim

4.Option

Options是Services的可选项,与service配合使用,是用来改动服务的。它们影响怎样及何时执行这个服务

  • disabled: 不随class自动启动,只有根据service名才启动;
  • oneshot: service退出后不再重启;
  • user/group: 设置执行服务的用户/用户组,默认都是root;
  • class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;
  • onrestart:当服务重启时执行相应命令;
  • socket: 创建名为/dev/socket/的socket
  • critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式

default: 意味着disabled=false,oneshot=false,critical=false

示例:

service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm
  1. 所属类 core核心类
  2. user,group 都为system
  3. 规定时间内不断重启(系统崩溃)进入recovery恢复模式
  4. servicemanager重启可触发healthd,zygote,media,surfaceflinger,drm重启

以上我们了解了init.rc文件的基本构成。其思路就是配置相关核心服务和方法,用于在内核启动后系统的核心服务的初始化过程。既然我们知道了其基本组成,下面我们就看看能不能在这里面做点文章。

案例1:

我们需要在系统开机时候启动一个我们自定义的核心服务,就可以在init.rc文件中配置它!

  1. 新建测试文件loop.cpp到device/*/test下

    新建cpp文件,控制打印输出5句“I am a process”

    #include<stdio.h>
    int main(){  
        int i=0;  
        for(i;i<5;i++)  
        {  
          printf("I am a process\n");  
        }  
        return 0;  
    }
    
  2. 编写文件的Android.mk

    控制编译生成二进制程序loop,默认路径在/system/bin下

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_SRC_FILES := loop.cpp
    
    LOCAL_MODULE := loop
    
    include $(BUILD_EXECUTABLE)
    
  3. 修改init.rc文件

    声明service loop,属类main,即main类服务运行时,统一属类main的service都会启动。console控制台显示,oneshot只启动一次。

    # new code by allies for testing
    service loop /system/bin/loop
       class main
       console
       oneshot
    
  4. 添加对应二进制程序的.te文件

    对应的具体内容可以参考如下,主要是移植external/sepolicy/uncrypt.te文件,其中缺失的内容是编译过程报错的选项,这里直接删除,可以通过。

    # loop
    type loop, domain, mlstrustedsubject;
    type loop_exec, exec_type, file_type;
    
    init_daemon_domain(loop)
    
    # Read /cache/recovery/command
    # Read /cache/recovery/loop_file
    # Write to pipe file /cache/recovery/loop_status
    allow loop cache_file:dir rw_dir_perms;
    allow loop cache_file:file create_file_perms;
    allow loop cache_file:fifo_file w_file_perms;
    
    # Set a property to reboot the device.
    set_prop(loop, powerctl_prop)
    
  5. make系统,刷机查看控制台输出结果

    重新编译系统,制作升级包,刷机后开机查看log是否有输出。

案例2:

开机进行检查工作或者拷贝工作,通过向init.rc文件添加属性值监测代码调用自己的脚本实现。
例如开机进行apk文件的检查,不存在便进行安装并启动。

  1. init.rc文件添加开机启动属性监测

    声明服务,并添加判断,开机完成后启动此脚本。如果不加此判断,会导致机器还没完全起来,属类main运行时,pm和am命令用不了,以至于APK未能安装。

    service Test-setup /system/bin/sh /system/etc/init.test.sh
        class main
        user root
        group root
        disabled
        oneshot
    
    1. 属类 main
    2. user,group均为root
    3. 不随class自动启动,只有根据service名才启动
    4. 只启动一次
    
    on property:dev.bootcomplete=1
        start Test-setup
    系统启动完成,属性改变开启Test-setup服务
    
  2. 编写对应的启动脚本init.Test.sh

    if [ ! -e /data/app/com.android.test-1.apk ]
    then
        pm install /system/test/test.apk
        am startservice -n com.android.test/com.android.test.TestService
    fi
    
    1. 检测文件夹下文件是否存在(存在即有安装)
    2. 不存在,调用pm命令安装指定路径下apk
    3. 使用am命令启动制定apk
    
  3. 拷贝文件和脚本到相关文件夹下,编译系统刷机

    1. 根据如上脚本,test.apk放置源码的/out/system/test/test.apk
    2. init.Test.sh脚本放置于/system/bin/下

      对应拷贝的Android.mk文件可以这样编写

      PRODUCT_COPY_FILES += $(LOCAL_PATH)/test.apk:$(TARGET_OUT)/system/test/test.apk
      PRODUCT_COPY_FILES += $(LOCAL_PATH)/init.Test.sh:$(TARGET_OUT)/system/bin/init.Test.sh
      
    3. make,全局编译生成imsge,刷机测试

总结

本章节主要介绍了init.rc脚本的基础语法和两个小案例,到这里我们不禁就会想联想到了代码结构中的高内聚低耦合规则,init进程通过遍历不同的rc脚本可以控制系统开机的程序,代码上无需做过多的改动,只需要在配置文件rc脚本中进行相应的修改即可,到这里不禁感叹谷歌工程师们的高超智慧了。

坚持原创技术分享,您的支持将鼓励我继续创作!