这是一个介绍 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(),该函数用于特色系统的初始化:

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() 的进程:

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 文件中的内容底子都是如下这样:

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 结构体:

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 文件中的四部分信息。

接着看怎样加载的:

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 解析字符串中的内容
// 将字符串按行切开,接着将每一行的信息放入到 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 函数进一步解析:

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 转换为自定义规矩的序列化字符串:

//调用进程
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 的定义与结束:

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 结构函数的结束了:

// 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 的整体结构如下:

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_ 用于保存节点信息:

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_ 中:

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 中的信息保存到字典树中:

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 详细刺进数据的进程:

// 假定要刺进的数据是 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
  • 根据切开后的名字在字典树中查找节点
  • 没有找到就向字典树添加新的节点

接下来就看看字典树的查找与刺进操作:

  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); }

查找操作就是从其时节点的子节点中遍历查找,刺进操作就是在子节点动态数组中刺进一个新节点。

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 表明是一个政策节点,代表一个特色:

  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 政策。

整个字典树的结构进程分析就完毕了,至此,字典树的结构如下:

特色系统源码分析一

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

调用进程:

auto trie_serializer = TrieSerializer();
*serialized_trie = trie_serializer.SerializeTrie(trie_builder);

这儿运用 TrieSerializer 政策来进行序列化操作:

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 :

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, '');
    }
    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 函数进行字典树序列化的进程:

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_

序列化后的字符串的底子结构如下:

特色系统源码分析一

接着我们逐个分析每类数据的序列化进程:

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

// 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;
  }
}

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

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

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

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

class TrieBuilderNode {
 // ......
 private:
  // 用于保存节点的数据节点数据
  PropertyEntryBuilder property_entry_;
  // 其时节点的子节点
  std::vector<TrieBuilderNode> children_;
  std::vector<PropertyEntryBuilder> prefixes_;
  std::vector<PropertyEntryBuilder> exact_matches_;
};

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

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 结构体序列化后,写入内存:

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 政策:

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 字符串中.

全部的特色相关的信息都序列化结束,接下来就要把他写入文件了:

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,可以注重我的微信大众号和抖音,我会继续同享我的学习阅历,帮助正在学习的你少走一些弯路。学习进程中假如你有疑问或许你的阅历想要同享给我们可以添加我的微信,我拉你进技术交流群。

特色系统源码分析一