Android 属性系统
1. 总览
2. 属性基础
2.1 属性格式
安卓系统的属性名称格式:xx.xx.xx.xx
2.2 属性特点
- 全局性
- 跨语言(java、c/c++)
- 进程运行需要的条件
2.3 属性简单区分
- 如何查看系统中有哪些属性:
getprop
- 如何设置属性:
setprop vendor.my.work.test
- 简单区分:
- init.svc.xxx:由 init 启动的一些服务进程的属性
- ro.xxx.xx:均是只读属性,不能 set
- ro.kernel.xxx:这类属性来自 uboot 以及内核参数传递至安卓系统中
- ro.build.xxx:与编译相关
- persist.xx.xx.xx:表示永久化,启动后不丢失,但是可以写。
- 为什么需要 persist 这种属性:属性是共享内存,会掉电丢失,除非是编译自动生成的 xxx.prop 文件中的属性(默认属性)。而 persist 开头的属性会保存在
/data/property/persistent_properties
文件中,因为永久化的存储就是文件存储。
- 为什么需要 persist 这种属性:属性是共享内存,会掉电丢失,除非是编译自动生成的 xxx.prop 文件中的属性(默认属性)。而 persist 开头的属性会保存在
- sys.xx.xx 开头的
- ctl.start、ctl.stop、ctl.restart:用于控制启动与关闭服务,整个服务可以理解为后台的一些守护进程,但是控制时需要有 selinux 权限,检查权限时会去查看是否有以下权限:
-r--r--r-- 1 root root 131072 2024-12-16 04:19 u:object_r:ctl_restart_prop:s0
-r--r--r-- 1 root root 131072 2024-12-16 04:19 u:object_r:ctl_start_prop:s0
-r--r--r-- 1 root root 131072 2024-12-16 04:19 u:object_r:ctl_stop_prop:s0
- ctl.xxx 使用场景:
3. 属性技术原理
3.1 特点
3.1.1 客户端进程
- shell
- c/c++ 程序进程
- app 进程
- 系统服务进程等
- 客户端的特点:所有进程理论上都可以访问属性共享内存(/dev/properties),因此 读是直接内存映射,非常快;写操作必须走属性服务(init 进程),因为要经过权限和安全上下文检查。
3.1.2 服务器(属性服务)
- 属性服务进程:位于 init 进程,监听 socket(通常是 property_service socket)。
- 主要功能:
- 统一处理来自客户端的 setprop 请求;
- 校验调用者的 UID、GID、SELinux 权限;
- 更新共享内存中对应的属性值;
- 如果是 persist.xx.xx 属性,还需要落盘保存到 /data/property/(或者对应的持久化分区),以保证重启后仍然生效。
3.1.3 属性文件与共享内存
- 属性共享内存:
/dev/__properties__
- init 进程在启动时创建,供所有进程 mmap;
- 每个属性存储为 key-value,使用树形结构进行快速查找;
- 只读访问直接 mmap 查找即可,不需走 IPC。
- 属性文件:
- 作用:为属性共享内存提供 初始数据和持久化数据;
- 文件存储路径:
/dev/__properties__/u:object_r:xxx_prop:s0
(按 SELinux 安全上下文拆分);- 每个文件大小固定为 1MB;
- 存储结构是树形(prefix tree / trie)索引。
3.1.4 流程示意
- 读属性(fast path):
客户端 → 直接 mmap/dev/__properties__
→ 查找 key → 返回 value。 - 写属性(secure path):
客户端 → 通过 property_service socket → 请求 init → 校验权限 → 更新共享内存 & 持久化(如 persist.xx.xx)。
3.1.5 示例
假设我们执行:
setprop persist.sys.locale en-US
- persist.sys.locale 属于 system_prop 域;
- setprop 命令调用 libc → 通过
property_service
socket 向 init 发送请求;
key = "persist.sys.locale"
value = "en-US"
- init 检查调用者的 UID/GID/SELinux 权限是否能写入 system_prop;判断 key 属于哪个属性文件(此处是 system_prop);
- 若通过,则更新
/dev/__properties__/u:object_r:system_prop:s0
中的共享内存节点; - 同时因为是 persist.xx.xx,还会落盘到 /data/property/persist.sys.locale。
3.1.6 例如 /dev/__properties__/u:object_r:system_prop:s0
- 每个属性文件(例如
/dev/__properties__/u:object_r:system_prop:s0
)本质是一个 mmap 共享内存区域。 - 这块内存中存储的是一个属性表,它并不是简单的 KV 顺序表,而是:
- 属性目录树 (property_info_area) → 用来快速定位 key;
- 属性条目 (prop_info) → 实际存放 key 和 value。
3.2 属性文件哪里来
3.2.1 实例:初始化 ro.build.version.release=13 属性
我们以 ro.build.version.release=13 这个最常见的系统属性为例,看看它是如何:
- 从哪里来;
- 被 init 加载;
- 存到哪儿;
- 最终怎么被 getprop 读到。
3.2.2 第一步:属性来源
在源码中,这个属性最早定义在:build/make/core/sysprop.mk
,然后编译到系统中,会写进 /system/build.prop
:
# /system/build.prop 中内容
ro.build.version.release=13
ro.build.version.sdk=33
ro.build.id=TQ3A.230805.001
...
其它属性定义还有:
/vendor/build.prop
/product/build.prop
/odm/build.prop
/default.prop
3.2.3 第二步:init 启动时加载 build.prop
在 init 启动时,会调用:
load_properties_from_file("/system/build.prop", /*required=*/true);
- 这段代码会:
- 打开 /system/build.prop
- 一行一行读取 key=value
- 把它们存到共享内存属性系统
3.2.4 第三步:写入共享内存节点 /dev/__properties__/u:object_r:system_prop:s0
根据属性的 SELinux 类型(由 property_contexts 文件决定),ro.build.version.release 被标记为:
ro.build.version.release u:object_r:system_prop:s0
所以它会被写入共享内存文件:/dev/__properties__/u:object_r:system_prop:s0
{"ro.build.version.release": "13","ro.build.version.sdk": "33",...
}
3.2.5 第四步:客户端访问
当你在 shell 中运行:
getprop ro.build.version.release
或者 Java 中调用:
SystemProperties.get("ro.build.version.release");
流程是这样的:
- 客户端进程 mmap
/dev/__properties__/u:object_r:system_prop:s0
- 查找 key “ro.build.version.release”
- 直接读取值 “13”
- 返回
4. 属性如何自定义
- 一般在 device/ 下面进行属性的定制(核心定制方式之一),比如:
要生成 system/build.prop --> device/ 下面某个产品配置的 system.prop
要生成 vendor/build.prop --> device/ 下面某个产品配置的 vendor.prop
要生成 product/build.prop --> device/ 下面某个产品配置的 product.prop - build/ 下面的一些编译脚本里的变量添加自定义属性,这些变量有:
ADDITIONAL_SYSTEM_PROPERTIES
ADDITIONAL_VENDOR_PROPERTIES
ADDITIONAL_ODM_PROPERTIES
ADDITIONAL_PRODUCT_PROPERTIES
ADDITIONAL_BUILD_PROPERTIES
举个例子:
ADDITIONAL_SYSTEM_PROPERTIES += ro.actionable_compatible_property.enabled=true - 我们在 build/core/main.mk 文件中发现:
ifneq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true) ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES)
else ifndef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES)...
PRODUCT_PROPERTY_OVERRIDES 会被添加到如 ADDITIONAL_SYSTEM_PROPERTIES 等变量中,因此我们可以在 aosp/device/ 下面的产品配置中添加自定义的属性信息在 PRODUCT_PROPERTY_OVERRIDES 变量中,这样就可以写入 ADDITIONAL_SYSTEM_PROPERTIES 等变量中然后被编译到属性文件。(这种方法通用性更好,不需要更改系统的核心编译源码)
如下面在 aosp/device 目录下搜索:有许多 PRODUCT_PROPERTY_OVERRIDES 来定义的自定义属性:
<you aosp dir>/device$ grep "PRODUCT_PROPERTY_OVERRIDES" ./* -rn
./amlogic/yukawa/device-yukawa.mk:22:PRODUCT_PROPERTY_OVERRIDES += ro.product.device=$(TARGET_DEV_BOARD)
./amlogic/yukawa/device-common.mk:175:PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=320
./amlogic/yukawa/device-common.mk:190:PRODUCT_PROPERTY_OVERRIDES += \
./amlogic/yukawa/device-common.mk:212:PRODUCT_PROPERTY_OVERRIDES += wifi.interface=wlan0 \
./amlogic/yukawa/device-common.mk:258:PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4 \
./amlogic/yukawa/device-yukawa_sei510.mk:7:PRODUCT_PROPERTY_OVERRIDES += ro.product.device=sei510
./generic/goldfish/board/emu64a/details.mk:19:PRODUCT_PROPERTY_OVERRIDES += \
./generic/goldfish/board/emu64a16k/details.mk:19:PRODUCT_PROPERTY_OVERRIDES += \
./generic/goldfish/board/emu64x16k/details.mk:19:PRODUCT_PROPERTY_OVERRIDES += \
./generic/goldfish/board/emu64x/details.mk:19:PRODUCT_PROPERTY_OVERRIDES += \
./generic/car/emulator/audio/car_emulator_audio.mk:28:PRODUCT_PROPERTY_OVERRIDES += ro.hardware.audio.primary=caremu
- 除此以外,还有如下:
# from variable PRODUCT_SYSTEM_PROPERTIES
# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES
5. 应用属性接口分析
5.1 C 语言属性 API 源码及头文件目录
- 源码头文件路径 :
aosp/system/core/libcutils/include/cutils/properties.h
- 实现源码路径 :
aosp/system/core/libcutils/properties.cpp
int property_get(const char* key, char* value, const char* default_value);
int8_t property_get_bool(const char *key, int8_t default_value);
int64_t property_get_int64(const char *key, int64_t default_value);
int32_t property_get_int32(const char *key, int32_t default_value);
int property_set(const char *key, const char *value);
5.2 C++ 语言属性 API 源码及头文件目录
- C++ 头文件路径:
aosp/system/libbase/include/android-base/properties.h
- C++ 源码路径 :
aosp/system/libbase/properties.cpp
namespace android {
namespace base {
std::string GetProperty(const std::string& key, const std::string& default_value);
bool GetBoolProperty(const std::string& key, bool default_value);
template <typename T> T GetIntProperty(const std::string& key,T default_value,T min = std::numeric_limits<T>::min(),T max = std::numeric_limits<T>::max());
template <typename T> T GetUintProperty(const std::string& key,T default_value,T max = std::numeric_limits<T>::max());
bool SetProperty(const std::string& key, const std::string& value);
5.3 Java 系统级别的属性接口
- 实现源码路径:framework/base/core/java/android/os/SystemProperties.java
/*** Get the String value for the given {@code key}.** @param key the key to lookup* @param def the default value in case the property is not set or empty* @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty* string otherwise* @hide*/@NonNull@SystemApipublic static String get(@NonNull String key, @Nullable String def) {if (TRACK_KEY_ACCESS) onKeyAccess(key);return native_get(key, def);}// 请注意,@SystemApi @hide的含义。public static int getInt(@NonNull String key, int def) {if (TRACK_KEY_ACCESS) onKeyAccess(key);return native_get_int(key, def);}public static long getLong(@NonNull String key, long def) {if (TRACK_KEY_ACCESS) onKeyAccess(key);return native_get_long(key, def);}public static boolean getBoolean(@NonNull String key, boolean def) {if (TRACK_KEY_ACCESS) onKeyAccess(key);return native_get_boolean(key, def);}
- 应用例子:
import android.os.SystemProperties;
SystemProperties.set("my.work.time", 8);
6. 属性实战案例
6.1 C++ 属性编写实战:写一个 C++ 实战接口代码
#include <cutils/properties.h> // 引入 Android 属性 API
#include <string>#define LOG_TAG "PropertyDemo"
#include <log/log.h>// 获取属性
void getSystemProperty(const std::string& property) {char value[PROP_VALUE_MAX];if (property_get(property.c_str(), value, "") > 0) {ALOGD("Property Value for %s: %s", property.c_str(), value);} else {ALOGE("Failed to get property: %s", property.c_str());}
}// 设置属性
void setSystemProperty(const std::string& property, const std::string& value) {if (property_set(property.c_str(), value.c_str()) == 0) {ALOGD("Successfully set %s to %s", property.c_str(), value.c_str());} else {ALOGE("Failed to set property: %s", property.c_str());}
}int main() {// 获取系统属性ALOGD("Getting system property 'ro.build.version.release':");getSystemProperty("ro.build.version.release");// 设置系统属性// ALOGD("Setting system property 'persist.sys.debug' to '1'");// setSystemProperty("persist.sys.debug", "1");setSystemProperty("vendor.my.work.time", "8");// 停止某个服务ALOGD("Stopping service...");setSystemProperty("ctl.stop", "vendor.gatekeeper-1-0");// 启动某个服务ALOGD("Starting service...");setSystemProperty("ctl.start", "vendor.gatekeeper-1-0");while(1){usleep(1000*1000);ALOGD("sleep for one second.");}return 0;
}
6.2 Android.bp 编译脚本编写
cc_binary {name: "propertydemo", // 模块名称srcs: ["main.cpp", // 源代码文件],shared_libs: ["liblog", // 依赖 Android 日志库"libcutils", //依赖属性接口],cflags: ["-Wall", // 编译时启用所有警告"-std=c++11", // 使用 C++11 标准],vendor: true,
}
6.3 sepolicy 内容编写
file_contexts property_contexts propertydemo.te property.te
file_contexts:应用程序安全上下文标签描述
property_contexts:自定义属性上下文标签描述
propertydemo.te:应用程序selinux策略
property.te:自定义属性类型
6.3.1 file_contexts 编写
/vendor/bin/propertydemo u:object_r:propertydemo_dt_exec:s0
6.3.2 property_contexts 编写
# first version# second version
# vendor.my.work.time u:object_r:vendor_my_work_prop:s0
6.3.3 propertydemo.te 编写
type propertydemo_dt , domain;
type propertydemo_dt_exec, exec_type, vendor_file_type,file_type;init_daemon_domain(propertydemo_dt)domain_auto_trans(shell, propertydemo_dt_exec, propertydemo_dt)# 这个后面再打开
# set_prop(propertydemo_dt , vendor_my_work_prop);
# set_prop(propertydemo_dt , ctl_default_prop);
# set_prop(propertydemo_dt , ctl_start_prop);
# set_prop(propertydemo_dt , ctl_stop_prop);
6.3.4 property.te 编写
# first version# second version
# type vendor_my_work_prop, property_type;# third version
# type vendor_my_work_prop, property_type, vendor_property_type;
6.3.5 添加编译
添加到编译脚本中 build/make/target/board/emulator_x86_64/BoardConfig.mk
,需要在这个文件中配置 sepolicy 所在的路径,这样系统可以将其加入编译到 selinux_policy。
BOARD_SEPOLICY_DIRS += device/hello/propertydemo/sepolicy
6.3.6 结果
make selinux_policy
mmm device/hello/propertydemo/adb remount
adb push out/target/product/emulator_x86_64/vendor/bin/propertydemo /vendor/bin/
adb push out/target/product/emulator_x86_64/vendor/etc/selinux /vendor/etc/emulator -verbose -writable-system
6.3.6.1 first version
- property_contexts
# first version# second version
# vendor.my.work.time u:object_r:vendor_my_work_prop:s0
- property.te
# first version# second version
# type vendor_my_work_prop, property_type;# third version
# type vendor_my_work_prop, property_type, vendor_property_type;
- 编译通过。实际运行结果如下。
总的来说,就是设置自定义属性:vendor.my.work.time失败。执行"ctl.stop/start"属性失败,因为我们在策略 te 中并未添加相关的属性访问策略,先来查看下这些属性对应的安全上下文标签是哪些:
6.3.6.2 second version 编译报错
- property_contexts
# first version# second version
vendor.my.work.time u:object_r:vendor_my_work_prop:s0
- property.te
# first version# second version
type vendor_my_work_prop, property_type;# third version
# type vendor_my_work_prop, property_type, vendor_property_type;
make selinux_policy
结合错误日志:libsepol.report_failure: neverallow on line 64 of system/sepolicy/private/property.te
,找到文件 system/sepolicy/private/property.te
的第 64 行:
treble_sysprop_neverallow(`enforce_sysprop_owner(`neverallow domain {property_type-system_property_type-product_property_type-vendor_property_type}:file no_rw_file_perms;
')
.....
翻译一下就是说,不允许 domain 主域访问 property_type 类型的资源,除了 -system_property_type
、-product_property_pype
、-vendor_property_type
。解决方法就是用这里豁免的vendor_property_type
,修改 property.te 文件如下(即前面的 second version)可以编译通过:
# first version
# type vendor_my_work_prop, property_type;# second version
type vendor_my_work_prop, property_type, vendor_property_type;
6.3.6.3 third version 结果运行
根据前面 6.3.6.1 和 6.3.6.2 的分析,在 propertydemo.te 中添加以下访问属性的策略:使用属性宏 set_prop() 来添加访问权限。
type propertydemo_dt, domain;
type propertydemo_dt_exec, exec_type, vendor_file_type, file_type;init_daemon_domain(propertydemo_dt)
domain_auto_trans(shell, propertydemo_dt_exec, propertydemo_dt)# 这个后面再打开
set_prop(propertydemo_dt, vendor_my_work_prop);
set_prop(propertydemo_dt, ctl_default_prop);
set_prop(propertydemo_dt, ctl_start_prop);
set_prop(propertydemo_dt, ctl_stop_prop);
- property.te
# first version# second version
# type vendor_my_work_prop, property_type;# third version
type vendor_my_work_prop, property_type, vendor_property_type;
- property_contexts
# first version# second version
vendor.my.work.time u:object_r:vendor_my_work_prop:s0
重新编译后,push 到系统中,重新验证:得到正确结论。
6.4 扩展
- 思维扩展:如果一个vendor分区的进程想访问并设置 system_prop 的属性,如 persist.sys. 开头的属性,怎么办?
- 定义 init 服务: 在 init.rc 文件中,创建一个负责设置属性的服务:
on property:vendor.sys.request_debug=1 setprop persist.sys.debug 1