属性系统源码分析一

这是一个介绍 Android 属性系统的系列文章:

  • Android 属性系统入门
  • 属性文件生成过程分析
  • 如何添加系统属性
  • 属性与 SeLinux
  • 属性系统源码分析一(本文)
  • 属性系统源码分析二
  • 属性系统源码分析三

本文基于 AOSP android-10.0.0_r41 分支讲解

1. 属性系统整体架构

  • Android 系统启动时会从若干属性配置文件中加载属性内容,所有属性(key/value)会存入同一块共享内存中
  • 系统中的各个进程会43 4白板将这块共享内存映射到自己的内存空间,这样就可以直接读取属性内容了
  • 系统中只有 init 进程可以直接修改属性值,其他进程不能直接修改属性值
  • init 进程启动了一个 Socket 服务端,一般称为 Property Service,其他进程通过 socket 方式,向 Property Service 发出属性修改请求。
  • 共享内存中的键值内容会以一种字典树的形式进行组织。

属性系统的整体架构如下:

接下来会从两个方面解析整个属性系统:

  • 属性系统初始化过程
  • 属性系统的使用过程

2. 属性系统初始化过程之 property_info 文件生成过程分析

2.1 整体流程

属性系统是在 init 进程中初始化的,大致可以分为以下几步:

  • 生成 property_info 文件
  • /dev/__properties__ 目录下生成每个属性对应的文件
  • 加载各个分区中的属性配置文件

接下来我们就先分析生成 property_info 文件的过程:

内核启动后,第一个启动的进程是 init,对应到的代码是 system/core/init/init.cpp 中的 main 函数,该函数会调用到 SecondStageMain 函数,SecondStageMain 函数进一步调用到 property_init(),该函数用于属性系统的初始化:

cpp 复制代码
void property_init() {

    // 创建 /dev/__properties__ 文件夹
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);

    // 生成 /dev/__properties__/property_info 文件
    CreateSerializedPropertyInfo();

    // 生成属性文件
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }

    // 这里应该是重复了,新版本代码已经删除
    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }
}

这个函数流程很清晰:

  • mkdir 函数会创建好 /dev/__properties__ 文件夹
  • 接着调用 CreateSerializedPropertyInfo() 函数加载安全上下文信息,序列化操作后保存到 /dev/__properties__/property_info 文件中
  • 然后调用 __system_property_area_init() 函数,在 /dev/__properties__ 目录下创建每个安全上下文对应的属性文件
  • 最后调用 LoadDefaultPath 函数,加载属性值,并把属性值写入属性文件

目前主要分析 CreateSerializedPropertyInfo() 的过程:

cpp 复制代码
void CreateSerializedPropertyInfo() {
    
    auto property_infos = std::vector<PropertyInfoEntry>();

    // 将各个分区的 property_contexts 加载进动态数组 vector<PropertyInfoEntry> property_infos 中
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {
            return;
        }
        // Don't check for failure here, so we always have a sane list of properties.
        // E.g. In case of recovery, the vendor partition will not have mounted and we
        // still need the system / platform properties to function.
        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
                                     &property_infos);
        }
        if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
                                     &property_infos);
        }
        if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
        }
    } else {
        if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
            return;
        }
        if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
        }
        LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
        LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
    }

    auto serialized_contexts = std::string();
    auto error = std::string();

    // 序列化  vector<PropertyInfoEntry> -> String
    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,&error)) {
        LOG(ERROR) << "Unable to serialize property contexts: " << error;
        return;"/dev/__properties__/property_info
    }

    //序列化后的 String 写入文件 /dev/__properties__/property_info
    constexpr static const char kPropertyInfosPath[] = ";
    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
        PLOG(ERROR) << "Unable to write serialized property infos to file";
    }
    selinux_android_restorecon(kPropertyInfosPath, 0);
}

CreateSerializedPropertyInfo() 函数可以分为三个阶段:

  • 通过 LoadPropertyInfoFromFile 把各个分区中的属性 SeLinux 配置文件加载进内存中
  • BuildTrie 函数根据上一步加载的数据构建一个字典树,并序列化为一个 std::string 对象
  • 把上一步的序列化字符串写入 /dev/__properties__/property_info 文件

接下来我们逐步分析。

2.2 LoadPropertyInfoFromFile 加载属性 SeLinux 配置过程

我们在系统源码中配置的属性相关的 SeLinux 信息会被编译进各个分区的 property_contexts 文件中,property_contexts 文件中的内容基本都是如下这样:

bash 复制代码
persist.bluetooth.btsnoopenable u:object_r:exported_bluetooth_prop:s0 exact bool
persist.config.calibration_fac u:object_r:exported3_default_prop:s0 exact string
persist.dbg.volte_avail_ovr u:object_r:exported3_default_prop:s0 exact int

里面有属性名、属性安全上下文、exact、类型等信息。

在 CreateSerializedPropertyInfo 函数中会多次调用 LoadPropertyInfoFromFile 函数,将各个分区中 property_contexts 文件中的内容加载进动态数组 vector property_infos 中。

我们先看下 PropertyInfoEntry 结构体:

cpp 复制代码
struct PropertyInfoEntry {
  PropertyInfoEntry() {}
  template <typename T, typename U, typename V>
  PropertyInfoEntry(T&& name, U&& context, V&& type, bool exact_match)
      : name(std::forward<T>(name)),
        context(std::forward<U>(context)),
        type(std::forward<V>(type)),
        exact_match(exact_match) {}
  std::string name;
  std::string context;
  std::string type;
  bool exact_match;
};

结构体比较简单,四个成员依次对应 property_contexts 文件中的四部分信息。

接着看怎么加载的:

cpp 复制代码
bool LoadPropertyInfoFromFile(const std::string& filename,
                              std::vector<PropertyInfoEntry>* property_infos) {
    auto file_contents = std::string();
    // 将文件内容读取到字符串中,
    if (!ReadFileToString(filename, &file_contents)) {
        PLOG(ERROR) << "Could not read properties from '" << filename << "'";
        return false;
    }

    auto errors = std::vector<std::string>{};
    bool require_prefix_or_exact = SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__;

    // 解析字符串
    ParsePropertyInfoFile(file_contents, require_prefix_or_exact, property_infos, &errors);
    // Individual parsing errors are reported but do not cause a failed boot, which is what
    // returning false would do here.
    for (const auto& error : errors) {
        LOG(ERROR) << "Could not read line from '" << filename << "': " << error;
    }

    return true;
}
  • ReadFileToString 将文件中的内容加载到 file_contents 字符串中
  • 接着调用 ParsePropertyInfoFile 解析字符串中的内容
cpp 复制代码
// 将字符串按行分割,接着将每一行的信息放入到 PropertyInfoEntry 结构体中。
void ParsePropertyInfoFile(const std::string& file_contents, bool require_prefix_or_exact,
                           std::vector<PropertyInfoEntry>* property_infos,
                           std::vector<std::string>* errors) {
  // Do not clear property_infos to allow this function to be called on multiple files, with
  // their results concatenated.
  errors->clear();
  //按行分割并遍历
  for (const auto& line : Split(file_contents, "\n")) {
    auto trimmed_line = Trim(line);
    // 过滤空行和注释行
    if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
      continue;
    }

    auto property_info_entry = PropertyInfoEntry{};
    auto parse_error = std::string{};
    
    // 解析每一行数据
    if (!ParsePropertyInfoLine(trimmed_line, require_prefix_or_exact, &property_info_entry,
                               &parse_error)) {
      errors->emplace_back(parse_error);
      continue;
    }

    // 插入动态数组
    property_infos->emplace_back(property_info_entry);
  }
}

这里将传入的字符串按行分割后,将每一行的数据传入 ParsePropertyInfoLine 函数进一步解析:

cpp 复制代码
bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std::string* error) {

  auto tokenizer = SpaceTokenizer(line);
  
  // 解析出属性名
  auto property = tokenizer.GetNext();
  if (property.empty()) {
    *error = "Did not find a property entry in '" + line + "'";
    return false;
  }
 
 // 解析出安全上下文
  auto context = tokenizer.GetNext();
  if (context.empty()) {
    *error = "Did not find a context entry in '" + line + "'";
    return false;
  }

  // It is not an error to not find exact_match or a type, as older files will not contain them.
  // 解析出 exact
  auto exact_match = tokenizer.GetNext();
  // We reformat type to be space deliminated regardless of the input whitespace for easier storage
  // and subsequent parsing.
  auto type_strings = std::vector<std::string>{};
  // 解析出 type
  auto type = tokenizer.GetNext();
  while (!type.empty()) {
    type_strings.emplace_back(type);
    type = tokenizer.GetNext();
  }

  if (!type_strings.empty() && !IsTypeValid(type_strings)) {
    *error = "Type '" + Join(type_strings, " ") + "' is not valid";
    return false;
  }
  // 解析出的数据写入结构体
  *out = {property, context, Join(type_strings, " "), exact_match == "exact"};
  return true;
}

这里将每一行的每一个数据(属性名,安全上下文,exact,类型)解析出来后,保存到 PropertyInfoEntry 结构体中,然后返回到 ParsePropertyInfoFile 函数中,将构建好的 PropertyInfoEntry 结构体插入到动态数组 vector property_infos 中。

至此,系统中所有的属性信息就保存到内存中的动态数组 vector property_infos 中了。

2.3 BuildTrie 构建字典树与序列化过程分析

接下来程序回到 CreateSerializedPropertyInfo 函数中,接着调用 BuildTrie 函数,这个函数主要功能是把动态数组 vector property_infos 转换为自定义规则的序列化字符串:

cpp 复制代码
//调用过程
BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,&error)

//具体实现
bool BuildTrie(const std::vector<PropertyInfoEntry>& property_info,
               const std::string& default_context, const std::string& default_type,
               std::string* serialized_trie, std::string* error) {
  
  // 构建字典树
  auto trie_builder = TrieBuilder(default_context, default_type);

  // 把属性名,属性的安全上下文,类型,exact 信息插入字典树
  for (const auto& [name, context, type, is_exact] : property_info) {
    if (!trie_builder.AddToTrie(name, context, type, is_exact, error)) {
      return false;
    }
  }

  // 将字典树序列化后,保存到字符串中
  auto trie_serializer = TrieSerializer();
  *serialized_trie = trie_serializer.SerializeTrie(trie_builder);
  return true;
}

BuildTrie 主要执行了以下这些操作:

  • 通过 TrieBuilder 对象构建一个用于保存属性信息的字典树
  • 调用 AddToTrie 函数,向字典树中添加数据
  • 通过 TrieSerializer 对象的 SerializeTrie 函数将字典树序列化后保存在字符串中

我们分步看一下代码的具体实现:

先看下 TrieBuilder 的定义与实现:

cpp 复制代码
class TrieBuilder {
 public:
  TrieBuilder(const std::string& default_context, const std::string& default_type);
  bool AddToTrie(const std::string& name, const std::string& context, const std::string& type,
                 bool exact, std::string* error);

  const TrieBuilderNode builder_root() const { return builder_root_; }
  const std::set<std::string>& contexts() const { return contexts_; }
  const std::set<std::string>& types() const { return types_; }

 private:
  bool AddToTrie(const std::string& name, const std::string* context, const std::string* type,
                 bool exact, std::string* error);
  const std::string* StringPointerFromContainer(const std::string& string,
                                                std::set<std::string>* container);

  //字典树的根节点
  TrieBuilderNode builder_root_; 
  std::set<std::string> contexts_;
  std::set<std::string> types_;
};

从 TrieBuilder 的名字就可以看出,该类用于构建一个字典树,其成员:

  • TrieBuilderNode builder_root_ 是字典树的根节点
  • std::set<std::string> contexts_ 用于保存属性对应的安全上下文信息,所有属性的安全上下文都会保存在这里,字典树中的子节点可以保存一个 index 索引值即可,当需要使用安全上下文时,通过这个 index 索引值就可以在这个 set 中找到了
  • std::set<std::string> types_ 用于保存属性对应的类型信息,所有属性的类型信息都会保存在这里,字典树中的子节点可以保存一个 index 索引值即可,当需要使用类型信息时,通过这个 index 索引值就可以在这个 set 中找到了

接下来,我们就可以来看 TrieBuilder 构造函数的实现了:

cpp 复制代码
// TrieBuilder 构造函数实现

//传入的两个参数是 "u:object_r:default_prop:s0", "string"
TrieBuilder::TrieBuilder(const std::string& default_context, const std::string& default_type)
    : builder_root_("root") { //初始化根节点
  auto* context_pointer = StringPointerFromContainer(default_context, &contexts_);
  builder_root_.set_context(context_pointer);
  auto* type_pointer = StringPointerFromContainer(default_type, &types_);
  builder_root_.set_type(type_pointer);
}

首先这里初始化了字典树根节点 builder_root_("root"),TrieBuilderNode 的整体结构如下:

cpp 复制代码
class TrieBuilderNode {
 public:
  TrieBuilderNode(const std::string& name) : property_entry_(name, nullptr, nullptr) {}

  //......

  const std::string& name() const { return property_entry_.name; }
  const std::string* context() const { return property_entry_.context; }
  void set_context(const std::string* context) { property_entry_.context = context; }
  const std::string* type() const { return property_entry_.type; }
  void set_type(const std::string* type) { property_entry_.type = type; }

  const PropertyEntryBuilder property_entry() const { return property_entry_; }

  const std::vector<TrieBuilderNode>& children() const { return children_; }
  const std::vector<PropertyEntryBuilder>& prefixes() const { return prefixes_; }
  const std::vector<PropertyEntryBuilder>& exact_matches() const { return exact_matches_; }

 private:
  // 用于保存节点的数据节点数据
  PropertyEntryBuilder property_entry_;
  // 当前节点的子节点
  std::vector<TrieBuilderNode> children_;
  std::vector<PropertyEntryBuilder> prefixes_;
  std::vector<PropertyEntryBuilder> exact_matches_;
};

其中成员 PropertyEntryBuilder property_entry_ 用于保存节点信息:

cpp 复制代码
struct PropertyEntryBuilder {
  PropertyEntryBuilder() : context(nullptr), type(nullptr) {}
  PropertyEntryBuilder(const std::string& name, const std::string* context, const std::string* type)
      : name(name), context(context), type(type) {}
  std::string name;
  const std::string* context;
  const std::string* type;
};

每个节点都保存了属性的名字,安全上下文和类型信息。

TrieBuilderNode 构造函数主要是初始化了其成员 PropertyEntryBuilder property_entry_,初始化时传入了一个参数 name,保存在 PropertyEntryBuilder 对象的 name 成员中。

TrieBuilder 的构造函数中,接着调用 StringPointerFromContainer 函数将安全上下文信息 "u:object_r:default_prop:s0" 保存到 TrieBuilder 的成员 std::set<std::string> contexts_ 中:

cpp 复制代码
const std::string* TrieBuilder::StringPointerFromContainer(const std::string& string,
                                                           std::set<std::string>* container) {
  auto [iterator, _] = container->emplace(string); 
  return &(*iterator);
}

接着会调用 TrieBuilderNode builder_root_ 根节点的 set_context 函数,将安全上下文信息保存到根节点中。

最后同样的手法,将 type 信息保存到根节点中。

至此,对于根节点的构造就分析就完了,相关的类结构如下:

接下来就会通过 AddToTrie 函数将动态数组 vector<PropertyInfoEntry> property_infos 中的信息保存到字典树中:

cpp 复制代码
bool TrieBuilder::AddToTrie(const std::string& name, const std::string& context,
                            const std::string& type, bool exact, std::string* error) {
  // 首先把 context 和 type 保存到 TrieBuilder 的两个集合成员中
  auto* context_pointer = StringPointerFromContainer(context, &contexts_);
  auto* type_pointer = StringPointerFromContainer(type, &types_);
  // 接着把这些信息插入字典树
  return AddToTrie(name, context_pointer, type_pointer, exact, error);
}

接下来就来看看 AddToTrie 具体插入数据的过程:

cpp 复制代码
// 假设要插入的数据是 audio.camerasound.force u:object_r:exported_audio_prop:s0 exact bool
bool TrieBuilder::AddToTrie(const std::string& name, const std::string* context,
                            const std::string* type, bool exact, std::string* error) {

  // 拿到根节点
  TrieBuilderNode* current_node = &builder_root_;
 
  // 将属性名分割开
  // 返回的类型是 vector<std::string>
  auto name_pieces = Split(name, ".");

  // 判断属性是否以点结束
  bool ends_with_dot = false;
  if (name_pieces.back().empty()) {
    ends_with_dot = true;
    name_pieces.pop_back();
  }

  // Move us to the final node that we care about, adding incremental nodes if necessary.
  while (name_pieces.size() > 1) { // 注意这里是大于 1,会留一个数据 force
    //在字典树中查找
    auto child = current_node->FindChild(name_pieces.front());
    if (child == nullptr) { //没有找到就添加进字典树
      child = current_node->AddChild(name_pieces.front());
    }
    if (child == nullptr) {
      *error = "Unable to allocate Trie node";
      return false;
    }
    current_node = child;
    name_pieces.erase(name_pieces.begin());
  }
    // 省略后续代码
    //......
}
  • 属性名分割开,保存到 vector<std::string> name_pieces
  • 根据分割后的名字在字典树中查找节点
  • 没有找到就向字典树添加新的节点

接下来就看看字典树的查找与插入操作:

cpp 复制代码
  TrieBuilderNode* FindChild(const std::string& name) {
    for (auto& child : children_) {
      if (child.name() == name) return &child;
    }
    return nullptr;
  }

  TrieBuilderNode* AddChild(const std::string& name) { return &children_.emplace_back(name); }

查找操作就是从当前节点的子节点中遍历查找,插入操作就是在子节点动态数组中插入一个新节点。

cpp 复制代码
bool TrieBuilder::AddToTrie(const std::string& name, const std::string* context,
                            const std::string* type, bool exact, std::string* error) {
    
    // 接上面的代码
    // ......
  // Store our context based on what type of match it is.
  if (exact) { // 走这里,有 exact 表示是一个目标节点,代表一个属性
    if (!current_node->AddExactMatchContext(name_pieces.front(), context, type)) {
      *error = "Duplicate exact match detected for '" + name + "'";
      return false;
    }
  } else if (!ends_with_dot) {
    if (!current_node->AddPrefixContext(name_pieces.front(), context, type)) {
      *error = "Duplicate prefix match detected for '" + name + "'";
      return false;
    }
  } else {
    auto child = current_node->FindChild(name_pieces.front());
    if (child == nullptr) {
      child = current_node->AddChild(name_pieces.front());
    }
    if (child == nullptr) {
      *error = "Unable to allocate Trie node";
      return false;
    }
    if (child->context() != nullptr || child->type() != nullptr) {
      *error = "Duplicate prefix match detected for '" + name + "'";
      return false;
    }
    child->set_context(context);
    child->set_type(type);
  }
  return true;
}

有 exact 表示是一个目标节点,代表一个属性:

cpp 复制代码
  bool AddExactMatchContext(const std::string& exact_match, const std::string* context,
                            const std::string* type) {
    if (std::find_if(exact_matches_.begin(), exact_matches_.end(), [&exact_match](const auto& t) {
          return t.name == exact_match;
        }) != exact_matches_.end()) {
      return false;
    }

    exact_matches_.emplace_back(exact_match, context, type);
    return true;
  }

当遇到 exact 时,不会创建新的节点,而是在最后一个节点内部的 exact 数组中插入一个新的 PropertyEntryBuilder 对象。

整个字典树的构造过程分析就结束了,至此,字典树的结构如下:

接下来分析对字典树的序列化操作:

调用过程:

cpp 复制代码
auto trie_serializer = TrieSerializer();
*serialized_trie = trie_serializer.SerializeTrie(trie_builder);

这里使用 TrieSerializer 对象来进行序列化操作:

cpp 复制代码
class TrieSerializer {
 public:
  TrieSerializer();

  std::string SerializeTrie(const TrieBuilder& trie_builder);

 private:
  void SerializeStrings(const std::set<std::string>& strings);
  uint32_t WritePropertyEntry(const PropertyEntryBuilder& property_entry);

  // Writes a new TrieNode to arena, and recursively writes its children.
  // Returns the offset within arena.
  uint32_t WriteTrieNode(const TrieBuilderNode& builder_node);

  const PropertyInfoArea* serialized_info() const {
    return reinterpret_cast<const PropertyInfoArea*>(arena_->data().data());
  }

  std::unique_ptr<TrieNodeArena> arena_;
};

TrieSerializer 类有个重要成员 TrieNodeArena :

cpp 复制代码
class TrieNodeArena {
 public:
  TrieNodeArena() : current_data_pointer_(0) {}

  // We can't return pointers to objects since data_ may move when reallocated, thus invalidating
  // any pointers.  Therefore we return an ArenaObjectPointer, which always accesses elements via
  // data_ + offset.
  template <typename T>
  ArenaObjectPointer<T> AllocateObject(uint32_t* return_offset) {
    uint32_t offset;
    AllocateData(sizeof(T), &offset);
    if (return_offset) *return_offset = offset;
    return ArenaObjectPointer<T>(data_, offset);
  }

  uint32_t AllocateUint32Array(int length) {
    uint32_t offset;
    AllocateData(sizeof(uint32_t) * length, &offset);
    return offset;
  }

  uint32_t* uint32_array(uint32_t offset) {
    return reinterpret_cast<uint32_t*>(data_.data() + offset);
  }

  uint32_t AllocateAndWriteString(const std::string& string) {
    uint32_t offset;
    char* data = static_cast<char*>(AllocateData(string.size() + 1, &offset));
    strcpy(data, string.c_str());
    return offset;
  }

  void AllocateAndWriteUint32(uint32_t value) {
    auto location = static_cast<uint32_t*>(AllocateData(sizeof(uint32_t), nullptr));
    *location = value;
  }

  void* AllocateData(size_t size, uint32_t* offset) {
    //4字节对齐
    size_t aligned_size = size + (sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1);

    if (current_data_pointer_ + aligned_size > data_.size()) {
      auto new_size = (current_data_pointer_ + aligned_size + data_.size()) * 2;
      data_.resize(new_size, '\0');
    }
    if (offset) *offset = current_data_pointer_;

    uint32_t return_offset = current_data_pointer_;
    current_data_pointer_ += aligned_size;
    return &data_[0] + return_offset;
  }

  uint32_t size() const { return current_data_pointer_; }

  const std::string& data() const { return data_; }

  std::string truncated_data() const {
    auto result = data_;
    result.resize(current_data_pointer_);
    return result;
  }

 private:
  std::string data_;
  uint32_t current_data_pointer_;
};

从源码可以看出 TrieNodeArena 是一个工具类,用于从内部的 std::string data_ 字符串中分配一块内存给外部使用。

接下来,我们就来看看 SerializeTrie 函数进行字典树序列化的过程:

cpp 复制代码
std::string TrieSerializer::SerializeTrie(const TrieBuilder& trie_builder) {
  arena_.reset(new TrieNodeArena());

  // 分配一块 PropertyInfoAreaHeader 内存 
  auto header = arena_->AllocateObject<PropertyInfoAreaHeader>(nullptr);
  header->current_version = 1;
  header->minimum_supported_version = 1;

  // Store where we're about to write the contexts.
  
  // 序列化安全上下文
  header->contexts_offset = arena_->size();
  SerializeStrings(trie_builder.contexts());

  // 序列化类型信息
  // Store where we're about to write the types.
  header->types_offset = arena_->size();
  SerializeStrings(trie_builder.types());

  // 序列化字典树
  // We need to store size() up to this point now for Find*Offset() to work.
  header->size = arena_->size();

  uint32_t root_trie_offset = WriteTrieNode(trie_builder.builder_root());
  header->root_offset = root_trie_offset;

  // Record the real size now that we've written everything
  header->size = arena_->size();

  return arena_->truncated_data();
}

SerializeTrie 函数主要完成四件件事:

  • 在序列化字符串 std::string data_ 中分配一块 PropertyInfoAreaHeader 内存
  • 将所有的安全上下文序列化后存入 std::string data_
  • 将所有的类型信息序列化后存入 std::string data_
  • 将字典树序列化后存入 std::string data_

序列化后的字符串的基本结构如下:

接着我们一一分析每类数据的序列化过程:

字符串数据的序列化过程:

cpp 复制代码
// PropertyInfoAreaHeader 对象序列化过程

// std::set<std::string> 序列化过程
void TrieSerializer::SerializeStrings(const std::set<std::string>& strings) {

  // 先写入字符串长度
  arena_->AllocateAndWriteUint32(strings.size());

  // 分配一个数组记录每个字符串的位置
  // Allocate space for the array.
  uint32_t offset_array_offset = arena_->AllocateUint32Array(strings.size());

  // 写入字符串与偏移位置
  auto it = strings.begin();
  for (unsigned int i = 0; i < strings.size(); ++i, ++it) {
    uint32_t string_offset = arena_->AllocateAndWriteString(*it);
    arena_->uint32_array(offset_array_offset)[i] = string_offset;
  }
}

从源码中可以看出来,序列化一个字符串集合分为以下几步:

  • 分配一块内存,写入字符串长度
  • 分配一块数组内存,用于保存每个字符串的位置
  • 分配内存写入每个字符串,并在上一步的数组中记录字符串的位置信息

字典树的序列化会复杂一些:

以下代码,是字典树节点的结构:

cpp 复制代码
class TrieBuilderNode {
 // ......
 private:
  // 用于保存节点的数据节点数据
  PropertyEntryBuilder property_entry_;
  // 当前节点的子节点
  std::vector<TrieBuilderNode> children_;
  std::vector<PropertyEntryBuilder> prefixes_;
  std::vector<PropertyEntryBuilder> exact_matches_;
};

字典树内部有四个成员,四个成员的结构都相对复杂,我们看下源码是怎么来序列化字典树节点的:

cpp 复制代码
uint32_t TrieSerializer::WriteTrieNode(const TrieBuilderNode& builder_node) {
  uint32_t trie_offset;
  auto trie = arena_->AllocateObject<TrieNodeInternal>(&trie_offset);

  //序列化并写入 property_entry_ 对象
  trie->property_entry = WritePropertyEntry(builder_node.property_entry());


  //排序后,序列化并写入 prefixes_ 动态数组
  auto sorted_prefix_matches = builder_node.prefixes();
  std::sort(sorted_prefix_matches.begin(), sorted_prefix_matches.end(),
            [](const auto& lhs, const auto& rhs) { return lhs.name.size() > rhs.name.size(); });

  trie->num_prefixes = sorted_prefix_matches.size();

  uint32_t prefix_entries_array_offset = arena_->AllocateUint32Array(sorted_prefix_matches.size());
  trie->prefix_entries = prefix_entries_array_offset;

  for (unsigned int i = 0; i < sorted_prefix_matches.size(); ++i) {
    uint32_t property_entry_offset = WritePropertyEntry(sorted_prefix_matches[i]);
    arena_->uint32_array(prefix_entries_array_offset)[i] = property_entry_offset;
  }

  // 排序后,序列化并写入 prefixes_ 动态数组
  auto sorted_exact_matches = builder_node.exact_matches();
  std::sort(sorted_exact_matches.begin(), sorted_exact_matches.end(),
            [](const auto& lhs, const auto& rhs) { return lhs.name < rhs.name; });

  trie->num_exact_matches = sorted_exact_matches.size();

  uint32_t exact_match_entries_array_offset =
      arena_->AllocateUint32Array(sorted_exact_matches.size());
  trie->exact_match_entries = exact_match_entries_array_offset;

  for (unsigned int i = 0; i < sorted_exact_matches.size(); ++i) {
    uint32_t property_entry_offset = WritePropertyEntry(sorted_exact_matches[i]);
    arena_->uint32_array(exact_match_entries_array_offset)[i] = property_entry_offset;
  }

  //排序后,序列化并写入 children_动态数组
  auto sorted_children = builder_node.children();
  std::sort(sorted_children.begin(), sorted_children.end(),
            [](const auto& lhs, const auto& rhs) { return lhs.name() < rhs.name(); });

  trie->num_child_nodes = sorted_children.size();
  uint32_t children_offset_array_offset = arena_->AllocateUint32Array(sorted_children.size());
  trie->child_nodes = children_offset_array_offset;

  for (unsigned int i = 0; i < sorted_children.size(); ++i) {
    arena_->uint32_array(children_offset_array_offset)[i] = WriteTrieNode(sorted_children[i]);
  }
  return trie_offset;
}

WriteTrieNode 函数总共四个流程:

  • 序列化并向内存中写入 property_entry_ 对象
  • 排序后,序列化并向内存中写入 prefixes_ 动态数组
  • 排序后,序列化并向内存中写入 prefixes_ 动态数组
  • 排序后,递归调用 WriteTrieNode 函数将 children_ 动态数组序列化后写入内存

流程很多,但是核心就是 WritePropertyEntry 函数,将 PropertyEntryBuilder 结构体序列化后,写入内存:

cpp 复制代码
uint32_t TrieSerializer::WritePropertyEntry(const PropertyEntryBuilder& property_entry) {
  // 从上面的动态数组里找到索引
  uint32_t context_index = property_entry.context != nullptr && !property_entry.context->empty()
                               ? serialized_info()->FindContextIndex(property_entry.context->c_str())
                               : ~0u;
  // 从上面的动态数组里找到索引
  uint32_t type_index = property_entry.type != nullptr && !property_entry.type->empty()
                            ? serialized_info()->FindTypeIndex(property_entry.type->c_str())
                            : ~0u;
  uint32_t offset;
  auto serialized_property_entry = arena_->AllocateObject<PropertyEntry>(&offset);
  serialized_property_entry->name_offset = arena_->AllocateAndWriteString(property_entry.name);
  serialized_property_entry->namelen = property_entry.name.size();
  serialized_property_entry->context_index = context_index;
  serialized_property_entry->type_index = type_index;
  return offset;
}

函数中的 serialized_info() 返回的是 TrieNodeArena 管理对象内部的 data_ 字符串,内部保存了我们准备序列化的整个数据,其中包含了序列化后的所有的安全上下文集合 std::set<std::string contexts_ 和所有的类型信息集合 std::set<std::string types_

PropertyEntryBuilder 结构体中有 context 和 type,我们只需要再上面的集合中找到它们对应的 index 索引值,然后保存到目标序列化字符串中即可。

这里分配了一个 PropertyEntry 对象,用于序列化上面的 PropertyEntryBuilder 对象:

cpp 复制代码
struct PropertyEntry {
  uint32_t name_offset;
  uint32_t namelen;
  uint32_t context_index;
  uint32_t type_index;
};

接着写入 PropertyEntryBuilder 对象的 name,并把 name 的偏移值和长度保存在 PropertyEntry 的 name_offset 和 namelen 中。最有把 context 和 type 的偏移值保存到 context_index 和 type_index 中。

至此整个 BuildTrie 过程就分析完了,过程很繁琐,完成的工作其实很简单,构建一个保存属性信息的字典树,并将其序列化后,保存到一个 std::string 字符串中.

所有的属性相关的信息都序列化完成,接下来就要把他写入文件了:

cpp 复制代码
void CreateSerializedPropertyInfo() {

    // ......
  
    //序列化后的 String 写入文件 /dev/__properties__/property_info
    constexpr static const char kPropertyInfosPath[] = ";
    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
        PLOG(ERROR) << "Unable to write serialized property infos to file";
    }
    selinux_android_restorecon(kPropertyInfosPath, 0);
}

在 CreateSerializedPropertyInfo 函数的最后部分,调用 WriteStringToFile 将序列化后的字符串写入 /dev/properties/property_info 文件中

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,工作内容主要涉及 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
500了6 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵7 小时前
Android Debug Bridge(ADB)完全指南
android·adb
小雨cc5566ru11 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
bianshaopeng13 小时前
android 原生加载pdf
android·pdf
hhzz13 小时前
Linux Shell编程快速入门以及案例(Linux一键批量启动、停止、重启Jar包Shell脚本)
android·linux·jar
火红的小辣椒14 小时前
XSS基础
android·web安全
勿问东西15 小时前
【Android】设备操作
android
五味香16 小时前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
图王大胜17 小时前
Android Framework AMS(01)AMS启动及相关初始化1-4
android·framework·ams·systemserver
工程师老罗19 小时前
Android Button “No speakable text present” 问题解决
android