安卓系列教程之ROM系统开发-百问100ask
系统:Android10.0
设备: FireFly RK3399 (ROC-RK3399-PC-PLUS)
除了了解属性的基本概念,还要知道如何利用属性开发和编程, 更要理解属性背后的涉及原理, 也就是源码也要去研究一下, 本章节重点介绍属性系统初始化。
属性的初始化分为客户端和服务器端, 我们重点讲解服务端,也就是init进程的初始化整理过程,详细代码就不一一展开, 主要重点讲解主线代码, 先上框架图:
- 服务段(init进程)负责创建共享内存(即属性安全上下文文件),并以可读写的方式mmap映射全部共享内存, 而客户端如果要读属性, 就以只读方式mmap映射特定属性的共享内存。
- init进程会收集各个分区中的property_contexts文件,把文件里的内容写入/dev/__properties__/property_info, 并将文件内容进行TrieBuilerNode对象序列化,每个对象都有一个索引(从0开始), 每个对象包含了属性名和对应的安全上下文文件名(多个属性对应一个安全上下文文件)。
- 客户端和服务端(init进程)都会将/dev/__properties__/property_info作为一个桥梁信息文件,分别构建一个匿名共享内存, 构建一个ContextNode对象数组,ContextNode会记录共享内存的地址,并将property_info文件中的TrieBuilerNode对象和ContextNode对象做成一一对应的关系。
- 不管是客户端读取属性,还是服务器端修改属性, 最终都是通过属性名, 找到TrieBuilerNode和它的index索引, 根据索引就可以找到一一对应的ContextNode, 之后根据ContexNode找到共享内存的地址,并操作内存中的属性值。
核心代码:
int SecondStageMain(int argc, char** argv){
//创建/dev/__properties__/property_info,并对内容进行序列化
// 构建和映射所有属性的共享内存
property_init();
// 读取特定设备树中的信息,并设置ro.boot开头的属性, RK3399上没有相关信息。
process_kernel_dt();
//将内核中cmdline中所有的有=号的参数,以及特殊的androidboot.xx=xxx的形式设置成相应的属性
//如果是模拟器, bootargs中包含: androidboot.hardware=ranchu 和 init=/init
// 就会设置ro.kernel.androidboot.hardware= ranchu ro.kernel.init=/init 和 ro.boot.hardware=ranchu,
//如果是真机, 只针对androidboot.xx=xx的参数设置,
//如androidboot.storagemedia=emmc, 就会有root.boot.storagemedia=emmc
process_kernel_cmdline();
// 将列表中特定属性的值设置到另外一个属性的值中去
//如将ro.boot.serialno的值设置到ro.serialno中去
//一般ro.boot开头的属性很多都是来自内核的cmdline
export_kernel_boot_props();
//加载各个分区中的属性文件,如prop.default, build.pro, default.prop,之前详细讲过属性文件
property_load_boot_defaults(load_debug_prop);
//创建本地套接字, 用于接收客户端的设置请求,并将套接字文件描述符加入到epoll监控机制
//套接字有数据之后的处理函数是: handle_property_set_fd()
StartPropertyService(&epoll);
}
其中property_init()的调用过程:
property_init()
|
CreateSerializedPropertyInfo();// 1
__system_property_area_init() // 2
|
system_properties.AreaInit()
|
contexts_ = new (contexts_data_) ContextsSerialized()
contexts_->Initialize(true, property_filename_, fsetxattr_failed)
注释1: init进程会收集各个分区中的property_contexts文件,把文件里的内容写入/dev/__properties__/property_info, 并将文件内容进行TrieBuilerNode对象序列化,每个对象都有一个索引(从0开始), 每个对象包含了属性名和对应的安全上下文文件名
注释2: 最终调用ContextsSerialized::Initialize()
ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed)函数的第一个参数非常重要,
如果为true, 就会创建:
如果为false,则不会创建安全上下文文件,并且mmap时只读方式, 客户端就是走这个逻辑。
客户端的程序一般都会加载libc, 而动态库加载的时候, 就会自动对属性进行初始化,初始化函数就是__system_properties_init(),具体调用的过程通过如下分析来说明:
int __system_properties_init(),通过搜索:
__libc_init_common in libc_init_common.cpp (/bionic/libc/bionic) : __system_properties_init();
如下所示:
void __libc_init_common() {
//省略代码...
__system_properties_init(); // Requires 'environ'.
__libc_init_fdsan(); // Requires system properties (for debug.fdsan).
}
该函数会被如下函数调用:bionic/libc/bionic/libc_init_dynamic.cpp
// We flag the __libc_preinit function as a constructor to ensure that
// its address is listed in libc.so's .init_array section.
// This ensures that the function is called by the dynamic linker as
// soon as the shared library is loaded.
// We give this constructor priority 1 because we want libc's constructor
// to run before any others (such as the jemalloc constructor), and lower
// is better (http://b/68046352).
__attribute__((constructor(1))) __libc_preinit()
|
__libc_preinit_impl();
|
__libc_init_common();
|
__system_properties_init();
|
system_properties.Init(PROP_FILENAME)
注意__attribute__((constructor))这个属性, 根据代码中注释, 加了该属性的函数会放在libc.so的.init_array段, 这个gcc的特有属性确保了加载libc.so动态库的时候, 该函数会尽快被动态linker调用, 即程序使用了libc, 就会尽早执行__libc_preinit()函数。这样一来,我们的函数放心调用property_get(),因为一开始属性就被初始化。
bool SystemProperties::Init(const char* filename){
contexts_ = new (contexts_data_) ContextsSerialized();
contexts_->Initialize(false, property_filename_, nullptr)
}
有别于init启动过程中的初始化, 此处contexts_->Initialize(false, property_filename_, nullptr)的第一个参数为false,就决定了这个过程不会创建所有/dev/__properties__/property_info/u:object_r:*:s0属性安全上下文文件, 并且mmap的时候, 只是只读属性。
详细代码就不具体贴出来了, 代码量非常多, 重点是把基本逻辑讲清楚, 细读代码,就交给大家去进行。同时强调的是, 属性代码使用其实很简单, 但是背后的框架逻辑代码其实还是蛮复杂的, 理解这些可以帮助大家形成一些设计思维。