Android 8.0 系统启动流程之init.rc语法规则(六)

1、概述

    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的一些语法。

2、Android Init Language语法

定义在platform/system/core/init/README.md

    .rc文件主要配置了两个东西,一个是action,一个是service,trigger和command是对action的补充,options是对service的补充。action加上trigger以及一些command,组成一个Section;service加上一些option,也组成一个Section ;.rc文件就是由一个个Section组成。.rc文件头部有一个import的语法,表示这些.rc也一并包含并解析,接下来我们重点讲下action和service.

2.1 action

action的格式如下:

    on <trigger> [&& <trigger>]*
       <command>
       <command>
       <command>

以on开头,trigger是判断条件,command是具体执行一些操作,当满足trigger条件时,执行这些command。

trigger可以是一个字符串,如:

on early //表示当trigger earlyQueueEventTrigger("early")调用时触发

也可以是属性,如:

on property:sys.boot_from_charger_mode=1//表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发
on property:sys.sysctl.tcp_def_init_rwnd=* // *表示任意值

条件可以是多个,用&&连接,如:

//表示当zygote-start触发并且ro.crypto.state属性值为unencrypted时触发
on zygote-start && property:ro.crypto.state=unencrypted

command就是一些具体的操作,如:

mkdir /dev/fscklogs 0770 root system //新建目录
class_stop charger //终止服务
trigger late-init  //触发late-init

2.2 service

services的格式如下:

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

    以service开头,name是指定这个服务的名称,pathname表示这个服务的执行文件路径,argument表示执行文件带的参数,option表示这个服务的一些配置。
一个典型的例子;如:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

这个是配置在 /init.zygote64_32.rc文件中的service, 它就是我们常说的zygote进程的启动配置。zygote是进程名,可执行文件路径在/system/bin/app_process64,执行文件参数(就是可执行程序main函数里面的那个args)是
-Xzygote /system/bin –zygote –start-system-server –socket-name=zygote

后面的option是一些服务配置,比如:class main表示所属class是main,相当于一个归类,其他service也可以归为main,他们会被一起启动或终止,service有一个name,也有一个class,就像工作中,你有一个名字叫foxleezh,也可以说你属于android部门.

2.3 Options

Options是Services的参数配置. 它们影响Service如何运行及运行时机

console

console [<console>]

Service需要控制台. 第二个参数console的意思是可以设置你想要的控制台类型,默认控制台是/dev/console ,
/dev 这个前缀通常是被忽略的,比如你要设置控制台 /dev/tty0 ,那么只需要设置为console tty0

critical


critical

表示Service是严格模式. 如果这个Service在4分钟内退出超过4次,那么设备将重启进入recovery模式

disabled

disabled

表示Service不能以class的形式启动,只能以name的形式启动

setenv

setenv <name> <value>

在Service启动时设置name-value的环境变量

socket

socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]

创建一个unix域的socket,名字叫/dev/socket/name , 并将fd返回给Service. type 只能是 "dgram", "stream" 
or "seqpacket".User 和 group 默认值是 0. 'seclabel' 是这个socket的SELinux安全上下文,它的默认值是
service安全策略或者基于其可执行文件的安全上下文.它对应的本地实现在libcutils的android_get_control_socket

file

file <path> <type>

打开一个文件,并将fd返回给这个Service. type 只能是 "r", "w" or "rw". 它对应的本地实现在libcutils的
android_get_control_file 

user

user <username>

在启动Service前将user改为username,默认启动时user为root(或许默认是无).在Android M版本,如果一个进程想拥有
Linux capabilities(相当于Android中的权限吧),也只能通过设置这个值. 以前,一个程序要想有Linux 
capabilities,必须先以root身份运行,然后再降级到所需的uid.现在已经有一套新的机制取而代之,
它通过fs_config允许厂商赋予特殊二进制文件Linux capabilities. 这套机制的说明文档在
http://source.android.com/devices/tech/config/filesystem.html.当使用这套新的机制时,
程序可以通过user参数选择自己所需的uid,而不需要以root权限运行. 在Android O版本,程序可以通过
capabilities参数直接申请所需的能力,参见下面的capabilities说明

group


group <groupname> [ <groupname>\* ]

在启动Service前将group改为第一个groupname,第一个groupname是必须有的,
默认值为root(或许默认值是无),第二个groupname可以不设置,用于追加组(通过setgroups).

capabilities

capabilities <capability> [ <capability>\* ]

在启动Service时将capabilities设置为capability. 'capability' 不能是"CAP_" prefix, like "NET_ADMIN" 
or "SETPCAP". 参考http://man7.org/linux/man-pages/man7/capabilities.7.html ,
里面有capability的说明.

seclabel

seclabel <seclabel>

在启动Service前将seclabel设置为seclabel. 主要用于在rootfs上启动的service,比如ueventd, adbd.
在系统分区上运行的service有自己的SELinux安全策略,如果不设置,默认使用init的安全策略.

oneshot

oneshot

退出后不再重启

class

class <name> [ <name>\* ]Service指定class名字. 同一个class名字的Service会被一起启动或退出,默认值是"default",第二个name可以不设置,
用于service组.

animation

animation class

animation class 主要包含为开机动画或关机动画服务的service. 它们很早被启动,而且直到关机最后一步才退出.
它们不允许访问/data 目录,它们可以检查/data目录,但是不能打开/data目录,而且需要在/data不能用时也正常工作。

onrestart

onrestart 

在Service重启时执行命令.

writepid

writepid <file> [ <file>\* ]

当Service调用fork时将子进程的pid写入到指定文件. 用于cgroup/cpuset的使用,当/dev/cpuset/下面没有文件
但ro.cpuset.default的值却不为空时,将pid的值写入到/dev/cpuset/cpuset_name/tasks文件中

priority

priority <priority>

设置进程优先级. 在-20~19之间,默认值是0,能过setpriority实现

namespace

namespace <pid|mnt>

当fork这个service时,设置pid或mnt标记

oom_score_adjust

oom_score_adjust <value>

设置子进程的 /proc/self/oom_score_adj 的值为 value,在 -10001000之间.

Triggers

Triggers

Triggers 是个字符串,当一些事件发生满足该条件时,一些actions就会被执行

Triggers分为事件Trigger和属性Trigger

事件Trigger由trigger 命令或QueueEventTrigger方法触发.它的格式是个简单的字符串,比如'boot''late-init'.

属性Trigger是在属性被设置或发生改变时触发. 格式是'property:<name>=<value>''property:<name>=*',它会在init初始化设置属性的时候触发.

属性Trigger定义的Action可能有多种触发方式,但是事件Trigger定义的Action可能只有一种触发方式

比如:
on boot && property:a=b 定义了action的触发条件是,boot Trigger触发,并且属性a的值等于b
on property:a=b && property:c=d 这个定义有三种触发方式:
在初始化时,属性a=b,属性c=d.
在属性c=d的情况下,属性a被改为b.
A在属性a=b的情况下,属性c被改为d.

2.4 Commands

bootchart

bootchart [start|stop]

启动或终止bootcharting. 这个出现在init.rc文件中,但是只有在/data/bootchart/enabled文件存在的时候才有效,
否则不能工作

chmod

chmod <octal-mode> <path>

修改文件读写权限

chown

chown <owner> <group> <path>

修改文件所有者或所属用户组

class_start

class_start <serviceclass>

启动所有以serviceclass命名的未启动的service(service有一个name,也有个class,这里的serviceclass就是
class,class_start和后面的start是两种启动方式,class_start是class形式启动,start是name形式启动)

class_stop

class_stop <serviceclass> 

终止所有以serviceclass命名的正在运行的service

class_reset

class_reset <serviceclass>

终止所有以serviceclass命名的正在运行的service,但是不禁用它们. 它们可以稍后被class_start重启

copy

copy <src> <dst>

复制一个文件,与write相似,比较适合二进制或比较大的文件.

对于src,从链接文件、world-writable或group-writable复制是不允许的.

对于dst,如果目标文件不存在,则默认权限是0600,如果存在就覆盖掉

domainname

domainname <name>

设置域名

enable

enable <servicename>

将一个禁用的service设置为可用.
如果这个service在运行,那么就会重启.
一般用在bootloader时设置属性,然后启动一个service,比如
on property:ro.boot.myfancyhardware=1
enable my_fancy_service_for_my_fancy_hardware
exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]
新建子进程并运行一个带指定参数的命令. 这个命令指定了seclabel(安全策略),user(所有者),group(用户组).
直到这个命令运行完才可以运行其他命令,seclabel可以设置为 - 表示用默认值,argument表示属性值.
直到子进程新建完毕,init进程才继续执行.

exec_start

exec_start <service>

启动一个service,只有当执行结果返回,init进程才能继续执行. 这个跟exec相似,只是将一堆参数的设置改在在service中定义

export

export <name> <value>

设置环境变量name-value. 这个环境变量将被所有已经启动的service继承

hostname

hostname <name> 

设置主机名

ifup

ifup <interface>

开启指定的网络接口

insmod

insmod [-f] <path> [<options>]

安装path下的模块,指定参数options.

-f 表示强制安装,即便是当前Linux内核版本与之不匹配

load_all_props

load_all_props

加载/system, /vendor等目录下的属性,这个用在init.rc中

load_persist_props

load_persist_props

加载/data 下的持久化属性. 这个用在init.rc中

loglevel

loglevel <level>

设置日志输出等级,level表示等级

mkdir

mkdir <path> [mode] [owner] [group]

创建一个目录,path是路径,mode是读写权限,默认值是755,owner是所有者,默认值root,group是用户组,
默认值是root.如果该目录已存在,则覆盖他们的mode,owner等设置

mount_all

mount_all <fstab> [ <path> ]\* [--<option>]

当手动触发 "early""late"时,调用fs_mgr_mount_all 函数,指定fstab配置文件,并导入指定目录下的.rc文件
详情可以查看init.rc文件中的有关定义

mount

mount <type> <device> <dir> [ <flag>\* ] [<options>]

在dir目录下挂载一个名叫device的设备

_flag 包括 "ro", "rw", "remount", "noatime", ...

options包括"barrier=1","noauto_da_alloc", "discard", ... 用逗号分开,比如barrier=1,noauto_da_alloc

restart

restart <service>

终止后重启一个service,如果这个service刚被重启就什么都不做,如果没有在运行,就启动

restorecon

restorecon <path> [ <path>\* ]

恢复指定目录下文件的安全上下文.第二个path是安全策略文件. 指定目录不需要必须存在,因为它只需要在init中正确标记

restorecon_recursive

restorecon_recursive <path> [ <path>\* ]

递归地恢复指定目录下的安全上下文,第二个path是安全策略文件位置

rm

rm <path>

调用 unlink(2)删除指定文件. 最好用exec -- rm ...代替,因为这样可以确保系统分区已经挂载好

rmdir

rmdir <path>

调用 rmdir(2) 删除指定目录

setprop

setprop <name> <value>

设置属性name-value 

setrlimit

setrlimit <resource> <cur> <max>

指定一个进程的资源限制

start

start <service> 

启动一个未运行的service

stop

stop <service>

终止一个正在运行的service

swapon_all

swapon_all <fstab>

调用 fs_mgr_swapon_all,指定fstab配置文件.

symlink

symlink <target> <path>

在path下创建一个指向target的链接

sysclktz

sysclktz <mins_west_of_gmt>

重置系统基准时间(如果是格林尼治标准时间则设置为0)

trigger

trigger <event>

触发事件event,由一个action触发到另一个action队列

umount

umount <path>

卸载指定path的文件系统

verity_load_state

verity_load_state

内部实现是加载dm-verity的状态

verity_update_state

verity_update_state <mount-point>

内部实现是设置dm-verity的状态,并且设置partition.mount-point.verified的属性. 用于adb重新挂载,
因为fs_mgr 不能直接设置它。 

wait


wait <path> [ <timeout> ]

查看指定路径是否存在. 如果发现则返回,可以设置超时时间,默认值是5秒

wait_for_prop

wait_for_prop <name> <value>

等待name属性的值被设置为value,如果name的值一旦被设置为value,马上继续

write

write <path> <content>

打开path下的文件,并用write(2)写入content内容. 如果文件不存在就会被创建,如果存在就会被覆盖掉

Imports

import关键字不是一个命令,但是如果有.rc文件包含它就会马上解析它里面的section,用法如下:

import <path>

解析path下的.rc文件 ,括展当前文件的配置。如果path是个目录,这个目录下所有.rc文件都被解析,但是不会递归,
import被用于以下两个地方:

1.在初始化时解析init.rc文件

2.在mount_all时解析{system,vendor,odm}/etc/init/等目录下的.rc文件

后面的内容主要是一些跟调试init进程相关的东西,比如init.svc.可以查看service启动的状态,
ro.boottime.init记录一些关键的时间点,Bootcharting是一个图表化的性能监测工具等,由于与语法关系不大,就不作翻译了
明白了.rc文件的语法,我们再来看看init进程是如何解析.rc文件,将这些语法转化为实际执行的代码的

3、小结

本文解释了Android启动文件init.rc的语法规则,下一篇将分析一下init进程是如何解析这个启动配置文件。未完待续。。。

Android 8.0 及以上版本,为了增强应用程序的安全性,Android 引入了后台限制,禁止未在前台运行的应用程序启动服务。如果您想在后台启动服务,需要使用 `startForegroundService()` 方法。这个方法会启动一个前台服务,然后你可以在服务启动后在通知栏显示一个通知,以此来告知用户服务正在运行。 以下是一个使用 `startForegroundService()` 的示例代码: ``` if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 创建一个 NotificationChannel NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT); // 向系统注册 NotificationChannel NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(channel); } // 创建一个 Intent,启动你的服务 Intent serviceIntent = new Intent(this, YourService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 在 Android 8.0 及以上版本上,需要调用 startForegroundService() 方法启动服务。 startForegroundService(serviceIntent); } else { // 在 Android 8.0 以下版本上,可以直接调用 startService() 方法启动服务。 startService(serviceIntent); } ``` 注意:如果你使用的是 `startForeground()` 方法,会在 Android 8.0 及以上版本上抛出 `IllegalStateException` 异常,因为 Android 8.0 及以上版本禁止在后台启动服务。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值