Android属性系统

基于AndroidO 分析

Android属性系统初始化流程

  1. property_init

    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

    • 解析过程会创建两个链表, contextsprefixes。解析完成后,会遍历contexts链表,为其中的每一个context_node创建一个关联文件,并且打开该文件将其映射到进程的虚拟地址空间,并在该地址空间上构建一个prop_area对象。

    解析之后的各个属性之间之间的关系如下:
    安卓属性系统流程图
    对照真实设备的/dev/__properties目录:

    image-20210401174758800

  2. process_kernel_dt

    解析dts中,内核配置的属性。 目录是/proc/device-tree/firmware/android/

  3. process_kernel_cmdline

    处理/proc/cmdline中的属性,由顺序可以知道,dts中的properties 的优先级大于command-line

  4. export_kernel_boot_props

    将前面内核comand-line中的属性转换成init 进程需要的属性。这种操作不是很懂暂时。

  5. property_load_boot_defaults

    解析默认的属性文件,这些文件包括:

    /system/etc/prop.default
    /odm/default.prop
    /vendor/default.prop
    
  6. start_property_service

    打开属性服务,这里会创建一个 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.propbuiltin命令,其实现位于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.propbuiltin命令,其实现位于system/core/init/builtins.cpp@do_load_persist_props中。

这些持久化的属性都保存在/data/property目录下,属性名对应文件名,文件内容对应属性值。

image-20210401210616705

再看一下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.
 */

属性系统的内存管理

  1. prop_bt

    ro.build.product为例,将该属性key 以.分割成robuildproduct三个部分,每个部分都对应一个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`
    };
    
  2. 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] = '';
      }
    };
    
  3. prop_area

    描述一个内存区域,通过mmapprop_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;
}
  1. 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* prefixescontext_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;
    }
    
  2. 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/,然后设置过程就是该目录下除了compatiblename的其他文件的内容。

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这里就不做贴代码了,就是打开属性文件逐行读取,然后将每一行分割为keyvalue两部分,再调用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-fstrigge后执行,这里和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

  1. __system_property_update

    当 属性已经存在时,用于更新 非只读属性的值。回顾一下prop_info,其保存属性值得value字段长度固定为PROP_VALUE_MAX,更新时只要简单的覆盖就行了。

  2. __system_property_add

    当属性不存在时,添加属性,先在prop_area中查找属性,找不到就创建prop_bt最后创建prop_info,完成属性树的构建。

  3. 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();
    }
}
原文地址:https://www.cnblogs.com/liutimo/p/14611102.html