系列文章请扫关注公众号!
简介
Android的init进程是启动各种服务的核心进程,并处理属性设置等。怎么启动各个服务和监听属性的呢?启动过程中会解析rc文件,并存下来。当系统属性更改或启动某项服务时,init就会按照rc中的设置运行对应应用。
Init 语言:http://aospxref.com/android-10.0.0_r47/xref/system/core/init/README.md
Rc文件是按照一定规范组成的。分为5大类语句:Actions, Commands, Services, Options,Imports.
C++/java都是面向对象的语言,init语言是面向行的,就是一行就是一个语句。由空格分隔。也可使用\插入空格,双引号可防止空格拆分文本,即双引号下是一个整体。行最后使用\表示折行,即续接下行。
#开头的是注释
引用系统属性使用${property.name},还可以字符拼接;例如 `import /init.recovery.${ro.hardware}.rc`
Actions 和 Services 开头等于隐式声明了一个section,所有的Commands、Options都属于这个section。
Services只有唯一的名字,如果名字重复,第二个将被忽略,并输出异常log。
Init rc文件
是以rc为后缀的文本文件
/init.rc是重要文件,在init进程启动时加载,并负责初始化系统设置。在加载完/init.rc后,执行first stage mount即加载/{system,vendor,odm}/etc/init/路径的rc文件,实现/system, /vendor
的挂载。
Mount_all命令可以指定fstab文件,挂载相应分区。没有指定就搜索默认路径/{system,vendor,odm}/etc/init/。这主要是为了支持工厂模式和其它非标准启动模式。正常启动应该使用如下3个路径的。
1. /system/etc/init/ 用于系统核心项,例如SurfaceFlinger, MediaService,logcatd.
2. /vendor/etc/init/ 用于SoC vendor 项,例如core SoC 需要的actions 或 daemons.
3. /odm/etc/init/ 给设备制造商使用, 例如外设、运动传感器等。
/{system,vendor,odm}目录下的bin文件都在其对应的 /etc/init/下有其对应项。系统中存在一个编译宏LOCAL_INIT_RC给开发者使用。每一个rc文件应该包含某个服务的全部关联操作。
例如:logcat
system/core/logcat/Android.bp
system/core/logcat/logcatd.rc
Init 加载logcatd.rc并将任务放入队列,合适时机运行。
根据init .rc文件的守护进程拆分init .rc文件比以前使用的整体init .rc文件更好。这样可以确保init读取的是唯一的服务entry和action,还有助于解决服务冲突。
mount_all命令可以有"early" "late"两个选项。当在可选路径后带 "early",init可执行程序将跳过挂载分区中带有"latemount" flag的挂载项,并触发fs 加密状态事件。设置"late" 选项后,不导入rc文件并只挂载带"latemount" flag的项。如果没有设置就会mount所有项。
Actions 理解为一系列命令操作,并有一个触发器 trigger。trigger决定Action何时执行。当有一个事件匹配了Action的trigger,就会将此Action放入to_be_excuted queue的尾部。如果已经在to_be_excuted queue中就跳过。
Action按顺序执行,Action中的command也按顺序执行。
Init在两个命令中间可以执行服务重启等任务:(device creation/destruction, property setting, process restarting)
Actions 格式:
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
Action的执行顺序按照添加队列的顺序执行,也就是import导入的顺序放入队列,然后按照单个文件顺序执行。
例如一个文件包含
on boot
setprop a 1
setprop b 2
on boot && property:prop_true=true
setprop c 1
setprop d 2
on boot
setprop e 1
setprop f 2
当trigger boot时并且 属性prop_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
是Services的修饰符,决定了init什么时候,怎么运行这个service。
capabilities
参考https://man7.org/linux/man-pages/man7/capabilities.7.html
`class <name> [ <name>\* ]`
指定服务的类别,同一类别的class会一起启动和停止,例如class core;
如果没有指定就是default。
class `animation` 应该包含所有boot animation和shutdown animation的服务。所有这些服务可以在bootup阶段启动并且运行到shutdown的最后阶段。animation阶段不能保证/data分区没无法保证的。应用可以access文件,但不应该open /data下的文件,并且应该保证/data 分区不可用时可正常工作。
Class `console [<console>]` 表明服务需要一个console。第二个可选参数指定一个console代替默认的。默认的console "/dev/console" 可以被内核参数"androidboot.console"设置修改。/dev前缀被省略,所以想指定/dev/tty0,只需设置console tty0
`critical` 关键服务。如果这个服务在启动完成之前退出,或者4分钟内退出4次,将进入BootLoader。
`disabled` 表示这个服务不会随着 此类服务一起启动。需要通过接口或名字启动。
`enter_namespace <type> <path>`
进入path下的type namespace,目前type只支持net。
`file <path> <type>`
打开一个文件,并将fd给到进程,type必须是"r", "w" or "rw".对于native层应用,可以参考libcutils android_get_control_file()函数。
`group <groupname> [ <groupname>\* ]`
服务运行前更改组名为groupname,第二哥groupname表示附加组,通过setgroups()。默认是root
`interface <interface name> <instance name>`
将此服务与提供的aidl hidl服务进行关联。例如用于servicemanager、 hwservicemanager启动lazily services
例如:interface vendor.foo.bar@1.0::IBaz default
`ioprio <class> <priority>`
IO优先级,可以通过SYS_ioprio_set syscall设置,class必须是 "rt", "be", or "idle",_priority_ 是 0 - 7
`keycodes <keycode> [ <keycode>\* ]`
设置按键触发,通常用于bugreporter。
`memcg.limit_in_bytes <value>` and `memcg.limit_percent <value>`
系统启动了memcgrooup时,设置子进程的内存限制。
`memcg.limit_property <value>`
`memcg.soft_limit_in_bytes <value>`
`memcg.swappiness <value>`
`namespace <pid|mnt>`
Fork这个服务时指定一个新的pid,或者mount一个namespace。
`oneshot`
这个程序退出时不重启它。
`onrestart`
当服务重启时,执行一个命令
`oom_score_adjust <value>`
设置value值到 /proc/self/oom\_score\_adj,范围是 -1000 - 1000.
`override`
用于覆盖之前定义的同名服务,一般用于/odm 覆盖 /vendor的,init使用最后一个override定义的服务。
`priority <priority>`
进程调度优先级,-20 to 19,默认是0,-20优先级更高。setpriority()
`restart_period <seconds>`
重启期间段;一个non-oneshot服务,从运行时开始计时,seconds秒后restart一次。默认是5s,防止服务频繁crash。可以用于周期性服务,例如3600 ,表示每1小时重启一次。
`rlimit <resource> <cur> <max>`
用于resource limit,这个限制会继承给子进程,所以rlimit是应用于一个进程树。命令解析方式可以参考setrlimit
`seclabel <seclabel>`
更改服务的 'seclabel',主要用于rootfs运行的服务使用,例如uevent、adbd.
`setenv <name> <value>`
服务运行时,设置环境变量到系统中。
`shutdown <shutdown_behavior>`
设置服务在关机时的行为。如果未指定,进程被SIGTERM and SIGKILL
如果设置了shutdown_behavior是"critical",即shutdown critical
进程在shutdown执行超时之前不会被kill掉,当shutdown timeout时,标记shutdown critical的服务也会被kill掉。设置shutdown critical时,如果关机进程启动时此服务不存在,就会启动此服务。
`sigstop`
在执行此服务之前先发送SIGSTOP ,用于debug调试。
`socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]`
创建一个unix domain socket 名字为/dev/socket/_name_,并把fd传给服务。_type_ 必须是 "dgram", "stream" or "seqpacket"。User 和 group 默认 0.
'seclabel' 是这个socket的 SELinux security context。
参考libcutils android_get_control_socket()
`timeout_period <seconds>`
在服务被kill掉时,延时多长时间。设置timeout_period后oneshot 设置还是有效的,也就是说不会restart。但没设置oneshot的服务会再seconds秒后restart。这个和restart_period 可以混合使用。
`updatable`
用于apex更新,一个服务带着`updatable`启动,在APEXes 完全激活之前,进程是delayed的。如果一个服务没有updatable,就不能被APEXes 更新。
`user <username>`
程序执行前修改 'username' ,默认是root。
`writepid <file> [ <file>\* ]`
写子进程的pid到文件,用于cgroup/cpuset。如果writepid时, /dev/cpuset/下的文件不存在,但系统属性ro.cpuset.default是非空cpuset name,例如'/foreground'。这个pid就会写到/dev/cpuset/_cpuset\_name_/tasks.
Triggers
--------
触发器,让一个Action运行。分为event triggers 和property triggers。
'trigger'命令和init中的 QueueEventTrigger() 属于event trigger。比如'boot' or 'late-init。
Property triggers是当属性设置或发生变化时触发;形式是'property:<name>=<value>' and 'property:<name>=\*'
Property triggers在init的boot阶段,会额外触发。
一个Action可以有多个属性trigger,但只能有一个event trigger。
`on boot && property:a=b` d
`on property:a=b && property:c=d` d
Commands
--------
执行的命令
`bootchart [start|stop]`
Start/stop bootcharting
出现在默认的init.rc中,只有当 /data/bootchart/enabled存在时才有意义。
`chmod <octal-mode> <path>`
修改文件访问权限,和手动执行chmod没区别。
`chown <owner> <group> <path>`
修改文件的owner、group
`class_start <serviceclass>`
启动某一类服务,比如class core
`class_start_post_data <serviceclass>`
和`class_start <serviceclass>`差不多,只考虑在/data/分区mount之后才运行的服务,并且要class_reset_post_data调用之后。一般用于FDE devices
`class_stop <serviceclass>`
如果某类服务运行着,就stop并disable掉。
`class_reset <serviceclass>`
停止某类服务,但并不disable掉,只有还可以用class_start <serviceclass>启动。
`copy <src> <dst>`
理解为cp命令,对于src文件, symbolic link file and world-writable or group-writable files是不允许的。对于dst文件,如果不存在,默认 0600权限,如果已经存在就会截断覆盖。
`domainname <name>`
设置domain name.
`enable <servicename>`
当一个服务没被设置为disable时,将这个服务设置为enable。如果一个预期需要运行,那enable servicename的服务就立即运行了。例如BootLoader设置一个属性要运行这个服务。
on property:ro.boot.myfancyhardware=1
enable my_fancy_service_for_my_fancy_hardware
`exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]`
Fork并运行带参数的command,Init 暂停执行其它commands 直到command进程退出.
`exec_background [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]`
和exec 差不多,当并不暂停执行其它命令。
`exec_start <service>`
启动一个服务并暂停init的其它command执行,和exec类似,只是把命令改为了服务名。
`export <name> <value>`
相当于export命令,export name=value,设置一个全局环境变量,在这之后运行的程序都可以访问到。
`hostname <name>`
设置host name
`ifup <interface>`
使network interface _interface_ online
`insmod [-f] <path> [<options>]`
Insmod path路径下的ko。Option是其选项,-f标志强制insmod,不管是否和内核版本匹配。
`load_system_props`
这个强烈不建议使用。
`load_persist_props`
在/data/解密之后加载persistent properties。应该包含在默认的init.rc
`loglevel <level>`
设置内核loglevel等级
`mark_post_data`
用于mark /data挂载的时刻。用于`class_reset_post_data` and `class_start_post_data`
`mkdir <path> [mode] [owner] [group]`
创建path路径的文件夹,如果默认不带其它参数,mode是755 ,owner、group是root。
`mount_all <fstab> [ <path> ]\* [--<option>]`
调用 fs_mgr_mount_all mount fstab文件中的分区,option有late和early,详见前面的init.rc说明。
`mount <type> <device> <dir> [ <flag>\* ] [<options>]`
尝试挂载 dir 路径下的 device设备, _flag_s 包含"ro", "rw", "remount", "noatime", ...
_options_ 包含"barrier=1", "noauto_da_alloc", "discard", ... 并用逗号‘,’分开,eg: barrier=1,noauto_da_alloc
`parse_apex_configs`
用于解析apex的配置,一般用在 apexd.status 属性设置时。
`restart <service>`
停止并重启此服务,如果正在重启中,不做任何事情,
`restorecon <path> [ <path>\* ]`
将 _path_ 命名的文件恢复到 file_contexts 中指定的安全上下文。
`restorecon_recursive <path> [ <path>\* ]`
上边命令的递归恢复。
`rm <path>`
对path路径调用unlink(2),也可以用"exec -- rm ..."代替。
`rmdir <path>`
对path路径调用 rmdir(2)
`readahead <file|dir> [--fully]`
对file|dir调用readahead(2),--fully表示完整的读取整个内容。
`setprop <name> <value>`
设置属性名name值为value
`setrlimit <resource> <cur> <max>`
对某项资源的限制,对全局的进程有效。应该在init之前设置,比如设置'cpu', 'rtio'或'RLIM_CPU', 'RLIM_RTIO',,设置'unlimited' or '-1'表示不限制。
`start <service>`
如果一个服务不是running就启动它。这个操作是不保证状态同步的,也就是同一时刻可能运行两次这个服务,可能存在冲突。
`stop <service>`
停止一个服务
`swapon_all <fstab>`
对fstab文件调用fs_mgr_swapon_all
`symlink <target> <path>`
对path创建一个软链接。
`sysclktz <mins_west_of_gmt>`
设置系统时钟基准,如果已GMT标准为0.
`trigger <event>`
触发一个event
`umount <path>`
Unmount the filesystem mounted at that path.
`verity_load_state`
用于加载 dm-verity 状态的内部实现细节。
`verity_update_state <mount-point>`
用于加载 dm-verity 状态的内部实现细节和adb remount 设置属性partition._mount-point_.verified 。fs_mgr不直接操作。
`wait <path> [ <timeout> ]`
轮询给定文件是否存在,找到或超时返回。如果未指定超时,默认为五秒。
`wait_for_prop <name> <value>`
等待属性name值为value,如果相等,立即执行。
`write <path> <content>`
对path路径的文件写content内容,相当于调用 write(2)。Path不存在就创建,存在会被截断重写,相当与覆盖而不是追加。
-------
`import <path>`
导入path路径的rc文件给init解析,如果是一个文件夹,只解析当前路径下的所有rc文件,不会解析包含的子文件夹。
import关键字不是命令,而是它自己的部分,这意味着它不是作为Action的一部分发生,而是作为正在解析的文件处理导入,并遵循以下逻辑。
只有在以下三种情况下才导入rc文件。
- 导入/init.rc 或boot initail 时设置`ro.boot.init_rc`属性时,
2、在导入/init.rc后,first mount stage阶段导入{system,vendor,odm}/etc/init/ 时。
3、当mount_all指定路径时,导入指定路径/{system,vendor,odm}/etc/init/或.rc文件
由于兼容性和历史原因,import文件是一个复杂的问题,没法保证顺序。
只有两种方式可以保证一个服务运行于另一个服务之前。1、放在一个可以更早trigger的Action中运行,2、放在同一个文件同一个Action的更早行中执行。
first stage mount devices的顺序是:
1、/init.rc 然后递归解析import的rc文件,Android12+是 /system/etc/init/hw/init.rc
2、/system/etc/init/是按字母顺序导入的解析的,解析后递归执行。
3、再按照步骤2处理 /vendor/etc/init ,然后处理/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.svc.<name>`
Name服务的运行状态,("stopped", "stopping", "running", "restarting")
`dev.mnt.blk.<mount_point>`
块设备的挂载点,例如`dev.mnt.blk.root`
-----------
启动时序,init通过系统属性记录系统的启动时序。
`ro.boottime.init`
单位是ns,这个属性值记录的是init的FirstStageMain()函数结束,马上要执行secondstage了的时刻。First stage start启动完成的时间。
{
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
}
`ro.boottime.init.selinux`
First stage start 阶段selinux SelinuxInitialize初始化耗时。
`ro.boottime.init.cold_boot_wait`
等待coldboot的时间,即等待文件"/dev/.coldboot_done"可用。
`ro.boottime.<service-name>`
service-name服务首次运行的时间,单位ns。
-----------
这个一个Android系统提供的分析启动时间的工具,可以生成图表。可以再Ubuntu上配合分析工具可以使用。
设备上enable此功能
adb shell 'touch /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
Comparing two bootcharts
Usage: system/core/init/compare-bootcharts.py _base-bootchart-dir_ _exp-bootchart-dir_
Debugging init
--------------
一般无法手动启动init服务。因为它初始化了大量的环境。如果要一开始就调试服务,就使用`sigstop`选项,这样会在执行服务前发送SIGSTOP,让你有机会使用strace命令调用attached它。
这个flag可以通过 ctl.sigstop_on 、ctl.sigstop_off 属性动态设置.
Below is an example of doing the same but with 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 a different shell)
kill -SIGCONT 4343
> strace runs
-----------------------------
脚本的校验。
- 对于脚本中的action, service and import sections,Action没有on触发机制,没有import的关联行。
2、所有命令都映射到有效的关键字,并且参数数量在正确的范围内。
3、所有服务选项均有效。这比检查服务命令的方式更严格选项的参数已被完全解析,例如UID 和 GID 必须解析。
也有一些脚本是运行时解析,不会在构建时检查;
- 参数的有效性不检查;例如文件是否存在,selinux是否允许这个操作,UID、GID是否允许解析。
Early Init Boot Sequence
------------------------
Early Init 启动顺序分为三个阶段:first stage init, SELinux setup,second stage init.
First stage init 阶段是初始化一个最低要求的环境,用于加载系统其余部分。具体来说,包含mount /dev, /proc, mounting 'early mount' partitions (包含系统代码的所有分区,例如system,vendor),对于有ramdisk的设备,将system.img挂载早/目录。
Android Q 中,system.img始终包含 TARGET_ROOT_OUT,并且始终由First stage init阶段初始化完成的时间。Android Q 还需要动态分区,因此
需要使用 ramdisk 来启动 Android。
根据设备配置,First stage init有三种变化:
通用场景
术语库
1)对于system-as-root设备,第一阶段init是/system/bin/init的一部分,为了向后兼容,/init上的符号链接指向/system/bin/init。这些设备不需要做任何事情来安装系统。Img,因为根据定义,它已经被内核挂载为根文件。
2)对于带有ramdisk的设备,第一阶段init是位于/init的静态可执行文件。这些设备挂载系统。Img /system然后执行切换根操作,将挂载在/system的挂载移动到/。挂载完成后释放ramdisk的内容。
3)对于使用recovery作为ramdisk的设备,首先将其初始化包含在位于恢复ramdisk /init的共享init中。这些设备首先将root切换到/first_stage_ramdisk从环境中删除恢复组件,然后进行与2)相同的操作。请注意,如果androidboot,则决定正常引导到Android而不是引导到恢复模式。中存在Force_normal_boot =1内核命令行。
first stage init结束后,执行 /system/bin/init selinux_setup.
selinux_setup结束后, 执行 /system/bin/init second_stage,这是main的主阶段执行,继续通过rc文件启动脚本。
启动顺序:
在init second stage阶段,会trigger early-init,init,late-init
am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
就会执行init.rc中的
on early-init
on init
on late-init
trigger post-fs
trigger load_system_props_action
trigger post-fs-data
trigger load_persist_props_action
trigger firmware_mounts_complete
trigger boot
上面是init源码中调用的,下面是trigger触发的。
on post-fs //挂载文件系统
start logd
mount rootfs rootfs / ro remount
mount rootfs rootfs / shared rec
mount none /mnt/runtime/default /storage slave bind rec
...
on post-fs-data //挂载data
start logd
start vold //启动vold
...
on boot //启动核心服务
...
class_start core //启动core class
汇总:触发器的执行顺序为on early-init -> init -> late-init,从上面的代码可知,在late-init触发器中会触发文件系统挂载以及on boot。再on boot过程会触发启动core class。至于main class的启动是由vold.decrypt的以下4个值的设置所决定的, 该过程位于system/vold/cryptfs.c文件。
on nonencrypted
class_start main
class_start late_start
on property:vold.decrypt=trigger_restart_min_framework
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start main
on property:vold.decrypt=trigger_restart_framework
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start_post_data hal
class_start_post_data core
class_start main
class_start late_start
setprop service.bootanim.exit 0
start bootanim
Android Display Graphics系列文章-汇总
系列文章请扫关注公众号!