今天是农历正月初一,一切又是全新的开始。在这个有着特殊意义的日子里,让我们来一起学习一下Android系统的启动是如何开始的。
init进程是一切的开始,在Android系统中,所有进程的进程号都是不确定的,唯独init进程的进程号一定是1。因为这个进程一定是系统起来的第一个进程。并且,init进程掌控了整个系统的启动逻辑。
我们知道,Android可能运行在各种不同的平台,不同的设备上。因此,启动的逻辑是不尽相同的。 为了适应各种平台和设备的需求,init进程的初始化工作通过 init.rc
配置文件来管理。init.rc以Android Init Language作为配置语法,下文我们简称Android Init Language为init语言。配置文件的主入口文件是/init.rc,这个文件会通过 import
关键字引入其他的配置文件。在这里,我们统称这些文件为 init.rc
。
/init.rc可能 import
以下路径中的.rc文件:
init语言以换行为语句分隔,以空格来为符号分隔。配置文件中支持五种类型的表达式:
这其中,Action和Service需要保证名称唯一。
Action表达式的语法如下:
on <trigger> [&& <trigger>]* <command> <command> <command>
这里的Trigger是Action执行的触发器,当触发器条件满足时,command会被执行。触发器有两类:
QueueEventTrigger()
函数发出 一个Action可以有多个属性触发器,但是只能包含一个事件触发器。下面是一些实例:
on boot && property:a=b
在”boot”事件发生时,并且属性a的值是b时触发 on property:a=b && property:c=d
在属性a的值是b并且属性c的值是d时触发 Action中支持的命令如下表所示:
| Command | 参数格式 | 说明 |
|---|---|---|
| bootchart_init | - | 启动bootchart |
| chmod | octal-mode path | 改变文件的访问权限 |
| chown | owner group path | 改变文件的拥有者和组 |
| class_start | serviceclass | 启动指定类别的服务 |
| class_stop | serviceclass | 停止并disable指定类别的服务 |
| class_reset | serviceclass | 停止指定类别的服务,但是不disable它们 |
| copy | src dst | 拷贝文件 |
| domainname | name | 设置域名 |
| enable | servicename | enable一个被disable的服务 |
| exec | [ seclabel [ user [ group ]]] -- command [ argument ]* | fork一个子进程来执行指定的命令 |
| export | name value | 导出环境变量 |
| hostname | name | 设置host名称 |
| ifup | iterface | 使网卡在线 |
| insmod | path | 安装指定路径的模块 |
| load_all_props | - | 从/system, /vendor等路径载入属性 |
| load_persist_props | - | 载入持久化的属性 |
| loglevel | level | 设置内核的日志级别 |
| mkdir | path [ mode ] [ owner ] [ group ] | 创建目录 |
| mount_all | fstab [ path ]* [-- option ] | 挂载文件系统并且导入指定的.rc文件 |
| mount | type device dir [ flag ]* [ options ] | 挂载一个文件系统 |
| powerctl | - | 内部实现使用 |
| restart | service | 重启服务 |
| restorecon | path [ path ]* | 设定文件的安全上下文 |
| restorecon_recursive | path [ path ]* | restorecon的递归版本 |
| rm | path | 对于指定路径调用unlink(2) |
| rmdir | path | 删除文件夹 |
| setprop | name value | 设置属性值 |
| setrlimit | resource cur max | 指定资源的rlimit |
| start | service | 启动服务 |
| stop | service | 停止服务 |
| swapon_all | fstab | 在指定文件上调用fs_mgr_swapon_all |
| symlink | target path | 创建符号链接 |
| sysclktz | mins_west_of_gmt | 指定系统时钟基准 |
| trigger | event | 触发一个事件 |
| umount | path | unmount指定的文件系统 |
| verity_load_state | - | 内部实现使用 |
| verity_update_state | mount_point | 内部实现使用 |
| wait | path [ timeout ] | 等待某个文件存在直到超时 |
| write | path content | 写入内容到指定文件 |
Service是init进程启动的可执行程序。服务可以选择在自己退出之后,由init将其重启。
Service表达式的语法格式如下:
service <name> <pathname> [ <argument> ]* <option> <option> ...
Option是对服务的修饰,它们影响着init进程如何以及何时启动服务。所有支持的option如下表所示:
| option | 参数格式 | 说明 |
|---|---|---|
| critical | - | 标识为系统关键服务,该服务若退出多次将导致系统重启到recovery模式 |
| disabled | - | 不会随着类别自动启动,必须明确start |
| setenv | name value | 为启动的进程设置环境变量 |
| socket | name type perm [ user [ group [ seclabel ]]] | 创建Unix Domain Socket |
| user | username | 在执行服务之前切换用户 |
| group | groupname [ groupname ]* | 在执行执行之前切换组 |
| seclabel | seclabel | 在执行服务之前切换seclabel |
| oneshot | - | 一次性服务,死亡之后不用重启 |
| class | name | 指定服务的类别 |
| onrestart | - | 当服务启动时执行命令 |
| writepid | file… | 写入子进程的pid到指定文件 |
这其中有两个特别重要的进程就是: zygote 和 system_server 进程。
zygote
fork出来的子进程,因此zygote进程是所有应用进程的父进程。 ActivityManagerService
。 import是一个关键字,并不是一个命令。可以在.rc文件中通过这个关键字来加载其他的.rc文件。它的语法很简单:
import path
path可以是另外一个.rc文件,也可以是一个文件夹。如果是文件夹,那么这个文件夹下面的所有文件都会被导入,但是不会循环加载子目录。
AOSP中包含了Android系统需要的最基本的.rc文件,它们位于这个路径:/system/core/rootdir/ 。
我们选取其中了一两个代码片段来了解一下:
# /system/core/rootdir/init.rc
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
# Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
mkdir /mnt 0775 root system
# Set the security context of /postinstall if present.
restorecon /postinstall
start ueventd
on init
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom
# Backward compatibility.
symlink /system/etc /etc
symlink /sys/kernel/debug /d
# Link /vendor to /system/vendor for devices without a vendor partition.
symlink /system/vendor /vendor
...
这是根目录/init.rc文件中一开始的代码片段。有了前面的讲解之后,这段代码应当还是比较好理解的。在这段代码中:
“eraly-init”和”init”事件都是由init进程发出的。
下面,我们再来看另外一个代码片段:
# /system/core/rootdir/init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
这段代码定义了Andorid系统中一个非常重要的服务: zygote
。这个服务是通过可执行命令/system/bin/app_process启动的,启动的时候传递了参数: -Xzygote /system/bin --zygote --start-system-server
。
关于Zygote我们已经在另外一篇文章中讲解过了,参见这里Zygote进程。