Android启动篇 — init原理(二)
======================================================== ========================================================
= 【原创文章】:参考部分博客内容,学习之余进行了大量的筛减细化分析 = = 【特殊申明】:避讳抄袭侵权之嫌疑,特此说明,欢迎转载! =
======================================================== ========================================================
【前言】
Android启动篇 — init原理(一)中讲解分init进程分析init创建系统目录并挂在相应系统文件、初始化属性域、设置系统属性、启动配置属性服务端等一系列复杂工作,很多工作和知识点跟Linux关系很大,所以没有作过多介绍,而本此对于init.rc的解析则是重中之重,所以单独拿出来进行详细分析。
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */• /* 05. 重新设置属性 */ /* 06. 创建epoll句柄 */ /* 07. 装载子进程信号处理器 */ /* 08. 设置默认系统属性 */ /* 09. 启动配置属性的服务端 */ /* 10. 匹配命令和函数之间的对应关系 */
------------------------------------------------------------------------------------------- // Android启动篇 — init原理(一)中讲解
/* 11. 解析init.rc */ Parser& parser = Parser::GetInstance(); // 构造解析文件用的parser对象 // 增加ServiceParser为一个section,对应name为service parser.AddSectionParser("service",std::make_unique<ServiceParser>()); // 增加ActionParser为一个section,对应name为action parser.AddSectionParser("on", std::make_unique<ActionParser>()); // 增加ImportParser为一个section,对应name为service parser.AddSectionParser("import", std::make_unique<ImportParser>()); parser.ParseConfig("/init.rc"); // 开始实际的解析过程
【正文】
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本,主要包含五种类型语句:Action、Command、Service、Option和Import,在分析代码的过程中我们会详细介绍。
init.rc的配置代码在:system/core/rootdir/init.rc 中
init.rc文件是在init进程启动后执行的启动脚本,文件中记录着init进程需执行的操作。
init.rc文件大致分为两大部分,一部分是以“on”关键字开头的动作列表(action list):
on early-init // Action类型语句 # Set init and its forked children's oom_adj. // #:注释符号 write /proc//oom_score_adj - ... ... start ueventd
Action类型语句格式:
on <trigger> [&& <trigger>]* // 设置触发器 <command> <command> // 动作触发之后要执行的命令
另一部分是以“service”关键字开头的服务列表(service list): 如 Zygote
service ueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0
Service类型语句格式:
service <name> <pathname> [ <argument> ]* // <service的名字><执行程序路径><传递参数> <option> // option是service的修饰词,影响什么时候、如何启动services <option> ...
借助系统环境变量或Linux命令,动作列表用于创建所需目录,以及为某些特定文件指定权限,而服务列表用来记录init进程需要启动的一些子进程。如上面代码所示,service关键字后的第一个字符串表示服务(子进程)的名称,第二个字符串表示服务的执行路径。
值得一提的是在Android 7.0中对init.rc文件进行了拆分,每个服务一个rc文件。我们要分析的zygote服务的启动脚本则在init.zygoteXX.rc中定义。
在init.rc的import段我们看到如下代码:
import /init.${ro.zygote}.rc // 可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件
说明:
从android5.0开始,android开始支持64位的编译,zygote本身也就有了32位和64位的区别,所以在这里用ro.zygote属性来控制启动不同版本的zygote进程。
init.rc位于/system/core/rootdir下。在这个路径下还包括四个关于zygote的rc文件。分别是Init.zygote32.rc,Init.zygote32_64.rc,Init.zygote64.rc,Init.zygote64_32.rc,由硬件决定调用哪个文件。
这里拿32位处理器为例,init.zygote32.rc的代码如下所示:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main # class是一个option,指定zygote服务的类型为main socket zygote stream root system # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket onrestart write /sys/android_power/request_state wake # onrestart是一个option,说明在zygote重启时需要执行的command onrestart write /sys/power/state on onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart netd writepid /dev/cpuset/foreground/tasks
“service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server”
在Init.zygote32.rc中,定义了一个zygote服务:zygote,由关键字service告诉init进程创建一个名为zygote的进程,这个进程要执行的程序是:/system/bin/app_process,给这个进程四个参数:
· -Xzygote:该参数将作为虚拟机启动时所需的参数
· /system/bin:代表虚拟机程序所在目录
· --zygote:指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
· --start-system-server:告诉Zygote进程启动SystemServer进程
接下来,我们回到源码当中,继续分析main函数:
/* 11. 解析init.rc */ Parser& parser = Parser::GetInstance(); // 构造解析文件用的parser对象 // 增加ServiceParser为一个section,对应name为service parser.AddSectionParser("service",std::make_unique<ServiceParser>()); // 增加ActionParser为一个section,对应name为action parser.AddSectionParser("on", std::make_unique<ActionParser>()); // 增加ImportParser为一个section,对应name为service parser.AddSectionParser("import", std::make_unique<ImportParser>()); parser.ParseConfig("/init.rc"); // 开始实际的解析过程
说明:
上面在解析init.rc文件时使用了Parser类(在init目录下的init_parser.h中定义), 初始化ServiceParser用来解析 “service”块,ActionParser用来解析"on"块,ImportParser用来解析“import”块,“import”是用来引入一个init配置文件,来扩展当前配置的。
/system/core/init/readme.txt 中对init文件中的所有关键字做了介绍,主要包含了Actions, Commands, Services, Options, and Imports等,可自行学习解读。
分析init.rc的解析过程:函数定义于system/core/init/ init_parser.cpp中
bool Parser::ParseConfig(const std::string& path) { if (is_dir(path.c_str())) { // 判断传入参数是否为目录地址 return ParseConfigDir(path); // 递归目录,最终还是靠ParseConfigFile来解析实际的文件 } return ParseConfigFile(path); // 传入传输为文件地址 }
继续分析ParseConfigFile():
bool Parser::ParseConfigFile(const std::string& path) { ... ... Timer t; std::string data; if (!read_file(path.c_str(), &data)) { // 读取路径指定文件中的内容,保存为字符串形式 return false; } ... ... ParseData(path, data); // 解析获取的字符串 ... ... }
跟踪ParseData():
void Parser::ParseData(const std::string& filename, const std::string& data) { ... ... parse_state state; ... ... std::vector<std::string> args; for (;;) { switch (next_token(&state)) { // next_token以行为单位分割参数传递过来的字符串,最先走到T_TEXT分支 case T_EOF: if (section_parser) { section_parser->EndSection(); // 解析结束 } return; case T_NEWLINE: state.line++; if (args.empty()) { break; } // 在前文创建parser时,我们为service,on,import定义了对应的parser // 这里就是根据第一个参数,判断是否有对应的parser ])) { if (section_parser) { // 结束上一个parser的工作,将构造出的对象加入到对应的service_list与action_list中 section_parser->EndSection(); } // 获取参数对应的parser section_parser = section_parsers_[args[]].get(); std::string ret_err; // 调用实际parser的ParseSection函数 if (!section_parser->ParseSection(args, &ret_err)) { parse_error(&state, "%s\n", ret_err.c_str()); section_parser = nullptr; } } else if (section_parser) { std::string ret_err; // 如果第一个参数不是service,on,import // 则调用前一个parser的ParseLineSection函数 // 这里相当于解析一个参数块的子项 if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) { parse_error(&state, "%s\n", ret_err.c_str()); } } args.clear(); // 清空本次解析的数据 break; case T_TEXT: args.emplace_back(state.text); //将本次解析的内容写入到args中 break; } } }
至此,init.rc解析完,接下来init会执行几个重要的阶段:
int main(int argc, char** argv) {
/* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */• /* 05. 重新设置属性 */ /* 06. 创建epoll句柄 */ /* 07. 装载子进程信号处理器 */ /* 08. 设置默认系统属性 */ /* 09. 启动配置属性的服务端 */ /* 10. 匹配命令和函数之间的对应关系 */
/* 11. 解析init.rc*/
----------------------------------------------------------------------------
/* 12. 向执行队列中添加其他action */
// 获取ActionManager对象,需要通过am对命令执行顺序进行控制 ActionManager& am = ActionManager::GetInstance(); // init执行命令触发器主要分为early-init,init,late-init,boot等 am.QueueEventTrigger("early-init"); // 添加触发器early-init,执行on early-init内容 // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); // ... so that we can start queuing up actions that require stuff from /dev. am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); am.QueueBuiltinAction(keychord_init_action, "keychord_init"); am.QueueBuiltinAction(console_init_action, "console_init"); // Trigger all the boot actions to get us started. am.QueueEventTrigger("init"); // 添加触发器init,执行on init内容,主要包括创建/挂在一些目录,以及symlink等 // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random // wasn't ready immediately after wait_for_coldboot_done am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); // Don't mount filesystems or start core system services in charger mode. if (bootmode == "charger") { am.QueueEventTrigger("charger"); // on charger阶段 } ) == ) { NOTICE("Booting into ffbm mode\n"); am.QueueEventTrigger("ffbm"); } else { am.QueueEventTrigger("late-init"); // 非充电模式添加触发器last-init } // Run all property triggers based on current state of the properties. am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
在last-init最后阶段有如下代码:
# Mount filesystems and start core system services. on late-init trigger early-fs # Mount fstab in init.{$device}.rc by mount_all command. Optional parameter # '--early' can be specified to skip entries with 'latemount'. # /system and /vendor must be mounted by the end of the fs stage, # while /data is optional. trigger fs trigger post-fs # Load properties from /system/ + /factory after fs mount. Place # this in another action so that the load will be scheduled after the prior # issued fs triggers have completed. trigger load_system_props_action # Mount fstab in init.{$device}.rc by mount_all with '--late' parameter # to only mount entries with 'latemount'. This is needed if '--early' is # specified in the previous mount_all command on the fs stage. # With /system mounted and properties form /system + /factory available, # some services can be started. trigger late-fs # Now we can mount /data. File encryption requires keymaster to decrypt # /data, which in turn can only be loaded when system properties are present. trigger post-fs-data # Load persist properties and override properties (if enabled) from /data. trigger load_persist_props_action # Remove a file to wake up anything waiting for firmware. trigger firmware_mounts_complete trigger early-boot trigger boot
可见出发了on early-boot和on boot两个Action。
我们看一下on boot:
on boot # basic network init ifup lo hostname localhost domainname localdomain ... ... class_start core
在on boot
的最后class_start core
会启动class为core的服务,这些服务包括ueventd、logd、healthd、adbd(disabled)、lmkd(LowMemoryKiller)、servicemanager、vold、debuggerd、surfaceflinger、bootanim(disabled)等。
回到主题,分析trigger触发器的代码,QueueEventTrigger():位于system/core/init/action.cpp
void ActionManager::QueueEventTrigger(const std::string& trigger) { trigger_queue_.push(std::make_unique<EventTrigger>(trigger)); }
此处QueueEventTrigger函数就是利用参数构造EventTrigger,然后加入到trigger_queue_中。后续init进程处理trigger事件时,将会触发相应的操作。
再看一下QueueBuiltinAction()函数:同样位于system/core/init/action.cpp
void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) { // 创建action auto action = std::make_unique<Action>(true); std::vector<std::string> name_vector{name}; // 保证唯一性 if (!action->InitSingleTrigger(name)) { return; } // 创建action的cmd,指定执行函数和参数 action->AddCommand(func, name_vector); trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get())); actions_.emplace_back(std::move(action)); }
QueueBuiltinAction函数中构造新的action加入到actions_中,第一个参数作为新建action携带cmd的执行函数;第二个参数既作为action的trigger name,也作为action携带cmd的参数。
接下来继续分析main函数:
int main(int argc, char** argv) {
/* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */• /* 05. 重新设置属性 */ /* 06. 创建epoll句柄 */ /* 07. 装载子进程信号处理器 */ /* 08. 设置默认系统属性 */ /* 09. 启动配置属性的服务端 */ /* 10. 匹配命令和函数之间的对应关系 */
/* 11. 解析init.rc*/
/* 12. 向执行队列中添加其他action */
-------------------------------------------------------------------
/* 13. 处理添加到运行队列的事件 */
while (true) {
// 判断是否有事件需要处理 if (!waiting_for_exec) { // 依次执行每个action中携带command对应的执行函数 am.ExecuteOneCommand(); // 重启一些挂掉的进程 restart_processes(); } // 以下决定timeout的时间,将影响while循环的间隔 ; // 有进程需要重启时,等待该进程重启 if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * ; ) timeout = ; } // 有action待处理,不等待 if (am.HasMoreCommands()) { timeout = ; } // bootchart_sample应该是进行性能数据采样 bootchart_sample(&timeout); epoll_event ev; // 没有事件到来的话,最多阻塞timeout时间 , timeout)); ) { ERROR("epoll_wait failed: %s\n", strerror(errno)); } ) { //有事件到来,执行对应处理函数 //根据上文知道,epoll句柄(即epoll_fd)主要监听子进程结束,及其它进程设置系统属性的请求 ((void (*)()) ev.data.ptr)(); } } ; } // end main
看一下ExecuteOneComand()函数:同样位于system/core/init/action.cpp
void ActionManager::ExecuteOneCommand() { // Loop through the trigger queue until we have an action to execute // 当前的可执行action队列为空, trigger_queue_队列不为空 while (current_executing_actions_.empty() && !trigger_queue_.empty()) { // 循环遍历action_队列,包含了所有需要执行的命令,解析init.rc获得 for (const auto& action : actions_) { // 获取队头的trigger, 检查actions_列表中的action的trigger,对比是否相同 if (trigger_queue_.front()->CheckTriggers(*action)) { // 将所有具有同一trigger的action加入当前可执行action队列 current_executing_actions_.emplace(action.get()); } } // 将队头trigger出栈 trigger_queue_.pop(); } if (current_executing_actions_.empty()) { // 当前可执行的actions队列为空就返回 return; } auto action = current_executing_actions_.front(); // 获取当前可执行actions队列的首个action ) { std::string trigger_name = action->BuildTriggersString(); INFO("processing action (%s)\n", trigger_name.c_str()); } action->ExecuteOneCommand(current_command_); // 执行当前的命令 // If this was the last command in the current action, then remove // the action from the executing list. // If this action was oneshot, then also remove it from actions_. ++current_command_; // 不断叠加,将action_中的所有命令取出 if (current_command_ == action->NumCommands()) { current_executing_actions_.pop(); current_command_ = ; if (action->oneshot()) { auto eraser = [&action] (std::unique_ptr<Action>& a) { return a.get() == action; }; actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser)); } } }
我们来观察一下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 // 后面我们即将重点分析zygote进程
通过ro.zygote的属性import对应的zygote的rc文件。
我们查看init.zygote64_32.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote class main socket zygote stream 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 service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary class main socket zygote_secondary stream root system onrestart restart zygote writepid /dev/cpuset/foreground/tasks
可以看到zygote的class是main, 它是在on nonencrypted
时被启动的,如下:
on boot # basic network init ifup lo hostname localhost domainname localdomain ... ... class_start core on nonencrypted # A/B update verifier that marks a successful boot. exec - root cache -- /system/bin/update_verifier nonencrypted class_start main class_start late_start
至此,Init.cpp的main函数分析完毕!init进程已经启动完成,一些重要的服务如core服务和main服务也都启动起来,并启动了zygote(/system/bin/app_process64)进程,zygote初始化时会创建虚拟机,启动systemserver等。
Android启动篇 — init原理(二)的更多相关文章
- Android启动篇 — init原理(一)
======================================================== ================================== ...
- Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)
======================================================== ================================== ...
- Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)
======================================================== ================================== ...
- init进程 && 解析Android启动脚本init.rc && 修改它使不启动android && init.rc中启动一个sh文件
Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径:system/core/init,编译 ...
- Android启动脚本init.rc(2)
在Android中使用启动脚本init.rc,可以在系统的初始化中进行简单的操作. init.rc启动脚本路径:system/core/rootdir/init.rc 内容: Commands:命令 ...
- 【朝花夕拾】Android性能篇之(二)Java内存分配
前言 在内存方面,相比于C/C++程序员,咱们java系程序员算是比较幸运的,因为对于内存的分配和回收,都交给了JVM来处理了,而不需要手动在代码中去完成.有了虚拟机内存管理机制,也就不 ...
- Android启动脚本init.rc说明文档readme.txt翻译
Android Init Language--------------------- Android初始化语言--------------------- The Android Init Langua ...
- 【朝花夕拾】Android性能篇之(一)序言及JVM
序言 笔者从事Anroid开发有些年头了,深知掌握Anroid性能优化方面的知识的必要性,这是一个程序员必须修炼的内功.在面试中,它是面试官的挚爱,在工作中,它是代码质量的拦路虎,其重要 ...
- 【朝花夕拾】Android性能篇之(三)Java内存回收
在上一篇日志([朝花夕拾]Android性能篇之(二)Java内存分配)中有讲到,JVM内存由程序计数器.虚拟机栈.本地方法栈.GC堆,方法区五个部分组成.其中GC堆是一块多线程的共享区域,它存在的作 ...
随机推荐
- 2017-02-19C#基础 - 数据类型与类型转换
数据类型 基本数据类型 1)整形:byte short int long 整数类型 2)浮点型:fioat(.NET类型 Single 值后面要加f float = 10.5f;) doubl ...
- AtomicInteger源码分析——基于CAS的乐观锁实
1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换.切换涉及 ...
- Sort List leetcode
这个题一开始本想用快速排序的,但是想了20分钟都没有头绪,难点在于快速排序的随机访问无法用链表实现,不过如果可以实现快速排序partition函数就可以了,但是这可能比较复杂,于是改用其他排序方法,上 ...
- python实现视频下载
最近一两年短视频业务风生水起,各个视频网站都有各自特色的短视频内容.如果有这样一个程序,可以把各大视频网站的热门用户最新发布的视频都下载下来,不仅方便自己观看,还可以将没有版权的视频发布在个人社交网站 ...
- CCF2014093字符串匹配(C语言版)
问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那些行.你的程序还需支持大小写敏感选项:当选项打开时,表示同一个字母的大写和小写看作不同的字符:当选项关闭时,表示同一个字母的大写和小写 ...
- 2017年最新chrome必备插件推荐
1. 上网必备 Speed dial plus新标签页, 直接替换掉chrome自带的毫无新意的新标签页,简洁优美快速,我本人非常喜欢. &lt;img src="https ...
- click和onclick本质的区别
原生javascript的click在w3c里边的阐述是DOM button对象,也是html DOM click() 方法,可模拟在按钮上的一次鼠标单击. button 对象代表 HTML 文档中的 ...
- java对象克隆以及深拷贝和浅拷贝
1.什么是"克隆"? 在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不 ...
- 用Visual Studio Code Debug世界上最好的语言
前言 这阵子因缘巧合接手了一个辣鸡项目,是用世界上最好的拍黄片写的,项目基本是另一个小伙伴在撸码,我就兼职打杂和发布做点运维的工作. 然后昨天项目上了测试版之后,一用起来Error满天飞了.让小伙伴查 ...
- Https握手协议以及证书认证
1. 什么是https Https = http + 加密 + 认证 https是对http的安全强化,在http的基础上引入了加密和认证过程.通过加密和认证构建一条安全的传输通道.所以https可以 ...