1、Android Init Language 


        Android Init Language,也就是Android初始化语言,就是init.rc文件的语法
        Android 初始化语言包含五种主要的语句类:Actions, Commands, Services, Options, and Imports.(操作命令服务选项导入

        所有这些都是面向行的,由空格分隔的标记组成。可以使用c风格的反斜杠转义将空白插入到令牌中。使用双引号还可以用于防止空格将文本分割成多个标记。当反斜杠是一行中的最后一个字符时,它可以用于行折叠。
        以#开头的行(允许前导空格)是注释。可以使用语法扩展系统属性“$ {property.name}”。这也适用于连接的上下文中需要,例如“import/init.recovery.${ro.hardware}.rc”。
        Actions(操作)和Services(服务)隐式地声明一个新的部分。所有命令或选项属于最近声明的部分。命令或第一部分之前的选项将被忽略。而服务有唯一的名称。如果定义了第二个服务,如果名称与现有名称相同,则会忽略它并出现错误消息记录。

 

2、Init .rc文件

       init 语言在纯文本文件中使用,该文件采用.rc文件扩展。通常有多个这些在多个系统上的位置,如下所述:

       /init.rc 是主要的.rc文件,由init可执行文件在开始执行的时候进行加载,负责初始的建立系统。

       通过第一阶段 first stage 挂载机制挂载 /system/vendor 的设备在加载完主要的/init.rc 之后就会去马上加载 / {system,vendor,odm} / etc / init / 目录下的所有文件

       没有第一阶段挂载机制的旧式设备会执行以下操作:
              1. /init.rc 中进行 import /init.${ro.hardware}.rc 去导入,这是主要的供应商提供的.rc文件。
              2.在 mount \ _all 命令期间,init可执行文件将加载所有 / {system,vendor,odm} / etc / init / 目录中包含的文件。
              这些目录适用于之后使用的所有操作和服务文件系统安装。

       可以在mount\_all命令行中指定路径来导入它.rc文件,而不是上面列出的默认路径。这主要用于支持工厂模式和其他非标准启动模式。这三个默认路径应该用于正常的引导过程。
       这些目录的目的是:
              1. /system/etc/init/ 用于核心系统项,例如SurfaceFlinger、MediaService和logcatd。
              2. /vendor/etc/init/ 是针对SoC供应商项目,例如action或核心SoC功能所需的守护进程。
              3./odm/etc/init/ 是设备制造商的项目,如动作传感器或其他外设所需的动作或守护进程功能。

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

       一个例子是位于 system/core/logcat 目录中的 logcatd.rc 和 Android.mk 文件。在构建过程中,Android.mk 文件中的LOCAL\_INIT\_rc 宏将 logcatd.rc 放置在 /system/etc/init/ 中。init 在 mount\_all 命令期间加载 logcatd.rc,并允许在适当情况下运行服务和排队操作。

       根据init.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有一个trigger触发器,该触发器用于确定操作的执行时间。 当事件发生与Action的trigger触发器匹配,该Action将添加到要执行的队列的尾部(除非它已经在队列)。

        队列中的每个 action 按顺序排列,action 中的每个命令都按顺序执行。init 在执行命令的活动之间处理其他活动(设备创建/销毁、属性设置、进程重新启动)。

        Actions 的格式:

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

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

        例如如果一个文件包含:

            on boot
               setprop a 1
               setprop b 2

            on boot && property:true=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> ]*
               <option>
               <option>
               ...

  • Options(选项)

        Options(选项)是services的修饰符。它们影响init如何以及何时运行service。

        `console [<console>]`

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

        `critical`
        > 这是一个设备关键服务。如果在四分钟内退出超过四次,设备将重新启动进入恢复模式。

        `disabled`
        > 这个服务不会从它的类去自动启动。它必须通过名称显式地启动。

        `setenv <name> <value>`
        > 在启动的进程中,将环境变量_name_设置为_value_。

        `socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]`
        > 创建一个名为 /dev/socket/_name_的 unix 域套接字,并将其 fd 传递给启动的进程。
           _type_必须是“dgram”、“stream”或“seqpacket”。
           user 和 group 默认为0。
           “seclabel” 是套接字的 SELinux 安全上下文。
           它默认为服务安全上下文,由 seclabel 指定或基于服务可执行文件安全上下文计算
           有关本机可执行文件,请参见 libcutils android\_get\_control\_socket()

        `enter_namespace <type> <path>`
        >进入位于_path_的_type_类型的名称空间。将_type_设置为“net”只支持网络名称空间。
           注意,只能输入给定_type_的一个名称空间。

        `file <path> <type>`
        > 打开一个文件路径并将其fd传递给启动的进程。
          _type_必须是“r”、“w”或“rw”。
          有关本机可执行文件,请参见libcutils android\_get\_control\_file()。

        `user <username>`
        > 在执行此服务之前更改为“username”。当前默认为root。(???可能应该对任何人都不违约)。
          对于Android M,进程应该使用这个选项,即使它们需要Linux功能。
          以前,为了获得Linux功能,进程需要以根用户身份运行,请求这些功能,然后将其放到所需的uid。
          fs\_config提供了一种新的机制,允许设备制造商将Linux功能添加到应该使用的文件系统的特定二进制文件中。
          这个机制在<http://source.android.com/devices/tech/config/filesystem.html>中描述。
          在使用这种新机制时,进程可以使用user选项来选择所需的uid,而不必作为根用户运行。
          与Android O一样,进程也可以直接在.rc文件中请求功能。
          参见下面的“capabilities”(功能)选项。

        `group <groupname> [ <groupname>\* ]`
        > 在执行此服务之前更改为"groupname"。
          除第一个组名称之外的其他组名称用于设置进程的补充组(通过 setgroup())。
          当前默认为 root。 (???可能应该默认为none)

        `capabilities <capability> [ <capability>\* ]`
        > 执行此服务时设置功能。
          “capability”(功能)应该是一个没有“CAP\_”前缀的Linux功能,比如“NET\_ADMIN”或“SETPCAP”。
          有关Linux功能的列表,请参见http://man7.org/linux/man7/pages/man7/capabilities.7.html。

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

        `seclabel <seclabel>`
        > 执行此服务前更改为“seclabel”。
          主要用于从rootfs运行的服务,如ueventd, adbd。
          系统分区上的服务可以根据其文件安全上下文使用策略定义的转换。
          如果没有指定,并且在策略中没有定义转换,则默认为init上下文。

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

        `class <name> [ <name>\* ]`
        > 为服务指定类名。命名类中的所有服务都可以一起启动或停止。
           如果没有通过类选项指定服务,则服务处于“默认”类中。
           除了(必需的)第一个类名以外的其他类名用于对服务进行分组。

        `animation class`
        > “animation”类应该包括所有启动动画和关机动画所需的服务。
           由于这些服务可以在启动过程中很早就启动,并且可以运行到关闭的最后阶段,因此无法保证对/数据分区的访问。
           这些服务可以检查/data下的文件,但它不应该保持文件处于打开状态,当/data不可用时应该可以工作。

        `onrestart`
        > 当服务重新启动时,执行一个命令。  

        `writepid <file> [ <file>\* ]`
        > 当它分叉派生出来时,将子进程的 pid 写入给定的文件。
           用于 cgroup/cpuset 的使用。
           如果没有指定 /dev/cpuset/ 下的文件,但是系统属性 “ ro.cpuset.default ” 被设置为非空的 cpuset 名称
           (比如:'/前台')时,那么将 pid 写入文件 /dev/cpuset/_cpuset\_name_/tasks。

        `priority <priority>`
        > 调度服务进程的优先级。这个值必须在-20到19之间。默认优先级为0。
           通过setpriority()设置优先级。

        `namespace <pid|mnt>`
        > 在创建服务时输入新的PID或装载命名空间

        `oom_score_adjust <value>`
        > 将子程序的/proc/Self/oom\_core\_adj设置为指定的值,该值必须从-1000到1000。

        `memcg.swappiness <value>`
        > 将子节点的 memory.swappiness 设置为指定的值(仅在挂载memcg时),该值必须等于或大于0。

        memcg可参考:https://blog.csdn.net/pillarbuaa/article/details/79207036 和 https://segmentfault.com/a/1190000008125359
        memory.swappiness
        设置和显示当前的swappiness,该文件的值默认和全局的swappiness
        (/proc/sys/vm/swappiness)一样,修改该文件只对当前cgroup生效,
        其功能和全局的swappiness一样.有一点和全局的swappiness不同,那就是:
        如果这个文件被设置成0,就算系统配置的有交换空间,当前cgroup也不会使用交换空间

        `memcg.soft_limit_in_bytes <value>`
        > 将子节点的 memory.soft_limit_in_bytes 设置为指定的值(仅在挂载memcg时),该值必须等于或大于0。
           memory.soft_limit_in_bytes: 设置/显示当前限制的内存软额度

        `memcg.limit_in_bytes <value>`
        将子节点的 memory.limit_in_bytes 设置为指定的值(仅在挂载memcg时),该值必须等于或大于0。                                                 memory.limit_in_bytes :设置/显示当前限制的内存额度

        `shutdown <shutdown_behavior>`
        > 设置服务进程的关闭行为。
           如果未指定此参数,则在关闭过程中使用SIGTERM和SIGKILL关闭服务。
           在关机超时之前,具有shutdown_behavior为“critical”的服务不会在关机期间被关闭。
           当关机超时时,即使标记为“关键关机”的服务也将被关闭。
           当关闭启动时标记为“shutdown critical”的服务不运行时,它将被启动。

 

  • Triggers(触发器)

        触发器是字符串,可以用来匹配某些类型的事件,并用于导致一个动作发生。
        触发器分为事件触发器和属性触发器。
        事件触发器是由 'trigger' 命令或 init 可执行文件中的 QueueEventTrigger() 函数触发的字符串。
        它们以简单字符串的形式出现,如 'boot' 或 'late-init'。
        属性触发器是在命名属性将值更改为给定的新值或命名属性将值更改为任何新值时触发的字符串。
        它们的形式分别是 'property:<name>=<value>' 和 'property:<name>=\*'。
        属性触发器在init的初始启动阶段将被计算并相应地触发。

        一个操作可以有多个属性触发器,但可能只有一个事件触发器。
        例如:
        `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]`
        > 启动/停止bootchart。
           这些在默认的init.rc文件中出现,但只有当 /data/bootchart/enabled 存在时,
           bootchart 才会被激活;否则,启动/停止 bootchart 是无效的。

        `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>`
        > 拷贝一个文件。与写类似,但对二进制/大量数据很有用。
           对于src文件,不允许从符号链接文件和全局可写或组可写的文件中复制。
           对于dst文件,如果它不存在,则创建的默认模式是0600。
           如果dst文件是一个普通的常规文件并且已经存在,那么它将被截断。

        `domainname <name>`
        > 设置域名

        `enable <servicename>`
        > 将禁用的服务转换为启用的服务,就好像服务没有指定禁用一样。
           如果服务应该正在运行,那么它现在就会启动。
           通常在引导加载程序设置一个变量时使用,该变量指示应在需要时启动特定的服务。如:
             on property:ro.boot.myfancyhardware=1
                 enable my_fancy_service_for_my_fancy_hardware

        `exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]`
        > 使用给定的参数 fork 一个进程并且执行 command
           命令在 " -- " 之后开始,以便提供可选的安全上下文、用户和补充组。
           在这个命令完成之前,不会运行其他命令。
           _seclabel_ 可以是一个-来表示默认值。
           属性在 _argument_ 中展开。
           Init 暂停执行命令,直到 fork 的进程退出。

        `exec_background [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]`
        > 使用给定的参数 fork 一个进程并执行命令。
          处理方式与 "exec" 命令类似。
          区别在于,在进程退出 `exec_background`之前,init 不会停止执行命令。用的好的话,这里可适当的减少系统启动的时间

        `exec_start <service>`
        > 启动一个给定的服务,并停止其他 init 命令的处理,直到它返回。
           该命令的功能类似于' exec '命令,但使用现有的服务定义代替exec参数向量。

        `export <name> <value>`
        > 在全局环境中将环境变量 _name_ 设置为 _value_ (执行此命令后启动的所有进程都将继承该变量)

        `hostname <name>`
        > 设置主机名

        `ifup <interface>`
        > 使网络接口 _interface_ 联机

        `insmod [-f] <path> [<options>]`
        > 使用指定的选项在 _path_ 安装模块。
            -f :强制安装模块,即使运行的内核版本和编译模块的内核版本不匹配。

        `load_all_props`
        > 从/system、/vendor等加载属性。 这包括在默认init.rc中。

        `load_persist_props`
        > 在/data 被解密后加载 persistent 持久属性。这包括在默认init.rc中

        `loglevel <level>`
        > 将内核日志级别设置为level。属性在_level_中展开

        `mkdir <path> [mode] [owner] [group]`                               
        > 在 _path_ 处创建一个目录,可选地使用给定的模式、所有者和组。
           如果没有提供,则使用权限755创建目录,并由根用户和根组拥有。
           如果提供了,如果目录已经存在,模式、所有者和组将被更新。

        `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_目录下挂载命名的device设备
           _flag_s包括“ro”、“rw”、“remount”、“noatime”、…
           _options_包括"barrier=1" "noauto\_da\_alloc" " "… 作为逗号分隔的字符串,
           例如: barrier=1,noauto\_da\_alloc

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

        `restorecon <path> [ <path>\* ]`
        > 将_path_命名的文件恢复到 file\_contexts 配置中指定的安全上下文。
           init.rc 创建的目录不需要,因为这些会被init自动正确地标记。

        `restorecon_recursive <path> [ <path>\* ]`
        > 递归地将_path_命名的目录树恢复到 file\_contexts 配置中指定的安全上下文

        `rm <path>`
        > 调用给定路径上的 unlink(2)。您可能想要使用“exec—rm…”来代替(假设系统分区已经挂载)

        `rmdir <path>`
        > 在给定的路径上调用 rmdir(2)

        `readahead <file|dir> [--fully]`
        > 对给定目录中的文件调用 readahead(2),也就是预读取。使用选项--fully 读取完整的文件内容

        `setprop <name> <value>`
        > 将系统属性_name_设置为_value_。属性在_value_中展开

        `setrlimit <resource> <cur> <max>`
        > 设置资源的rlimit。这适用于在设置限制之后启动的所有进程。它打算在init的早期设置并全局应用。
           _resource_最好使用它的文本表示('cpu'、'rtio'等或'RLIM_CPU'、'RLIM_RTIO'等)来指定。
           它也可以被指定为资源enum所对应的int值。

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

        > 这就产生了一个重要的结果,如果服务为其他服务提供了功能,例如提供了一个通信通道,
           那么仅仅在这些服务之前启动这个服务不足以保证在这些服务请求之前就已经设置好了这个通道。
           必须有一个单独的机制来做出这样的保证。

        `stop <service>`
        > 如果服务当前正在运行,则停止它的运行

        `swapon_all <fstab>`
        > 在给定的fstab文件上调用fs\_mgr\_swapon\_all

        `symlink <target> <path>`
        > 使用_target_值在_path_处创建一个符号链接

        `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 所使用的 
           partition._mount-point_.verified属性,因为fs\_mgr不能自己直接设置它们

        `wait <path> [ <timeout> ]`
        > 轮询给定文件是否存在,并在找到时返回,否则超时已到。如果未指定超时,则默认为5秒。

        `wait_for_prop <name> <value>`
        > 等待系统属性_name_变为_value_。属性在_value_中展开。
           如果属性_name_已经设置为_value_,则立即继续。

        `write <path> <content>`
        > 在_path_打开文件,用write(2)向它写入一个字符串。
           如果文件不存在,它将被创建。
           如果它确实存在,它将被截断。属性在_content_中展开

 

  • Imports(导入)

        `import <path>`
        > 解析 init 配置文件,扩展当前配置。
           如果 _path_ 是一个目录,则该目录中的每个文件都被解析为一个配置文件。
           它不是递归的,嵌套目录不会被解析。
           import 关键字不是命令,而是它自己的部分,
           这意味着它不是作为操作的一部分发生的,而是作为正在解析的文件处理的,并遵循以下逻辑。
          
           init可执行文件只有三次导入.rc文件:
              1.当它在初始引导期间导入 /init.rc 或属性 'ro.boot.init_rc' 所指示的脚本时。
              2.当它在导入 /init.rc 之后立即导入  /{ system,vendor,odm}/etc/init/  用于第一阶段安装设备时。
              3.在 mount_all 期间,当它在指定路径上导入 /{system,vendor,odm}/etc/init/ .rc 文件时。

           由于遗留原因和保持向后兼容性,导入文件的顺序有点复杂。它没有得到严格的保证。
           确保命令在一个不同的命令之前运行的唯一正确方法是:
              1.将它放在一个带有先前执行的触发器的Action中,
                或者
              2、将它放在一个具有相同触发器的Action中,该触发器位于同一文件中的一个较早的行中。

           尽管如此,第一阶段安装设备的实际顺序是:
              1. /init.rc 被解析,然后递归地解析它的每个导入。
              2. /system/etc/init/ 的内容按字母顺序排列和解析,每个文件解析后递归地进行导入。
              3.对于 /vendor/etc/init 重复执行第2步,然后是 /odm/etc/init

           以下伪代码可以更清楚地解释这一点:

            fn Import(file)
              Parse(file)
              for (import : file.imports)
                Import(import)

            Import(/init.rc)
            Directories = [
/system/etc/init , /vendor/etc/init , /odm/etc/init ]
            for (directory : Directories)
              files = <Alphabetical order of directory's contents>     
//目录内容的字母顺序
              for (file : files)
                Import(file)

 

  • Properties

        Init 通过以下属性提供有关它负责的服务的信息。
        `init.svc.<name>`
        • 命名服务的状态("stopped", "stopping", "running", "restarting")

 

 

  • Boot timing 启动时间

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

        `ro.boottime.init`
        > 在启动后以ns为单位(通过时钟\_BOOTTIME时钟),初始化的第一阶段开始的时间。


        `ro.boottime.init.selinux`
        > 初始化SELinux花费了多长时间?


        `ro.boottime.init.cold_boot_wait`
        > init等待ueventd的冷启动阶段结束的时间。


        `ro.boottime.<service-name>`
        > 启动后以ns为单位(通过时钟\_BOOTTIME时钟),服务首次启动的时间。

 

  • Debugging init

        默认情况下,由 init 执行的程序将会输出 stdout 和 stderr 到/dev/null
        为了帮助调试,您可以通过 Android 程序日志包装器执行程序。这将重定向到 Android 日志记录系统(通过 logcat 访问)。

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

 

init.rc文件加打印:

write /dev/kmsg "debug ***"

然后在kernel log中可以找到加的打印
 


 





 



 

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐