Android 初始化语言 --init.rc

 

学习这个玩意儿就是因为之前遇到的DAC配置问题,翻译init/README.md文件,以后在遇到此类问题就可以修改相应的rc文件即可

Android初始化语言

Android初始语言由五大类语句组成:操作,命令,服务,选项和导入。

所有这些都是面向行的,由用空格分隔的标记组成。c样式的反斜杠转义符可用于将空格插入到令牌中。双引号也可用于防止空格将文本分成多个标记。反斜杠,当它是一行上的最后一个字符时,可用于行折叠。

#(以前允许的空格)开头的行是注释。

可以使用语法扩展系统属性${property.name}。这也适用于需要连接的上下文,例如import /init.recovery.${ro.hardware}.rc

操作和服务隐式声明一个新部分。所有命令或选项都属于最近声明的部分。忽略第一部分之前的命令或选项。

服务有独特的名称。如果定义的第二个服务名称与现有服务名称相同,则会忽略该服务并记录错误消息。

Init .rc文件

init语言用于带.rc文件扩展名的纯文本文件。在系统上的多个位置通常存在多个这些,如下所述。

/init.rc是主.rc文件,在执行开始时由init可执行文件加载。它负责系统的初始设置。

通过第一阶段安装机制挂载/ system/ vendor的设备在加载主/init.rc后立即加载/ {systemvendorodm} / etc / init /目录中包含的所有文件。在此文件的导入部分中对此进行了更详细的说明。

没有第一阶段安装机制的旧设备执行以下操作:

  1. /init.rc导入/init.${ro.hardware}.rc这是主要供应商提供的.rc文件。
  2. mount_all命令期间,init可执行文件加载/ {systemvendorodm} / etc / init /目录中包含的所有文件。这些目录适用于文件系统安装后使用的所有操作和服务。

可以在mount_all命令行中指定路径,使其在指定的路径中导入.rc文件,而不是上面列出的默认路径。这主要用于支持工厂模式和其他非标准引导模式。应该将三个默认路径用于正常引导过程。

这些目录的意图是:

  1. / system / etc / init /用于核心系统项,如SurfaceFlingerMediaServicelogcatd
  2. / vendor / etc / init /用于SoC供应商项目,例如核心SoC功能所需的操作或守护程序。
  3. / odm / etc / init /用于设备制造商项目,例如运动传感器或其他外围功能所需的操作或守护程序。

二进制文件驻留在系统,供应商或odm分区上的所有服务都应将其服务条目放入相应的init .rc文件中,该文件位于它们所在分区的/ etc / init /目录中。有一个构建系统宏LOCAL_INIT_RC,可以为开发人员处理这个问题。每个init .rc文件还应包含与其服务相关的任何操作。

一个示例是位于system / core / logcat目录中的logcatd.rcAndroid.mk文件。Android.mk文件中的LOCAL_INIT_RC宏在构建过程中将logcatd.rc放在/ system / etc / init /中。Initmount_all命令期间加载logcatd.rc并允许运行服务并在适当时排队操作。

根据其守护进程分解init .rc文件比以前使用的单片init .rc文件更受欢迎。这种方法确保init读取的唯一服务条目和init执行的唯一操作对应于其二进制文件实际存在于文件系统上的服务,而单片init .rc文件则不然。当将多个服务添加到系统时,这另外将有助于合并冲突解决,因为每个服务将进入单独的文件。

mount_all命令中有两个选项“early”“late”,可以在可选路径之后设置。设置“--early”后,init可执行文件将跳过带有“latemount”标志的挂载条目并触发fs加密状态事件。设置“--late”后,init可执行文件只会挂载带有“latemount”标志的条目,但会跳过导入rc文件。默认情况下,不设置任何选项,mount_all将处理给定fstab中的所有条目。

Actions

actions由一系列命令组成。actions具有触发器,用于确定何时执行操作。当一个事件与一个actions的触发器匹配时,该action被添加到一个待执行队列的尾部(除非它已经在队列中)。

队列中的每个操作都按顺序出列,并且该操作中的每个命令都按顺序执行。Init处理活动中命令执行之间的其他活动(设备创建/销毁,属性设置,进程重启)。

Actions采取以下形式:

<trigger> [&& <trigger>] *

   <命令>

   <命令>

   <命令>

操作将添加到队列中,并根据解析包含它们的文件的顺序执行(请参阅导入部分),然后按顺序在单个文件中执行。

例如,如果文件包含:

on boot

   setprop a 1

   setprop b 2

 

on boot && propertytrue = true

   setprop c 1

   setprop d 2

 

on boot

   setprop e 1

   setprop f 2

然后当boot触发器发生并且假设属性true等于时true,执行的命令的顺序将是:

setprop a 1

setprop b 2

setprop c 1

setprop d 2

setprop e 1

setprop f 2

Services

服务是init退出时启动和(可选)重新启动的程序。服务采取以下形式:

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

   <选项>

   <选项>

   ...

Options

选项是服务的修饰符。它们会影响init运行服务的方式和时间。

capabilities <capability> [ <capability>\* ]

执行此服务时设置功能。‘capability‘应该是没有“CAP_”前缀的Linux功能,如“NET_ADMIN”“SETPCAP”。有关Linux功能的列表,请参见http://man7.org/linux/man-pages/man7/capabilities.7.html

class <name> [ <name>\* ]

指定服务的类名。命名类中的所有服务可以一起启动或停止。如果未通过class选项指定服务,则服务在默认类中。除了(必需的)第一个之外的其他类名用于对服务进行分组。该animation级应包括所有必要的两种开机动画和关机动画服务。由于这些服务可以在启动期间很早启动,并且可以运行到关闭的最后阶段,因此无法保证对/ data分区的访问。这些服务可以检查/ data下的文件,但它不应该保持文件打开,并且在/ data不可用时应该工作。

console [<console>]

此服务需要一个控制台。可选的第二个参数选择特定的控制台而不是默认控制台。可以通过设置“androidboot.console”内核参数来更改默认的“/ dev / console”。在所有情况下,应省略前导“/ dev /”,因此“/ dev / tty0”将被指定为“console tty0”

critical

这是一项设备关键型服务。如果它在四分钟内退出四次以上,设备将重启进入恢复模式。

disabled

此服务不会自动从其类开始。它必须通过名称或接口名称显式启动。

enter_namespace <type> <path>

输入位于path的类型类型的命名空间。类型设置为“net”时仅支持网络命名空间。请注意,只能输入给定类型的一个名称空间。

file <path> <type>

打开文件路径并将其fd传递给已启动的进程。type必须是“r”“w”“rw”。对于本机可执行文件,请参阅libcutils android_get_control_file()。

group <groupname> [ <groupname>\* ]

在执行此服务之前更改为“groupname”。除了(必需的)第一个之外的其他组名用于设置进程的补充组(通过setgroups())。目前默认为root。(???可能默认为没人)

interface <interface name> <instance name>

将此服务与其提供的HIDL服务列表相关联。接口名称必须是完全限定名称,而不是值名称。这用于允许hwservicemanager懒洋洋地启动服务。例如:interface vendor.foo.bar@1.0 :: IBaz默认值

ioprio <class> <priority>

通过SYS_ioprio_set系统调用为此服务设置IO优先级和IO优先级。class必须是“rt”“be”“idle”之一。priority必须是07范围内的整数。

keycodes <keycode> [ <keycode>\* ]

设置将触发此服务的密钥代码。如果同时按下与传递的键码对应的所有键,则服务将开始。这通常用于启动错误报告服务。

memcg.limit_in_bytes <value>

将子目录的memory.limit_in_bytes设置为指定值(仅当安装了memcg时),该值必须等于或大于0

memcg.soft_limit_in_bytes <value>

将子节点的memory.soft_limit_in_bytes设置为指定的值(仅当安装了memcg时),该值必须等于或大于0

memcg.swappiness <value>

将子目录的memory.swappiness设置为指定值(仅当安装了memcg时),该值必须等于或大于0

namespace <pid|mnt>

在分叉服务时输入新的PIDmount命名空间。

oneshot

退出时不要重新启动服务。

onrestart

服务重新启动时执行命令(见下文)。

oom_score_adjust <value>

将子项的/ proc / self / oom_score_adj设置为指定值,该值必须介于-10001000之间。

override

表示此服务定义旨在覆盖具有相同名称的服务的先前定义。这通常意味着/ odm上的服务可以覆盖/ vendor上定义的服务。init使用此关键字解析的最后一个服务定义是服务定义将用于此服务。请密切关注init.rc文件的解析顺序,因为它具有一些特性,以便向后兼容。此文件的导入部分提供了有关订单的更多详细信息。

priority <priority>

调度服务进程的优先级。该值必须在-2019的范围内。默认优先级为0.优先级通过setpriority()设置。

rlimit <resource> <cur> <max>

这将给定的rlimit应用于服务。rlimits由子进程继承,因此这有效地将给定的rlimit应用于此服务启动的进程树。它的解析类似于下面指定的setrlimit命令。

seclabel <seclabel>

在执行此服务之前更改为“seclabel”。主要供从rootfs运行的服务使用,例如ueventdadbd。系统分区上的服务可以基于其文件安全上下文使用策略定义的转换。如果未指定且策略中未定义转换,则默认为init上下文。

setenv <name> <value>

在启动的进程中将环境变量名称设置为value

shutdown <shutdown_behavior>

设置服务进程的关闭行为。如果未指定,则在关闭过程中使用SIGTERMSIGKILL终止服务。shutdown_behavior“critical”的服务在关闭期间不会被终止,直到关闭超时。当关机超时时,即使标记为关键关键的服务也将被终止。当关闭开始时标记为关键关键的服务未运行时,将启动它。

sigstop

在调用exec之前立即将SIGSTOP发送到服务。这是用于调试。请参阅以下有关如何使用它的调试部分。

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

创建名为/ dev / socket / nameunix域套接字,并将其fd传递给已启动的进程。 type必须是“dgram”“stream”“seqpacket”。用户和组默认为0.‘seclabel‘是套接字的SELinux安全上下文。它默认为服务安全性上下文,由seclabel指定或基于服务可执行文件安全性上下文计算。对于本机可执行文件,请参阅libcutils android_get_control_socket()。

user <username>

在执行此服务之前更改为用户名。目前默认为root。(???可能默认为nobody)从Android M开始,进程应该使用此选项,即使它们需要Linux功能。以前,要获得Linux功能,进程需要以root身份运行,请求功能,然后转到所需的uid。通过fs_config有一种新机制允 许设备制造商将Linux功能添加到应该使用的文件系统上的特定二进制文件中。http://source.android.com/devices/tech/config/filesystem.html上介绍了此机制。使用此新机制时,进程可以使用user选项选择所需的uid,而无需以root身份运行。从Android O开始,进程也可以直接在.rc文件中请求功能。请参阅下面的功能选项。

writepid <file> [ <file>\* ]

在分叉时将孩子的pid写入给定的文件。意味着cgroup / cpuset的使用。如果没有指定/ dev / cpuset /下的文件,但是系统属性‘ro.cpuset.default‘被设置为非空的cpuset名称(例如‘/ foreground‘),则pid被写入文件/ dev / cpuset / cpuset_name / tasks

Triggers

触发器是一些字符串,可用于匹配某些类型的事件并用于导致Action发生。

触发器细分为事件触发器和属性触发器。

事件触发器是由“trigger”命令或init可执行文件中的QueueEventTrigger()函数触发的字符串。它们采用简单字符串的形式,例如‘boot‘‘late-init‘

属性触发器是当命名属性将值更改为给定新值或命名属性将值更改为任何新值时触发的字符串。它们分别采用‘property=‘‘property= *‘的形式。在init的初始引导阶段,还会相应地评估和触发属性触发器。

Action可以有多个属性触发器,但可能只有一个事件触发器。

例如:on boot && property:a=b定义仅在“boot”事件触发器发生且属性a等于b时执行的操作。

on property:a=b && property:c=d 定义了三次执行的操作:

  1. 在初始引导期间,如果属性a = b且属性c = d
  2. 任何时候属性a转换为值b,而属性c已经等于d
  3. 任何时候属性c转换为值d,而属性a已经等于b

Commands

bootchart [start|stop]

启动/停止启动。这些文件存在于默认的init.rc文件中,但只有文件/ data / bootchart / enabled存在时,才会激活启动图否则bootchart start / stopno-ops

chmod <octal-mode> <path>

更改文件访问权限。

chown <owner> <group> <path>

更改文件所有者和组。

class_start <serviceclass>

如果尚未运行,则启动指定类的所有服务。有关启动服务的更多信息,请参阅开始条目。

class_stop <serviceclass>

如果当前正在运行,则停止并禁用指定类的所有服务。

class_reset <serviceclass>

如果当前正在运行,则停止指定类的所有服务,而不禁用它们。它们可以在以后重新启动class_start

class_restart <serviceclass>

重新启动指定类的所有服务。

copy <src> <dst>

复制文件。与write类似,但对二进制/大量数据有用。关于src文件,不允许从符号链接文件和世界可写或组可写文件进行复制。关于dst文件,如果不存在,则创建的默认模式为0600。如果dst文件是普通的常规文件并且已经存在,它将被截断。

domainname <name>

设置域名。

enable <servicename>

将已禁用的服务转换为已启用的服务,就像服务未指定已禁用一样。如果该服务应该正在运行,它将立即启动。通常在引导加载程序设置指示特定服务的变量时使用,以便在需要时启动。例如

在属性上:ro.boot.myfancyhardware = 1

    启用my_fancy_service_for_my_fancy_hardware

exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]

使用给定参数forkexecute命令。该命令在“ - ”之后开始,以便可以提供可选的安全上下文,用户和补充组。在此完成之前,不会运行任何其他命令。seclabel可以是 - 表示默认值。属性在参数内扩展。Init停止执行命令,直到分叉进程退出。

exec_background [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]

使用给定参数forkexecute命令。这与exec命令类似地处理。区别在于init在进程退出之前不会暂停执行命令exec_background

exec_start <service>

启动给定服务并停止处理其他init命令,直到它返回。该命令的功能与exec命令类似,但使用现有的服务定义来代替exec参数向量。

export <name> <value>

在全局环境中将环境变量名称设置为value(将在执行此命令后由所有进程继承)

hostname <name>

设置主机名。

ifup <interface>

使网络接口接口联机。

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

使用指定选项在路径中安装模块。-f:强制安装模块,即使正在运行的内核的版本和编译模块的内核版本不匹配。

load_all_props

/ system/ vendor等加载属性。这包含在默认的init.rc中。

load_persist_props

在解密/ data时加载持久性属性。这包含在默认的init.rc中。

loglevel <level>

将内核日志级别设置为level。属性在级别内扩展。

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

在路径中创建目录,可选择使用给定模式,所有者和组。如果未提供,则使用权限755创建目录,并由root用户和根组拥有。如果提供,则如果目录已存在,则将更新模式,所有者和组。

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

在给定的fs_mgr-format fstab上调用fs_mgr_mount_all并在指定的路径上导入.rc文件(例如,在刚安装的分区上),并选择“early”“late”选项。有关详细信息,请参阅“Init .rc文件部分。

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

尝试将命名设备挂载到目录dir _flag_s包括“ro”“rw”“remount”“noatime”... 选项包括“barrier = 1”“noauto_da_alloc”“discard”...作为逗号分隔的字符串,例如:barrier = 1noauto_da_alloc

restart <service>

停止并重新启动正在运行的服务,如果服务当前正在重新启动,则不执行任何操作,否则,它只会启动该服务。

restorecon <path> [ <path>\* ]

path命名的文件还原到file_contexts配置中指定的安全上下文。init.rc创建的目录不需要,因为init会自动标记这些目录。

restorecon_recursive <path> [ <path>\* ]

递归地将path命名的目录树恢复到file_contexts配置中指定的安全上下文。

rm <path>

在给定路径上调用unlink2)。您可能希望使用“exec - rm ...”(假设系统分区已经安装)。

rmdir <path>

在给定路径上调用rmdir2)。

readahead <file|dir> [--fully]

在给定目录中的文件或文件上调用readahead2)。使用选项--fully读取完整的文件内容。

setprop <name> <value>

将系统属性名称设置为value。属性在值内扩展。

setrlimit <resource> <cur> <max>

设置资源的rlimit。这适用于设置限制后启动的所有进程。它旨在早期在init中设置并在全球范围内应用。资源最好使用其文本表示(‘cpu‘‘rtio‘等或‘RLIM_CPU‘‘RLIM_RTIO‘等)指定。它也可以指定为资源枚举对应的int值。

start <service>

如果服务尚未运行,请启动它。请注意,这不是同步的,即使它是,也不能保证操作系统的调度程序将充分执行服务以保证服务状态的任何信息。

这将创建一个重要的后果是,如果该服务提供的功能的其他服务,如提供通信通道,只需启动该服务之前,这些服务是不足够的,以保证通道已经建立这些服务提出要求之前。必须有一个单独的机制来做出任何此类保证。

stop <service>

如果服务当前正在运行,请停止运行。

swapon_all <fstab>

在给定的fstab文件上调用fs_mgr_swapon_all

symlink <target> <path>

在路径上使用值target创建符号链接

sysclktz <mins_west_of_gmt>

设置系统时钟基准(如果系统时钟以GMT为单位,则为0

trigger <event>

触发事件。用于对来自其他操作的操作进行排队。

umount <path>

卸载在该路径上安装的文件系统。

verity_load_state

用于加载dm-verity状态的内部实现细节。

verity_update_state <mount-point>

内部实现细节用于更新dm-verity状态并设置分区。adb remount使用的mount-point .verified属性,因为fs_mgr无法直接设置它们。

wait <path> [ <timeout> ]

轮询存在给定文件并在找到时返回,或者已达到超时。如果未指定超时,则当前默认为五秒。

wait_for_prop <name> <value>

等待系统属性名称为值。属性在值内扩展。如果属性名称已设置为值,请立即继续。

write <path> <content>

在路径中打开文件,并使用write2)向其写入一个字符串。如果该文件不存在,则将创建该文件。如果确实存在,则会被截断。属性在内容中扩展。

Imports

import <path>

解析init配置文件,扩展当前配置。如果path是目录,则将目录中的每个文件解析为配置文件。它不是递归的,不会解析嵌套目录。

import关键字不是命令,而是它自己的部分,这意味着它不会作为Action的一部分发生,而是在解析文件时处理导入并遵循以下逻辑。

init可执行文件导入.rc文件的次数只有三次:

  1. ro.boot.init_rc在初始引导期间导入/init.rc或属性指示的脚本时。
  2. 在导入/init.rc后立即为第一阶段安装设备导入/ {systemvendorodm} / etc / init /
  3. mount_all期间在指定路径导入/ {systemvendorodm} / etc / init /.rc文件时。

由于遗留原因并保持向后兼容性,导入文件的顺序有点复杂。它没有严格的保证。

保证在另一个命令之前运行命令的唯一正确方法是:1)将其置于具有较早执行触发器的Action中,或者2)将其置于具有相同触发器的Action中早先的一行。

尽管如此,第一阶段安装设备的实际订单是:

  1. 解析/init.rc然后递归地解析每个导入。
  2. / system / etc / init /的内容按字母顺序排列并按顺序解析,在解析每个文件后以递归方式进行导入。
  3. 对于 / vendor / etc / init then/ odm / etc / init 重复步骤2

下面的伪代码可以更清楚地解释这个:

fn Import(file)

  Parse(file)

  forimportfile.imports

    Import(import)

 

Import(/init.rc

Directories = [/ system / etc / init/ vendor / etc / init/ odm / etc / init]

for(directories:Directories)

  files = <Alphabetical order of directory‘s contents>

  forfilefiles

    Import(file)

Properties

Init通过以下属性提供有关其负责的服务的信息。

init.svc.<name>

命名服务的状态(停止停止运行重新启动

Boot timing

Init在系统属性中记录一些启动时序信息。

ro.boottime.init

ns中启动后的时间(通过CLOCK_BOOTTIME时钟),init的第一个阶段开始。

ro.boottime.init.selinux

第一阶段初始化SELinux需要多长时间。

ro.boottime.init.cold_boot_wait

init等待ueventd的冷启动阶段结束的时间。

ro.boottime.<service-name>

ns中启动后的时间(通过CLOCK_BOOTTIME时钟)服务首次启动。

Bootcharting

此版本的init包含执行“bootcharting”的代码:生成日志文件,以后可以通过http://www.bootchart.org/提供的工具进行处理。

在模拟器上,使用-bootchart timeout选项以在超时秒激活的bootcharting的情况下引导。

在设备上:

adb shell‘touch / data / bootchart / enabled‘

完成数据收集后,别忘了删除此文件!

日志文件写入/ data / bootchart /。提供了一个脚本来检索它们并创建一个bootchart.tgz文件,该文件可以与bootchart命令行实用程序一起使用:

sudo apt-get install pybootchartgui

grab-bootchart.sh使用$ ANDROID_SERIAL

$ ANDROID_BUILD_TOP /系统/核心/ INIT / grab-bootchart.sh

需要注意的一点是,启动图将显示init,就好像它开始在0运行一样。当内核实际启动init时,你必须查看dmesg才能解决问题。

比较两个引导图

名为compare-bootcharts.py的便捷脚本可用于比较所选进程的开始/结束时间。前面提到的grab-bootchart.sh将在/ tmp / android-bootchart中留下名为bootchart.tgzbootchart tarball。如果在主机上在不同目录下保留了两个这样的barball,则脚本可以列出时间戳差异。例如:

用法:system / core / init / compare-bootcharts.py base-bootchart-dir exp-bootchart-dir

process:baseline(delta - Unit is ms(系统上jiffy10 ms

------------------------------------

/ init50 40-10

/ system / bin / surfaceflinger4320 4470+150

/ system / bin / bootanimation6980 6990+10

zygote6410410 10640+230

zygote10410 10640+230

system_server15350 15150-200

bootanimation ends at33790 31230-2560

Systrace

Systracehttp://developer.android.com/tools/help/systrace.html)可用于在userdebugeng版本的引导期间获取性能分析报告。

以下是“wm”“am”类别的跟踪事件示例:

$ ANDROID_BUILD_TOP / external / chromium-trace / systrace.py \

      wm am --boot

此命令将导致设备重新启动。重新启动设备并完成引导顺序后,将从设备获取跟踪报告,并通过按Ctrl + C将其写为主机上的trace.html

限制:在加载持久属性后启动记录跟踪事件,因此不记录在此之前发出的跟踪事件。voldsurfaceflingerservicemanager等几个服务受此限制的影响,因为它们是在加载持久属性之前启动的。Zygote初始化和从受精卵分叉的进程不受影响。

调试init

建议不使用init启动init服务,因为init会设置难以手动复制的大量环境(用户,组,安全标签,功能等)。

如果需要从一开始就调试服务,sigstop则添加服务选项。此选项将在调用exec之前立即将SIGSTOP发送到服务。这给出了一个窗口,开发人员可以在使用SIGCONT继续服务之前附加调试器,strace等。

也可以通过ctl.sigstop_onctl.sigstop_off属性动态控制此标志。

下面是通过上面的动态调试logd的示例:

stop logd

setprop ctl.sigstop_on logd

start logd

ps -e | grep logd

> logd 4343 1 18156 1684 do_signal_stop 538280 T init

gdbclient.py -p 4343

b main

C

C

C

>breakpoint 1mainargc = 1argv = 0x7ff8c9a488)at system / core / logd / main.cpp427

以下是使用strace执行相同操作的示例

stop logd

setprop ctl.sigstop_on logd

start logd

ps -e | grep logd

> logd 4343 1 18156 1684 do_signal_stop 538280 T init

strace -p 4343

 

(From different shell)

kill -SIGCONT 4343

 

> strace runs

主机Init脚本验证

在构建期间检查Init脚本的正确性。具体来说,检查以下内容。

  1. 格式良好的action,service和import sections,例如没有前面的‘on‘没有动作,并且‘import‘语句后没有多余的行。
  2. 所有命令都映射到有效关键字,参数计数在正确的范围内。
  3. 所有服务选项均有效。这比检查命令的方式更严格,因为服务选项的参数被完全解析,例如UIDGID必须解析。

init脚本的其他部分仅在运行时解析,因此在构建期间不进行检查,其中包括以下内容。

  1. 命令参数的有效性,例如,不检查文件路径是否实际存在,SELinux是否允许操作,或者UIDGID是否解析。
  2. 不检查服务是否存在或是否定义了有效的SELinux
  3. 不检查先前是否在不同的init脚本中定义了服务。

Android 初始化语言 --init.rc

上一篇:Redis


下一篇:网络分流器|网络分流器|网络分流器在移动互联网监控应用案例