查看原文
其他

Android 10.0系统启动之init进程(四)-「Android取经之路」

IngresGe IngresGe 2021-11-05


上一节主要讲解了Init进程的第二阶段启动过程以及信号处理过程。

Android取经之路——启动篇

Android系统架构-[Android取经之路]

Android是怎么启动的-[Android取经之路]

Android系统启动之init进程(一)-「Android取经之路」

Android系统启动之init进程(二)-「Android取经之路」

Android 10.0系统启动之init进程(三)-「Android取经之路」

本节主要讲解init进程第二阶段的属性服务和init.rc启动流程。


6.属性服务

我们在开发和调试过程中看到通过property_set可以轻松设置系统属性,那干嘛这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性, Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看具体的流程是什么。



6.1 property_init

代码路径:platform/system/core/property_service.cpp 作用:初始化属性系统,并从指定文件读取属性,并进行SELinux注册,进行属性权限控制    清除缓存,这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为 /dev/_properties_ 然后就是调用CreateSerializedPropertyInfo加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存

void property_init() { //设置SELinux回调,进行权限控制 selinux_callback cb; cb.func_audit = PropertyAuditCallback; selinux_set_callback(SELINUX_CB_AUDIT, cb); mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); CreateSerializedPropertyInfo(); if (__system_property_area_init()) { LOG(FATAL) << "Failed to initialize property area"; } if (!property_info_area.LoadDefaultPath()) { LOG(FATAL) << "Failed to load serialized property info file"; }}

通过CreateSerializedPropertyInfo 来加载以下目录的contexts:

1)与SELinux相关

/system/etc/selinux/plat_property_contexts

/vendor/etc/selinux/vendor_property_contexts

/vendor/etc/selinux/nonplat_property_contexts

/product/etc/selinux/product_property_contexts

/odm/etc/selinux/odm_property_contexts

2)与SELinux无关

/plat_property_contexts

/vendor_property_contexts

/nonplat_property_contexts

/product_property_contexts

/odm_property_contexts


6.2 StartPropertyService

代码路径:platform/system/core/init.cpp

作用:启动属性服务

首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性,

最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd

void StartPropertyService(Epoll* epoll) { property_set("ro.property_service.version", "2");
//建立socket连接 if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, {})) { property_set_fd = *result; } else { PLOG(FATAL) << "start_property_service socket creation failed: " << result.error(); }
// 最大监听8个并发 listen(property_set_fd, 8);
// 注册property_set_fd,当收到句柄改变时,通过handle_property_set_fd来处理 if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) { PLOG(FATAL) << result.error(); }}


6.3 handle_property_set_fd

代码路径:platform/system/core/property_service.cpp

作用:建立socket连接,然后从socket中读取操作信息,根据不同的操作类型,调用HandlePropertySet做具体的操作

    HandlePropertySet是最终的处理函数,以"ctl"开头的key就做一些Service的Start,Stop,Restart操作,其他的就是调用property_set进行属性设置,不管是前者还是后者,都要进行SELinux安全性检查,只有该进程有操作权限才能执行相应操作。


static void handle_property_set_fd() { static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
// 等待客户端连接 int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC); if (s == -1) { return; }
ucred cr; socklen_t cr_size = sizeof(cr); // 获取连接到此socket的进程的凭据 if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED"; return; }
// 建立socket连接 SocketConnection socket(s, cr); uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0; // 读取socket中的操作信息 if (!socket.RecvUint32(&cmd, &timeout_ms)) { PLOG(ERROR) << "sys_prop: error while reading command from the socket"; socket.SendUint32(PROP_ERROR_READ_CMD); return; }
// 根据操作信息,执行对应处理,两者区别一个是以char形式读取,一个以String形式读取 switch (cmd) { case PROP_MSG_SETPROP: { char prop_name[PROP_NAME_MAX]; char prop_value[PROP_VALUE_MAX];
if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) || !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) { PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket"; return; }
prop_name[PROP_NAME_MAX-1] = 0; prop_value[PROP_VALUE_MAX-1] = 0;
std::string source_context; if (!socket.GetSourceContext(&source_context)) { PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed"; return; }
const auto& cr = socket.cred(); std::string error; uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, &error); if (result != PROP_SUCCESS) { LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error; }
break; }
case PROP_MSG_SETPROP2: { std::string name; std::string value; if (!socket.RecvString(&name, &timeout_ms) || !socket.RecvString(&value, &timeout_ms)) { PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket"; socket.SendUint32(PROP_ERROR_READ_DATA); return; }
std::string source_context; if (!socket.GetSourceContext(&source_context)) { PLOG(ERROR) << "Unable to set property '" << name << "': getpeercon() failed"; socket.SendUint32(PROP_ERROR_PERMISSION_DENIED); return; }
const auto& cr = socket.cred(); std::string error; uint32_t result = HandlePropertySet(name, value, source_context, cr, &error); if (result != PROP_SUCCESS) { LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error; } socket.SendUint32(result); break; }
default: LOG(ERROR) << "sys_prop: invalid command " << cmd; socket.SendUint32(PROP_ERROR_INVALID_CMD); break; }}



7.第三阶段init.rc

当属性服务建立完成后,init的自身功能基本就告一段落,接下来需要来启动其他的进程。但是init进程如何其他其他进程呢?其他进程都是一个二进制文件,我们可以直接通过exec的命令方式来启动,例如 ./system/bin/init second_stage,来启动init进程的第二阶段。但是Android系统有那么多的Native进程,如果都通过传exec在代码中一个个的来执行进程,那无疑是一个灾难性的设计。

在这个基础上Android推出了一个init.rc的机制,即类似通过读取配置文件的方式,来启动不同的进程。接下来我们就来看看init.rc是如何工作的。

init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本。

init.rc在手机的目录:./init.rc

init.rc主要包含五种类型语句:

  • Action

  • Command

  • Service

  • Option

  • Import

7.1 Action

动作表示了一组命令(commands)组成.动作包括一个触发器,决定了何时运行这个动作

Action: 通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:

  • on early-init; 在初始化早期阶段触发;

  • on init; 在初始化阶段触发;

  • on late-init; 在初始化晚期阶段触发;

  • on boot/charger: 当系统启动/充电时触发;

  • on property:<key>=<value>: 当属性值满足条件时触发;


7.2 Command

command是action的命令列表中的命令,或者是service中的选项 onrestart 的参数命令,命令将在所属事件发生时被一个个地执行.

下面列举常用的命令

  • class_start <service_class_name>: 启动属于同一个class的所有服务;

  • class_stop <service_class_name> : 停止指定类的服务

  • start <service_name>: 启动指定的服务,若已启动则跳过;

  • stop <service_name>: 停止正在运行的服务

  • setprop <name> <value>:设置属性值

  • mkdir <path>:创建指定目录

  • symlink <target> <sym_link>: 创建连接到<target>的<sym_link>符号链接;

  • write <path> <string>: 向文件path中写入字符串;

  • exec: fork并执行,会阻塞init进程直到程序完毕;

  • exprot <name> <name>:设定环境变量;

  • loglevel <level>:设置log级别

  • hostname <name> : 设置主机名

  • import <filename> :导入一个额外的init配置文件


7.3 Service

服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。

命令:

service <name><pathname> [ <argument> ]*    <option>    <option>

参数

含义

<name>

表示此服务的名称

<pathname>

此服务所在路径因为是可执行文件,所以一定有存储路径。

<argument>

启动服务所带的参数

<option>

对此服务的约束选项

init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。

例如: service servicemanager /system/bin/servicemanager代表的是服务名为servicemanager,服务执行的路径为/system/bin/servicemanager。


7.4 Options

Options是Service的可选项,与service配合使用

  • disabled: 不随class自动启动,只有根据service名才启动;

  • oneshot: service退出后不再重启;

  • user/group: 设置执行服务的用户/用户组,默认都是root;

  • class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;

  • onrestart:当服务重启时执行相应命令;

  • socket: 创建名为/dev/socket/<name>的socket

  • critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式

default: 意味着disabled=false,oneshot=false,critical=false。


7.5 import

用来导入其他的rc文件

命令:

import <filename>


7.6 init.rc 解析过程

7.6.1 LoadBootScripts

代码路径:platform\system\core\init\init.cpp

作用:如果没有特殊配置ro.boot.init_rc,则解析./init.rc

把/system/etc/init,/product/etc/init,/product_services/etc/init,/odm/etc/init,

/vendor/etc/init 这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里的rc文件。


static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } if (!parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/etc/init"); } if (!parser.ParseConfig("/product_services/etc/init")) { late_import_paths.emplace_back("/product_services/etc/init"); } if (!parser.ParseConfig("/odm/etc/init")) { late_import_paths.emplace_back("/odm/etc/init"); } if (!parser.ParseConfig("/vendor/etc/init")) { late_import_paths.emplace_back("/vendor/etc/init"); } } else { parser.ParseConfig(bootscript); }}

Android7.0后,init.rc进行了拆分,每个服务都有自己的rc文件,他们基本上都被加载到/system/etc/init,/vendor/etc/init, /odm/etc/init等目录,等init.rc解析完成后,会来解析这些目录中的rc文件,用来执行相关的动作。

代码路径:platform\system\core\init\init.cpp

作用:创建Parser解析对象,例如service、on、import对象


Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) { Parser parser;
parser.AddSectionParser( "service", std::make_unique<ServiceParser>(&service_list, subcontexts, std::nullopt)); parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts)); parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;}


7.6.2 执行Action动作

    按顺序把相关Action加入触发器队列,按顺序为 early-init -> init -> late-init. 然后在循环中,执行所有触发器队列中Action带Command的执行函数。

am.QueueEventTrigger("early-init");am.QueueEventTrigger("init");am.QueueEventTrigger("late-init");...while (true) {if (!(waiting_for_prop || Service::is_exec_service_running())) { am.ExecuteOneCommand(); }}


7.6.2 Zygote启动

    从Android 5.0的版本开始,Android支持64位的编译,因此zygote本身也支持32位和64位。通过属性ro.zygote来控制不同版本的zygote进程启动。

    在init.rc的import段我们看到如下代码:

    import /init.${ro.zygote}.rc     // 可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件

 init.rc位于/system/core/rootdir下。在这个路径下还包括四个关于zygote的rc文件。

分别是

init.zygote32.rc,

init.zygote32_64.rc,

init.zygote64.rc,

init.zygote64_32.rc,由硬件决定调用哪个文件。

这里拿64位处理器为例,init.zygote64.rc的代码如下所示:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server class main # class是一个option,指定zygote服务的类型为main priority -20 user root group root readproc reserved_disk socket zygote stream 660 root system # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket socket usap_pool_primary stream 660 root system 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 onrestart restart wificond writepid /dev/cpuset/foreground/tasks

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:

service zygote :init.zygote64.rc 中定义了一个zygote服务。init进程就是通过这个service名称来创建zygote进程

/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:

zygote这个服务,通过执行进行/system/bin/app_process64 并传入4个参数进行运行:

参数1:-Xzygote  该参数将作为虚拟机启动时所需的参数

参数2:/system/bin 代表虚拟机程序所在目录

参数3:--zygote 指明以ZygoteInit.java类中的main函数作为虚拟机执行入口

参数4:--start-system-server 告诉Zygote进程启动systemServer进程


8.总结

init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略。

init进程第二阶段主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些功能落实。

init进行第三阶段主要是解析init.rc 来启动其他进程,进入无限循环,进行子进程实时监控。





: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存