Android init.rc整理

AIL概述

init.rc由AIL语言编写而成。可以参考system/core/init/README.md来学习AIL语法相关知识。不同Android版本关于AIL的说明存在一些细微差异,但基本语法和总的思路是不变的。往往我们可以先查看对应的system/core/init/README.md来了解这些差异。 以下是我们参考Android11下的system/core/init/README.md,对AIL的学习总结。

  • Android Init Language由五大类语句组成:Actions, Commands, Services, Options, Imports
  • 换行为语句分隔符。空格为词分隔符。反斜杠转义符可用于插入不做分隔的空格符。双引号用于包括不需要空格分隔的词。行尾反斜杠用于多行衔接为一行。
  • 行首以 # 开始用于注释。
  • 使用${var}可进行变量扩展。
  • 一段内容往往以Actions 或者Services开头,并以Commands或者Options结尾。
    Commands 或者Options用于修饰Actions 或者Services。(阅读时注意)
  • Service有唯一名称,如果已经定义过一个相同的Service,后定义的被忽略,且log中记录了此异常。

Init .rc 文件

  • AIL语言用于扩展名为rc的文件中。
  • 一般设备系统中多个路径下都存在rc文件。一般规范的路径为:/{system,vendor,odm}/etc/init/*.rc。
  • 根目录/init.rc为主rc文件,它是rc文件被加载的入口。它通过Imports语句将其他各个路径下的rc文件都加载进来。
  • 不支持第一阶段进行mout机制的Legacy设备(legacy是什么意思?与uefi有什么区别),原先是可以在mount_all的时候进行导入rc文件,但这种方式已被弃用,并且在Android Q(Android10)之后是不允许的。
rc文件目录规范:
  1. /system/etc/init/ 用于核心系统功能,例如SurfaceFlinger、MediaService和logd等。
  2. /vendor/etc/init/ 用于芯片供应商的一些核心操作或者守护进程功能。
  3. /odm/etc/init/ 用于设备制造厂商的功能,比如传感器和其他外围设备所需的操作或者守护进程。
  • 服务对应的二进制程序如果放在system|vendor|odm分区下,相应的定义服务的rc文件也需要在相同分区的etc/init/目录下。一般我们开发的时候rc和二进制程序是在相同的工程目录下,在Android.mk 中通过 LOCAL_INIT_RC宏定义指定rc文件能将其编译到对应分区的etc/init/目录下。(Android.bp 是通过配置init_rc)。
//logcatd开发的工程目录在system/core/logcat
//logcatd  logcatd.rc都在此工程目录下
//通过Android.bp指定init_rc 

//@system/core/logcat/Android.bp
sh_binary {
    name: "logpersist.start",
    src: "logpersist",
    init_rc: ["logcatd.rc"],
    required: ["logcatd"],
    symlinks: [
        "logpersist.stop",
        "logpersist.cat",
    ],
//最终编译出来的logcatd.rc就位于system/etc/init/logcatd.rc
$ cd out/target/product/${TARGET_PRODUCT}
$ find -name logcatd.rc
./system/etc/init/logcatd.rc

说明: 将二进制程序相关的服务配置到它独立对应的rc文件中相比之前那样在一个统一的rc文件中全部定义进程的服务更好。这种方法确保该二进制程序对应的Service是唯一的。并且更加直观感受到服务配置和二进制程序是完整的整体,理解该二进制程序就是在init时候初始化的。同时也避免出现多个服务绑定一个程序,或者重复服务的定义。


语法详解

Services

Service指的是需要在初始化就启动的服务,通过定义还可以让他们在退出的时候自动重启。格式如下:

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

Option 是修饰service的属性,它用于定义service何时启动以及怎样启动。

  • capabilities [ <capability>\* ] 表示执行该service的能力,这些就是Linux MAN定义的,但需要去掉"CAP_"前缀。如果没有增加capabilities修饰,则表示该服务没有任何capabilities。

      ###example1, BLOCK_SUSPEND:
      service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service.ranchu
          class hal
          user audioserver
          # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
          group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
          #有能力block系统休眠
          capabilities BLOCK_SUSPEND
          ioprio rt 4
          task_profiles ProcessCapacityHigh HighPerformance
          onrestart restart audioserver
          
      ###example2,NET_ADMIN NET_RAW:
      service ril-daemon /vendor/bin/hw/libgoldfish-rild
          class main
          user radio
          group radio cache inet misc audio log readproc wakelock
          #NET_ADMIN:执行和网络相关的一系列操作,例如接口配置,防火墙配置等等
          #NET_RAW: 使用RAW and PACKET socket接字绑定到任何地址进行透明代理
          capabilities BLOCK_SUSPEND NET_ADMIN NET_RAW
      
      ###example3,SYS_BOOT:
      service charger /system/bin/charger
          class charger
          seclabel u:r:charger:s0
          user system
          group system wakelock input
          #有能力进行reboot或者调用kexec_load
          capabilities SYS_BOOT
          file /dev/kmsg w
          file /sys/fs/pstore/console-ramoops-0 r
          file /sys/fs/pstore/console-ramoops r
          file /proc/last_kmsg r
    
  • class <name> [ <name>\* ] 用于指定service的类名,相同的类名可以在action中配置成一起启动或者一起停止。 一个service至少有一个class,如果没有指定 class name, 默认class 为"default"。当然也可以指定多个class name, 多个class name便于service分组管理。 TODO:关于core,main,late_start具体启动时间我们在后面章节再继续详细说明。

      ###example1, animation 
      service vendor.gralloc-3-0 /vendor/bin/hw/android.hardware.graphics.allocator@3.0-service
          interface android.hardware.graphics.allocator@3.0::IAllocator default
          #`animation` calss是指可以进行开机动画或者关机动画的服务组。
          #这些服务往往很早就运行或者在关机的最后阶段运行的
          #因此需要注意他们不能去打开/data下的文件,应该要保证/data分区即便卸载了也能正常运行。
          class hal animation
          interface android.hardware.graphics.allocator@3.0::IAllocator default
          user system
          group graphics drmrpc
          capabilities SYS_NICE
          onrestart restart surfaceflinger
          
      ###example2, core
      service shutdownanim /system/bin/bootanimation shutdown
          #core 一般是早启动,最晚结束的一些系统核心程序
          class core
          user graphics
          group graphics audio
          disabled
          oneshot
      	
      
      ###example3, main
      service wpa_supplicant /vendor/bin/hw/wpa_supplicant \
          /vendor/etc/wifi/wpa_config.txt
      #   we will start as root and wpa_supplicant will switch to user wifi
      #   after setting up the capabilities required for WEXT
      #   user wifi
      #   group wifi inet keystore
          interface android.hardware.wifi.supplicant@1.0::ISupplicant default
          interface android.hardware.wifi.supplicant@1.1::ISupplicant default
          interface android.hardware.wifi.supplicant@1.2::ISupplicant default
          interface android.hardware.wifi.supplicant@1.3::ISupplicant default
          #class main 一般为framework层进程
          class main
          socket wpa_wlan0 dgram 660 wifi wifi
          disabled
          oneshot
      
      
      ###example4, late_start
      service bugreport /system/bin/dumpstate -d -p -B -z \
              -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
          #late_start: 一般在初始化最后阶段才需要启动的程序
          class late_start
          disabled
          oneshot
          keycodes 114 115 116
    
  • console [<console>] 用于指定该服务输出 是否要打印到dmesg。 默认输出到/dev/console, 而/dev/console可以通过设置androidboot.console来指定。

#@*.rc
service recovery /sbin/recovery
    console
    seclabel u:r:recovery:s0
#@BoardConfig_*.mk
 BOARD_KERNEL_CMDLINE := androidboot.wificountrycode=CN androidboot.hardware=rk30board androidboot.console=ttyFIQ0 firmware_class.path=/vendor/etc/firmware init=/init rootwait ro init=/init

#@device 可以查看console相关属性:
$ getprop |grep conso                                                                                                                                                               
[init.svc.console]: [running]
[ro.boot.console]: [ttyFIQ0]
[ro.boottime.console]: [3672952642]
[ro.consoleable]: [1]

  • critical用于说明关键服务,当该服务4分钟内或者在开机完成前退出超过4次,将会导致设备进入bootloader模式。

      service ueventd /sbin/ueventd
          #指明是关键服务
          critical
          seclabel u:r:ueventd:s0
    
  • disabled 表明该服务无法自动通过class启动,必须通过name或者interface name来启动。

  • enter_namespace <type> <path> 很少用到,Android11 rockchip未查到使用。输入路径的namespace。network type可以指定为"net". 只能设置一个type。

  • file <path> <type> 打开文件,并将它的fd传递给进程,C 层可使用android_get_control_file获取到fd。 type类为:“r”, “w” ,“rw”。

      service charger /system/bin/charger
          class charger
          seclabel u:r:charger:s0
          user system
          group system wakelock input
          capabilities SYS_BOOT
          file /dev/kmsg w
          file /sys/fs/pstore/console-ramoops-0 r
          file /sys/fs/pstore/console-ramoops r
          file /proc/last_kmsg r
    
  • group <groupname> [ <groupname>\* ] 设置用户组, 默认为root组。

  • interface <interface name> <instance name>关联HIDL 接口,名称必须是完整的,这样才能方便hwservicemanager快速启动这些HIDL服务。可关联多个HIDL 接口。

      service wpa_supplicant /vendor/bin/hw/wpa_supplicant -Dnl80211 -iwlan0 -c/vendor/etc/wifi/wpa_supplicant.conf -g@android:wpa_wlan0
          interface android.hardware.wifi.supplicant@1.0::ISupplicant default
          interface android.hardware.wifi.supplicant@1.1::ISupplicant default
          interface android.hardware.wifi.supplicant@1.2::ISupplicant default
          interface android.hardware.wifi.supplicant@1.3::ISupplicant default
          socket wpa_wlan0 dgram 660 wifi wifi
          group system wifi inet
          oneshot
          disabled
    
  • ioprio <class> <priority> 通过SYS_ioprio_set syscall设置此service的IO优先级和IO优先级类。类为“rt”、“be”或“idle”。优先级为0-7。

      service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service.ranchu
          class hal
          user audioserver
          # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
          group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
          capabilities BLOCK_SUSPEND
          ioprio rt 4
          task_profiles ProcessCapacityHigh HighPerformance
          onrestart restart audioserver
    
  • keycodes <keycode> [ <keycode>\* ] 设置keycode,当同时触发这些按键事件时,服务将启动。常用于bugreport service。

      # bugreport is triggered by holding down volume down, volume up and power
      service bugreport /system/bin/dumpstate -d -p -B -z \
              -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
          class late_start
          disabled
          oneshot
          keycodes 114 115 116
    

也可以通过一个propertry来定义keycodes, propertry的属性格式为"keycode1,keycode2,keycode3"或者"none"。由于keycodes在很早就初始化了,使用的property必须为PRODUCT_DEFAULT_PROPERTY_OVERRIDES 。
例如:

# bugreport is triggered by holding down volume down, volume up and power
service bugreport /system/bin/dumpstate -d -p -B -z \
        -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
    class late_start
    disabled
    oneshot
    #ro.keycodes.bugreport 设置为"114,115,116"。 若设置为"none"则表示该服务不会响应keycodes
    keycodes ${ro.keycodes.bugreport:-none}
  • memcg.limit_in_bytes <value> 设置子进程的内存最小值, memcg.limit_percent <value> 设置子进程的最小内存占比(物理内存)。较少用到。

  • memcg.limit_property <value> 使用一个property来定义 memcg.limit_in_bytes.设置后将覆盖memcg.limit_in_bytes 以及memcg.limit_percent 中设置的值。较少用到。

  • memcg.soft_limit_in_bytes <value>设置子进程的运行内存最小值。较少用到。

  • memcg.swappiness <value> swappiness是Linux内核参数,控制交换出运行时内存的相对权重。swappiness参数值可设置范围在0到100之间。 低参数值会让内核尽量少用交换,更高参数值会使内核更多的去使用交换空间。默认值为60。 也较少使用到。

$ cat /proc/sys/vm/swappiness    
100
  • namespace <pid|mnt> namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。详细可参考Pid Namespace 原理与源码分析

1、进程所属的 PID namespace 在它创建的时候就确定了,不能更改,所以调用 unshare 和 nsenter 等命令后,原进程还是属于老的 PID namespace,新 fork 出来的进程才属于新的 PID namespace;
2、PID namespace 可以嵌套;
3、PID namespace 中的 init 进程。当一个进程的父进程退出后,该进程就变成了孤儿进程。孤儿进程会被当前 PID namespace 中 PID 为 1 的进程接管,而不是被最外层的系统级别的 init 进程接管。

  • oneshot 如果服务退出则不要自动重启服务。

  • onrestart 当该service重启时,运行命令。

      service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service.ranchu
          class hal
          user audioserver
          # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
          group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
          capabilities BLOCK_SUSPEND
          ioprio rt 4
          task_profiles ProcessCapacityHigh HighPerformance
          onrestart restart audioserver
    
  • oom_score_adjust <value> 自定义oom_score,等同于优先级,优先级越高值越小(-1000 ~1000)。分值越小越不会被kill。 详细参考linux内核的oom score是咋算出来的

#某个系统应用:
$ cat /proc/${process_id}/oom_score_adj                                                                                                                                                    
-800

#adb shell优先级最高
$ cat  /proc/self/oom_score_adj 
-1000 

  • override一般是odm service使用,用于覆盖之前system或者vendor已经定义的服务配置。如果rc中存在多个override标识的相同类名服务,则最后一个被解析的服务才生效。可以通过imports分析,避免出错。

  • priority <priority>设置service进程的优先级。范围为-20~19. 默认为0. 实际是通过setpriority()函数设置。

  • reboot_on_failure <target> 当该服务启动失败或者运行中异常退出时,执行重启。 参数 其实就是设置property sys.powerctl 的值。格式一般为"reboot,reason"。

$ setprop sys.powerctl reboot,test_service_abnormal_exit
##system reboot
$ getprop |grep reasason
[persist.sys.boot.reason]: [reboot,test_service_abnormal_exit]
[ro.boot.bootreason]: [reboot,test_service_abnormal_exit]
[sys.boot.reason]: [reboot,test_service_abnormal_exit]  
  • restart_period <seconds> 设置后non-oneshot 服务将以此周期重启服务。比如restart_period 3600 表示该服务将会每隔1小时启动。

  • rlimit <resource> <cur> <max> 对当前服务以及子进程生效。等同于setrlimit命令。rootfs,ueventd, adbd等,默认为init.

      service syslogd /system/xbin/syslog.sh
          class main
          oneshot
          seclabel u:r:netd:s0
    
  • setenv <name> <value> 在启动的进程中设置环境变量

  • shutdown <shutdown_behavior> 指定当关机时service进程行为。如果没有具体说明,关机时,使用SIGTERM和SIGKILL关闭service。关机期间,“critical” 标识的service不会被关闭直到关机超时。“critical” 标识的service在关机开始时,如果没处于运行状态,则它还会被启动。

  • sigstop 发送SIGSTO给该服务。用于调试。

  • socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]创建一个名为/dev/socket/name的UNIX域socket,并将其fd传递给service程序。type必须为“dgram”、“stream”或“seqpacket”。type可以加后缀“+passcred”,启用SO_PASSCRE。用户和组默认值为0。”seclabel’指的是selinux context. 进程中C层可以使用 libcutils android_get_control_socket()来获取这个fd。

  • stdio_to_kmsg将stdout和stderr重定向到/dev/kmsg_debug。对于那些启动较早或者未使用android log的程序来说,调试就方便多了。仅在 /dev/kmsg_debug 启用时生效。(userdebug/eng生效,user不生效)。而stdin还是使用的console。

  • task_profiles <profile> [ <profile>\* ]设置任务profiles。这是为了取代writepid来设置当前service的profiles。 ( 早期是通过writepid <file> [ <file>\* ], 将service 的pid写入文件,以支持 cgroup的使用。详细可参考LINUX CGROUP总结)

      service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service.ranchu
          class hal
          user audioserver
          # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
          group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
          capabilities BLOCK_SUSPEND
          ioprio rt 4
          task_profiles ProcessCapacityHigh HighPerformance
          onrestart restart audioserver
    
  • timeout_period <seconds> 超过这个时间kill掉service。oneshot service不会被重启,其他服务会自动重启。与restart_period配合使用可以创建周期运行的service。

  • updatable标识服务可以在之后的启动顺序中通过APEX被覆盖(通过“override” option)。携带了updatable标识的service如果在全部的APEX激活前启动,则这个程序会被推迟到所有APEX激活后运行。没有携带updatable标识的service是不能被APEX覆盖。

  • user <username> 指定user,默认为root。早期要获取Linux capabilities,进程需要以root运行并请求capabilities,之后再修改成它期望的运行uid。通过fs_config有一种新的机制来替换早期的那种实现,现在允许厂商将程序的Linux capabilities添加到文件系统中。这个新的机制在http://source.android.com/devices/tech/config/filesystem.html中有具体的描述,支持不需要通过以root用户运行的身份来指定这些capabilities。在Android O(8.0),进程可以在rc中通过“capabilities” option 直接请求capabilities。

  • writepid <file> [ <file>\* ] 将子进程的pid写入文件中,以支持cgroup/cpuset的使用。如果指定的不是/dev/cpuset/,但是property ‘ro.cpuset.default’ 中设置了非空的cpuset名称 (比如: ‘/foreground’),那么pid会被写入/dev/cpuset/cpuset_name/tasks。 此option仅适用于子进程,如果要设置当前service的task profiles, 就需要使用task_profiles.

Actions

Actions 是一系列commands的集合,同时包含一个trigger来决定是否要执行这一系列的命令。当触发器被触发时,Action就被加入执行队列(已在队列中则忽略)。每个队列中的Action是按顺序执行和移除的,而Action中的命令也是按顺序执行的。而其他的一些操作包括设备创建销毁,property的设置以及进程的重启是穿插在各个命令执行之间的。格式如下:

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

同时被触发的Action将按照他们在rc文件中定义的先后顺序加入到执行队列中。例如:

on boot
   setprop a 1
   setprop b 2

on boot && property:test=true
   setprop c 1
   setprop d 2

on boot
   setprop e 1
   setprop f 2

如果当前启机且test属性为true,则执行顺序如下:

setprop a 1
setprop b 2
setprop c 1
setprop d 2
setprop e 1
setprop f 2
Triggers

Triggers 是一系列明确的事件,用于触发action操作。触发器分为事件触发器和属性触发器。

事件触发器是由“trigger”命令或init程序中的QueueEventTrigger()函数触发的。它们一般采用简单字符串的形式,例如“boot”或“late init”。

属性触发器是由指定的Property更改时触发的。Property变更为某个指定的value,格式为property:<name>=<value>. Property只要有变更就触发,格式为property:<name>=\*

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

例如:

on boot && property:a=b 表示在开机并且 property:a=b时触发action。
on property:a=b && property:c=d 表示以下三种情况下触发action:

   1. 开机并且property a=b并且property c=d.
   2. property:c=d 并且a 的值变更为b, 
   3. property:a=b 并且c 的值变更为d
Commands
  • bootchart [start|stop] 启动或者停止Android开机性能分析工具 Bootchart,这个命令一般直接在default init.rc中使用,前提是“ /data/bootchart/enabled ”文件必须要存在(否则仍然无法启动bootchart)。

  • chmod <octal-mode> <path> 等同linux chmod命令,修改文件访问权限。

  • chown <owner> <group> <path>等同linux chown命令,修改文件所有者以及文件组。

  • class_start <serviceclass> 启动名为且未运行的service。

  • class_start_post_data <serviceclass>class_start一样,也是用于启动服务,不同的是class_start_post_data针对在data mount之后启动service。仅仅适用于FDE(Full Disk Encryption)设备, FDE详细可参考Android FDE 加密过程

  • class_stop <serviceclass> 停止并禁用名为且正在运行的service.

  • class_reset <serviceclass>停止名为且正在运行的service,但不禁用他们,他们仍然可以通过class_start重新启动。

  • class_reset_post_data <serviceclass> 类似class_reset,但是仅针对那些在data mount后才启动的服务。仅适用于FDE设备。

  • class_restart <serviceclass> 重启名为的service.

  • copy <src> <dst>拷贝文件,特别适用于数据量比较大的文件。src文件不能是链接路径,也不可以是777权限的文件。dst 如果文件不存在,则创建这个文件,权限默认为0600。dst文件如果已存在是正常的常规文件并且已经存在,内容将被覆盖。

  • domainname <name> 设置domian name.

  • enable <servicename> 启用service。如果该服务此时需要启动则就会被启动。例如:bootloader需要启动一个服务时会设置一系列的属性,表示一个服务是否应该启动了。

      on property:ro.boot.myfancyhardware=1
          enable my_fancy_service_for_my_fancy_hardware
    
  • exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ] fock一个进程来运行这些命令。init进程将挂起(不执行其他命令),直到当前进程退出。

      on boot
          exec -- /system/bin/init.eth0.sh
    
  • exec_background [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ] fock进程后台运行这些命令。init继续处理其他命令。

  • exec_start <service> 运行指定的service,且停止执行其他命令直到启动该service成功后再执行其他命令。类似于exec,但是此处参数是一个定义好的服务,而不是直接将命令和参数加入。

  • export <name> <value> 设置全局环境变量。所有再此之后运行的程序都将继承此环境变量。

  • hostname <name>设置hostname。

  • ifup <interface> up网络接口

  • insmod [-f] <path> [<options>] 安装kernel模块,-f:强制安装模块,即使运行的kernel版本与要安装的kernel版本不一致。

  • interface_start <name> 查找并启动HIDL或者AIDL 接口. name格式为 <hidl_interface>/<instance>或者aidl/<interface> .例如:android.hardware.secure_element@1.1::ISecureElement/eSE1 或者
    aidl/aidl_lazy_test_1.
    注意:这些命令仅作用于interface service指定的interface,而不是接口服务选项指定的接口,也不是在运行时注册的interface。

  • interface_restart <name> 查找并重启HIDL或者AIDL 接口.

  • interface_stop <name>查找并停止HIDL或者AIDL 接口.

  • load_system_props 该命令已无效

  • load_persist_props 当data分区被加密时,加载persistent 属性, 默认的 init.rc中会用到该命令。

  • loglevel <level> 设置init的log 等级0~7,虽然跟kernel log等级是对应的,但是该选项仅仅作用于init, kerkel log需要通过 write 命令写入 /proc/sys/kernel/printk来进行调整。

  • mark_post_data标识该触发器是在data分区挂载后运行的。

  • mkdir <path> [<mode>] [<owner>] [<group>] [encryption=<action>] [key=<key>]创建目录,可以指定mode,owner和group. 如果不指定则按权限为755,root用户和root组创建。如果指定了,但目录已存在,则目录的权限和用户,组信息将被更新。

action 可以指定为:

  • None: 不需要进行加密操作,目录根据上层目录加密算法进行处理。
  • Require: 需要加密,如果加密失败则终止开机进程。
  • Attempt: 尝试加密,加密失败仍可继续。
  • DeleteIfNecessary: 递归处理:如果存在子目录,但当前策略的加密算法不匹配,则删除子目录。

key 可以指定为:

  • ref: 使用systemwide DE key
  • per_boot_ref: 使用每次开机都更新的key
  • mount_all [ <fstab> ] [--<option>] 对给出的 fstab 表调用 fs_mgr_mount_all 接口,option为“early” 或者 “late”. option如果是--early则init将会跳过“latemount”标识的的item,并触发fs encryption state 事件。option如果是--late则init就只mount “latemount”标识的的item. 如果不携带[–]则mount的是fstab 表中全部item。 如果fstab 也没有指定,那么就以fstab.${ro.boot.fstab_suffix}, fstab.${ro.hardware} ,fstab.${ro.hardware.platform} 按顺序扫描处理。

      on fs
          mount_all /vendor/etc/fstab.${ro.hardware} --early
      
      on late-fs
          # Start services for bootanim
          start servicemanager
          start hwcomposer-2-1
          start gralloc-2-0
          start surfaceflinger
          start bootanim
      
          exec_start wait_for_keymaster
          # Mount RW partitions which need run fsck
          mount_all /vendor/etc/fstab.${ro.hardware} --late
    
:/vendor/etc $ cat fstab.rk30board                                                                                                                                                       
# Android fstab file.
#<src>  <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
# The filesystem that contains the filesystem checker binary (typically /system) cannot
# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK

/dev/block/by-name/system                          /                          ext4      ro,barrier=1                                                      wait,avb,slotselect
/dev/block/by-name/cache                           /cache                    ext4       noatime,nodiratime,nosuid,nodev,noauto_da_alloc,discard                wait,check
/dev/block/by-name/metadata                        /mnt/vendor/metadata       ext4       noatime,nodiratime,nosuid,nodev,noauto_da_alloc,discard                wait
/dev/block/by-name/misc                            /misc                      emmc       defaults                                                           defaults
/dev/block/by-name/frp                             /frp                       emmc       defaults                                                           defaults
/dev/block/by-name/parameter                        /parameter                 emmc      defaults                                                            defaults
/dev/block/by-name/baseparamer                    /baseparamer                emmc        defaults                                                            defaults
/dev/block/by-name/resource	                     /resource	                  emmc	     defaults				                              defaults
/devices/platform/*usb*                               auto                     vfat       defaults                                                        voldmanaged=usb:auto
/dev/block/zram0                                     none                      swap       defaults                                                        zramsize=50%
# For sdmmc
/devices/platform/fe320000.dwmmc/mmc_host*           auto                      auto           defaults                                                  voldmanaged=sdcard1:auto
# Full disk encryption has less effect on rk3399, so default to enable this.
/dev/block/by-name/userdata                         /data                      f2fs      noatime,nodiratime,nosuid,nodev,discard,inline_xattr,fsync_mode=strict     wait,check,notrim,forceencrypt=/cache/key_file,quota,reservedsize=128M

  • mount <type> <device> <dir> [ <flag>\* ] [<options>] mount名为 处于dir 目录的设备, flag包括"ro", “rw”, “remount”, “noatime”, …, options包括 “barrier=1”, “noauto_da_alloc”, “discard”, … 。

  • parse_apex_configs 从已挂载的APEXes中解析配置文件。apexd通过设置apexd.status为ready通知mount事件.(仅通知一次)

  • restart <service>stop再start service,如果service正在restart,则不重复处理。

  • restorecon <path> [ <path>\* ]将目录指定为file_contexts中定义context。如果文件是init.rc创建的,则不需要调用此命令,创建的时候已经标记正确。

     	restorecon /vendor/bin/tee-supplicant
    
  • restorecon_recursive <path> [ <path>\* ]递归处理目录下全部路径,将目录指定为file_contexts中定义context。

          restorecon_recursive /mnt/vendor/metadata/tee/
    
  • rm <path> 对path调用unlink(2) 。 你可能想直接使用 exec --rm,但这个exec --rm的前提是系统部分已完成挂载。

  • rmdir <path> 对path调用rmdir(2)。

  • readahead <file|dir> [--fully]对给定的file或者给定的目录下的file调用readahead(2)。--fully 读取全部的文件内容。详细可参考Linux内核的文件预读(readahead)

  • setprop <name> <value>设置Properties value。

  • setrlimit <resource> <cur> <max> 设置资源使用限制,linux下每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。详细可参考linux下getrlimit()与setrlimit()函数说明及使用, 此命令对所有之后启动的应用生效。该命令一般在init的早期使用,从而能全局生效。resource最好能按照缩写指定,例如‘cpu’, ‘rtio’, ‘RLIM_CPU’, ‘RLIM_RTIO’等,当然也可以指定为resource枚举对应的int值。cur 和 max 可以设置为 ‘unlimited’ 或者 ‘-1’ 表示没有限制。

  • start <service>启动一个未运行的服务,注意不是同步的。因此假如这个service给其他service提供了什么communication channel, 只是简单的先使用该命令start 这个服务并不能保证其他服务请求channel时这些channel已创建。如果要确保这个先后顺序还需要用户使用其他机制进行保证。

  • stop <service>如果该服务正在运行,则停止它。

  • swapon_all [ <fstab> ]对给定的fstab调用fs_mgr_swapon_all函数。如果fstab未指定,将会直接顺序处理/odm/etc, /vendor/etc,或者根目录下的 fstab.${ro.boot.fstab_suffix}, fstab.${ro.hardware} , fstab.${ro.hardware.platform}表文件。

  • symlink <target> <path> 创建symbolic链接。

      symlink /system/etc /etc 
    
  • sysclktz <minutes_west_of_gmt>设置时区,0表示GMT

  • trigger <event> 触发一个事件,用于另一个action插入执行。

  • umount <path> unmunt 文件系统

  • umount_all [ <fstab> ]对给定的fstab调用fs_mgr_umount_all函数,如果fstab未指定,将会直接顺序处理/odm/etc, /vendor/etc,或者根目录下的 fstab.${ro.boot.fstab_suffix}, fstab.${ro.hardware} , fstab.${ro.hardware.platform}表文件。

  • verity_update_state <mount-point> 内部实现是设置dm-verity的状态,并且设置partition.*.verified的属性. 用于adb remount,因为fs_mgr 不能直接设置它。

      on early-boot
          # Update dm-verity state and set partition.*.verified properties
          verity_update_state
    
  • wait <path> [ <timeout> ] 等待指定路径创建,如果路径已存在则返回并继续下一个命令。可以设置超时时间,以秒为单位,float类型,默认值为5s。

  • wait_for_prop <name> <value>等待property被设置为某个value值,如果当前property已经为value则直接继续下条命令。

  • write <path> <content> 打开一个文件,并将content写入。如果该文件不存在,将会新创建该文件,如果该文件存在,则直接写入内容,原先的内容可能被覆盖。

Imports
  • import <path>导入一个新的rc文件内容,如果path是个目录,则会导入该目录下所有rc文件内容,仅导入当前目录,不支持递归,不能导入子目录下的rc文件。

import关键字并不是一个命令,它遵循以下规则,总共三种情况:

  1. 开机时,加载根目录下的/init.rc 或者加载 `ro.boot.init_rc` 中指定的脚本。
  2. 在加载/init.rc之后mount {system,vendor,odm}的时候就加载对应的 /{system,vendor,odm}/etc/init/ 下的rc文件。
  3. (已失效) 在mount_all时加载全部的 /{system,vendor,odm}/etc/init/ 下的rc文件或者特殊定义的rc文件。这个情况在Android Q之后已经不适用。

由于legacy设备的历史原因,以及为了向下兼容legacy设备(不是严格保证),当前导入rc文件的顺序仍然有些复杂。只有两点是可以保证一个命令比另一个命令先运行:

1) 将一个需要先运行的命令放入到一个会先触发的action中。
2) 将两个命令都放入同一个action,并把先运行command排前。

尽管有些复杂,实际的第一阶段rc加载的顺序为:

1. /init.rc 先被解析加载,同时在它内部import的rc文件会被递归加载。
2. /system/etc/init/下的rc文件按名字的字母排序排序后加载进来,加载一个的同时递归加载import进来的rc文件。
3. 再重复2继续处理r /vendor/etc/init 之后  /odm/etc/init 的rc文件。

以下伪代码可以更清楚地解释这个顺序:

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)

init相关的Properties

  • init.svc.<name> init使用该属性表示某个name的service状态。value包括:“stopped”, “stopping”, “running”, “restarting”.
  • dev.mnt.blk.<mount_point>一个mount分区实际存储设备(block device)的base name。mount分区路径如果存在"/“则用”."来替换。如果就是根目录分区,则直接用"root"代替。主要用于动态分区。Init中有一个机制可以跟踪mounts并异步更新Android属性。“mount_point”一般为root、system、data或vendor。详细请参考Android10 动态分区介绍
:/ $ getprop | grep dev.mnt.blk
[dev.mnt.blk.data]: [sda]
[dev.mnt.blk.firmware]: [sde]
[dev.mnt.blk.metadata]: [sde]
[dev.mnt.blk.persist]: [sda]
[dev.mnt.blk.root]: [dm-0]
[dev.mnt.blk.vendor]: [dm-1]
:/ $ getprop | grep dev.mnt.blk
[dev.mnt.blk.data]: [dm-4]
[dev.mnt.blk.metadata]: [sda]
[dev.mnt.blk.mnt.scratch]: [sda]
[dev.mnt.blk.mnt.vendor.persist]: [sdf]
[dev.mnt.blk.product]: [dm-2]
[dev.mnt.blk.root]: [dm-0]
[dev.mnt.blk.system_ext]: [dm-3]
[dev.mnt.blk.vendor]: [dm-1]
[dev.mnt.blk.vendor.firmware_mnt]: [sda]
  • ctl.[<target>_]<command> 以ctl打头的这个格式的属性能够触发init的响应,且该属性是只用于set而不用于read的。<target>_是可选的,一般为一个service的option,只能指定一个option。<commands>只能为start restart stop 中的一个。这个属性的value就是对应的service名称。 如果option是interface那么它的value就是一个service提供的interface,而不是这个service名称。
    AndroidQ版本引入了LAZY HAL概念,以支持低性能的Android设备,Lazy hal可以使hal服务在使用的时候开启,而当不使用时,所有client都注销服务,关闭hal服务,因此,这个功能可以有效地提高Android设备的性能和降低功耗。如果服务是 lazy HALs,需要oneshot on。详细可参考Camera_service中打开Lazy_Hal功能
    //就会运行logd的start option。
    SetProperty("ctl.start", "logd")
    //就会启动一个提供了 `aidl aidl_lazy_test_1`接口 的service 的start option. 		
    SetProperty("ctl.interface_start", "aidl/aidl_lazy_test_1")
    //按oneshort启动,如果进程挂掉不再重启。
    SetProperty("ctl.oneshot_one_start", "logd") 
    // 进程挂掉后还要重新启动。
    SetProperty("ctl.oneshot_off_start", "logd")
    //发送SIGSTO给该服务。用于调试。
    SetProperty("ctl.sigstop_on_start", "logd")
    SetProperty("ctl.sigstop_off_start", "logd")
Boot timing

init在属性中也记录了一些开机时间相关的信息(以ns为单位,1秒=1000毫秒,1毫秒=1000微秒,1微秒=1000纳秒):

  • ro.boottime.init 开机后时钟运行时长 (2,098,647,130ns = 2s)
  • ro.boottime.init.first_stage 开机的第一阶段耗时多少
  • ro.boottime.init.selinux 在运行SELinux阶段耗时多少
  • ro.boottime.init.cold_boot_waitinit等待 ueventd coldboot解析结束
getprop |grep ro.boottime.init                                                                                                                                                      
[ro.boottime.init]: [2098647130]
[ro.boottime.init.cold_boot_wait]: [146]
[ro.boottime.init.first_stage]: [89287676]
[ro.boottime.init.fsck.cache]: [46]
[ro.boottime.init.fsck.data]: [18]
[ro.boottime.init.fsck.metadata]: [62]
[ro.boottime.init.fsck.scratch]: [43]
[ro.boottime.init.mount.cache]: [2]
[ro.boottime.init.mount.data]: [54]
[ro.boottime.init.mount.metadata]: [6]
[ro.boottime.init.mount.scratch]: [2]
[ro.boottime.init.mount_all.early]: [199]
[ro.boottime.init.mount_all.late]: [148]
[ro.boottime.init.selinux]: [145286473]

  • ro.boottime.<service-name> 某个服务第一次启动的时间(以ns为单位)
    //可以比较各个服务启动的先后顺序
:/ $ getprop |grep boottime                                                                                                                                                              
[ro.boottime.adbd]: [11866422925]
[ro.boottime.audioserver]: [3573729716]
[ro.boottime.auth_pem]: [11533756891]
[ro.boottime.bootanim]: [4405600758]
[ro.boottime.cameraserver]: [11539368558]
[ro.boottime.console]: [3561321923]
[ro.boottime.drm]: [11546434767]
[ro.boottime.gatekeeperd]: [11776180082]
[ro.boottime.generate_cert]: [3353394194]
[ro.boottime.health-hal-2-0]: [3520695378]
[ro.boottime.healthd]: [3452477163]
[ro.boottime.hidl_memory]: [3450825162]
[ro.boottime.hwservicemanager]: [3104810753]
...

相关调试工具介绍

Bootcharting

Android11的init支持使用"bootcharting"来获取log相关的文件。相关工具可参考http://www.bootchart.org/.

  • 模拟器上可以直接带上-bootchart timeout参数来启动bootchart的方式启动模拟器。
  • 真实设备中使用 adb shell 'touch /data/bootchart/enabled'来启用bootchart。当调试完成后需要手动清除 /data/bootchart/enabled文。

log文件写入了/data/bootchart/, 使用如下命令可以自动打包这些文件并获取这些文件。

sudo apt-get install pybootchartgui
# grab-bootchart.sh uses $ANDROID_SERIAL.
$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

还需要注意的一点是:这些log中会按照init运行时间为0,进行记录的。如果想要知道相比于开机时钟运行时间,则再看一下init是什么时候启动的,增加这个时间,就能知道了。

Comparing two bootcharts

使用compare-bootcharts.py可以便捷地比较选择的进程的开始时间和结束时间。前面提到的grab-bootchart.sh会在主机的/tmp/android-bootchart下创建一个bootchart.tgz。如果将两次抓的log解压到主机两个不同的目录,就能对比两份数据。例如:

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

process: baseline experiment (delta) - Unit is ms (a jiffy is 10 ms on the system)
------------------------------------
/init: 50 40 (-10)
/system/bin/surfaceflinger: 4320 4470 (+150)
/system/bin/bootanimation: 6980 6990 (+10)
zygote64: 10410 10640 (+230)
zygote: 10410 10640 (+230)
system_server: 15350 15150 (-200)
bootanimation ends at: 33790 31230 (-2560)
Systrace

Systrace (http://developer.android.com/tools/help/systrace.html) 用于获取userdebug/eng设备启动期间的性能分析报告.

如下例子是wm am策略的trance event:

$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py \
      wm am --boot

该命令会让设备重启,等设备重启且启动阶段已完成后,主机敲下" Ctrl+C",trace 报告就能从设备中获取并以trace.html形式保存到主机上。
注意:trace事件是在persistent properties 加载之后才进行记录的,如果trace事件在此之前就发生了,是不会被记录的,比如vold, surfaceflinger, and servicemanager就受到这个限制的影响,因为他们是在persistent properties 加载之前就启动了。Zygote 初始化,以及是从Zygote孵化的进程就不会受到此限制的影响。


如何调试init

当一个service由init启动,它有可能执行execv() 失败,导致服务没有成功启动。但一般来说,这个情况不是典型的,有可能是链接的时候出错了。Android中的链接会将它的log打印到logdstderr,所以如果出现错误,是能够在logcat中看到错误信息进行排查的。如果出错的时候,还无法访问logcat,那么可以使用stdio_to_kmsg 选项将 stderr 输出到kmsg ,这样通过串口就能查看。

init service就应该要通过init启动,不建议使用其它方式来启动service进程,因为init提供了一系列难以手动复制的方式来创建service的环境(用户、组、安全标签、功能等)。

另外,如果想要从一开始就调试service,可以使用sigstop选项。此选项将在调用exec之前就立即向service发送SIGSTOP信号。这是一扇窗户

开发者可以在继续使用SIGCONT服务之前附加调试器、strace等。这使得开发者能够在继续服务之前准备好debugger, strace等调试环境。sigstop甚至还可以通过ctl.sigstop_on_start, ctl.sigstop_off_start进行动态控制。
以下是动态调试logd的例子:

 @终端1:   
  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 1, main (argc=1, argv=0x7ff8c9a488) at system/core/logd/main.cpp:427

以下是在另一个终端中使用strace进行操作:

@终端2:
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 a different shell)
kill -SIGCONT 4343

> strace runs

主机端init脚本验证

init脚本的校验是在编译阶段进行的。尤其是以下情况:

1. action,service,import格式校验。
2. 所有命令都是有效的关键字,并且参数值在正确的范围内。
3. 所有service的option都是有效的,这比解析comment更加严格,属于完全解析。例如要求uid和gid是必须要指明的。

也有一部分解析工作是在运行时处理的,因此以下这几种情况编译期间是不做检查的:

1. 编译期间不检查command的参数的有效性,例如,不检查文件路径是否确实存在,SELinux是否允许操作,或者UID和GID是否已指定。
2. 编译期间不检查service是否存在或者是否已定义SELinux domain。
3. 编译期间不检查service是否在其他init脚本中已经被定义。

开机早期启动顺序

开机早期启动顺序包括三个阶段:第一阶段初始化、SELinux设置和第二阶段初始化。
第一阶段初始化主要是mount system的最基本设置。尤其包括挂载/dev, /proc,挂载 ‘early mount’ 分区(包括系统代码的那些分区,比如:system 和vendor)以及给带有虚拟硬盘的设备将system.img挂载到根目录。
需要注意的是,在Android Q, system.img总是包含TARGET_ROOT_OUT,并且总是在第一阶段阶段初始化结束后挂载到根目录下。Android Q 也会需使用动态分区,因此也会需要带虚拟硬盘来启动Android.

根据设备配置,第一阶段初始化有以下三种不同情况:

1. system-as-root 设备, 第一阶段初始化是/system/bin/init的一部分,/init链接指向/system/bin/init以实现向后兼容。这些设备不需要挂载system.img的事情,因为根据这个定义,它已经被内核挂载为rootfs。
2. 对于带有虚拟硬盘的设备,第一阶段初始化是位于虚拟硬盘/init的静态可执行文件。设备会先加载system.img到/system目录下。然后以root身份将/system移到根目录。操作完成后,虚拟硬盘中的内容会被释放。
3. 对于使用recovery作为虚拟硬盘的设备,第一阶段init包含在recovery ramdisk中位于/init的共享init中。这些设备首先以root身份将内容切换到/first_stage_ramdisk,并在环境中删除掉recovery 组件,之后就和2一样的操作。请注意:之所以android 是以正常启机的方式启动而不是按recovery mode启动取决于内核是否使用了androidboot.force_normal_boot=1命令。

第一阶段初始化完成后,使用“selinux_setup”参数执行/system/bin/init。这个阶段是选择性地编译SELinux并将其加载到系统中。selinux.cpp包含了有关该过程细节的更多信息。

最后一个阶段,第二阶段解析结束一完成,它将使用“second_stage”参数再次执行/system/bin/init。此时,init的主要阶段将运行,并通过init rc文件继续后续的开机流程。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值