###1. android init
android上, init的源码位于 “system/core/init” 目录, 可以编译出 init, ueventd, watchdogd, 本章只关注init
###2. init 启动流程
init 进程为用户空间的第一个进程, 其入口为 main(), 在android上, 启动参数为 “–second-stage”
//清除umask
umask(0);
//添加环境变量
add_environment("PATH", _PATH_DEFPATH);
init 的两个阶段:
- first statge : init第一次运行时, 处于SELinux的kernel domain
- second stage : 初始化SELinux之后, re-exec init, 切换到SELinux的 init domain
####2.1 first stage 的工作
//挂载虚拟文件系统 /dev /dev/pts /proc /sys
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
//初始化init的标准流
open_devnull_stdio();
//初始化log系统和log等级, 从现在开始可以打印log了
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
//初始化SELinux, 包括load SELinux 的policy
selinux_initialize(is_first_stage);
//切换init的domain为file_contexts文件中指定的上下文, re-exec init, 进入 second stage
if (is_first_stage) {
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
####2.3 second stage
//建立 /dev/.booting 文件, 标示kernel boot完成
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//初始化property 系统
property_init();
//处理通过 devictree 传递的kernerl启动参数
process_kernel_dt();
//处理通过 cmdline 传递的kernel启动参数
//设置boot属性, 即名为“ro.boot.*”的属性
//完成SELinux在second stage的初始化工作
selinux_initialize(is_first_stage);
//这些目录在SELinux初始化之前创建, 因此需要根据 file_contexts 文件的内容来重新设置上下文
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
//
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
//注册SIGCHLD信号的处理例程,init.rc中定义的service都是init的子进程
signal_handler_init();
//读取 /default.prop 文件中记录的属性
property_load_boot_defaults();
//启动 property service
start_property_service();
//解析 init.rc 脚本
init_parse_config_file("/init.rc");
//执行action队列中的command
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
action_for_each_trigger("init", action_add_queue_tail);
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
//进入循环, 处理事件
while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
后续将会详细解释 init.rc 脚本的处理, 以及init进入循环后的处理过程
###3. init.rc 脚本语法
Android init.rc 中可以分为3种section
import xxxx.rc //包含其他的rc文件
on trigger //定义action
command
command
......
service name path args //定义service
options
options
......
init.rc 脚本中的语句可以细分为4种语句
- commands : command是init支持的的内建命令, 例如 chmod, copy等
- actions : action是命名的command序列, 以关键字”on” 开头, 通过不同的条件来触发
- services : service是由init负责启动或者重新启动的程序
- options : options是service的修饰符
Android init.rc 脚本的语法风格为
- 每一个语句占据一行,所有关键字通过空格来分割
- c语言风格的反斜杠(/)将被转义为插入一个空格
- 如果一个关键字含有一个或多个空格,那么怎么保证关键字完整呢?可以使用双引号来确定关键字的范围
- 用于行尾的反斜杠表示续行符
- Actions和Services声明一个字段(section),紧随其后的Commands和Options均属于这个字段,在第一个字段之前的Commands和Options的没有意义
- Actions和Services有独一无二的名字,如果Actions和Services的名字有重名,那么将被视作错误
###3.1 Actions 语句
Action 是一组被命名的command序列,“on“ 是action的关键字, 表示下面的序列时Action 其格式如下, 当trigger条件触发时,init讲顺序执行所有的command
on <trigger>
<command>
<command>
<command>
###3.2 Service
Service 是由init负责启动或者重新启动的程序, 格式如下
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
其中 name 为service name, pathname 为对应的可执行程序的路径, argument为可执行程序的参数
android中开可以通过如下的方法来start/stop service, 以名为test的service为例
- 执行”start/stop test”
- 向ctl.start/ctl.stop 属性写入 ”int.svc.test“
####3.3 Option
Options是Services的修饰符
Option 的关键字在 init 源码的 keywords.h 中以类似于如下的语句定义
KEYWORD(class, OPTION, 0, 0)
所有的option 关键字如下
- class : 设置service的class, 相同class的service会被同时启动或者停止, 若不定义则被视为”default“class
- 语法为 ”class
“
- 语法为 ”class
- console :
- critical : 设备相关的关键服务, 若4分钟内该service重启了4次, 则设备将进入recovery模式
- disabled : 服务不会自启动, 必须显示地启动
- group : 在 exce 该进程前, 先切换到指定的group
- 语法为 ”group
“
- 语法为 ”group
- ioprio
- keycodes : 指定收到某些组合键后启动service
- oneshot : 当service退出后, 不重启, init进程默认在service每次退出后都重启
- onrestart : 当service重启时, 执行指定的comamnd
- 语法为 ”onrestart
“
- 语法为 ”onrestart
- seclabel : 在exec之前设置进程的SELinux安全上下文, 通常是rootfs中的执行程序使用
- 语法为 ”seclabel
“
- 语法为 ”seclabel
- setenv : 设置环境变量
- 语法为”setenv
”
- 语法为”setenv
- socket : 在“dev/sockets/“ 下创建一个unix domain socket, 并且传递fd给启动的进程
- 语法为 ”socket
[ [ [ ] ] ]“ - type 必须是 “dgram”, “stream”,”seqpacket” 中的一个, user和group默认为0, seclabel是该socket的SELinux上下文
- 语法为 ”socket
- user : 在执行该进程之前先切换user
- 语法为 ”user
“
- 语法为 ”user
- writepid : 当service中执行fork时, 讲children的pid写入指定的文件
- 语法为 ”writepid
“
- 语法为 ”writepid
####3.4 commands
Commands即是在满足triger条件后,Actions中执行的内容, init 支持的command 使用类似如下的语法定义在 ”keywords.h“ 中
KEYWORD(bootchart_init, COMMAND, 0, do_bootchart_init)
其中 ”bootchart_init“ 为command的关键字, ”do_bootchart_init“ 为该command的处理例程, init支持的command如下:
- bootchart_init
- chmod
- chown
- class_reset
- class_restart
- class_stop
- copy
- domainname
- enable
- exec
- export
- hostname
- ifup
- insmod
- installkey
- load_all_props
- load_persist_props
- loglevel
- mkdir
- mount_all
- mount
- powerctl
- restart
- restorecon
- restorecon_recursive
- rm
- rmdir
- setprop
- setrlimit
- start
- stop
- swapon_all
- symlink
- sysclktz
- trigger
- verity_load_state
- verity_update_stae
- wait
- write
####3.5 Trigger
触发器用来描述一个触发条件, 当条件满足时, 开始执行指定的action
android init原生支持的 trigger有如下几种
- early-init
- init
- late-init
- charger
“late-init” 和 “charger” 模式互斥, “late-init” 存在于正常模式, charger存在于充电模式
- boot : 这是init启动后, 触发的第一个trigger
- property:<name>=<value> : 当名为name的属性值变化为value时, 触发
- 可以使用”&&“进行与操作, 例如 ”on property:test.a=1 && property:test.b=1“
虽然init只支持这2种trigger, 但是配合init的”trigger” command, 却可以自定义trigger, 例如, 在init.rc 中有:
on early-fs
......
on fs
......
on post-fs
......
on post-fs-data
......
on boot
......
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger post-fs-data
trigger early-boot
trigger boot
“early-fs”, “fs”, “post-fs” “boot” 等, 都是在init.rc中自定义的trigger, 依靠 init 的 trigger 命令来触发
###4. init 处理 init.rc 脚本
####4.1 解析 init.rc 脚本
“init.rc” 是 init直接读取的文件, 而init.rc 中可以使用 “import” 关键字来包含另一个init.rc脚本, 类似于clang中的“#include”
在 init 中, init.rc 中的 command 使用如下结构来表示
struct command
{
struct listnode clist; //保存一个Action中所有的command的链表
int (*func)(int nargs, char **args); //command的处理例程
int line; //command在 *.rc文件中的行号
const char *filename; //command所在的rc文件名
int nargs; //command的参数数目
char *args[1]; //command的参数
};
在 init 中, init.rc 中的trigger 使用如下的数据结构来表示
struct trigger {
struct listnode nlist; //用于被添加到 struct action.triggers 链表中
const char *name; //trigger的名称
};
在 init 中, init.rc 中的action 使用如下的数据结构来表示
struct action {
struct listnode alist; //保存init中所有action的链表
struct listnode qlist; //保存所有待处理的action的链表
struct listnode tlist; //保存某一种trigger下的所有的action的链表, 暂时未被使用
unsigned hash;
struct listnode triggers; //该action的所有trigger, 一个action可以有多个trigger与操作
struct listnode commands; //该action的所有的command
struct command *current; //
};
init 中使用一个全局的链表 “action_list” 来保存所有的action的信息, 同时还有一个全局链表 “action_queue”, 所有要执行的action都添加到该链表中, 依次执行
struct action, struct trigger, struct command 这3种数据结构的关系如下图
在 init 中, init.rc 中的service 使用如下的数据结构来表示
struct service {
void NotifyStateChange(const char* new_state); //当service状态改变时修改 init.svc.xxx 属性
struct listnode slist; //所有service形成链表
char *name; //service name
const char *classname; //service class
unsigned flags;
pid_t pid;
//下面这3个成员和critical option相关
time_t time_started; //service的最后一次启动时间
time_t time_crashed; //在计时窗口内service的第一次crash的时间
int nr_crashed; //在计时窗口内service的crash次数,
uid_t uid;
gid_t gid;
gid_t supp_gids[NR_SVC_SUPP_GIDS]; //rc脚本中group option追加的group
size_t nr_supp_gids; //rc脚本中group option追加的group的数目
const char* seclabel; //rc脚本中的seclabel option
struct socketinfo *sockets; //rc脚本中的socket option
struct svcenvinfo *envvars;
struct action onrestart; //restart时的action, 由onrestart option指定
std::vector<std::string>* writepid_files_; //rc脚本中的writepid option
/* keycodes for triggering this service via /dev/keychord */
int *keycodes;
int nkeycodes;
int keychord_id;
IoSchedClass ioprio_class;
int ioprio_pri;
int nargs; //service的参数个数
char *args[1]; //service的参数
};
init 中使用一个全局的链表 “service_list” 来保存所有的service的信息
####4.2 init.rc脚本的处理
action_for_each_trigger("early-init", action_add_queue_tail);
将所有由 “early-init” 这一trigger触发的action依次添加到待执行action队列 “action_queue” 中
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
添加一个init的内建的action,同时添加到待执行action队列 等待 “/dev/.coldboot_done” 文件出现才继续执行,该文件由ueventd创建, 标示ueventd已经成功启动, queue_buildin_action() 接收一个处理例程和一个trigger名称作为参数, 生成一个action, 添加到 全局的 action_list 链表和 action_queu 列表的末尾
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
添加一个init内建的action, 同时添加到待执行action队列, 该action负责初始化init的keychord功能, init 监听到“/dev/keychord” 接收组合键, 当读取到一个组合键后, 会启动所有在init.rc脚本中使用keycode option声明了该组合键的service, 当然只有 adbd service处于运行状态时, init才会处理keychord才会起作
queue_builtin_action(console_init_action, "console_init");
添加一个init内建的action, 同时添加到待执行action队列, 若“ro.boot.console”属性指定的console存在, 则该actionl讲打开“/dev/tty0”, 并且在第14行写入“A N D R O I D” 字符, 即android设备开机时在屏幕上看到的字符
action_for_each_trigger("init", action_add_queue_tail);
将所有由 “init” 这一trigger触发的action添加到带执行action队列 “action_queue” 尾部
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
若是充电模式, 则将将所有由 “charge” 这一trigger触发的action添加到带执行action队列 “action_queue” 尾部, 若是正常开机模式, 则将将所有由 “late-init” 这一trigger触发的action添加到带执行action队列 “action_queue” 尾部
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
添加所有能够被当前的property值trigger的action到待执行action队列
所有的待执行的action都只是被添加到待执行队列中, 并未开始执行, 同样, 所有的service也为被启动, 只有等到init进入循环之后, 才开始执行待执行的action, 启动service
####4.3 trigger的触发顺序
在开机过程中, init.rc中, 各trigger的触发顺序如下:
early-init
init
late-init
early-fs
fs
post-fs
post-fs-data
load_all_props_action
firmware_mounts_complete
early-boot
boot
其中, 前3个trigger由init触发, 后续的trigger则由late-init触发
需要注意的是, 若是从charge mode boot时, 也会在init.rc 中触发late-init
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init
####4.4 service的启动顺序
对于声明了 option disable 的service, init 不会主动启动它们, 需要显式地通过 service 管理命令或者 trigger 来启动它们, 而对于需要自启动的service, 其启动顺序和service的class相关
init.rc中定义的service可以使用 option class 来指定service的class, init.rc 中可以使用class_start
class_stop
class_reset
命令来控制同一class的service的启动/停止/重启, 共有如下几种class
- 正常模式下:
- core : 例如 ueventd, logd, healthd, console, vold, servicemanager, surfaceflinger, bootanim等service
- main : 例如 netd, ril-daemo, keystore, mdnsd, zygote等service
- late_start : logcatd, audiod等service
- default : 这一类service不会被init自启动, 通常被声明为 disabled
- 充电模式下:
- charger
在正常模式下, ”core“, “main”, “late_start” 这3种class的service同过如下的条件触发启动
on boot
......
class_start core
on nonencrypted
class_start main
class_start late_start
on property:vold.decrypt=trigger_restart_min_framework
class_start main
on property:vold.decrypt=trigger_restart_framework
class_start main
class_start late_start
需要注意的是上述的后3种trigger, 它们与android的设备加密相关,设备加密是为了保护android中的 /data 分区和 sdcard 中的用户数据,在 init.target.rc 中有
on fs
wait /dev/block/bootdevice
mount_all fstab.qcom
init 中由 do_mount_all()处理 init.rc 中的 mount_all 命令,它需要考虑设备加密的各种情况
- FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION : 系统第一次开机且系统设置为设备必须加密
- FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED : 非系统第一次开机, 且设备已经加密
- FS_MGR_MNTALL_DEV_NOT_ENCRYPTED : 非系统第一次开机, 且设备没有加密
设备加密后,系统的启动流程与设备未加密时由很大的不同, 仅仅考虑设备未加密时的情况, init会在处理mount_all” 命令时, 触发 “nonencrypted”, 因此 “on nonencrypted”这一 action 会被添加到待执行action队列的末尾, 因此会在在 ”on boot” action 之后执行
因此, 不同class的service的启动顺序为: core, main, late_start, 且都位于“on boot“ trigger后
#####5. init 循环处理事件
while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
进入 while 循环之后执行如下的步骤
- 若当前没有正在执行的 ”exec“ 命令, 则
- 执行待执行action队列中的队列头的action的一条command (每一轮循环只执行一条command, 处理完一个action后, 再处理下一个action)
- 重启所有需要重启的service
- 计算监听事件的epoll 的timeout值, 如果还有待处理的action, 则timeout值为0, 否则可以放宽, 最长为5ms
- 通过 epoll 监听事件
init通过 register_eapol_handler() 注册了3个文件的事件监听
- /dev/keychord : 监听key chord事件
- /dev/spckets/property_service : 监听属性变化事件
- 一个socket pair, 当接收到SIGCHLD信号后, init的信号处理函数从一端socket中写入”1“, init poll另一端的socket以检查是否有子进程退出