属性系统与系统配置管理:Android的全局配置中心

引言

在之前的文章中,我们见证了init进程如何"开天辟地",AMS如何管理应用生命周期。但是,Android系统如何存储和管理全局配置?如何让不同进程共享配置信息?如何控制配置的访问权限?

答案就是属性系统(Property System)------Android的"全局配置中心"。

想象这样的场景:

  • 你修改屏幕亮度,系统立即生效
  • 你开启飞行模式,所有网络服务自动关闭
  • 应用查询系统版本号,瞬间获得结果
  • init进程根据属性值决定是否启动某个服务

这些看似简单的操作,背后都依赖属性系统

ini 复制代码
属性系统的核心作用:
┌───────────────────────────────────────┐
│  全局配置存储                          │
│  - ro.build.version.sdk = 35         │  (只读,系统版本)
│  - persist.sys.timezone = Asia/...   │  (持久化,时区)
│  - sys.boot_completed = 1            │  (临时,启动状态)
└────────────┬──────────────────────────┘
             │
      共享给所有进程访问
             │
   ┌─────────┼──────────┐
   │         │          │
  init    应用进程   系统服务
   │         │          │
 控制服务  查询配置  监听变更

📖 系列前置阅读:建议先阅读第12篇(init进程与属性系统基础),理解init在属性系统中的核心角色。


属性系统是什么?

属性系统的定义

属性系统(Property System) 是Android提供的一个全局键值对存储机制,用于:

  • 存储系统配置信息(版本号、设备信息、用户设置等)
  • 进程间共享配置数据(通过共享内存实现)
  • 触发系统事件(属性变更 → 触发器 → 执行动作)
  • 控制服务行为(启动/停止服务、调整参数等)

属性的分类

根据属性名前缀,属性分为不同类型:

前缀 类型 说明 示例
ro. 只读属性 系统启动后不可修改 ro.build.version.sdk=35
persist. 持久化属性 重启后保留 persist.sys.timezone=Asia/Shanghai
sys. 系统属性 临时属性,重启后丢失 sys.boot_completed=1
ctl. 控制属性 用于控制服务启停 ctl.start=zygote
debug. 调试属性 用于调试开关 debug.atrace.tags.enableflags=0
无前缀 普通属性 各模块自定义 vendor.camera.aux.packagelist=...

属性值的限制

cpp 复制代码
// system/core/property_service/libpropertyinfoparser/property_info_parser.cpp
#define PROP_NAME_MAX   92   // 属性名最大长度
#define PROP_VALUE_MAX  92   // 属性值最大长度

重要限制:

  • 属性名长度: 最多92个字符
  • 属性值长度: 最多92个字符
  • 属性数量: 系统限制(Android 15约为~2000个)

属性系统架构

整体架构图

为了帮助理解属性系统的完整架构,下图展示了从共享内存到应用访问的全链路:

图:属性系统架构,init进程管理共享内存,SELinux控制访问权限,应用通过Binder或直接内存访问读取属性

这张图涵盖了:

  • 共享内存层: /dev/__properties__内存映射
  • init进程: 属性服务管理者,处理写请求
  • SELinux层: 属性权限控制(property_contexts)
  • 访问接口: Java/Native/Shell三层API
  • 触发器机制: 属性变更触发init.rc中的Action

接下来,我们将逐一深入这些核心机制的源码实现。


共享内存实现原理

为什么使用共享内存?

传统的进程间通信(Binder、Socket)每次读取都需要跨进程调用,开销较大。属性系统采用共享内存设计:

优势:

  • ✅ 读取速度快(直接内存访问,无需Binder调用)
  • ✅ 支持高频读取(系统属性被频繁查询)
  • ✅ 内存占用小(所有进程共享同一块内存)

劣势:

  • ❌ 写入需要保护(通过init进程的Socket接口控制)
  • ❌ 属性数量有限(共享内存大小固定)

共享内存布局

ini 复制代码
/dev/__properties__ (共享内存文件,128KB)
┌─────────────────────────────────────────────────┐
│  Header (头部,记录属性区域信息)                  │
│  - magic: 0x00504F4D (识别标记)                 │
│  - version: 2 (格式版本)                        │
│  - num_prop_areas: 1 (属性区域数量)             │
├─────────────────────────────────────────────────┤
│  Property Area 1 (属性区域,存储键值对)           │
│  ┌───────────────────────────────────────────┐  │
│  │  ro.build.version.sdk = 35                │  │
│  │  (name: 28字节, value: 92字节)            │  │
│  ├───────────────────────────────────────────┤  │
│  │  persist.sys.timezone = Asia/Shanghai     │  │
│  ├───────────────────────────────────────────┤  │
│  │  sys.boot_completed = 1                   │  │
│  ├───────────────────────────────────────────┤  │
│  │  ... (更多属性)                            │  │
│  └───────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘

共享内存初始化源码

cpp 复制代码
// system/core/init/property_service.cpp (Android 15)
void PropertyInit() {
    // 1. 创建共享内存文件 /dev/__properties__
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);

    // 2. 创建属性区域(Property Area)
    // 每个区域128KB,最多支持~2000个属性
    CreateSerializedPropertyInfo();

    // 3. 初始化属性服务Socket
    // 用于接收其他进程的写请求
    if (!InitPropertySet("u:object_r:properties_serial:s0")) {
        LOG(FATAL) << "Failed to initialize property set";
    }

    // 4. 加载默认属性
    // 从 /system/etc/prop.default 加载
    LoadPropertiesFromFile("/system/etc/prop.default");

    // 5. 加载持久化属性
    // 从 /data/property 加载 persist.* 属性
    LoadPersistentProperties();

    // 6. 启动属性服务Socket监听
    StartPropertyService(&property_set_fd);
}

属性读取机制

Java层读取

java 复制代码
// frameworks/base/core/java/android/os/SystemProperties.java
public class SystemProperties {
    /**
     * 读取字符串属性
     * @param key 属性名
     * @param def 默认值
     * @return 属性值
     */
    public static String get(String key, String def) {
        // 调用Native方法
        String ret = native_get(key, def);
        return ret;
    }

    /**
     * 读取整数属性
     */
    public static int getInt(String key, int def) {
        String value = get(key);
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return def;
        }
    }

    /**
     * 读取布尔属性
     */
    public static boolean getBoolean(String key, boolean def) {
        String value = get(key);
        if (value == null || value.length() == 0) {
            return def;
        }
        return "1".equals(value) || "true".equalsIgnoreCase(value)
                || "y".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)
                || "on".equalsIgnoreCase(value);
    }

    // Native方法声明
    private static native String native_get(String key, String def);
}

Native层读取

cpp 复制代码
// system/core/libcutils/properties.cpp
int property_get(const char *key, char *value, const char *default_value) {
    // 1. 从共享内存中查找属性
    const prop_info *pi = __system_property_find(key);

    if (pi == nullptr) {
        // 属性不存在,返回默认值
        if (default_value) {
            strlcpy(value, default_value, PROP_VALUE_MAX);
            return strlen(value);
        }
        return 0;
    }

    // 2. 读取属性值(直接从共享内存读取,速度极快)
    return __system_property_read(pi, nullptr, value);
}

// 从共享内存查找属性
const prop_info* __system_property_find(const char *name) {
    // 使用Trie树(前缀树)快速查找
    // 共享内存中的属性以Trie树组织,查找时间复杂度O(m),m为属性名长度
    return prop_area->find(name);
}

性能优化:

  • 共享内存直接读取,无Binder开销
  • Trie树索引,查找速度O(m)
  • 属性值缓存,避免重复解析

Shell层读取

bash 复制代码
# getprop命令读取属性
getprop ro.build.version.sdk
# 输出: 35

# 读取所有属性
getprop | grep "ro.build"

# 输出示例:
# [ro.build.date]: [Thu Jan 10 12:00:00 UTC 2026]
# [ro.build.id]: [AP3A.241105.008]
# [ro.build.version.sdk]: [35]
# [ro.build.version.release]: [15]

属性写入机制

写入流程

属性写入必须通过init进程的Socket接口,以保证权限控制和触发器执行:

scss 复制代码
应用进程
    ↓ (调用SystemProperties.set())
通过Socket连接到init进程
    ↓ (/dev/socket/property_service)
init进程接收请求
    ↓
1. 权限检查(SELinux)
    ↓
2. 写入共享内存
    ↓
3. 通知其他进程(属性变更)
    ↓
4. 执行触发器(如果有)

Java层写入

java 复制代码
// frameworks/base/core/java/android/os/SystemProperties.java
public class SystemProperties {
    /**
     * 写入属性
     * 需要权限: android.permission.WRITE_SETTINGS 或 root权限
     */
    public static void set(String key, String val) {
        // 权限检查
        if (!checkPermission()) {
            throw new SecurityException("Requires WRITE_SETTINGS permission");
        }

        // 调用Native方法,通过Socket发送到init进程
        native_set(key, val);

        // 等待属性写入完成
        PropertyInvalidatedCache.invalidateCache(key);
    }

    private static native void native_set(String key, String val);
}

Native层写入

cpp 复制代码
// system/core/init/property_service.cpp
void HandlePropertySet(const std::string& name, const std::string& value,
                       const std::string& source_context, const ucred& cr,
                       SocketConnection* socket, std::string* error) {
    // 1. 权限检查(SELinux)
    if (!CheckPermissions(name, value, source_context, cr, error)) {
        return; // 权限不足,拒绝写入
    }

    // 2. 特殊属性处理:ctl.* 用于控制服务
    if (StartsWith(name, "ctl.")) {
        HandleControlProperty(name, value, error);
        return;
    }

    // 3. 持久化属性:persist.* 需要写入文件
    if (StartsWith(name, "persist.")) {
        WritePersistentProperty(name, value);
    }

    // 4. 只读属性:ro.* 只能在启动阶段设置,之后禁止修改
    if (StartsWith(name, "ro.") && IsBootCompleted()) {
        *error = "Read-only property was already set";
        return;
    }

    // 5. 写入共享内存
    uint32_t* serial = __system_property_serial(prop_info);
    *serial = *serial + 1; // 序列号+1,通知监听者
    __system_property_update(prop_info, value.c_str(), value.size());
    *serial = *serial + 1; // 再次+1,完成写入

    // 6. 执行触发器(如果init.rc中定义了on property:xxx=yyy)
    ActionManager::GetInstance().QueuePropertyChange(name, value);
}

权限检查机制

属性写入受到SELinux 严格控制,权限定义在property_contexts:

bash 复制代码
# /system/etc/selinux/plat_property_contexts
# 格式: 属性名前缀  SELinux类型

# 只有init进程可以设置ro.*属性
ro.                     u:object_r:default_prop:s0

# persist.*属性可以被system_server设置
persist.sys.            u:object_r:system_prop:s0

# debug.*属性可以被shell和adb设置
debug.                  u:object_r:debug_prop:s0

# 自定义供应商属性
vendor.camera.          u:object_r:vendor_camera_prop:s0

属性触发器机制

触发器的作用

init.rc中可以定义属性触发器,当属性值变化时自动执行命令:

bash 复制代码
# /system/etc/init/init.rc

# 当sys.boot_completed=1时,执行以下命令
on property:sys.boot_completed=1
    # 启动bootanim服务(开机动画)
    start bootanim
    # 清理临时文件
    exec - system system -- /system/bin/rm -rf /data/local/tmp/*

# 当persist.sys.timezone变化时,更新时区
on property:persist.sys.timezone=*
    # 重启时区守护进程
    restart timezone-updater

# 当ro.debuggable=1时(调试模式),启用额外日志
on property:ro.debuggable=1
    # 设置日志级别
    setprop persist.log.tag V
    # 启动调试服务
    start adbd

触发器执行流程

cpp 复制代码
// system/core/init/action_manager.cpp
void ActionManager::QueuePropertyChange(const std::string& name,
                                        const std::string& value) {
    // 1. 遍历所有注册的Action
    for (const auto& action : actions_) {
        // 2. 检查Action的触发条件
        if (action->CheckPropertyTrigger(name, value)) {
            // 3. 将Action加入执行队列
            event_queue_.emplace(action);
        }
    }

    // 4. 唤醒init主循环,执行队列中的Action
    epoll_ctl(epoll_fd, EPOLL_CTL_MOD, property_fd, &ev);
}

实战应用场景

场景1: 查询系统信息

java 复制代码
// 获取系统版本号
int sdkVersion = SystemProperties.getInt("ro.build.version.sdk", 0);
// 输出: 35 (Android 15)

// 获取设备型号
String model = SystemProperties.get("ro.product.model", "Unknown");
// 输出: Pixel 8 Pro

// 获取系统启动状态
boolean bootCompleted = SystemProperties.getBoolean("sys.boot_completed", false);
// true表示启动完成

// 获取时区
String timezone = SystemProperties.get("persist.sys.timezone", "UTC");
// 输出: Asia/Shanghai

场景2: 控制服务启停

bash 复制代码
# 启动zygote服务
setprop ctl.start zygote

# 停止surfaceflinger服务
setprop ctl.stop surfaceflinger

# 重启audioserver服务
setprop ctl.restart audioserver

原理:

  • 设置ctl.start/stop/restart属性时,init进程会执行相应的服务控制命令
  • 这是Android推荐的服务控制方式(替代直接kill进程)

场景3: 调试开关

bash 复制代码
# 启用systrace追踪
setprop debug.atrace.tags.enableflags 0x1000

# 启用SurfaceFlinger调试日志
setprop debug.sf.showupdates 1

# 启用输入事件日志
setprop debug.input.trace 1

# 关闭硬件加速(用于调试渲染问题)
setprop debug.hwui.disable_vsync true

场景4: 监听属性变更

cpp 复制代码
// Native代码监听属性变更
#include <sys/system_properties.h>

void OnPropertyChange(const char *name, const char *value, void *cookie) {
    printf("Property %s changed to %s\n", name, value);

    // 执行相应操作
    if (strcmp(name, "persist.sys.locale") == 0) {
        // 重新加载语言配置
        ReloadLocale(value);
    }
}

int main() {
    // 注册属性变更监听器
    __system_property_set_callback(OnPropertyChange, nullptr);

    // 主循环
    while (true) {
        sleep(1);
    }
}

持久化属性

persist.*属性的存储

persist.*前缀的属性会持久化到磁盘,重启后保留:

bash 复制代码
# 持久化属性存储位置
/data/property/
├── persist.sys.timezone          # 单个文件存储单个属性
├── persist.sys.locale
├── persist.sys.usb.config
└── ...

# 查看持久化属性
ls -l /data/property/
# -rw-------  1 system system    15 Jan 10 12:00 persist.sys.timezone

# 读取持久化属性内容
cat /data/property/persist.sys.timezone
# Asia/Shanghai

持久化源码实现

cpp 复制代码
// system/core/init/persistent_properties.cpp
void WritePersistentProperty(const std::string& name, const std::string& value) {
    // 1. 检查是否为persist.*属性
    if (!StartsWith(name, "persist.")) {
        return;
    }

    // 2. 构建文件路径: /data/property/persist.*
    std::string path = "/data/property/" + name;

    // 3. 写入文件(权限: 0600, owner: system)
    WriteStringToFile(value, path, 0600, AID_SYSTEM, AID_SYSTEM);

    // 4. 同步到磁盘(确保数据落盘)
    sync();
}

void LoadPersistentProperties() {
    // 1. 读取 /data/property/ 目录
    std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir("/data/property"), closedir);

    // 2. 遍历所有文件
    struct dirent* entry;
    while ((entry = readdir(dir.get())) != nullptr) {
        std::string name = entry->d_name;

        // 3. 只加载persist.*文件
        if (!StartsWith(name, "persist.")) {
            continue;
        }

        // 4. 读取文件内容
        std::string value;
        ReadFileToString("/data/property/" + name, &value);

        // 5. 设置属性(加载到共享内存)
        __system_property_set(name.c_str(), value.c_str());
    }
}

Android 15的属性系统增强

1. 属性安全增强

Android 15加强了属性访问控制:

cpp 复制代码
// system/core/init/property_service.cpp (Android 15新增)
bool CheckPropertyPermissions(const std::string& name,
                              const std::string& value,
                              const std::string& source_context,
                              const ucred& cr) {
    // 1. 检查SELinux权限
    if (!CheckMacPerms(name, source_context)) {
        return false;
    }

    // 2. 新增:检查属性名合法性(防止路径遍历攻击)
    if (name.find("..") != std::string::npos ||
        name.find("/") != std::string::npos) {
        return false; // 拒绝包含..或/的属性名
    }

    // 3. 新增:检查属性值长度限制
    if (value.size() > PROP_VALUE_MAX) {
        return false;
    }

    // 4. 新增:限制高频写入(防止DoS攻击)
    if (!ThrottlePropertyWrites(name, cr.uid)) {
        return false;
    }

    return true;
}

2. 属性变更通知优化

Android 15优化了属性变更通知机制,减少CPU唤醒:

cpp 复制代码
// 批量通知,减少唤醒次数
void NotifyPropertyChangeBatch(const std::vector<std::string>& names) {
    // 合并多个属性变更为一次通知
    for (const auto& name : names) {
        property_change_queue_.push(name);
    }

    // 延迟100ms后统一通知,避免频繁唤醒
    if (!notification_pending_) {
        notification_pending_ = true;
        timer.schedule([]() {
            FlushPropertyChangeQueue();
            notification_pending_ = false;
        }, 100ms);
    }
}

3. 属性缓存优化

Android 15引入了属性缓存,进一步提升读取性能:

java 复制代码
// frameworks/base/core/java/android/os/SystemProperties.java (Android 15)
public class SystemProperties {
    // 属性缓存(LRU Cache,最多缓存100个高频属性)
    private static final PropertyCache sCache = new PropertyCache(100);

    public static String get(String key, String def) {
        // 1. 先查缓存
        String cached = sCache.get(key);
        if (cached != null) {
            return cached;
        }

        // 2. 缓存未命中,从共享内存读取
        String value = native_get(key, def);

        // 3. 存入缓存
        sCache.put(key, value);

        return value;
    }
}

常见问题(FAQ)

Q1: 为什么属性值长度限制为92字节?

A:

  • 历史原因:早期Android设计时,为了节省共享内存空间
  • 兼容性:修改限制会破坏现有应用
  • 解决方案:如果需要存储长内容,可以:
    • 使用多个属性拆分存储
    • 存储到文件,属性只记录文件路径
    • 使用SharedPreferences或数据库

Q2: 如何在应用中监听属性变更?

A: 应用无法直接监听(需要Native代码),推荐方案:

  • 使用广播:系统属性变更时发送广播
  • 使用ContentObserver:监听Settings数据库
  • 定时轮询:不推荐,耗电

Q3: persist.*属性何时生效?

A:

  • 写入后立即生效(写入共享内存)
  • 同时写入磁盘(/data/property/),重启后自动加载
  • 如果/data分区未挂载(如Recovery模式),persist.*属性不会加载

Q4: 为什么我的应用无法修改属性?

A: 可能的原因:

  1. 权限不足 :需要android.permission.WRITE_SETTINGS权限或root权限
  2. SELinux拒绝 :检查property_contexts是否允许修改该属性
  3. 只读属性:ro.*属性在启动后无法修改
  4. 应用沙箱:部分属性只能由系统进程修改

Q5: 如何调试属性相关问题?

bash 复制代码
# 1. 查看所有属性
adb shell getprop

# 2. 查看属性变更日志
adb logcat -b main -s PropertyService:V

# 3. 查看SELinux拒绝日志
adb logcat -b main | grep "avc: denied"

# 4. 测试属性读写
adb shell
setprop debug.test.prop "test_value"
getprop debug.test.prop

实战:自定义属性应用

场景:智能座舱模式切换

假设你在开发智能座舱系统,需要根据驾驶模式切换不同的配置。

1. 定义自定义属性

bash 复制代码
# 在 /vendor/etc/selinux/vendor_property_contexts 定义权限
vendor.ivi.mode    u:object_r:vendor_ivi_prop:s0

2. 在init.rc中定义触发器

bash 复制代码
# /vendor/etc/init/ivi.rc

# 驾驶模式:关闭娱乐功能,启用导航
on property:vendor.ivi.mode=driving
    stop media_server
    stop game_service
    start navigation_service
    setprop vendor.audio.mode driving

# 停车模式:启用全部娱乐功能
on property:vendor.ivi.mode=parking
    start media_server
    start game_service
    stop navigation_service
    setprop vendor.audio.mode parking

# 乘客模式:启用后排娱乐
on property:vendor.ivi.mode=passenger
    start rear_entertainment
    setprop vendor.display.mode dual

3. 应用层控制

java 复制代码
// 智能座舱控制器
public class IVIModeController {
    public static void setDrivingMode() {
        SystemProperties.set("vendor.ivi.mode", "driving");
        // init进程会自动执行on property触发器
    }

    public static void setParkingMode() {
        SystemProperties.set("vendor.ivi.mode", "parking");
    }

    public static void setPassengerMode() {
        SystemProperties.set("vendor.ivi.mode", "passenger");
    }

    public static String getCurrentMode() {
        return SystemProperties.get("vendor.ivi.mode", "driving");
    }
}

4. 监听模式变更

java 复制代码
// 服务监听模式变更
public class MediaService extends Service {
    @Override
    public void onCreate() {
        // 注册属性变更广播
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.PROPERTY_CHANGED");
        registerReceiver(mModeChangeReceiver, filter);
    }

    private BroadcastReceiver mModeChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String mode = SystemProperties.get("vendor.ivi.mode", "");
            if ("parking".equals(mode)) {
                // 进入停车模式,启用全部功能
                enableAllFeatures();
            } else if ("driving".equals(mode)) {
                // 进入驾驶模式,禁用娱乐功能
                disableEntertainment();
            }
        }
    };
}

总结

本文深度剖析了Android属性系统的核心机制:

核心要点回顾

  1. 属性系统定义:

    • 全局键值对存储,通过共享内存实现高效访问
    • 分类:ro(只读)、persist(持久化)、sys(临时)、ctl(控制)等
  2. 共享内存实现:

    • /dev/__properties__共享内存文件(128KB)
    • Trie树索引,查找时间O(m)
    • 读取速度快,无Binder开销
  3. 属性读写机制:

    • 读取:直接从共享内存读取,速度极快
    • 写入:通过init进程的Socket接口,受SELinux控制
  4. 权限控制:

    • SELinux策略文件(property_contexts)定义访问权限
    • 不同前缀的属性有不同的访问控制
  5. 触发器机制:

    • init.rc中定义on property:xxx=yyy触发器
    • 属性变更时自动执行命令
  6. 持久化机制:

    • persist.*属性存储在/data/property/
    • 重启后自动加载
  7. Android 15增强:

    • 属性安全增强(防路径遍历、限流)
    • 变更通知优化(批量通知,减少唤醒)
    • 属性缓存(LRU Cache,提升性能)

参考资料

  1. AOSP 15.0源码 - property_service.cpp
  2. AOSP 15.0源码 - SystemProperties.java
  3. SELinux Property Contexts

系列文章


本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品

相关推荐
zjttsh2 小时前
MySQL加减间隔时间函数DATE_ADD和DATE_SUB的详解
android·数据库·mysql
fengsen52113143 小时前
MySQL--》如何在MySQL中打造高效优化索引
android·mysql·adb
吴声子夜歌4 小时前
RxJava——Hot Observable和Cold Observable
android·rxjava
dreams_dream6 小时前
MySQL 主从复制(小白友好 + 企业级)
android·数据库·mysql
城东米粉儿6 小时前
Android PLT Hook 笔记
android
城东米粉儿6 小时前
leakcanary原理
android
龙之叶6 小时前
Android ADB Shell 常用命令
android·adb
城东米粉儿7 小时前
Android 图片内存问题分析、定位
android
之歆8 小时前
MySQL 主从复制完全指南
android·mysql·adb