Android N平台
涉及源码位置:
aosp/system/core/init/init.cpp
aosp/system/core/rootdir/init.rc
aosp/system/core/init/property_service.cpp
0 init进程的主要职责
- init如何创建zygote。
- init的属性服务是如何工作的。
1 init.cpp分析
1.1从init进程的入口函数main()开始分析
init进程的main()函数会执行两次,分别是第一阶段和第二阶段,main函数会进入两次,只是两次进去执行的代码不一样
int main(int argc, char** argv) { |
main函数里涉及不少东西,只是把当前知道的注释了一下,以后补充,这里关注一下,属性服务的启动,以及对init.rc文件的解析.
1.2 属性服务
Android中有很多属性,是通过属性服务(property service)来管理它们的.接着来分析属性服务的代码,从上面的init.cpp的main函数中涉及属性服务的代码有
property_init(); |
从property_init()开始分析,该方法的主要工作是初始化属性服务配置.位置在aosp/system/core/init/property_service.cpp
void property_init() { |
接下来查看start_property_service函数的具体代码:
void start_property_service() { |
listen(property_set_fd, 8);
中的8指属性服务最多可以同时为8个试图设置属性的用户提供服务.property_set_fd代表监听
的端口(socket),这样属性服务就建立了.register_epoll_handler(property_set_fd, handle_property_set_fd)
将property_set_fd
放入了epoll句柄中,用epoll来监听property_set_fd
:当property_set_fd
中有数据到来时,init进程将用handle_property_set_fd
函数进行处理。(网上资料说:在linux新的内核中,epoll用来替换select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。
因为内核中的select实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多,epoll还没有研究过,抽时间学习一下).
当有property_set_fd
这个socket有数据来时,就会产生调用到handle_property_set_fd
方法,接着分析该方法:
static void handle_property_set_fd() |
接着看property_set((char*) msg.name, (char*) msg.value)
的具体实现:
int property_set(const char* name, const char* value) { |
看来实现设置的活交给了property_set_impl(name, value)
:
static int property_set_impl(const char* name, const char* value) { |
property_set_impl
对以ro、net和persist开头的属性进行不同的处理,给张来自罗升阳blog的一张图,帮助对android属性服务有个整体上的认识(Android属性的实现框架):
1.3 读取init.rc文件
init.rc简单介绍
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本,它主要包含五种类型语句:
Action、Commands、Services、Options和Import.在init.rc文件中一条语句通常占用一行,单词之间是用空格符来相隔的。
如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是说,可以用反斜杠将多行代码连接成一行代码。并且使用#
来进行注释。在init.rc中分成三个部分(Section),而每一部分的开头需要指定on(Actions)、service(Services)或
import。也就是说,每一个Actions, import或 Services确定一个Section。而所有的Commands和Options只能属于最近定义的
Section。如果Commands和 Options在第一个Section之前被定义,它们将被忽略。Actions和Services的名称必须唯一。如果
有两个或多个Actions或Services拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。
完整的init文件比较长,这里重点分析Zygote的启动,后续要分析该进程.
下面简单的用init.rc中的例子对Action、Commands、Services、Options和Import进行说明。
Copyright (C) 2012 The Android Open Source Project |
对于这些commands在Android源码中有文档说明,在aosp/system/core/init/readme.txt
,每个命令都有对于的代码实现,接下来就会分析到.
有了对init.rc
文件的简单认识,回到init.cpp
中,解析init.rc
代码的位置,解析init.rc
主要任务由aosp/system/core/init/init_parser.cpp
实现.
开始分析:
Parser& parser = Parser::GetInstance(); |
Parser::GetInstance()
的实现在aosp/system/core/init/init_parser.cpp
:
Parser& Parser::GetInstance() { |
parser.AddSectionParser
同样在aosp/system/core/init/init_parser.cpp
:
void Parser::AddSectionParser(const std::string& name, |
这就是将service
,on
,import
设置为了3个Section.
parser.ParseConfig("/init.rc"); |
这就是解析init.rc函数的入口,在init_parser.cpp
里面:
bool Parser::ParseConfig(const std::string& path) { |
调用了ParseConfigFile(path):
bool Parser::ParseConfigFile(const std::string& path) { |
在该方法中调用的主要的方法有ParseData
,EndFile
接下来分别对这两部分进行分析,ParseData
:
void Parser::ParseData(const std::string& filename, const std::string& data) { |
重点分析!section_parser->ParseSection(args, &ret_err)
,section_parser->ParseLineSection
,ParseSection
方法在action
,service
,import
三个不同的section调用的位置不同:action
–>aosp/system/core/init/action.cpp
:service
–>aosp/system/core/init/service.cpp
import
–>aosp/system/core/init/import_parser.cpp
section_parser->ParseLineSection方法在action
,service
中嵌套在里面分析
依次分析这对应的三个ParseSection
方法:
action ParseSection解析
ParseSection:
bool ActionParser::ParseSection(const std::vector<std::string>& args, |
ParseLineSection:
bool ActionParser::ParseLineSection(const std::vector<std::string>& args, |
调用了AddCommand:
bool Action::AddCommand(const std::vector<std::string>& args, |
接着调用了AddCommand(function, args, filename, line):
void Action::AddCommand(BuiltinFunction f, |
service ParseSection解析
ParseSection:
bool ServiceParser::ParseSection(const std::vector<std::string>& args, |
定义的每个service都是一个新的进程,定义service还commands,这些commands和执行他们的方法对应关系定义是:
Service::OptionHandlerMap::Map& Service::OptionHandlerMap::map() const { |
ParseLineSection:
bool ServiceParser::ParseLineSection(const std::vector<std::string>& args, |
接着调用了HandleLine:
bool Service::HandleLine(const std::vector<std::string>& args, std::string* err) { |
EndSection:
void ServiceParser::EndSection() { |
void ServiceManager::AddService(std::unique_ptr<Service> service) { |
import ParseSection解析
bool ImportParser::ParseSection(const std::vector<std::string>& args, |
终于把ParseData
方法粗略的过了一遍,接下来分析EndFile
,该方法其实就在import_parser.cpp
中:
void ImportParser::EndFile(const std::string& filename) { |
到此,init.rc文件的解析工作完成,接下来的的工作就是执行这些配置,由于init.rc里面配置了太多,接下来以Zygote这个service为例,分析.
在init.rc
里import /init.${ro.zygote}.rc
,这就引入了不同的zygote配置:
init.zygote32_64.rc init.zygote32.rc init.zygote64_32.rc init.zygote64.rc |
这里以init.zygote32.rc
为例:
zygote是进程 |
通过对这个叫zygote的service的解析之后,在init.rc配置文件中配置了怎么去启动zygote:
on nonencrypted |
找到class_start
对应执行的函数就可以接着分析了,对应关系就在aosp/system/core/init/builtins.cpp
:
ltinFunctionMap::Map& BuiltinFunctionMap::map() const { |
对于在rc配置文件中的commands都对应一个方法函数,可以通过grep -nr "<command>" .
在aosp/system/core/init/
中搜索.
找到需要的对应关系:
{"class_start", {1, 1, do_class_start}}, |
进入do_class_start方法:
static int do_class_start(const std::vector<std::string>& args) { |
接着看StartIfNotDisabled(),位置aosp/system/core/init/service.cpp
:
bool Service::StartIfNotDisabled() { |
还调了Start(),接着看吧:cpp
bool Service::Start() {
……
//判断需要启动的Service的对应的执行文件是否存在,不存在则不启动该Service
struct stat sb;
if (stat(args_[0].c_str(), &sb) == -1) {
ERROR(“cannot find ‘%s’ (%s), disabling ‘%s’\n”,
args_[0].c_str(), strerror(errno), name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}
……
//每一个service都是一个新进程,必然需要fork
pid_t pid = fork();
if (pid == 0) {
umask(077);
for (const auto& ei : envvars_) {
add_environment(ei.name.c_str(), ei.value.c_str());
}
for (const auto& si : sockets_) {
int socket_type = ((si.type == "stream" ? SOCK_STREAM :
(si.type == "dgram" ? SOCK_DGRAM :
SOCK_SEQPACKET)));
const char* socketcon =
!si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();
int s = create_socket(si.name.c_str(), socket_type, si.perm,
si.uid, si.gid, socketcon);
if (s >= 0) {
PublishSocket(si.name, s);
}
}
......
//execve执行程序,在`init.zygote32.rc`里写了zygote的进程程序的位置以及参数
if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));
}
_exit(127);
}
......
NotifyStateChange("running");
return true;
}
在fork出来的新的子进程里就会进入java层面,`aosp/frameworks/base/cmds/app_process/app_main.cpp`的main()函数: |
最终使用runtime.start执行”com.android.internal.os.ZygoteInit”,接着分析runtime.start的具体实现.runtime是AppRuntime类,可是AppRuntime
类没有start方法,于是找到AppRuntime的父类AndroidRuntime的start方法:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) |
该方法做了一下几件事:
- 启动java虚拟机
- 将JNI方法注册到java虚拟机
- 进入到ZygoteInit.java的main()方法
进入到ZygoteInit.java也就是进入到java层,在分析Zygote的启动过程中再接着分析,这里告一段落.需要注意的是Zygote进程的启动是在解析init.Zygote32.rc开始的,
到这里还没有完成,只是到这,C++层的执行完了.在另一篇介绍Zygote启动的文章中,再接着ZygoteInit.java的main()分析,从java层分析.在C++层只是讲Zygote进程创建,
但是什么活也没干,干活是在java层面,因此文章将zygote进程的分析从此处分成两个部分,同事也是为了让文章内容是以init进程分析为主.
###总结
本文主要分析了,init进程的启动,主要分析了一下内容:
- init进程启动属性服务的过程,分析了属性服务建立过程
- init进程对rc配置文件的解析,分为对import,action,service,commands的的解析
- 以zygote进程为例子,分析了作为service被解析之后的执行过程,一直到调用到java层的过程
###参考blog
http://blog.csdn.net/fu_kevin0606/article/details/53339001
http://blog.csdn.net/innost/article/details/47204675
http://blog.csdn.net/luoshengyang/article/details/38102011
http://blog.csdn.net/itachi85/article/details/54783506
http://blog.csdn.net/kc58236582/article/details/52247547
http://blog.csdn.net/fu_kevin0606/article/details/53320515