简述安卓系统init.rc的启动与使用

说明

系统:Android9.0

前言

init.rc是我们经常修改和开发的文件, 掌握它对我们开发定制有很大的帮助,也是做ROM开发必须要掌握的。

我们详细的来学习一下init.rc中各种语法, 深入了解init.rc 各种细节。

  • 熟悉init进程启动过程,并加载init.rc

init 进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动流程中一个关键的步骤,它有很多重要的职责,比如创建Zygote(孵化器)和属性服务等。它的生命周期贯穿整个Linux内核运行的始终。Android中所有其它的进程共同的鼻祖均为init进程。

Android系统启动流程:

如图可知,Kernel启动后,寻找init关键文件,并在用户空间启动init进程,该进程作为用户空间的第一个进程,并调用init进程中的main方法开始启动Zygote进程。

init用户进程到底发生了什么

Kernel会启动init.cpp的main函数,重要的内容是加载linux的相关属性和加载init.rc文件。让我们狠扒一下到底发生了什么,在下面细节中会发现新的大陆:

  1. 启动一些安全Linux日志等服务。
  2. 创建和挂载启动所需的文件目录。
  3. 初始化和启动Linux属性服务。
  4. 解析init.rc配置文件,首先开启ServiceManager和MediaServer等关键进程。
  5. init进程fork启动Zygote服务进程。

Android P(9.0)的init入口函数由原先的init.cpp调整到了main.cpp。接下来就从main函数开始:

system/core/init/main.cpp

这个函数将调用到

  • 加载init.rc,并解析

init.rc是一个非常重要的配置文件,它是由Android初始化语言(Android Init Language)编写的脚本,这种语言主要包含5种类型语句:Action、Command、Service、Option和Import。

  1. 基本语法规则
    Init.rc中的语法都是Android自定义的, system/core/init/README.md有具体语法说明 ,整个init.rc 其实有以下几个部分组成:

Imports

导入其他rc文件

Actions

Commands

Commands

隶属于Action中的命令

Services

后台服务, 一般都是C/C++的守护进程,或者是shell脚本

Options

隶属于Service中选项, 比如守护进程的用户id, 组id, 类别 , 是否执行一次, 创建额外套接字等。

其他规则:

  1. #号作为注释。
  2. 一行为单位, 以空格作为分隔符, 行尾可以用反斜杠'\'表示连接下一行。
  3. 字符串中可以使用双引号(如“  ”)来表示空格。
  4. ${property.name} 可以展开属性。
  5. 以import, on, service开头的, 都表示一个section(段落),意味着一个关键词出现之后到出现第二个关键词之前, 关键词后面的内容都属于一个section。
  6. Service的关键词在所有的rc文件中都必须是唯一的, 如果出现第二个相同的service名字, 将会忽略和显示日志。
  7. Action可以在多个rc文件出现,比如on boot可以出现在init.rc ,也可以出现在init.unionman.rc中。

2)init.rc 示例

  1. Import介绍

早期的rc文件的内容都集中在/init.rc中, 大部分是service定义也是集中在这个文件中, 有可能会出现service对应的可执行程序没有编译进系统, 导致service执行失败, 现在是分散在不同分区中进行模块化,并且service对应代码和rc文件是一起编译的, 这样保证模块化的完整性。

/init.rc

最主要和最早被加载的rc文件, 一般都是做系统最初初始化的。

/init.${ro.hardware}.rc

/vendor/etc/init/hw/init.${ro.hardware}.rc

Soc对应的定制的初始化命令和服务。

/system/etc/init/

启动系统核心的服务,比如 SurfaceFlinger, MediaService, and logcatd。

/vendor/etc/init/

SOC厂商定制开机自启动的serviced对应的rc脚本,比如gnss,camera,sensor,drm等HAL相关服务

/odm/etc/init/

外设设备厂商定制自启动的serviced对应的rc脚本, 如运动传感器或其他外围设备所需的命令和服务。

  1. Action动作

Action其实就是一组命令的集合, 它有一个trigger触发器,当系统中出现与动作的触发器匹配的事件,这个Action就会被加入到执行队列的尾部, 等到这个Action执行时, Action中包含的所有命令将会按照先后顺序执行, 格式:

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

     <command>

     <command>

     <command>

trigger作为触发器,trigger有两种, event和proptery。

command: 类似shell命令,但是并不是shell命令, 因为这些命令是在init进程中单独实现的, 而命令行我们执行的shell命令, 是在toolbox中实现的, 并且rc脚本中的command的数量有限。

Action的示例:   

 on early-init

        # Disable sysrq from keyboard

        write /proc/sys/kernel/sysrq 0

        # Set the security context of /adb_keys if present.

        restorecon /adb_keys



    on property:ro.debuggable=1

        # Give writes to anyone for the trace folder on debug builds.

        # The folder is used to store method traces.

        chmod 0773 /data/misc/trace

        # Give reads to anyone for the window trace folder on debug builds.

        chmod 0775 /data/misc/wmtrace

        start console

触发器在哪里触发呢: 一般在init的代码中,如:system/core/init/init.cpp

am.QueueEventTrigger("early-init");

am.QueueEventTrigger("late-init");

常见的trigger:

    on early-init               #早期初始化

    on boot                   #系统启动触发

    on init                     #在初始化时触发

    on late-init                #在初始化晚期阶段触发

    on charger                  #当充电时触发

    on property:<key>=<value>   #当属性值满足条件时触发

    on post-fs                  #挂载文件系统

    on post-fs-data             #挂载data

常见的command:

大部分的命令都在system/core/init/builtins.cpp代码中实现:

// Builtin-function-map start

const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {

    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();

    // clang-format off

    static const Map builtin_functions = {

        {"bootchart",               {1,     1,    {false,  do_bootchart}}},

        {"chmod",                   {2,     2,    {true,   do_chmod}}},

        {"chown",                   {2,     3,    {true,   do_chown}}},

        {"class_reset",             {1,     1,    {false,  do_class_reset}}},

        {"class_reset_post_data",   {1,     1,    {false,  do_class_reset_post_data}}},

        {"class_restart",           {1,     1,    {false,  do_class_restart}}},

        {"class_start",             {1,     1,    {false,  do_class_start}}},

        {"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},

        {"class_stop",              {1,     1,    {false,  do_class_stop}}},

        {"copy",                    {2,     2,    {true,   do_copy}}},

        {"domainname",              {1,     1,    {true,   do_domainname}}},

        {"enable",                  {1,     1,    {false,  do_enable}}},

        {"exec",                    {1,     kMax, {false,  do_exec}}},

        {"exec_background",         {1,     kMax, {false,  do_exec_background}}},

        {"exec_start",              {1,     1,    {false,  do_exec_start}}},

        {"export",                  {2,     2,    {false,  do_export}}},

        {"hostname",                {1,     1,    {true,   do_hostname}}},

        {"ifup",                    {1,     1,    {true,   do_ifup}}},

        {"init_user0",              {0,     0,    {false,  do_init_user0}}},

        {"insmod",                  {1,     kMax, {true,   do_insmod}}},

        {"installkey",              {1,     1,    {false,  do_installkey}}},

        {"interface_restart",       {1,     1,    {false,  do_interface_restart}}},

        {"interface_start",         {1,     1,    {false,  do_interface_start}}},

        {"interface_stop",          {1,     1,    {false,  do_interface_stop}}},

        {"load_persist_props",      {0,     0,    {false,  do_load_persist_props}}},

        {"load_system_props",       {0,     0,    {false,  do_load_system_props}}},

        {"loglevel",                {1,     1,    {false,  do_loglevel}}},

        {"mark_post_data",          {0,     0,    {false,  do_mark_post_data}}},

        {"mkdir",                   {1,     4,    {true,   do_mkdir}}},

        // TODO: Do mount operations in vendor_init.

        // mount_all is currently too complex to run in vendor_init as it queues action triggers,

        // imports rc scripts, etc.  It should be simplified and run in vendor_init context.

        // mount and umount are run in the same context as mount_all for symmetry.

        {"mount_all",               {1,     kMax, {false,  do_mount_all}}},

        {"mount",                   {3,     kMax, {false,  do_mount}}},

        {"parse_apex_configs",      {0,     0,    {false,  do_parse_apex_configs}}},

        {"umount",                  {1,     1,    {false,  do_umount}}},

        {"umount_all",              {1,     1,    {false,  do_umount_all}}},

        {"readahead",               {1,     2,    {true,   do_readahead}}},

        {"restart",                 {1,     1,    {false,  do_restart}}},

        {"restorecon",              {1,     kMax, {true,   do_restorecon}}},

        {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},

        {"rm",                      {1,     1,    {true,   do_rm}}},

        {"rmdir",                   {1,     1,    {true,   do_rmdir}}},

        {"setprop",                 {2,     2,    {true,   do_setprop}}},

        {"setrlimit",               {3,     3,    {false,  do_setrlimit}}},

        {"start",                   {1,     1,    {false,  do_start}}},

        {"stop",                    {1,     1,    {false,  do_stop}}},

        {"swapon_all",              {1,     1,    {false,  do_swapon_all}}},

        {"enter_default_mount_ns",  {0,     0,    {false,  do_enter_default_mount_ns}}},

        {"symlink",                 {2,     2,    {true,   do_symlink}}},

        {"sysclktz",                {1,     1,    {false,  do_sysclktz}}},

        {"trigger",                 {1,     1,    {false,  do_trigger}}},

        {"verity_load_state",       {0,     0,    {false,  do_verity_load_state}}},

        {"verity_update_state",     {0,     0,    {false,  do_verity_update_state}}},

        {"wait",                    {1,     2,    {true,   do_wait}}},

        {"wait_for_prop",           {2,     2,    {false,  do_wait_for_prop}}},

        {"write",                   {2,     2,    {true,   do_write}}},

    };

    // clang-format on

    return builtin_functions;

}

特殊的几个命令:

bootchart

启动或终止bootcharting, bootcharting是用于分析系统启动时间的。

格式: bootchart [start|stop]

class_reset/class_restart

class_start/class_stop

复位/重启/启动/停止某一类的所有Service,语法:

class_start <serviceclass>

例子: class_reset main

copy

拷贝, 和shell命令cp一样

例子: copy /proc/cmdline /dev/urandom

domainname

设置域名

例子: domainname localdomain

enable

将一个Service的标志从disable改成enable

语法: enable <servicename>

例子:

on property:ro.boot.myfancyhardware=1

 enable my_fancy_service

exec

启动一个只执行一次的临时Service,

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

exec - system system -- /system/bin/vdc checkpoint markBootAttempt

exec_start

启动一个特定的service, 语法

exec_start service_name

例子: exec_start apexd-bootstrap

ifup

启动网络接口, 语法: ifup <interface>

insmod

加载ko驱动,格式:insmod [-f] <path> [<options>]

例子: insmod /vendor/lib/modules/8822bs.ko

setrlimit

设置系统资源使用限制,linux下每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。

语法: setrlinit  <resource>  <cur>  <max>

<resource>有: cpu, fsize, stack, nice等

<cur>表示软限制,<max>表示硬限制, 值如果为unlimited表示最大值

start/stop/restart

启动/停止/重启指定的服务 格式: start <servicename>

symlink

符号链接, 类似shell命令中的ln -s

格式: symlink <target> <sym_link>

sysclktz

设置当前系统时钟值, sysclktz 0就表示将系统时钟设置成GMT 0

trigger

触发特定的action的触发器

例子: trigger early-fs

wait_for_prop

等待特定的属性变成特定的值

例子: wait_for_prop apexd.status ready

wait

在指定时间内等待特定文件的出现

格式: wait <file_path> [timeout]

  1. Service服务

Service表示一个开机自启动服务, 服务可以常驻型(不死型), 也可以是只执行一次, 可以选择开机自启动或者在特定的时候启动,语法:    

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

           <option>

           <option>

           ...

    <name> service的名字,要保证唯一

    <pathname> service对应的可执行程序路径

    <argument> 启动service所带的参数

    <option>  启动service时约束和附加选项,它影响着服务以什么方式和什么时候启动,

具体参考system/core/init/README.md

常见的option如下所示:

capabilities [ <capability>\* ]

为启动的服务设置能力, 在执行特权操作时,如果进程的有效身份不是 root,就去检查是否具有该特权操作所对应的 capabilites,并以此决定是否可以进行该特权操作。比如要向进程发送信号(kill()),就得具有 capability CAP_KILL;如果设置系统时间,就得具有 capability CAP_SYS_TIME。

class <name> [ <name>\* ]

为服务设置类别, 非常重要, 设置之后,系统就可以批量启动或停止一类服务。

console [<console>]

critical

表示这是一个对设备至关重要的一个服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式,, 比如zygote服务就是这种。

disabled

此服务不会自动启动,而是需要通过显式调用服务名来启动

file <path> <type>

打开一个文件,并将文件的fd传递给启动的服务, 服务进程中可以通过android_get_control_file()拿到这个fd, <type>可以是"r", "w" or "rw"

setenv<name><value>

设置环境变量<name>为某个值<value>

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

创建一个名为/dev/socket/<name>的unix domain socket,然后将它的fd值传给启动它的进程,有效的<type>值包括dgram,stream和seqacket,而user和group的默认值是0,也可以设置进程对应的selinux安全上下文。

user<username>

在启动服务前将用户组切换为<username>,默认情况下用户都是root

group<groupname>[<groupname>]*

在启动服务前将用户组切换为<groupname>,可以是多个用户组

oneshot

只启动一次,当这个服务推出后,不会重启

onrestart

当此服务重启时,需要执行某些命令

writepid <file> [ <file>\* ]

当服务进行fork的时候, 将自进程的pid号写入到指定的文件中,用于cgroup/cpuset

例子:    

service logd /system/bin/logd

        socket logd stream 0666 logd logd

        socket logdr seqpacket 0666 logd logd

        socket logdw dgram+passcred 0222 logd logd

        file /proc/kmsg r

        file /dev/kmsg w

        user logd

        group logd system package_info readproc

        capabilities SYSLOG AUDIT_CONTROL SETGID  #允许使用 syslog() 系统调用

        writepid /dev/cpuset/system-background/tasks
  • 项目中可能遇到的问题:
  1. 开机默认启动服务了,却仍在开机完成后再启动服务。

示例:

on property:sys.boot_completed=1

    start HNFirewall

service HNFirewall /system/bin/HN_Firewall.sh

    class main

    user root

oneshot
  1. 给system分区权限
# on early-boot

on boot

    # According to cts testcase changes: fbd00778, we remove /proc/interrupts and /proc/stat read permission.

    ifup eth0

    chmod 755 /system/bin/configserver  (#错误的命令)
  1. 给服务增加运行权限
    on boot
    
       start init_unionman
    
       setprop wifi.direct.interface p2p-dev-wlan0
    
    
    
    service init_unionman /system/bin/sh /system/bin/init_unionman.sh
    
        user root (使用 root 权限)
    
        group root (使用 root 权限)
    
        oneshot
    
        disabled
    
        seclabel u:r:init_unionman:s0

seclabel u:r:init_unionman:s0
这句的具体含义大概是设置init进程的安全上下文,不加这个会提示没有权限:service does not have a SELinux domain defined

添加开机启动服务程序关于Selinux权限问题说明:

当需要添加一个binder服务xxx程序,并且设置成开机自启动时,需要按照如下步骤操作:

第一步,我们可以在init.rc中添加了如下代码行:

service xxxx /system/bin/xxxx

  class main

  user root

  group root

  oneshot

  seclabel u:r:xxxx:s0   #这句是为加selinux权限添加的,android5.1以后不加则无法启动该服务

编译img后烧到机器,发现服务xxx无法启动,kernel log中有如下提示(例如这里新加的服务程序是abcd的log):

init: Service abcd does not have a SELinux domain defined.

该提示说明没有定义SELinux domain,导致服务abcd无法自启动。

第二步,为了解决这个问题我们按如下方式修改并且添加sepolicy文件:

修改seplicy/file_contexts文件,添加以下内容:

/system/bin/xxx     u:object_r:xxx_exec:s0

第三步,在sepolicy目录新增xxx.te文件,并在其中添加如下内容:

需要为新增的进程增加域、执行权限

type xxx, domain;

type xxx_exec, exec_type, file_type;

然后启用这个域init_daemon_domain(xxx)

例如我下面添加的一个sepolicy/abcd.te文件的例子:

type abcd, domain;

type abcd_exec, exec_type, file_type;

init_daemon_domain(abcd)

allow abcd init_tmpfs:file create_file_perms;

allow abcd self:capability { dac_override net_admin net_raw setgid setuid };

allow abcd device:dir { open read };

allow abcd shell:lnk_file { read };

allow abcd rootfs:lnk_file { getattr };

allow abcd socket_device:sock_file { write };

详细可参考seplicy/目录下的其他te文件。

或者根据logcat中打印出来的avc:denied log报错来添加,先把相关的avc报错全部收集起来:

在相应的te、文件中增加语句,语句格式为

allow sourcecontext targetcontext:class { 许可 };

例如出现下面的avc log,可以这样添加:

[ 4.009832] type=1400 audit(1577344637.716:12): avc: denied { read } for pid=263 comm=“lowmem_manage.s” path=“/system/bin/sh” dev=“mmcblk1p10”

ino=467 scontext=u:r:lowmem_manage:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=1

sourcecontext指的是"scontext=u:r:lowmem_manage:s0"中的lowmem_manage,targetcontext指的是"tcontext=u:object_r:rfs_system_file:s0"中的rfs_system_file,

class指的是"tclass=file"中的file,许可指的是"avc: denied { read }"中的read。

所以增加语句

allow lowmem_manage rfs_system_file:file { read };

其他avc报错可以类似添加即可。

PS:

其实有时候可以在init.xxxx.rc文件执行一些拷贝或者赋权限的操作,例如我这里:

chmod 0664 /sys/class/graphics/fb0/cabc

Android8以及以后的安卓系统, Google 启动了 Treble 计划,对 system 分区和 vendor 分区进行严格的分离,所以这样的自定义脚本我们最好不要放在 /system/bin/ 目录下。我们可以把它放在 /vendor/bin/ 目录下,否则如果放在system目录,会出现以下打印,尤其是执行循环1秒操作的脚本的时候打印就不停:

[  365.808708] type=1401 audit(1663405174.780:2118): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  366.823168] type=1401 audit(1663405174.780:2118): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  366.823250] type=1401 audit(1663405175.793:2119): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  369.909818] type=1401 audit(1663405177.866:2124): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  369.909904] type=1401 audit(1663405178.880:2125): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  369.923998] type=1401 audit(1663405178.880:2125): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  369.924078] type=1401 audit(1663405178.893:2126): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  370.938655] type=1401 audit(1663405178.893:2126): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  370.938737] type=1401 audit(1663405179.910:2127): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  370.953006] type=1401 audit(1663405179.910:2127): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  370.953087] type=1401 audit(1663405179.923:2128): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  371.968127] type=1401 audit(1663405179.923:2128): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

[  371.968209] type=1401 audit(1663405180.940:2129): op=security_compute_sid invalid_context=u:r:init_usb_link:s0 scontext=u:r:init_usb_link:s0 tcontext=u:object_r:toolbox_exec:s0 tclass=process

同时,从 Android 8.0开始,SELinux for Android 也进行了模块化,为了避免不必要的权限问题,我们使用 /vendor/bin/sh 作为脚本解释器。例如我这里:

当然,改了使用 /vendor/bin/sh 作为脚本解释器,对应的脚本文件也要放置到vendor/bin目录,对应平台的init.rc文件的路径名称也需要修改,参考如下:

  • 总结

init.rc是我们经常修改和开发的文件, 掌握它对我们开发定制有很大的帮助,也是做ROM开发必须要掌握的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值