目录
一、前言
init经过前两个阶段后,已经建立了属性系统和SELinux系统,设置子进程的死亡监听,但是最关键的部分,就是init进程还需要执行启动许多关键的系统服务,但是如果都是像属性系统和SELinux系统那样一行行代码去做,显得有点杂乱繁琐,而且不容易扩展,所以Android系统引入了init.rc
init.rc是init进程启动的配置脚本,这个脚本是用一种叫Android Init Language(Android初始化语言)的语言写的,
在7.0以前,init进程只解析根目录下的init.rc文件,但是随着版本的迭代,init.rc越来越臃肿,
所以在7.0以后,init.rc一些业务被分拆到/system/etc/init,/vendor/etc/init,/odm/etc/init三个目录下,
在本篇文章中,我将讲解init.rc的一些语法,然后一步步分析init进程是如何去解析init.rc文件的
本文主要讲解以下内容
- Android Init Language语法
- 解析.rc文件
- 加入一些事件和一些Action
- 触发所有事件并不断监听新的事件
本文涉及到的文件
platform/system/core/init/README.md
platform/system/core/init/init.cpp
platform/system/core/init/init_parser.cpp
platform/system/core/init/action.cpp
platform/system/core/init/action.h
platform/system/core/init/keyword_map.h
platform/system/core/init/builtins.cpp
platform/system/core/init/service.cpp
platform/system/core/init/service.h
platform/system/core/init/import_parser.cpp
platform/system/core/init/util.cpp
二、 init.rc 语法
2.1 init.rc脚本由4种类型
- Actions(行为)
- Commands(命令)
- Services( 服务)
- Options(选项)
2.2 Actions(动作)
1)动作的基本格式(可以执行多个命令)
on <trigger> ##出发条件
<command1> ##命令1
<command2> ##命令2
<command3> ##命令3
...
那到底定义了哪些<trigger>呢 ? 请看下面
on boot #系统启动触发
on early-init #在初始化之前触发
on init #在初始化时触发(在启动配置文件/init.conf被装载之后)
on late-init #在初始化晚期阶段触发
on charger #当充电时触发
on property:<key>=<value> #当属性值满足条件时触发
on post-fs #挂载文件系统
on post-fs-data #挂载data
on device-added-<path> #在指定设备被添加时触发
on device-removed-<path> #在指定设备被移除时触发
on service-exited-<name> #在指定service退出时触发
on <name>=<value> #当属性<name>等于<value>时触发
trigger的执行顺序
on early-init //代码触发的第一个action
... 初始化操作,创建一些属性文件、目录、设定目录权限、启动ueventd守护进程等
on init //第二个
... 创建一些属性文件、目录、设定目录权限、启动服务等
on late-init //第三个
trigger early-fs
trigger fs
trigger post-fs
trigger post-fs-data
trigger load_all_props_action
trigger firmware_mounts_complete
trigger early-boot
trigger boot //最后一个,在此之前的有些trigger 还会触发其他trigger,按照链式管理, 也会比boot先执行
2.2 .Commands(命令)
1)命令将在所属事件发生(也就是上面 <trigger>发生)时一个一个顺序执行
2)常见的Command请看下面
1.exec<path>[ <argument> ]* fork并执行一个程序,其路径为<path>,这条命令将阻塞直到该程序启动完成,因此它有可能造成init程序在某个节点不停地等待
2.export<name><value> 设置某个环境变量<name>的值为<value>,这是对全局有效的,即其后所有进程都将继承这个变量
3.ifup<interface> 使网络接口<interface>成功连接
4.import<filename> 引入一个名为<filename>的文件
5.hostname<name> 设置主机名
6.chdir<dirc> 更改工作目录为<dirc>
7.chmod<octal-mode><path> 更改文件访问权限
8.chown<owner><group><path> 更改文件所有者和组群
9.chroot<direc> 更改根目录位置
10.class_start<serviceclass> 如果它们不在运行状态的话,启动由<serviceclass>类名指定的所有相关服务
11.class_stop<serviceclass> 如果它们在运行状态的话,停止domainname 设置域名
12.insmod 在<path>路径上安装一个模块
13.mkdir<path>[mode][owner][group] 在<path>路径上新建一个目录
14.mount<type><device><dir>[<mountoption>]* 尝试在指定路径上挂载一个设备
15.setkey 目前未定义
16.setprop<name><value> 设置系统属性<name>的值为<value>
17.setrlinit<resource><cur><max> 设置一种资源的使用限制。这个概念亦存在于Linux系统中,<cur>表示软限制,<max>表示硬限制
18.start<service> 启动一个服务
19.stop<service> 停止一个服务
20.symlink<target><path> 创建一个<path>路径的软链接,目标为<target>
21.sysclk<mins_west_of_gmt> 设置基准时间,如果当前时间时GMT,这个值是0
22.trigger<event> 触发一个事件
23.write<path><string>[<string>]* 打开一个文件,并写入字符串
其中最重要的是start <service>
2.4 Services(服务)
1)基本格式:
service <name><pathname> [ <argument> ]*
<option1>
<option2>
...
<name> 此service的名字
<pathname> 此service的路径,因为是可执行文件,所以一定有存储路径
<argument> 启动service所带的参数
<option> 对此service的约束选项,具体有哪些参考下面的Options
备注:service其实是一个可执行程序,它们在特定选项的约束下会被init程序运行或者重启(service可以在配置中指定是否需要退出时重启,这样当service出现异常或者程序crash时候就有机会复原)
2.5 Options(选项)
1.critical 表示这是一个对设备至关重要的一个服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式
2.disabled 此服务不会自动启动,而是需要通过显式调用服务名来启动
3.setenv<name><value> 设置环境变量<name>为某个值<value>
4.socket<name><type><perm>[ <user>[<group>]] 创建一个名为/dev/socket/<name>的unix domain socket,然后将它的fd值传给启动它的进程,有效的<type>值包括dgram,stream和seqacket,而user和group的默认值是0
5.user<username> 在启动服务前将用户组切换为<username>,默认情况下用户都是root
6.group<groupname>[<groupname>]* 在启动服务前将用户组切换为<groupname>
7.oneshot 只启动一次,当此服务退出时,不要主动去重启它
8.class<name> 为该服务指定一个class名,同一个class的所有服务必须同时启动或者停止。默认情况下服务的class名是“default”
9.onrestart 当此服务重启时,执行某些命令
2.6 实列解释
启动zygote service流程 ,也可参考zygote启动流程
//init.rc
import /init.${ro.zygote}.rc //系统 64&32 位 zygote进程
on late-init //中间trigger, 从上倒下的执行(按解析顺序)trigger
trigger early-fs
....
# Now we can start zygote for devices with file based encryption
trigger zygote-start // 启动zygote的trigger
....
on zygote-start && property:ro.crypto.state=unencrypted
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote //启动zygote 服务(具体服务定义参考下面),zygote进程启动
start zygote_secondary
//一般是定义在init.zygote.{ro.zygote}.rc //和上面的import对应
service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main //同一个class的所有服务必须同时启动或者停止。默认情况下服务的class名“default”
priority -20 //优先级-20, 进程最高优先级
user root
group root readproc
socket zygote stream 660 root system //创建socket节点,名称是zygote
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media //如果zygote死亡,会重启系统核心进程服务,如media/cameraserver/netd等
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
//依然还是init.rc 之中
on nonencrypted
class_start main // 这里会遍历启动所有定义为main的服务,因为zygote也是属于main,只是上面已启动过,不需要重复启动,直接跳过,内部会调用fork函数
class_start late_start
启动ueventd service流程
on boot //最后一个trigger
...
class_start core
service ueventd /sbin/ueventd
class core
critical //重要服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式
seclabel u:r:ueventd:s0
shutdown critical
最终执行的源码是在定义在builtins.cpp 之中
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, do_bootchart}},
.....
{"class_start", {1, 1, do_class_start}},
{"class_stop", {1, 1, do_class_stop}},
......
{"start", {1, 1, do_start}},
{"stop", {1, 1, do_stop}},
};
// clang-format on
return builtin_functions;
}
三、源码分析
int main(int argc, char** argv) {
...
const BuiltinFunctionMap function_map;
/*
* 1.C++中::表示静态方法调用,相当于java中static的方法
*/
Action::set_function_map(&function_map); //将function_map存放到Action中作为成员属性
Parser& parser = Parser::GetInstance();//单例模式,得到Parser对象
/*
* 1.C++中std::make_unique相当于new,它会返回一个std::unique_ptr,即智能指针,可以自动管理内存
* 2.unique_ptr持有对对象的独有权,两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作
* 3.移动操作的函数是 p1=std::move(p) ,这样指针p指向的对象就移动到p1上了
* 4.接下来的这三句代码都是new一个Parser(解析器),然后将它们放到一个map里存起来
* 5.ServiceParser、ActionParser、ImportParser分别对应service action import的解析
*/
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {//如果ro.boot.init_rc没有对应的值,则解析/init.rc以及/system/etc/init、/vendor/etc/init、/odm/etc/init这三个目录下的.rc文件
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {//如果ro.boot.init_rc属性有值就解析属性值
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
...
am.QueueEventTrigger("early-init"); //启动就决定了early-init先执行;
四、监听服务
之前的所有工作都是往各种数组、队列里面存入信息,并没有真正去触发,而接下来的工作就是真正去触发这些事件,以及用epoll不断监听新的事件
while (true) {
// By default, sleep until something happens.
int epoll_timeout_ms = -1; //epoll超时时间,相当于阻塞时间
/*
* 1.waiting_for_prop和IsWaitingForExec都是判断一个Timer为不为空,相当于一个标志位
* 2.waiting_for_prop负责属性设置,IsWaitingForExe负责service运行
* 3.当有属性设置或Service开始运行时,这两个值就不为空,直到执行完毕才置为空
* 4.其实这两个判断条件主要作用就是保证属性设置和service启动的完整性,也可以说是为了同步
*/
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand(); //执行一个command
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
restart_processes(); //重启服务
// If there's a process that needs restarting, wake up in time for that.
if (process_needs_restart_at != 0) { //当有进程需要重启时,设置epoll_timeout_ms为重启等待时间
epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout_ms = 0; //当还有命令要执行时,将epoll_timeout_ms设置为0
}
epoll_event ev;
/*
* 1.epoll_wait与上一篇中讲的epoll_create1、epoll_ctl是一起使用的
* 2.epoll_create1用于创建epoll的文件描述符,epoll_ctl、epoll_wait都把它创建的fd作为第一个参数传入
* 3.epoll_ctl用于操作epoll,EPOLL_CTL_ADD:注册新的fd到epfd中,EPOLL_CTL_MOD:修改已经注册的fd的监听事件,EPOLL_CTL_DEL:从epfd中删除一个fd;
* 4.epoll_wait用于等待事件的产生,epoll_ctl调用EPOLL_CTL_ADD时会传入需要监听什么类型的事件,
* 比如EPOLLIN表示监听fd可读,当该fd有可读的数据时,调用epoll_wait经过epoll_timeout_ms时间就会把该事件的信息返回给&ev
*/
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();//当有event返回时,取出ev.data.ptr(之前epoll_ctl注册时的回调函数),直接执行
//上一篇中在signal_handler_init和start_property_service有注册两个fd的监听,一个用于监听SIGCHLD(子进程结束信号),一个用于监听属性设置
}
}
return 0; //进程退出,不过看上面while处于死循环,也证明init 进程不会主动退出程序