Android13系统启动阶段大致分为FirstStageMain阶段和SecondStageMain,此章主要讲SecondStageMain阶段
(若分析有误敬请指教)
在基于Android13的系统启动流程分析(三)之FirstStageMain阶段已经讲解过android系统启动的基本介绍了,这里不再单独介绍了
我们先看是怎么进入该阶段的,仍然是由用户空间层main.cpp调用,先简单的说一下第二阶段主要是干什么的:
/dev/.booting
设备块代表init正在初始化执行中...
if (argc > 1) {
...
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);//第二阶段执行
}
}
return FirstStageMain(argc, argv); //第一阶段执行
}
位于/system/core/init/init.cpp
,直接上代码,代码注释中分步骤来分析
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
// 针对产生异常的进程进行信号处理,确保子进程能重启,如果主进程pid=1发生异常则触发crash
// 已经在上个文章分析过该函数
InstallRebootSignalHandlers();
}
...
// 初始化kernel log,所有的kernel log均输出在/dev/kmsg设备节点上
SetStdioToDevNull(argv);
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
...
// Init不应该因为依赖于任何其他进程而崩溃,因此我们忽略主进程的信号管道信息
// 但我们不想忽略子进程的SIGPIPE(信号管道),因此我们为信号处理程序设置了一个no op函数
// SIGPIPE信号产生的场景举例
// ① 初始时,C、S连接建立,若某一时刻,C端进程宕机或者被KILL而终止(终止的C端进程将会关闭打开的文件描述符,即向S端发送FIN段),S端收到FIN后,响应ACK
// ② 假设此时,S端仍然向C端发送数据:当第一次写数据后,S端将会收到RST分节; 当收到RST分节后,第二次写数据后,S端将收到SIGPIPE信号(S端进程被终止)
{
struct sigaction action = {.sa_flags = SA_RESTART};
action.sa_handler = [](int) {};
// sigaction是一个函数,可以用来查询或设置信号处理方式
sigaction(SIGPIPE, &action, nullptr);
}
// MIN_OOM_SCORE_ADJUST = -1000;
// MAX_OOM_SCORE_ADJUST = 1000;
// 设置进程的优先级,例如APK优先级是AMS计算出来并下发到/proc/1/oom_score_adj
// 统一由init进程设置/proc/**/oom_score_adj为-1000优先级
if (auto result =
WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
!result.ok()) {
LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
<< " to /proc/1/oom_score_adj: " << result.error();
}
...
// 创建 /dev/.booting 文件,就是个标记,表示booting进行中
// is_booting()函数会依靠空文件".booting"来判断是否进程处于初始化中,初始化结束后,这个文件会被删除
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// 当设备解锁时,允许adb root
// 如果设备unlocked(解锁了),则会修改selinux规则,放大用户权限,这一点在第一阶段已经完成了
// 并设置了INIT_FORCE_DEBUGGABLE环境变量,这里只是根据环境变量获取第一阶段的内容
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
bool load_debug_prop = false;
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
unsetenv("INIT_FORCE_DEBUGGABLE");
// 如果设备未unlock,则卸载关于debug版本的/debug_ramdisk
// 让属性值读取/ramdisk而不是/debug_ramdisk,因为非unlock,不需要debug ramdisk
if (!load_debug_prop) {
// setup 1
UmountDebugRamdisk();
}
// 初始化属性服务
// 获取system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,等其他build.prop并加载到properties map结构中
// 然后会将该properties结构,通过MMAP映射到全局内存中,供所有进程调用,主要ro是只读,只能够写一次,即初始化时给的一次值
// 注意build.prop有优先级,product下的优先级最高,因为是map结构,针对key相同的属性值时,会覆盖
// setup 2
PropertyInit();
// Umount second stage resources after property service has read the .prop files.
// 在属性服务读取.prop文件后,将卸载/second_stage_resources,因为已经用不到了,已经将属性值加载到内存当中了
UmountSecondStageRes();
// Umount the debug ramdisk after property service has read the .prop files when it means to.
// 若是debug版本,已经获取了属性值过后,也将卸载/debug_ramdisk
if (load_debug_prop) {
UmountDebugRamdisk();
}
// 挂载第二阶段(该阶段)的文件系统,第一阶段已经挂载了很多基本的文件系统了以及重要的分区
// 挂载/apex:简单点说apex为了解决性能而产生的机制,APK可以通过内置升级,但系统升级可是个大问题
// setup 3
MountExtraFilesystems();
...
// 之前初始化了属性服务,这里将开始属性服务,其实它就是一个socket
// 创建socket,处理客户端发来的请求,决定是更新属性值还是新增属性值
// setup 4
StartPropertyService(&property_fd);
...
// 根据ro.vndk.version 版本号,将/system/vendor_overlay和/product/vendor_overlay挂载在vendor上
// 也就是会覆盖vendor分区内容
// setup 5
fs_mgr_vendor_overlay_mount_all();
// 根据ro.oem_unlock_supported属性值来决定是否可以对设备进行unlock(解锁)
// 若ro.oem_unlock_supported:「1」则代表 设备支持刷写unlock,若不支持该值为0
// 如果设备支持刷写解锁,ro.boot.verifiedbootstate则会为orange,根据orange状态,把androidboot.flash.locked设置为1
// 如果设备不支持刷新解锁,ro.boot.verifiedbootstate则会为green,根据orange状态,把androidboot.flash.locked设置为0
// androidboot.flash.locked在系统启动完成后会形成属性值
// (或 /firmware/android/flash.locked DT 属性)设置为“1”(如果已锁定)或“0”(如果已解锁)来指示锁定状态。
export_oem_lock_status();
// 持续监控/proc/mounts 节点(fopen("/proc/mounts", "re")),主要是解析该文件
// 解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等(空格分割/dev/block/dm-33 /mnt/pass_through/0/emulated ext4 rw)
// 将解析内容生成实体类追加到要挂载的mounts_中,主要就是对mounts文件解析,更新mounts_中的信息
// setup 6
MountHandler mount_handler(&epoll);
...
// 将根目录下所有的目录设置为全局共享
// 将根目录/{分区}类型设置为共享,以便默认情况下所有进程都可以看到任何装载事件(例如/data)
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
...
// setup 7
// 创建ActionManager对象和ServiceList对象
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 加载rc文件,保存到action manager和service list中
// rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析
// 主要解析rc中的:service,on,Import,包含了zygote.rc,路径:/system/bin/app_process64
// 在文件系统挂载的第一阶段,system/vendor分区已经成功挂载,而其它分区的挂载则通过rc来挂载
// 后面主要分析一下on early-init和on init和zygote
LoadBootScripts(am, sm);
...
// setup 8
// 构建action和触发器(on early-init),放到event_queue,等待执行函数
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueEventTrigger("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(SetMmapRndBitsAction, "SetMmapRndBits");
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return {};
},
"KeychordInit");
// Trigger all the boot actions to get us started.
// 构建action和触发器(on init),放到event_queue,等待执行函数
am.QueueEventTrigger("init");
// Don't mount filesystems or start core system services in charger mode.
// 如果是充电模式则不需要挂载文件系统和不要启动核心服务
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
// 运行所有属性触发器(action),例如 on property
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
// Restore prio before main loop
// 设置进程优先级,主进程不能被销毁和退出,循环处理rc中的服务相关
setpriority(PRIO_PROCESS, 0, 0);
while (true) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()";
HandlePowerctlMessage(*shutdown_command);
shutdown_state.set_do_shutdown(false);
}
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
// 执行队列中的action
// 队列中依次执行每个action中携带command对应的执行函数
am.ExecuteOneCommand();
}
if (!IsShuttingDown()) {
// 处理sm(ServiceList)中服务超时重启相关(init.rc中的service)
auto next_process_action_time = HandleProcessActions();
if (next_process_action_time) {
epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
*next_process_action_time - boot_clock::now());
if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
}
}
...
return 0;
}
先贴一下setup 1的代码块,主要分析UmountDebugRamdisk
函数
if (!load_debug_prop) {
// setup 1
UmountDebugRamdisk();
}
static void UmountDebugRamdisk() {
if (umount("/debug_ramdisk") != 0) {
PLOG(ERROR) << "Failed to umount /debug_ramdisk";
}
}
如果设备未unlock,则卸载关于debug版本的/debug_ramdisk,让属性值读取/ramdisk而不是/debug_ramdisk
该步骤主要作用是初始化属性值服务,这里只是一个初始化的动作
// setup 2
PropertyInit();
system/build.prop
,vendor/build.prop
,/odm/build.prop
,/product/build.prop
,等其他build.prop并加载到properties map结构中,然后会将该properties结构,通过MMAP映射到全局内存中,供所有进程调用,主要ro是只读,只能够写一次,即初始化时给的一次值,注意build.prop有优先级,product下的优先级最高,因为是map结构,针对key相同的属性值时,会覆盖PropertyInit
位于/system/core/init/property_service.cpp
void PropertyInit() {
...
// 建立属性服务设备文件(linux思想,万物皆文件系统)
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
// 创建序列化过后的propertyInfo实体,主要就是读取property_contexts文件
CreateSerializedPropertyInfo();
// 这里主要步骤是:通过mmap映射,将文件(/dev/__properties__/{..})映射进内存(初始化属性内存映射文件)
// 这里将文件映射进内存,用于后续对__prooerties__目录(也可以说是文件,将代码块映射为file类型)进行内存共享
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
// 加载/dev/__properties__/property_info,此文件是序列化过的,无法直接查看内容
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
// 读取/proc/device-tree/firmware/android/目录下的文件,生成ro.boot.xxx属性值
// 这三个函数主要就是生成ro.boot.xx属性值,这里不详细研究
ProcessKernelDt();
ProcessKernelCmdline();
ProcessBootconfig();
// 初始化ro.xx,将ro.boot.xx的属性值复制给ro.xxx
// { "ro.boot.serialno", "ro.serialno", UNSET, },
// { "ro.boot.mode", "ro.bootmode", "unknown", },
// { "ro.boot.baseband", "ro.baseband", "unknown", },
// { "ro.boot.bootloader", "ro.bootloader", "unknown", },
// { "ro.boot.hardware", "ro.hardware", "unknown", },
// { "ro.boot.revision", "ro.revision", "0", },
ExportKernelBootProps();
// 读取{system/vendor/odm/product}/build.prop等...
// 将build.prop通过MMAP映射到全局内存中,供所有进程访问
PropertyLoadBootDefaults();
调用__system_property_area_init
通过mmap映射,将文件(/dev/properties/{包含了上下文和propertys_info实体:保存了property_contexts文件内容})映射进内存(初始化属性内存映射文件), 这里将文件映射进内存,用于后续对__prooerties__目录(也可以说是文件,将代码块映射为file类型)进行内存共享
CreateSerializedPropertyInfo:
创建序列化过后的属性值信息(既然序列化了,那肯定是要跨进程通信)
(1).读取{system_ext,vendor,product,odm,system}_property_contexts属性值安全上下文并赋值给:property_infos
(2).property_infos属于容器类型,读取不同的property_contexts将会追加到末尾,而不是覆盖原本内容
(3).将property_infos实体序列化,使其可以跨进程传递消息
(4).将property_infos实体写入/dev/properties/property_info驱动节点中
PropertyLoadBootDefaults
这里会将属性值全部写入build.prop里,分为:system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,注意是有优先级顺序的,按先后顺序覆盖,获取build.prop分别是直接从指定文件里获取和从指定分区中获取,这两个方式作用都一样,只不过第二种需要区分出分区里是否存在{partition}/{etc}/build.prop
,有的分区是不存在{partition}/etc/build.prop
这个文件,而是直接存在于{partition}/build.prop
一切都写到了注释里,继续分析比较重要的2个函数CreateSerializedPropertyInfo
和PropertyLoadBootDefaults
void CreateSerializedPropertyInfo() {
auto property_infos = std::vector<PropertyInfoEntry>();
//判断文件是否存在,并判断文件是否可写(属性服务的安全上下文,之前有提过设备节点,服务,属性值都要遵守selinux规则)
if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
// 加载property_contexts文件,该文件内容都是配置的属性值上下文,属于selinux相关知识
// 通过ParsePropertyInfoFile解析该文件,得到property_infos
if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
&property_infos)) {
return;
}
// 如果这里system_ext/vendor/product没有挂载上(例如在恢复的情况下,vendor分区将不会安装),则无法继续加载该上下文,该分区会在第一阶段挂载
// 从下面的代码可以看出来,property_infos的是容器类型vector()
// 所以这里并没有优先级也没有以哪个property_contexts为准,而是根据是否存在对应的分区而append加载
// 也就是在对应后面追加内容,而不是覆盖:property_infos->emplace_back(property_info_entry);
if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
&property_infos);
}
if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
&property_infos)) {
// Fallback to nonplat_* if vendor_* doesn't exist.
LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
&property_infos);
}
if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
&property_infos);
}
if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
}
// 若/system/etc/selinux/plat_property_contexts无法读取,则else
} else {
// 由于system下的安全上下文未创建,则可能是system出现异常未挂载上,或者供应商修改过plat_property_contexts
// 一般供应商都是复写,而不会直接更改文件名称
// 若找不到该文件,则加载根目录下的这些属性值安全上下文
if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
return;
}
LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
// Fallback to nonplat_* if vendor_* doesn't exist.
LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
}
LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
}
// 序列化property_infos实体,使其可以跨进程传递
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;
}
// 将property_infos写入/dev/__properties__/property_info设备文件中
constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
PLOG(ERROR) << "Unable to write serialized property infos to file";
}
selinux_android_restorecon(kPropertyInfosPath, 0);
}
以上的步骤都是对property_infos变量进行赋值,数据就是property_contexts文件内容,而property_infos是属于vector<PropertyInfoEntry>()容器类型,而每次加载property_infos时都是调用的property_infos->emplace_back(property_info_entry);
在容器后面追加数据,不会覆盖原有的数据
读取property_contexts文件内容,将内容传递给property_infos实体
void PropertyLoadBootDefaults() {
std::map<std::string, std::string> properties;
// 如果是恢复模式则加载/prop.default
if (IsRecoveryMode()) {
load_properties_from_file("/prop.default", nullptr, &properties);
}
// 这里还没执行,只是一个未执行的代码块,从分区里读取build.prop文件
const auto load_properties_from_partition = [&properties](const std::string& partition,
int support_legacy_path_until) {
// 加载{system_ext,product等分区}/etc/build.prop文件
// 以后代码上获取的属性值就是从该文件中获取的
auto path = "/" + partition + "/etc/build.prop";
if (load_properties_from_file(path.c_str(), nullptr, &properties)) {
return;
}
...
}
// 获取第一阶段生成的second_stage_resources/system/etc/ramdisk/build.prop
// 并追加到properties中(这里是map结构,注意会覆盖内容)
LoadPropertiesFromSecondStageRes(&properties);
// 先读取的/system/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
load_properties_from_file("/system/build.prop", nullptr, &properties);
// 获取/system_ext分区下的build.prop/default.prop,赋值给然后赋值给propertiess(这里是map结构,注意会覆盖内容)
load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30);
// 继续读取的/vendor/default.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
load_properties_from_file("/vendor/default.prop", nullptr, &properties);
// }
// 继续读取的/vendor/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
load_properties_from_file("/vendor/build.prop", nullptr, &properties);
// 继续读取的/vendor_dlkm/etc/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
load_properties_from_file("/vendor_dlkm/etc/build.prop", nullptr, &properties);
// 继续读取的/odm_dlkm/etc/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
load_properties_from_file("/odm_dlkm/etc/build.prop", nullptr, &properties);
// 获取/{odm,product}.prop/default.prop,赋值给然后赋值给propertiess(这里是map结构,注意会覆盖内容)
load_properties_from_partition("odm", /* support_legacy_path_until */ 28);
load_properties_from_partition("product", /* support_legacy_path_until */ 30);
// 因为propertiess是map结构,如果key一样则会覆盖内容,所以以上代码的顺序不能调换,优先级最高的是/product下的build.prop
// /system->/system_ext->/vendor->/omd->/product
// 如果"/debug_ramdisk/adb_debug.prop"存在,说明设备已经unlock过了,则加载unlock过后的属性值,例如ro.debugger=1,则是开启了调试模式
if (access(kDebugRamdiskProp, R_OK) == 0) {
LOG(INFO) << "Loading " << kDebugRamdiskProp;
load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
}
// 将从build.prop,default.prop获取的properties,循环设置属性值
// 这里是把.prop文件里的属性值通过mmap映射到内存中,使得所有进程可以访问(全局)
for (const auto& [name, value] : properties) {
std::string error;
// 如果是ro则是只读,只能设置一次,再次设置会无效,如果存在相同的key,则会调用update更新
if (PropertySet(name, value, &error) != PROP_SUCCESS) {
LOG(ERROR) << "Could not set '" << name << "' to '" << value
<< "' while loading .prop files" << error;
}
}
...
// 设置persist.sys.usb.config属性值来决定是否开启调试模式(adb或none)
update_sys_usb_config();
}
以上代码主要做的一个动作:读取各个分区里的build.prop或直接从指定目录下读取build.prop并调用PropertySet
设置到全局内存中,让所有进程访问
主要的两个函数:load_properties_from_partition
和load_properties_from_file
,分别从分区里读取和从指定文件读取,为什么这么做呢?因为有的分区下是没有/etc目录的,无法直接指定文件位置,所以通过调用 "/" + partition + "/etc/build.prop"
来读取,若不存在该文件则直接return。
注意这里读取了{system,system_ext,vendor,vendor_dlkm,odm_dlkm,odm,product}/build.prop,由于properties属于map结构,如果key相同是会覆盖原有的值,所以这里是有优先级排序的:
如果在system中自定义了属性值,又在product自定义了一样的属性值,那么是以product为准
这里只是init初始化过程,读取build.prop并解析出来每一行属性值并调用PropertySet
设置到全局内存中
继续分析一下PropertySet
函数,该函数比较简单
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
...
// 找到该属性值
prop_info* pi = (prop_info*) __system_property_find(name.c_str());
if (pi != nullptr) {
// 如果是以ro开头则代表只读,禁止写入,返回error
if (StartsWith(name, "ro.")) {
*error = "Read-only property was already set";
return PROP_ERROR_READ_ONLY_PROPERTY;
}
// 若存在该属性值且非ro 则更新属性值
__system_property_update(pi, value.c_str(), valuelen);
} else {
// 若找不到该属性值则新增
int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
if (rc < 0) {
*error = "__system_property_add failed";
return PROP_ERROR_SET_FAILED;
}
}
// 如果是以persist.开头,则会全部写进/data/property/persistent_properties
// 属于一个缓存机制
// std::string persistent_property_filename = "/data/property/persistent_properties";
if (persistent_properties_loaded && StartsWith(name, "persist.")) {
WritePersistentProperty(name, value);
}
...
return PROP_SUCCESS;
}
以上代码就是若存在属性值则更新,若不存在则新增。如果是ro开头的属性值则代表只读,只能初始化的时候给默认值,后续不允许修改值,如果是persist开头的则会全部缓存进/data/property/persistent_properties
,persist开头的属性值是可改的,如果用户修改过了persist开头的属性值相当于修改了/data/property/persistent_properties
里的属性值,那么重启后仍然生效,并不会还原默认值。这样即不影响属性值的原子性(原有的属性值),又给了开发者/用户操作的空间,如果是刷机或恢复出厂则会还原
如果是系统开发者自定义了属性值,但是发现默认定义的时候属性值无法写入,则可能是property_contexts安全上下文影响,可以直接修改这里的代码,__system_property_add
强行调用该方法即可
setup 4
MountExtraFilesystems();
static void MountExtraFilesystems() {
#define CHECKCALL(x) \
if ((x) != 0) PLOG(FATAL) << #x " failed.";
// /apex is used to mount APEXes
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /linkerconfig is used to keep generated linker configuration
CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
}
可以看到MountExtraFilesystems
主要就挂载了/apex和/linkerconfig并归属于tmpfs文件系统(运行在内存的文件系统,运行速度较快),这里主要探讨一下APEX.
应用程序可以通过更新APK来升级,供应商客制化系统后可以通过OTA进行系统升级,而针对google 开发者来修复原生的系统bug该如何更新呢?那就是通过google发布的安全patch和更新manline以及apex来解决
// setup 4
StartPropertyService(&property_fd);
在setup2中初始化了property,获取了build.prop和property_contexts并设置为内存中全局共享
void StartPropertyService(int* epoll_socket) {
// 在init阶段version=1,这里已经升级到2了
InitPropertySet("ro.property_service.version", "2");
// 创建sockets,套接字,可以用于网络通信,也可以用于本机内的进程通信
// socketpair()函数用于创建一对无名的,相互连接的套接字
// 如果函数创建成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1
int sockets[2];
// 参数1:表示协议族AF_UNIX
// 参数2:表示协议,SOCK_SEQPACKET提供连续可靠的数据包连接
// SOCK_CLOEXEC:当文件描述符设置了O_CLOEXEC属性后,在调用exec函数族时,文件描述符就会自动关闭,无需手动关闭
// 而SOCK_DGRAM是基于UDP的
// 参数3:表示类型,只能为0
// 参数4:套节字柄,该两个句柄作用相同,均能进行读写双向操作
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
PLOG(FATAL) << "Failed to socketpair() between property_service and init";
}
...
// PROP_SERVICE_NAME:/dev/socket/property_service,创建socket
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, {});
result.ok()) {
property_set_fd = *result;
} else {
LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
// 监听socket:/dev/socket/property_service,最大连接:8
listen(property_set_fd, 8);
// 开启线程处理socket
auto new_thread = std::thread{PropertyServiceThread};
property_service_thread.swap(new_thread);
}
这里就是创建了socket,而属性值服务的本质就是一个socket(/dev/socket/property_service),最大连接为:8,持续等待连接,连接成功后决定是更新还是新增属性值,仍然是调用的PropertySet
,继续分析一下PropertyServiceThread
,该函数里调用了handle_property_set_fd
函数,来看看具体实现
static void handle_property_set_fd() {
// 设置超时:2s
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
// 可以设置四个参数,所以用的是accept4,而当启用了SOCK_CLOEXEC参数后,进程在调用exec函数族时,文件描述符就会自动关闭,无需手动关闭
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
return;
}
...
switch (cmd) {
case PROP_MSG_SETPROP: {
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
...
prop_name[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
...
// 当收到客户端发来的请求,去更新或新增属性值时会调用HandlePropertySet去处理
const auto& cr = socket.cred();
std::string error;
uint32_t result =
HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &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;
}
...
}
}
可以看到设置了连接超时时间为2000ms,执行 case PROP_MSG_SETPROP
,获取属性值的名称和值,调用HandlePropertySet
里的PropertySet
进行更新属性值或新增。PropertySet
已经分析过了
至此属性值相关就分析完毕了,简单的来说就是读取build.prop属性值共享到全局内存中让所有进程获取,然后创建属性值服务(socket),持续监听客户端(哪个进程去调用更新属性值就是当前客户端)发来的请求,最大只能同时受理8个来自客户端的请求,若有客户端请求则去更新属性值或新增属性值
// setup 5
fs_mgr_vendor_overlay_mount_all();
这里的代码只要是针对如果有vendor_overlay分区,则覆盖/vendor分区,主要看供应商的客制化
const std::vector<const std::string> kVendorOverlaySourceDirs = {
"/system/vendor_overlay/",
"/product/vendor_overlay/",
};
bool fs_mgr_vendor_overlay_mount_all() {
...
// 获取 "/system/vendor_overlay/","/product/vendor_overlay/"下的所有子目录
// 将vendor_overlay挂载到vendor上,若存在该覆盖分区则会覆盖之前的vendor分区
const auto vendor_overlay_dirs = fs_mgr_get_vendor_overlay_dirs(vndk_version);
if (vendor_overlay_dirs.empty()) return true;
if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) {
LINFO << "vendor overlay: kernel does not support overlayfs";
return false;
}
// Mount each directory in /(system|product)/vendor_overlay/ on /vendor
// 挂载vendor_overlay到/vendor分区上
auto ret = true;
for (const auto& vendor_overlay_dir : vendor_overlay_dirs) {
if (!fs_mgr_vendor_overlay_mount(vendor_overlay_dir)) {
ret = false;
}
}
return ret;
}
// setup 6
MountHandler mount_handler(&epoll);
该函数主要功能:持续监控/proc/mounts
节点(fopen("/proc/mounts", "re"))
,主要是解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等, 将解析内容生成实体类追加到要挂载的mounts_中,主要就是对mounts文件解析,更新mounts_中的信息,挂载mounts信息里的分区
位置:/system/core/init/mount_handler.cpp
MountHandler::MountHandler(Epoll* epoll) : epoll_(epoll), fp_(fopen("/proc/mounts", "re"), fclose) {
if (!fp_) PLOG(FATAL) << "Could not open /proc/mounts";
auto result = epoll->RegisterHandler(
fileno(fp_.get()), [this]() { this->MountHandlerFunction(); }, EPOLLERR | EPOLLPRI);
if (!result.ok()) LOG(FATAL) << result.error();
}
继续看一下MountHandlerFunction
函数
void MountHandler::MountHandlerFunction() {
rewind(fp_.get());
std::vector<MountHandlerEntry> touched;
auto untouched = mounts_; //容器类型
char* buf = nullptr;
size_t len = 0;
// 循环读取文件内容中的每一行
while (getline(&buf, &len, fp_.get()) != -1) {
auto buf_string = std::string(buf);
// /proc/mounts文件下存在一系列代码
// 若读取到/0/emulated则跳过
if (buf_string.find("/emulated") != std::string::npos) {
continue;
}
// 根据读取的文件内容,来解析分区以及device path,type等
auto entry = ParseMount(buf_string);
auto match = untouched.find(entry);
// 若这一行解析到底了仍然没有匹配的信息,则这一条记录追加到touched中
// entry:举例--->对文件内容/dev/block/dm-33 /data_mirror/data_ce/null ext4 解析过后的实体
if (match == untouched.end()) {
touched.emplace_back(std::move(entry));
} else {
// 若找到了匹配的信息则移除
untouched.erase(match);
}
}
free(buf);
// 将匹配到的entry进行移除,并记录Mount属性值
for (auto& entry : untouched) {
SetMountProperty(entry, false);
mounts_.erase(entry);
}
// 将未匹配到的entry追加到mounts_,并记录Mount属性值
for (auto& entry : touched) {
SetMountProperty(entry, true);
// emplace是更具有性能的 更新或追加
mounts_.emplace(std::move(entry));
}
}
再看看是如何解析文件内容的auto entry = ParseMount(buf_string)
,解析文件内容
MountHandlerEntry ParseMount(const std::string& line) {
auto fields = android::base::Split(line, " ");
while (fields.size() < 3) fields.emplace_back("");
if (fields[0] == "/dev/root") {
auto& dm = dm::DeviceMapper::Instance();
std::string path;
// 根据名称获取system分区目录路径,若根据名称找不到则直接获取根目录/
// 若找到根目录则继续找/system,若找到则拿到device path
// 例如/system就是挂载在/dev/block/dm-1上,那么获取的就是这个玩意
// /dev/block/dm-3 /vendor ext4 ro,seclabel,relatime 0 0
if (dm.GetDmDevicePathByName("system", &path) || dm.GetDmDevicePathByName("vroot", &path)) {
fields[0] = path;
} else if (android::fs_mgr::Fstab fstab; android::fs_mgr::ReadDefaultFstab(&fstab)) {
auto entry = GetEntryForMountPoint(&fstab, "/");
if (entry || (entry = GetEntryForMountPoint(&fstab, "/system"))) {
fields[0] = entry->blk_device;
}
}
}
// 获取所有/dev目录下的device
// readlink 是Linux系统中的一个常用命令,主要用来找出符号链接所指向的位置
// 也就是找到devcie path:/dev/block/dm-33
if (android::base::StartsWith(fields[0], "/dev/")) {
if (std::string link; android::base::Readlink(fields[0], &link)) {
fields[0] = link;
}
}
// fields0:/dev/block/dm-33(blk_device)
// fields1:挂载在device上的分区/文件路径:/data_mirror/cur_profiles(mount_point)
// fields2:该分区的type类型,例如可能是ext4(fs_type)
return MountHandlerEntry(fields[0], fields[1], fields[2]);
}
让我们再来看看这个文件内容
可以看出来第一列是分区挂载的位置,第二列是哪个分区,第三列属于分区格式
// setup 7
// 创建ActionManager对象和ServiceList对象
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 解析rc文件
LoadBootScripts(am, sm);
on early-init
,on init
,on late-init
,on property
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
// 创建解析器,只解析init.rc文件中的service,on,Import类型
// action 使用 ActionParser,而 service 使用 ServiceParser 解析
Parser parser = CreateParser(action_manager, service_list);
// 获取ro.boot.init_rc属性值,此时该属性值应该是空的
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
// 解析/system/core/rootdir/init.rc
// 这里的路径就是将/system/core/rootdir/init.rc 拷贝到out目录下
parser.ParseConfig("/system/etc/init/hw/init.rc");
...
} else {
parser.ParseConfig(bootscript);
}
}
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
parser.AddSectionParser("service", std::make_unique<ServiceParser>(
&service_list, GetSubcontext(), std::nullopt));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
可以看到只需要解析init.rc文件中的service,on,import类型
// setup 8
// 构建action和触发器(on early-init),放到event_queue,等待执行函数
...
am.QueueEventTrigger("early-init");
// 构建action和触发器(on init),放到event_queue,等待执行函数
am.QueueEventTrigger("init");
// 如果是充电模式则不需要挂载文件系统和不要启动核心服务
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// 运行所有属性触发器(action),例如 on property
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
把action加入队列中按顺序依次执行,继续一下rc文件中做了什么动作, rc中action的执行顺序:on early-init
,on init
,on late-init
,on nonencrypted(启动zygote)
,on property
on early-init
...
mkdir /acct/uid
# 挂载linkerconfig(动态链接器)
mount none /linkerconfig/bootstrap /linkerconfig bind rec
# 启动ueventd(位于/system/bin/ueventd),ueventd是init启动的第一个进程
start ueventd
# memory.pressure_level used by lmkd
chown root system /dev/memcg/memory.pressure_level
chmod 0040 /dev/memcg/memory.pressure_level
# app mem cgroups, used by activity manager, lmkd and zygote
mkdir /dev/memcg/apps/ 0755 system system
mkdir /dev/memcg/system 0550 system system
mkdir /dev/net 0755 root root
symlink ../tun /dev/net/tun
...
# 挂载tracefs,可以通过指定方式到处trace日志,分析CPU和内存相关等问题
mount tracefs tracefs /sys/kernel/tracing gid=3012
# create sys dirctory
# 创建/sys目录并指定权限
mkdir /dev/sys 0755 system system
mkdir /dev/sys/fs 0755 system system
mkdir /dev/sys/block 0755 system system
可以看到针对lmkd(Low Memory Killer Daemon)
以及app 创建用户组,创建目录,挂载tracefs:可以通过指定方式到处trace日志,分析CPU和内存相关等问题。
第一个启动的核心服务是:start ueventd
,位于/system/bin/ueventd,ueventd是init启动的第一个服务进程
on init
...
chmod 0775 /dev/cpuset/system-background
chmod 0664 /dev/cpuset/foreground/tasks
chmod 0664 /dev/cpuset/background/tasks
chmod 0664 /dev/cpuset/system-background/tasks
chmod 0664 /dev/cpuset/top-app/tasks
chmod 0664 /dev/cpuset/restricted/tasks
chmod 0664 /dev/cpuset/tasks
chmod 0664 /dev/cpuset/camera-daemon/tasks
# 挂载bpf
mount bpf bpf /sys/fs/bpf nodev noexec nosuid
mkdir /dev/fscklogs 0770 root system
...
# 允许system组读写电源状态
chown system system /sys/power/state
chown system system /sys/power/wakeup_count
chmod 0660 /sys/power/state
...
# 在运行其他进程之前需要先启动log服务,说明init中启动的服务,第一个启动的进程是ueventd
start logd
# 启用 Low Memory Killer Daemon(lmkd)
# 1.基于Memory的CGroup进行进程的回收;2.作为frameworks与kernel的沟通桥梁传递参数与信息
# Start lmkd before any other services run so that it can register them
chown root system /sys/module/lowmemorykiller/parameters/adj
chmod 0664 /sys/module/lowmemorykiller/parameters/adj
chown root system /sys/module/lowmemorykiller/parameters/minfree
chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
start lmkd
# Start essential services.
# 启用ServiceManager,管理各个服务,非常重要
start servicemanager
start hwservicemanager
start vndservicemanager
可以看到第二个启动的核心服务是:start logd
,日志系统。
第三个核心服务是:start lmkd
,Low Memory Killer Daemon
作用:基于Memory的CGroup进行进程的回收,作为frameworks与kernel的沟通桥梁传递参数与信息。
接着启动了:servicemanager
,hwservicemanager
,vndservicemanager
,这些都属于核心服务
若核心服务未启动成功,那么其他服务将无法启动,系统将无法启动,其他服务必须依赖核心服务
# 装载文件系统并启动核心系统服务
on late-init
trigger early-fs
# 触发on fs和on post-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger load_persist_props_action
trigger load_bpf_programs
trigger zygote-start
trigger firmware_mounts_complete
trigger early-boot
trigger boot
可以看到调用顺序为:启动系统on late-init会先执行,然后继续触发on fs,on post-fs ,on late-fs,on zygote-start ,on boot等,在调用on zygote-start
后会解析zygote服务并指定class 名称,然后加入服务管理队列,后续等待调用on nonencrypted
来启动zygote服务
on nonencrypted
class_start main
class_start late_start
目录:/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
# class : 给服务指定一个类属
class main
priority -20
# user 在执行此服务之前先切换用户名。当前默认为root.
user root
# 切换组名
group root readproc reserved_disk
# 在/dev/socket/下创建一个socket,并传递创建的文件描述符fd给服务进程
# 其中type必须为dgram或stream,seqpacket.用户名和组名默认为0
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
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
# oneshot : 当此服务退出时不会自动重启.
# disabled:服务不会自动运行,必须显式地通过服务器来启动
# 据设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式。
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
可以看到通过class_start main来启动主函数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);
} 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.");
}
}
这里启动了zygote,并且携带参数启动了systemServer,关于zygote这里就不再详细分析了
rc文件中的command以及触发器,action等,对应的关系如下:
static const BuiltinFunctionMap builtin_functions = {
{"bootchart", {1, 1, {false, do_bootchart}}},
{"chmod", {2, 2, {true, do_chmod}}},
{"chown", {2, 3, {true, do_chown}}},
{"class_reset", {1, 1, {false, do_class_reset}}},
{"class_reset_post_data", {1, 1, {false, do_class_reset_post_data}}},
{"class_restart", {1, 1, {false, do_class_restart}}},
{"class_start", {1, 1, {false, do_class_start}}},
{"class_start_post_data", {1, 1, {false, do_class_start_post_data}}},
{"class_stop", {1, 1, {false, do_class_stop}}},
{"copy", {2, 2, {true, do_copy}}},
{"copy_per_line", {2, 2, {true, do_copy_per_line}}},
{"domainname", {1, 1, {true, do_domainname}}},
{"enable", {1, 1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
{"exec_start", {1, 1, {false, do_exec_start}}},
{"export", {2, 2, {false, do_export}}},
{"hostname", {1, 1, {true, do_hostname}}},
{"ifup", {1, 1, {true, do_ifup}}},
{"init_user0", {0, 0, {false, do_init_user0}}},
{"insmod", {1, kMax, {true, do_insmod}}},
{"installkey", {1, 1, {false, do_installkey}}},
{"interface_restart", {1, 1, {false, do_interface_restart}}},
{"interface_start", {1, 1, {false, do_interface_start}}},
{"interface_stop", {1, 1, {false, do_interface_stop}}},
{"load_exports", {1, 1, {false, do_load_exports}}},
{"load_persist_props", {0, 0, {false, do_load_persist_props}}},
{"load_system_props", {0, 0, {false, do_load_system_props}}},
{"loglevel", {1, 1, {false, do_loglevel}}},
{"mark_post_data", {0, 0, {false, do_mark_post_data}}},
{"mkdir", {1, 6, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {0, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
{"perform_apex_config", {0, 0, {false, do_perform_apex_config}}},
{"umount", {1, 1, {false, do_umount}}},
{"umount_all", {0, 1, {false, do_umount_all}}},
{"update_linker_config", {0, 0, {false, do_update_linker_config}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"remount_userdata", {0, 0, {false, do_remount_userdata}}},
{"restart", {1, 1, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
{"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}},
{"rm", {1, 1, {true, do_rm}}},
{"rmdir", {1, 1, {true, do_rmdir}}},
{"setprop", {2, 2, {true, do_setprop}}},
{"setrlimit", {3, 3, {false, do_setrlimit}}},
{"start", {1, 1, {false, do_start}}},
{"stop", {1, 1, {false, do_stop}}},
{"swapon_all", {0, 1, {false, do_swapon_all}}},
{"enter_default_mount_ns", {0, 0, {false, do_enter_default_mount_ns}}},
{"symlink", {2, 2, {true, do_symlink}}},
{"sysclktz", {1, 1, {false, do_sysclktz}}},
{"trigger", {1, 1, {false, do_trigger}}},
{"verity_update_state", {0, 0, {false, do_verity_update_state}}},
{"wait", {1, 2, {true, do_wait}}},
{"wait_for_prop", {2, 2, {false, do_wait_for_prop}}},
{"write", {2, 2, {true, do_write}}},
};
至此第二阶段就分析完毕了,一句话来总结:优先保证init进程的存活率(拉高优先级),处理设备是否unlock,决定是否卸载一些分区以及调整selinux规则和权限,创建并启动属性服务(实质上就是把属性值映射到全局内存中供所有进程访问,然后在创建socket等待进程来连接 实现更新和新增属性值),继续决定是否把vendor_overlay覆盖到/vendor分区中,然后持续监控/proc/mounts,如果有分区信息加入到该文件中则挂载此分区,接着解析rc文件(创建目录,修改权限,挂载分区,启动服务进程等),根据调用顺序启动核心服务(adbd,ueventd等)以及主服务(zygote)和其他服务