2024年Android最新Android系统启动流程(一)解析init进程启动过程,android高级面试题及答案简书

推荐学习资料

  • Android进阶学习全套手册

  • Android对标阿里P7学习视频

  • BAT TMD大厂Android高频面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

init的main方法做了很多事情,我们只需要关注主要的几点,在注释1处调用 property_init来对属性进行初始化并在注释2处的 调用start_property_service启动属性服务,关于属性服务,后面会讲到。注释3处 parser.ParseConfig(“/init.rc”)用来解析init.rc。解析init.rc的文件为system/core/init/init_parse.cpp文件,接下来我们查看init.rc里做了什么。

4、init.rc

=============

init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本,它主要包含五种类型语句:

Action、Commands、Services、Options和Import。init.rc的配置代码如下所示。

system/core/rootdir/init.rc

on init

sysclktz 0

Mix device-specific information into the entropy pool

copy /proc/cmdline /dev/urandom

copy /default.prop /dev/urandom

on boot

basic network init

ifup lo

hostname localhost

domainname localdomain

set RLIMIT_NICE to allow priorities from 19 to -20

setrlimit 13 40 40

这里只截取了一部分代码,其中#是注释符号。on init和on boot是Action类型语句,它的格式为:

on [&& ]* //设置触发器

//动作触发之后要执行的命令

为了分析如何创建zygote,我们主要查看Services类型语句,它的格式如下所示:

service [ ]* //<service的名字><执行程序路径><传递参数>

//option是service的修饰词,影响什么时候、如何启动services

需要注意的是在Android 7.0中对init.rc文件进行了拆分,每个服务一个rc文件。我们要分析的zygote服务的启动脚本则在init.zygoteXX.rc中定义,这里拿64位处理器为例,init.zygote64.rc的代码如下所示。

system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -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 /dev/stune/foreground/tasks

其中service用于通知init进程创建名zygote的进程,这个zygote进程执行程序的路径为/system/bin/app_process64,后面的则是要传给app_process64的参数。class main指的是zygote的class name为main,后文会用到它。

5、解析service

===============

接下来我们来解析service,会用到两个函数,一个是ParseSection,它会解析service的rc文件,比如上文讲到的init.zygote64.rc,ParseSection函数主要用来搭建service的架子。另一个是ParseLineSection,用于解析子项。代码如下所示。

system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vectorstd::string& args,

std::string* err) {

if (args.size() < 3) {

*err = “services must have a name and a program”;

return false;

}

const std::string& name = args[1];

if (!IsValidName(name)) {

*err = StringPrintf(“invalid service name ‘%s’”, name.c_str());

return false;

}

std::vectorstd::string str_args(args.begin() + 2, args.end());

service_ = std::make_unique(name, “default”, str_args);//1

return true;

}

bool ServiceParser::ParseLineSection(const std::vectorstd::string& args,

const std::string& filename, int line,

std::string* err) const {

return service_ ? service_->HandleLine(args, err) : false;

}

注释1处,根据参数,构造出一个service对象,它的classname为”default”。当解析完毕时会调用EndSection:

void ServiceParser::EndSection() {

if (service_) {

ServiceManager::GetInstance().AddService(std::move(service_));

}

}

接着查看AddService做了什么:

void ServiceManager::AddService(std::unique_ptr service) {

Service* old_service = FindServiceByName(service->name());

if (old_service) {

ERROR(“ignored duplicate definition of service ‘%s’”,

service->name().c_str());

return;

}

services_.emplace_back(std::move(service));//1

}

注释1处的代码将service对象加入到services链表中。上面的解析过程总体来讲就是根据参数创建出service对象,然后根据选项域的内容填充service对象,最后将service对象加入到vector类型的services链表中。

6、init启动zygote

==================

讲完了解析service,接下来该讲init是如何启动service,在这里我们主要讲解启动zygote这个service。在zygote的启动脚本中我们得知zygote的class name为main。在init.rc有如下配置代码:

system/core/rootdir/init.rc

on nonencrypted

A/B update verifier that marks a successful boot.

exec - root – /system/bin/update_verifier nonencrypted

class_start main

class_start late_start

其中class_start是一个COMMAND,对应的函数为do_class_start。我们知道main指的就是zygote,因此class_start main用来启动zygote。do_class_start函数在builtins.cpp中定义,如下所示。

system/core/init/builtins.cpp

static int do_class_start(const std::vectorstd::string& args) {

/* Starting a class does not start services

  • which are explicitly disabled. They must

  • be started individually.

*/

ServiceManager::GetInstance().

ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });

return 0;

}

来查看StartIfNotDisabled做了什么:

system/core/init/service.cpp

bool Service::StartIfNotDisabled() {

if (!(flags_ & SVC_DISABLED)) {

return Start();

} else {

flags_ |= SVC_DISABLED_START;

}

return true;

}

接着查看Start方法,如下所示。

bool Service::Start() {

flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));

time_started_ = 0;

if (flags_ & SVC_RUNNING) {//如果Service已经运行,则不启动

return false;

}

bool needs_console = (flags_ & SVC_CONSOLE);

if (needs_console && !have_console) {

ERROR(“service ‘%s’ requires console\n”, name_.c_str());

flags_ |= SVC_DISABLED;

return false;

}

//判断需要启动的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;

}

pid_t pid = fork();//1.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);

}

}

//2.通过execve执行程序

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);

}

return true;

}

通过注释1和2的代码,我们得知在Start方法中调用fork函数来创建子进程,并在子进程中调用execve执行system/bin/app_process,这样就会进入framework/cmds/app_process/app_main.cpp的main函数,如下所示。

frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])

{

if (zygote) {

runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote);//1

} else if (className) {

runtime.start(“com.android.internal.os.RuntimeInit”, args, zygote);

} else {

fprintf(stderr, “Error: no class name or --zygote supplied.\n”);

app_usage();

LOG_ALWAYS_FATAL(“app_process: no class name or --zygote supplied.”);

return 10;

}

}

从注释1处的代码可以得知调用runtime(AppRuntime)的start来启动zygote。

7、属性服务

==========

Windows平台上有一个注册表管理器,注册表的内容采用键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,它还是能够根据之前在注册表中的记录,进行相应的初始化工作。Android也提供了一个类似的机制,叫做属性服务。

在本文的开始,我们提到在init.cpp代码中和属性服务相关的代码有:

system/core/init/init.cpp

property_init();

start_property_service();

这两句代码用来初始化属性服务配置并启动属性服务。首先我们来学习服务配置的初始化和启动。

属性服务初始化与启动

property_init函数具体实现的代码如下所示。

system/core/init/property_service.cpp

void property_init() {

if (__system_property_area_init()) {

ERROR(“Failed to initialize property area\n”);

exit(1);

}

}

__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:

void start_property_service() {

property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,

0666, 0, 0, NULL);//1

if (property_set_fd == -1) {

ERROR(“start_property_service socket creation failed: %s\n”, strerror(errno));

exit(1);

}

listen(property_set_fd, 8);//2

register_epoll_handler(property_set_fd, handle_property_set_fd);//3

}

注释1处用来创建非阻塞的socket。注释2处调用listen函数对property_set_fd进行监听,这样创建的socket就成为了server,也就是属性服务;listen函数的第二个参数设置8意味着属性服务最多可以同时为8个试图设置属性的用户提供服务。注释3处的代码将property_set_fd放入了epoll句柄中,用epoll来监听property_set_fd:当property_set_fd中有数据到来时,init进程将用handle_property_set_fd函数进行处理。

在linux新的内核中,epoll用来替换select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为内核中的select实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

** 属性服务处理请求**

从上文我们得知,属性服务接收到客户端的请求时,会调用handle_property_set_fd函数进行处理:

system/core/init/property_service.cpp

static void handle_property_set_fd()

{

if(memcmp(msg.name,“ctl.”,4) == 0) {

close(s);

if (check_control_mac_perms(msg.value, source_ctx, &cr)) {

handle_control_message((char*) msg.name + 4, (char*) msg.value);

} else {

ERROR(“sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n”,

msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);

}

} else {

//检查客户端进程权限

if (check_mac_perms(msg.name, source_ctx, &cr)) {//1

property_set((char*) msg.name, (char*) msg.value);//2

} else {

ERROR(“sys_prop: permission denied uid:%d name:%s\n”,

cr.uid, msg.name);

}

close(s);

}

freecon(source_ctx);

break;

default:

close(s);

break;

}

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ission denied uid:%d name:%s\n",

cr.uid, msg.name);

}

close(s);

}

freecon(source_ctx);

break;

default:

close(s);

break;

}

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

[外链图片转存中…(img-7Ujfr1Y9-1714966626442)]

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值