基于AndroidO 分析
Android属性系统初始化流程
-
init进程的第二个阶段 调用
property_init
初始化 属性系统。此阶段属性文件还未解析,解析的是
property_contexts
文件。// system/core/init/property_service.cpp void property_init() { if (__system_property_area_init()) { exit(1); } }
解析错误将直接退出init进程,哈哈哈,可见属性系统的重要性。
__system_property_area_init()
主要工作如下:-
创建
/dev/__properties__
文件夹。 -
解析property_contexts文件。
Android 版本 在 N 及N以前, 对应的 文件路径是
/property_contexts
;Android O开始,对应的文件路径是/system/etc/selinux/plat_property_contexts
和/vendor/etc/selinux/nonplat_property_contexts
。 -
解析过程会创建两个链表,
contexts
和prefixes
。解析完成后,会遍历contexts
链表,为其中的每一个context_node
创建一个关联文件,并且打开该文件将其映射到进程的虚拟地址空间,并在该地址空间上构建一个prop_area
对象。
解析之后的各个属性之间之间的关系如下:
对照真实设备的/dev/__properties
目录: -
-
解析dts中,内核配置的属性。 目录是
/proc/device-tree/firmware/android/
。 -
处理
/proc/cmdline
中的属性,由顺序可以知道,dts中的properties 的优先级大于command-line
。 -
将前面内核
comand-line
中的属性转换成init 进程需要的属性。这种操作不是很懂暂时。 -
解析默认的属性文件,这些文件包括:
/system/etc/prop.default /odm/default.prop /vendor/default.prop
-
打开属性服务,这里会创建一个 unix domain soket(
/dev/socket/property_service
),然后设置为监听模式,加入到init 进程的epoll
实例中,其他进程可以通过/dev/socket/property_service
通知property service
设置属性。属性的设置需要交由
property_service
来完成,这样才能实现on property: ***
这样的action,以及一些全局的属性更改的监听。获取属性则无需通过property_service
,进程在打开libc 库的时候就会自动完成相应属性prop_area
的映射。
到这里,init进程的main函数中关于属性系统的初始化操作基本结束。但是/system/build.prop
在哪里加载呢???
既然是在/system
分区下,就肯定要等到/system
分区被挂载才能解析:
on post-fs
# Load properties from
# /system/build.prop,
# /odm/build.prop,
# /vendor/build.prop and
# /factory/factory.prop
load_system_props
# start essential services
start logd
start servicemanager
start hwservicemanager
start vndservicemanager
对应的load_system_props
就是用于解析build.prop
的builtin
命令,其实现位于system/core/init/builtins.cpp
@do_load_system_props中。
此外,还有一种persist
属性,其能够持久的保存属性值,哪怕是我们重启系统,解析persist
属性也是在init.rc中触发,触发顺序如下:
on late-init
# Load persist properties and override properties (if enabled) from /data.
trigger load_persist_props_action
on load_persist_props_action
load_persist_props
start logd
start logd-reinit
对应的load_persist_props
就是用于解析build.prop
的builtin
命令,其实现位于system/core/init/builtins.cpp
@do_load_persist_props中。
这些持久化的属性都保存在
/data/property
目录下,属性名对应文件名,文件内容对应属性值。再看一下
load_persist_props
的注释。/* When booting an encrypted system, /data is not mounted when the * property service is started, so any properties stored there are * not loaded. Vold triggers init to load these properties once it * has mounted /data. */
属性系统的内存管理
-
prop_bt
以
ro.build.product
为例,将该属性key 以.
分割成ro
、build
及product
三个部分,每个部分都对应一个prop_bt
。// Represents a node in the trie. struct prop_bt { uint32_t namelen; // 名称长度 atomic_uint_least32_t prop; // prop_info 的地址 atomic_uint_least32_t left; // 兄弟节点地址 兄弟节点按照名称从小到大排列。 atomic_uint_least32_t right; // 兄弟节点地址 atomic_uint_least32_t children; // 子节点地址 char name[0]; // 名称 `ro` `build` `product` };
-
prop_info
prop_bt
构成的三叉树中的叶子节点。记录属性值。struct prop_info { atomic_uint_least32_t serial; char value[PROP_VALUE_MAX]; // 固定value的长度,哈哈哈,更新属性值得时候就简单多了。。。。 char name[0]; prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen) { memcpy(this->name, name, namelen); this->name[namelen] = ' '; atomic_init(&this->serial, valuelen << 24); memcpy(this->value, value, valuelen); this->value[valuelen] = ' '; } };
-
prop_area
描述一个内存区域,通过
mmap
将prop_area
和/dev/__properties/<file>
映射起来。class prop_area { uint32_t bytes_used_; atomic_uint_least32_t serial_; uint32_t magic_; uint32_t version_; uint32_t reserved_[28]; char data_[0]; // 这里开始就是 prop_bt了。 }
总结一下这三个数据结构的关系。以源码注释中的为例:
/*
* Properties are stored in a hybrid trie/binary tree structure.
* Each property's name is delimited at '.' characters, and the tokens are put
* into a trie structure. Siblings at each level of the trie are stored in a
* binary tree. For instance, "ro.secure"="1" could be stored as follows:
*
* +-----+ children +----+ children +--------+
* | |-------------->| ro |-------------->| secure |
* +-----+ +----+ +--------+
* / / |
* left / right left / | prop +===========+
* v v v +-------->| ro.secure |
* +-----+ +-----+ +-----+ +-----------+
* | net | | sys | | com | | 1 |
* +-----+ +-----+ +-----+ +===========+
*/
放到连续的地址空间中的布局大概如下:
属性名被
.
分割成多个segment,每一个segment对应一个prop_bt
。这些prop_bt
会组织成一棵三叉树,兄弟节点之间按照大小关系从右到左排列。
当设置属性时,首先根据属性key查找prefixes
链表,找到匹配的context_node
,进而就能找到关联的prop_area
。然后按照上面的规则将属性插入到三叉树中。
三叉树的叶子节点一般都是prop_info
,其完整的记录了属性名和属性值。具体的实现细节参考property_set的实现。
初始化过程详细代码
*property_init
主要代码位于
bionic/libc/bionic/system_properties.cpp
中。
property_init
主要作用是解析property_contexts
文件,并创建不同属性对应的缓冲区文件及完成缓冲区文件和prop_area
的内存映射。
int __system_property_area_init() {
free_and_unmap_contexts();
// [1] 创建 /dev/__properties__
mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);
// [2] 解析 <noplat/plat>_property_contexts !创建 属性前缀 链表 和 context 链表, 并 建立 prefix 和 context 之间的关联。
if (!initialize_properties()) {
return -1;
}
bool open_failed = false;
bool fsetxattr_failed = false;
// 遍历 contexts 链表, 对每一个 context_node 执行 open 操作(创建 context file,映射到用户进程空间,在改地址空间上构造 prop_area)
list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
if (!l->open(true, &fsetxattr_failed)) {
open_failed = true;
}
});
// map_system_property_area 创建 properties_serial 文件 及 u:object_r:properties_serial:s0 context
if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {
free_and_unmap_contexts();
return -1;
}
initialized = true;
return fsetxattr_failed ? -2 : 0;
}
-
initialize_properties
首先从
/system
和/vendor
目录下加载property_contexts
, 如果不存在,就去根目录下加载。static bool initialize_properties() { // androidN 及之前的版本存在该 文件 if (initialize_properties_from_file("/property_contexts")) { return true; } if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) { // exist if (!initialize_properties_from_file("/system/etc/selinux/plat_property_contexts")) { return false; } initialize_properties_from_file("/vendor/etc/selinux/nonplat_property_contexts"); } else { if (!initialize_properties_from_file("/plat_property_contexts")) { return false; } initialize_properties_from_file("/nonplat_property_contexts"); } return true; }
首先看一下
property_contexts
的格式。* u:object_r:default_prop:s0 bluetooth. u:object_r:bluetooth_prop:s0 config. u:object_r:config_prop:s0 ctl. u:object_r:ctl_default_prop:s0 ctl.bootanim u:object_r:ctl_bootanim_prop:s0 ctl.bugreport u:object_r:ctl_bugreport_prop:s0 ctl.console u:object_r:ctl_console_prop:s0 ctl.dumpstate u:object_r:ctl_dumpstate_prop:s0 ...
每一行分为两部分,属性前缀和对应的 Selinux Context。在解析代码中,属性前缀使用
prefix_node
表示,Selinux Context使用context_node
表示。initialize_properties_from_file
将相应的property_contexts
文件 解析成prefix_node* prefixes
和context_node* contexts
两个链表。这里,对我来说比较难理解的就是链表的添加过程了,其按照prefix_node->prefix_len
按照从大到小的顺序排列,即头结点的prefix_len
最大,此外,由于*
属于通配符号,所以其需要放到最后。class context_node { public: context_node(context_node* next, const char* context, prop_area* pa) : next(next), context_(strdup(context)), pa_(pa), no_access_(false) { lock_.init(false); } context_node* next; }; struct prefix_node { prefix_node(struct prefix_node* next, const char* prefix, context_node* context) : prefix(strdup(prefix)), prefix_len(strlen(prefix)), context(context), next(next) { } struct prefix_node* next; }; // 支持 prefix_node 和 context_node的添加 template <typename List, typename... Args> //注意这里,List 是一个二级指针 static inline void list_add(List** list, Args... args) { *list = new List(*list, args...); } // list 是一个二级指针 static void list_add_after_len(prefix_node** list, const char* prefix, context_node* context) { size_t prefix_len = strlen(prefix); auto next_list = list; while (*next_list) { if ((*next_list)->prefix_len < prefix_len || (*next_list)->prefix[0] == '*') { // 进入到这里,next_list = &curr_node->next;,模板函数展开就是: // curr_node->next = new prefix_node(curr_node->next, prefix, context); // 这样就完成了 节点插入操作。这段代码在用到 链表时值得借鉴。 list_add(next_list, prefix, context); return; } // 指向当前节点的 next 的地址 next_list = &(*next_list)->next; } // 在这里, *next_list = nullptr; 第一次插入时满足该情况。此时next_list == &prefixs // 在尾部添加时也满足,此时,next_list == &curr_node->next list_add(next_list, prefix, context); }
理解了上面这段代码,
initialize_properties_from_file
就不足畏惧了:static bool initialize_properties_from_file(const char* filename) { // 打开文件 FILE* file = fopen(filename, "re"); char* buffer = nullptr; size_t line_len; char* prop_prefix = nullptr; char* context = nullptr; // 逐行读取文件 while (getline(&buffer, &line_len, file) > 0) { // 读取 prefix 和 context int items = read_spec_entries(buffer, 2, &prop_prefix, &context); if (items <= 0) { continue; } // ctl. 开头的属性属于控制属性,不需要保存到内存中。 if (!strncmp(prop_prefix, "ctl.", 4)) { free(prop_prefix); free(context); continue; } // 先查找 context链表 auto old_context = list_find(contexts, [context](context_node* l) { return !strcmp(l->context(), context); }); if (old_context) { //context 存在,就直接将 prop_prefix 添加到链表中。 list_add_after_len(&prefixes, prop_prefix, old_context); } else { // context 不存在,先将context添加到 contexts链表 list_add(&contexts, context, nullptr); // 再将 prop_prefix 添加到 prefixes链表中 list_add_after_len(&prefixes, prop_prefix, contexts); } } return true; }
-
list_foreach(contexts)
前面根据
preoperty_context
文件创建了两个链表,这里就遍历contexts
链表,并且为每一个context_node
创建一个128KB
的文件,然后通过mmap
将其映射到用户进程地址空间中,然后再在该地址空间上构建一个prop_area
对象。list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) { if (!l->open(true, &fsetxattr_failed)) { open_failed = true; } }); // context_node::open 函数是关键 bool context_node::open(bool access_rw, bool* fsetxattr_failed) { char filename[PROP_FILENAME_MAX]; // 形如 /dev/__properties__/u:object_r:audio_prop:s0 int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", property_filename, context_); if (access_rw) { // 初始化时为 true // 打开 context file,通过mmap映射到用户空间,并且在这段地址空间上 构造一个 prop_area 对象 pa_ = map_prop_area_rw(filename, context_, fsetxattr_failed); } else { pa_ = map_prop_area(filename); } lock_.unlock(); return pa_; } static prop_area* map_prop_area_rw(const char* filename, const char* context, bool* fsetxattr_failed) { //打开文件,没有就创建 const int fd = open(filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444); if (context) { // 设置 selinux 信息,不太懂,感觉 调用者进程 selinux属于当前 context 就不能获取该context 文件中对应的属性。 if (fsetxattr(fd, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0) != 0) { } } // 将文件大小设置为 128K ftruncate(fd, PA_SIZE); //这里就和我们前面的示意图对上了。 pa_size = PA_SIZE; pa_data_size = pa_size - sizeof(prop_area); // mmap void* const memory_area = mmap(nullptr, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); //在 mmap返回的地址上 构建 prop_area对象。 prop_area* pa = new (memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION); close(fd); return pa; }
关于
map_fd_ro
之后遇到了用到的情况再说,整体流程差不多。
process_kernel_dt
获取内核设备树中的android 相关的信息,并设置到属性系统中。
通过get_android_dt_dir()
获取android的设备树信息路径。
const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android/");
static std::string init_android_dt_dir() {
// Use the standard procfs-based path by default
std::string android_dt_dir = kDefaultAndroidDtDir;
// The platform may specify a custom Android DT path in kernel cmdline
import_kernel_cmdline(false,
[&](const std::string& key, const std::string& value, bool in_qemu) {
if (key == "androidboot.android_dt_dir") {
android_dt_dir = value;
}
});
LOG(INFO) << "Using Android DT directory " << android_dt_dir;
return android_dt_dir;
}
const std::string& get_android_dt_dir() {
// Set once and saves time for subsequent calls to this function
static const std::string kAndroidDtDir = init_android_dt_dir();
return kAndroidDtDir;
}
默认路径就是/proc/device-tree/firmware/android/
,然后设置过程就是该目录下除了compatible
和name
的其他文件的内容。
process_kernel_cmdline
处理内核启动参数中的属性信息。
static void process_kernel_cmdline() {
import_kernel_cmdline(false, import_kernel_nv);
if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}
void import_kernel_cmdline(bool in_qemu,
const std::function<void(const std::string&, const std::string&, bool)>& fn) {
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
// /proc/cmdline 中,通过 空格 来间隔不同的内容。
for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) {
std::vector<std::string> pieces = android::base::Split(entry, "=");
if (pieces.size() == 2) {
fn(pieces[0], pieces[1], in_qemu);
}
}
}
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
if (key.empty()) return;
if (for_emulator) { // 模拟器
// In the emulator, export any kernel option with the "ro.kernel." prefix.
property_set("ro.kernel." + key, value);
return;
}
if (key == "qemu") {
strlcpy(qemu, value.c_str(), sizeof(qemu));
} else if (android::base::StartsWith(key, "androidboot.")) {
property_set("ro.boot." + key.substr(12), value);
}
}
可以看到,/proc/cmdline
中,androidboot.*
属性信息,会被更改为ro.boot.*
的形式设置到属性系统中。
export_kernel_boot_props
static void export_kernel_boot_props() {
char cmdline[1024];
char* s3;
struct {
const char *src_prop;
const char *dst_prop;
const char *default_value;
} prop_map[] = {
// { "ro.boot.serialno", "ro.serialno", "", },
// { "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", },
};
proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) );
s3 = strstr(cmdline, "androidboot.oem_unlocked=1");
if (s3 == NULL){
//oem_unlocked is 0 or not set.
#ifdef DISABLE_VERIFY
property_set("ro.boot.verifiedbootstate","unsupported");
#else
property_set("ro.boot.verifiedbootstate","green");
#endif
}else{
property_set("ro.boot.verifiedbootstate","orange");
}
if(strstr(cmdline, "storagemedia=") != NULL){
// 额,这里并没有设置啊啊啊
LOG(INFO) << "Set ro.boot.storagemedia!";
} else {
LOG(INFO) << "Set ro.boot.storagemedia to unknown, may booting from unknown device";
property_set("ro.boot.storagemedia", "unknown");
}
for (size_t i = 0; i < arraysize(prop_map); i++) {
std::string value = GetProperty(prop_map[i].src_prop, "");
property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
}
}
在 /proc/cmdline
中有这么一个 androidboot.hardware=rk30board
字符串,process_kernel_cmdline
处理后,属性系统中就会添加一个ro.boot.hardware=rk30board
的属性,回到export_kernel_boot_props
,该函数值得作用就是将prop_map
数组中的那些内核属性,更改为android标准属性,对于cmdline中没有指定的属性,就设置为默认值。
内核属性 | android标准属性 | 默认值(cmdline中未设置) |
---|---|---|
ro.boot.baseband | ro.baseband | unknown |
ro.boot.bootloader | ro.bootloader | unknown |
ro.boot.hardware | ro.hardware | unknown |
ro.boot.revision | ro.revision | 0 |
记录这些主要还是为了混个眼熟,以后遇到这些属性,知道他们的值从哪来的。
property_load_boot_defaults
顾名思义,加载启动阶段的默认属性。
void property_load_boot_defaults() {
if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
// Try recovery path
if (!load_properties_from_file("/prop.default", NULL)) {
// Try legacy path
load_properties_from_file("/default.prop", NULL);
}
}
load_properties_from_file("/odm/default.prop", NULL);
load_properties_from_file("/vendor/default.prop", NULL);
update_sys_usb_config();
}
load_properties_from_file
这里就不做贴代码了,就是打开属性文件逐行读取,然后将每一行分割为key
和value
两部分,再调用property_set设置属性。
这些默认属性都有哪些?
RK平台:
1|rk3399:/ # cat default.prop
#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.secure=1
security.perf_harden=1
ro.allow.mock.location=0
ro.debuggable=1
#
# BOOTIMAGE_BUILD_PROPERTIES
#
ro.bootimage.build.date=Mon Mar 29 15:53:44 CST 2021
ro.bootimage.build.date.utc=1617004424
ro.bootimage.build.fingerprint=rockchip/rk3399/rk3399:8.1.0/OPM8.190605.005/155344:userdebug/test-keys
persist.sys.usb.config=adb
rk3399:/ # cat /vendor/default.prop
#
# ADDITIONAL VENDOR DEFAULT PROPERTIES
#
ro.zygote=zygote64_32
tombstoned.max_tombstone_count=50
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m
dalvik.vm.dex2oat-Xms=64m
dalvik.vm.dex2oat-Xmx=512m
ro.dalvik.vm.native.bridge=0
dalvik.vm.usejit=true
dalvik.vm.usejitprofiles=true
dalvik.vm.dexopt.secondary=true
dalvik.vm.appimageformat=lz4
pm.dexopt.first-boot=quicken
pm.dexopt.boot=verify
pm.dexopt.install=quicken
pm.dexopt.bg-dexopt=speed-profile
pm.dexopt.ab-ota=speed-profile
pm.dexopt.inactive=verify
pm.dexopt.shared=speed
debug.atrace.tags.enableflags=0
ro.enable_boot_charger_mode=0
ro.board.platform=rk3399
ro.target.product=tablet
ro.oem_unlock_supported=1
ro.adb.secure=0
update_sys_usb_config
的作用是啥?
// persist.sys.usb.config values can't be combined on build-time when property
// files are split into each partition.
// So we need to apply the same rule of build/make/tools/post_process_props.py
// on runtime.
static void update_sys_usb_config() {
bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false);
std::string config = android::base::GetProperty("persist.sys.usb.config", "");
if (config.empty()) {
property_set("persist.sys.usb.config", is_debuggable ? "adb" : "none");
} else if (is_debuggable && config.find("adb") == std::string::npos &&
config.length() + 4 < PROP_VALUE_MAX) {
config.append(",adb");
property_set("persist.sys.usb.config", config);
}
}
从注释看说 persist.sys.usb.config
不能再编译期间就组合起来,所以再启动时,按照 build/make/tools/post_process_props.py
的规则设置该属性。代码的大致意思是,如果 ro.debuggable == 1
,即 eng
版本,那么先获取一下 persist.sys.usb.config
的值,如果 adb
不存在于这个值里面,就把adb
加进去。然后再设置一下 persist.sys.usb.config
这个值。如果 ro.debuggable == 0
的话,就不操作,如果无法获取 persist.sys.usb.config
的值,就把它置为 “none”。
persist.sys.usb.config
的作用是用来配置 usb 的模式,比如常用的 adb、MTP等
*start_property_service
void start_property_service() {
// 设置 属性服务器版本,在AndroidO中,好像有两套 协议,后面分析就默认这里的 verison2
property_set("ro.property_service.version", "2");
// 创建一个 unix domain socket, 该操作会在生成 /dev/socket/property_service,其它进程通过 unix socket api打开该文件就能建立和
// property_service的连接
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr, sehandle);
listen(property_set_fd, 8);
// 注册到 epoll中。
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
属性系统的设置操作,仅有init
进程完成,所以其他进程就需要通过socket就来告知init进程设置相关的属性,这也是init
进程实现on property:...
Action的基础。
handle_property_set_fd
:
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
struct ucred cr;
socklen_t cr_size = sizeof(cr);
getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size);
SocketConnection socket(s, cr);
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
socket.RecvUint32(&cmd, &timeout_ms);
switch (cmd) {
//只靠考虑 version2
case PROP_MSG_SETPROP2: {
std::string name;
std::string value;
if (!socket.RecvString(&name, &timeout_ms) || //获取属性 name
!socket.RecvString(&value, &timeout_ms)) { //获取属性 value
socket.SendUint32(PROP_ERROR_READ_DATA);
return;
}
// 关键函数
handle_property_set(socket, name, value, false);
break;
}
}
}
handle_property_set
,可以猜测内部也是调用的property_set
,不过还有执行很多操作,比如执行action
,或者处理ctl.
开头的属性。
static void handle_property_set(SocketConnection& socket,
const std::string& name,
const std::string& value,
bool legacy_protocol) {
// 校验属性名称是否符合要求
if (!is_legal_property_name(name)) {
socket.SendUint32(PROP_ERROR_INVALID_NAME);
return;
}
struct ucred cr = socket.cred();
char* source_ctx = nullptr;
getpeercon(socket.socket(), &source_ctx);
if (android::base::StartsWith(name, "ctl.")) {
// 校验远程进程是否有权限
if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
// 执行控制命令
handle_control_message(name.c_str() + 4, value.c_str());
}
} else {
// 校验远程进程是否有权限
if (check_mac_perms(name, source_ctx, &cr)) {
// 设置属性
uint32_t result = property_set(name, value);
}
}
freecon(source_ctx);
}
uint32_t property_set(const std::string& name, const std::string& value) {
if (name == "selinux.restorecon_recursive") {
return PropertySetAsync(name, value, RestoreconRecursiveAsync);
}
return PropertySetImpl(name, value);
}
*do_load_system_props
该函数在on post-fs
被trigge
后执行,这里和property_load_boot_defaults的实现基本一致,只是加载的文件不同而已。
void load_system_props() {
load_properties_from_file("/system/build.prop", NULL);
load_properties_from_file("/odm/build.prop", NULL);
load_properties_from_file("/vendor/build.prop", NULL);
load_properties_from_file("/factory/factory.prop", "ro.*");
load_recovery_id_prop();
}
*do_load_persist_props
/* When booting an encrypted system, /data is not mounted when the
* property service is started, so any properties stored there are
* not loaded. Vold triggers init to load these properties once it
* has mounted /data.
*/
void load_persist_props(void) {
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
property_set("ro.persistent_properties.ready", "true");
}
static void load_override_properties() {
if (ALLOW_LOCAL_PROP_OVERRIDE) {
load_properties_from_file("/data/local.prop", NULL);
}
}
#define PERSISTENT_PROPERTY_DIR "/data/property"
static void load_persistent_properties() {
persistent_properties_loaded = 1;
// 遍历 /data/property目录
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(PERSISTENT_PROPERTY_DIR), closedir);
struct dirent* entry;
while ((entry = readdir(dir.get())) != NULL) {
if (strncmp("persist.", entry->d_name, strlen("persist."))) {
continue;
}
if (entry->d_type != DT_REG) {
continue;
}
// Open the file and read the property value.
int fd = openat(dirfd(dir.get()), entry->d_name, O_RDONLY | O_NOFOLLOW);
struct stat sb;
if (fstat(fd, &sb) == -1) {
continue;
}
// File must not be accessible to others, be owned by root/root, and
// not be a hard link to any other file.
// 只允许 root 用户读写 属性文件
if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) || sb.st_uid != 0 || sb.st_gid != 0 || sb.st_nlink != 1) {
continue;
}
char value[PROP_VALUE_MAX];
int length = read(fd, value, sizeof(value) - 1);
if (length >= 0) {
value[length] = 0;
// 又回到了 这里。。。。
property_set(entry->d_name, value);
}
close(fd);
}
}
/data/property 目录下,每一个文件对应一个 persist 属性。
property_set实现
从 libcutils
中的property_set
开始。
// system/core/libcutils/include/cutils/properties.h
/*
* property_set: returns 0 on success, < 0 on failure
*/
int property_set(const char *key, const char *value);
// system/core/libcutils/include/cutils/properties.cpp
int property_set(const char *key, const char *value) {
return __system_property_set(key, value);
}
既然属性的设置操作都是在init进程中完成的,那就说明__system_property_set
的实现应该是通过unix domain socket
按照属性协议通知init@propperty_service
完成属性设置操作。
//bionic/libc/bionic/system_properties.cpp
// 只考虑 Protocol version 2
int __system_property_set(const char* key, const char* value) {
// Use proper protocol
// 使用 /dev/socket/property_service 创建 连接到 property_service 的 socket
PropertyServiceConnection connection;
// 用于处理数据发送逻辑
SocketWriter writer(&connection);
// 将 key 和 value 通过socket 发送给 init进程。
if (!writer.WriteUint32(PROP_MSG_SETPROP2).WriteString(key).WriteString(value).Send()) {
return -1;
}
int result = -1;
if (!connection.RecvInt32(&result)) {
return -1;
}
if (result != PROP_SUCCESS) {
return -1;
}
return 0;
}
start_property_service中已经介绍过,在接收到socket数据后,property_service
经过一系列的属性格式和调用进程的权限校验后,最终调用property-service.cpp@PropertySetImpl
来执行属性设置操作。
static uint32_t PropertySetImpl(const std::string& name, const std::string& value) {
size_t valuelen = value.size();
// [1] 查找属性是否已经存在
prop_info* pi = (prop_info*) __system_property_find(name.c_str());
if (pi != nullptr) {
// ro属性不允许修改
if (android::base::StartsWith(name, "ro.")) {
return PROP_ERROR_READ_ONLY_PROPERTY;
}
// 更新已经存在的属性
__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) {
return PROP_ERROR_SET_FAILED;
}
}
// Don't write properties to disk until after we have read all default
// properties to prevent them from being overwritten by default values.
if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) {
// persist 类型的属性 持久化
write_persistent_property(name.c_str(), value.c_str());
}
// [2] 属性更改通知,执行 on property:....
property_changed(name, value);
return PROP_SUCCESS;
}
有了属性名称,我们就能根据前缀获取到与之关联的prop_area
,然后再该区域内部查找该属性关联 prop_info
。
-
__system_property_update
当 属性已经存在时,用于更新 非只读属性的值。回顾一下prop_info,其保存属性值得
value
字段长度固定为PROP_VALUE_MAX
,更新时只要简单的覆盖就行了。 -
__system_property_add
当属性不存在时,添加属性,先在
prop_area
中查找属性,找不到就创建prop_bt
最后创建prop_info
,完成属性树的构建。 -
property_changed
// system/core/init/init.cpp // on property void property_changed(const std::string& name, const std::string& value) { // If the property is sys.powerctl, we bypass the event queue and immediately handle it. // This is to ensure that init will always and immediately shutdown/reboot, regardless of // if there are other pending events to process or if init is waiting on an exec service or // waiting on a property. // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific // commands to be executed. if (name == "sys.powerctl") { // Despite the above comment, we can't call HandlePowerctlMessage() in this function, // because it modifies the contents of the action queue, which can cause the action queue // to get into a bad state if this function is called from a command being executed by the // action queue. Instead we set this flag and ensure that shutdown happens before the next // command is run in the main init loop. // TODO: once property service is removed from init, this will never happen from a builtin, // but rather from a callback from the property service socket, in which case this hack can // go away. shutdown_command = value; do_shutdown = true; } if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value); if (waiting_for_prop) { if (wait_prop_name == name && wait_prop_value == value) { ResetWaitForProp(); } } }
property_get 实现
int property_get(const char *key, char *value, const char *default_value) {
int len = __system_property_get(key, value);
if (len > 0) {
return len;
}
// 获取失败,设置默认值
if (default_value) {
len = strnlen(default_value, PROPERTY_VALUE_MAX - 1);
memcpy(value, default_value, len);
value[len] = ' ';
}
return len;
}
//bionic/libc/bionic/system_properties.cpp
int __system_property_get(const char* name, char* value) {
const prop_info* pi = __system_property_find(name);
if (pi != 0) {
// 找到属性对应的 prop_info,
return __system_property_read(pi, nullptr, value);
} else {
value[0] = 0;
return 0;
}
}
int __system_property_read(const prop_info* pi, char* name, char* value) {
while (true) {
uint32_t serial = __system_property_serial(pi); // acquire semantics
size_t len = SERIAL_VALUE_LEN(serial);
memcpy(value, pi->value, len + 1);
// TODO: Fix the synchronization scheme here.
// There is no fully supported way to implement this kind
// of synchronization in C++11, since the memcpy races with
// updates to pi, and the data being accessed is not atomic.
// The following fence is unintuitive, but would be the
// correct one if memcpy used memory_order_relaxed atomic accesses.
// In practice it seems unlikely that the generated code would
// would be any different, so this should be OK.
atomic_thread_fence(memory_order_acquire);
if (serial == load_const_atomic(&(pi->serial), memory_order_relaxed)) {
if (name != nullptr) {
// 简单的拷贝
size_t namelen = strlcpy(name, pi->name, PROP_NAME_MAX);
}
return len;
}
}
}
问题来了,属性系统读端是在android系统中各个进程中进行的,写端都是在init进程,如何保证读写的同步?
属性系统的同步操作
futex
ctl.*
如何工作
前面start_propperty_service中介绍了属性是如何被init处理的,在handle_property_set
中,如果属性以ctl.
开头,就会调用handle_control_message
来处理控制消息:
void handle_control_message(const std::string& msg, const std::string& name) {
Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
if (msg == "start") {
svc->Start();
} else if (msg == "stop") {
svc->Stop();
} else if (msg == "restart") {
svc->Restart();
}
}