安卓系统的开机,是由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
- 所属类 core核心类
- user,group 都为system
- 规定时间内不断重启(系统崩溃)进入recovery恢复模式
- servicemanager重启可触发healthd,zygote,media,surfaceflinger,drm重启
以上我们了解了init.rc文件的基本构成。其思路就是配置相关核心服务和方法,用于在内核启动后系统的核心服务的初始化过程。既然我们知道了其基本组成,下面我们就看看能不能在这里面做点文章。
案例1:
我们需要在系统开机时候启动一个我们自定义的核心服务,就可以在init.rc文件中配置它!
新建测试文件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; }
编写文件的Android.mk
控制编译生成二进制程序loop,默认路径在/system/bin下
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := loop.cpp LOCAL_MODULE := loop include $(BUILD_EXECUTABLE)
修改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
添加对应二进制程序的.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)
make系统,刷机查看控制台输出结果
重新编译系统,制作升级包,刷机后开机查看log是否有输出。
案例2:
开机进行检查工作或者拷贝工作,通过向init.rc文件添加属性值监测代码调用自己的脚本实现。
例如开机进行apk文件的检查,不存在便进行安装并启动。
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服务
编写对应的启动脚本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
拷贝文件和脚本到相关文件夹下,编译系统刷机
- 根据如上脚本,test.apk放置源码的/out/system/test/test.apk
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
make,全局编译生成imsge,刷机测试
总结
本章节主要介绍了init.rc脚本的基础语法和两个小案例,到这里我们不禁就会想联想到了代码结构中的高内聚低耦合规则,init进程通过遍历不同的rc脚本可以控制系统开机的程序,代码上无需做过多的改动,只需要在配置文件rc脚本中进行相应的修改即可,到这里不禁感叹谷歌工程师们的高超智慧了。