前几天连续两次重伤风,停更了几天。今日伤风好得差不多了,电脑又坏了!

这是一个介绍 Android 特点体系的系列文章:

  • Android 特点体系入门
  • 特点文件生成进程剖析
  • 如何增加体系特点
  • 特点与 SeLinux
  • 特点体系源码剖析一
  • 特点体系源码剖析二(本文)
  • 特点体系源码剖析三

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

1.回忆

上一节说到,在 init 进程中,会调用到 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";
    }
}

在上一节中我要首要剖析了 CreateSerializedPropertyInfo() 函数的完成细节,总结一下便是:把各个分区的特点 SeLinux 配置文件解析后加载进内存,序列化处理后,存入 /dev/__properties__/property_info 文件中。能够经过 PropertyInfoArea 类来读写这个文件中的数据,用一张图表明便是:

特点体系源码剖析二

2. 清晰两个称号

为避免混杂,先清晰两个称号

在手机上咱们的特点大都保存在以下一些文件中:

/default.prop
/data/local.prop
/system/build.prop
/system/product/build.prop
/vendor/build.prop
/vendor/odm/etc/build.prop
/vendor/default.prop
/data/property/ 目录下

本文称这些文件叫特点配置文件,这些文件中的内容大概长下面这样:

ro.actionable_compatible_property.enabled=true
ro.postinstall.fstab.prefix=/system
ro.secure=0
ro.allow.mock.location=1
ro.debuggable=1
debug.atrace.tags.enableflags=0
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m

/dev/__properties__ 目录下,也有许多特点相关的文件:

cd /dev/__properties__
ls -lZ 
total 576
-r--r--r-- 1 root root u:object_r:properties_serial:s0                               131072 2023-10-31 10:46 properties_serial
-r--r--r-- 1 root root u:object_r:property_info:s0                                    34640 2023-10-31 10:46 property_info
-r--r--r-- 1 root root u:object_r:apexd_prop:s0                                      131072 2023-10-31 10:46 u:object_r:apexd_prop:s0
-r--r--r-- 1 root root u:object_r:audio_prop:s0                                      131072 2023-10-31 10:46 u:object_r:audio_prop:s0
-r--r--r-- 1 root root u:object_r:bluetooth_a2dp_offload_prop:s0                     131072 2023-10-31 10:46 u:object_r:bluetooth_a2dp_offload_prop:s0
-r--r--r-- 1 root root u:object_r:bluetooth_audio_hal_prop:s0                        131072 2023-10-31 10:46 u:object_r:bluetooth_audio_hal_prop:s0
-r--r--r-- 1 root root u:object_r:bluetooth_prop:s0                                  131072 2023-10-31 10:46 u:object_r:bluetooth_prop:s0
-r--r--r-- 1 root root u:object_r:bootloader_boot_reason_prop:s0                     131072 2023-10-31 10:46 u:object_r:bootloader_boot_reason_prop:s0
# ......

本文称 /dev/__properties__ 目录下的这些文件叫特点文件

properties_serial 与 property_info 存储一些办理信息,暂时不论。其他文件,文件名便是其特点安全上下文,内部存储的内容运用 cat 查看是乱码,剖析过代码后,咱们就会知道,文件的内容是一个序列化后的字典树,字典树用于存储特点的名字和值,同一个文件中保存的特点都有相同的安全上下文。

3. 源码剖析

__system_property_area_init() 函数的根本流程:

  • /dev/__properties__/property_info 文件中加载信息
  • 根据加载到的信息,构建出一个 ContextNode 数组
  • /dev/__properties__ 目录下,创立特点文件

函数的详细完成如下:

// bionic/libc/bionic/system_property_api.cpp
#define PROP_FILENAME "/dev/__properties__"
static SystemProperties system_properties;
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int __system_property_area_init() {
  bool fsetxattr_failed = false;
  return system_properties.AreaInit(PROP_FILENAME, &fsetxattr_failed) && !fsetxattr_failed ? 0 : -1;
}

system_properties 是在 bionic/libc/bionic/system_property_api.cpp 文件中界说的 static 全局变量。__system_property_area_init() 函数进一步骤用到 SystemProperties 的 AreaInit 函数:

// bionic/libc/system_properties/system_properties.cpp
bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
  if (strlen(filename) >= PROP_FILENAME_MAX) {
    return false;
  }
  strcpy(property_filename_, filename);
  contexts_ = new (contexts_data_) ContextsSerialized();
  if (!contexts_->Initialize(true, property_filename_, fsetxattr_failed)) {
    return false;
  }
  initialized_ = true;
  return true;
}

AreaInit 函数中会将 SystemPropertiesContexts* contexts_ 成员初始化为一个 ContextsSerialized 目标,接着调用ContextsSerialized 目标的 Initialize 函数。

ContextsSerialized 类的界说如下:

// bionic/libc/system_properties/include/system_properties/contexts_serialized.h
class ContextsSerialized : public Contexts {
 public:
  virtual ~ContextsSerialized() override {
  }
  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) override;
  virtual prop_area* GetPropAreaForName(const char* name) override;
  virtual prop_area* GetSerialPropArea() override {
    return serial_prop_area_;
  }
  virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) override;
  virtual void ResetAccess() override;
  virtual void FreeAndUnmap() override;
 private:
  bool InitializeContextNodes();
  bool InitializeProperties();
  bool MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed);
  const char* filename_;
  //  /dev/__properties__/property_info 文件对应的办理类 
  android::properties::PropertyInfoAreaFile property_info_area_file_;
  // 是一个 ContextNode 数组,用于保存从 property_info 文件中获取到的数据
  ContextNode* context_nodes_ = nullptr;
  size_t num_context_nodes_ = 0;
  size_t context_nodes_mmap_size_ = 0;
  prop_area* serial_prop_area_ = nullptr;
};

这儿首要关注 ContextsSerialized 类的成员:

  • PropertyInfoAreaFile property_info_area_file_/dev/__properties__/property_info 文件对应的办理类,经过这个类,能够方便地从文件中读写数据
  • ContextNode* context_nodes 是一个数组,用于保存从 /dev/__properties__/property_info 文件中获取到的数据,ContextNode 有一个内部成员 prop_area* pa_, 是特点文件对应的办理类,能够方便地从特点文件中读写数据

接着看 ContextsSerialized 目标的 Initialize 函数:

// writable:true
// filename:"/dev/__properties__"
bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
  filename_ = filename;
  if (!InitializeProperties()) {
    return false;
  }
  if (writable) { // 走这个分支
    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
    bool open_failed = false;
    if (fsetxattr_failed) {
      *fsetxattr_failed = false;
    }
    for (size_t i = 0; i < num_context_nodes_; ++i) {
      if (!context_nodes_[i].Open(true, fsetxattr_failed)) {
        open_failed = true;
      }
    }
    // 走这儿 
    if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
      FreeAndUnmap();
      return false;
    }
  } else {
    if (!MapSerialPropertyArea(false, nullptr)) {
      FreeAndUnmap();
      return false;
    }
  }
  return true;
}

这个函数首要三部分功用:

  • InitializeProperties 函数,从 /dev/__properties__/property_info 中读取信息,创立一个 ContextNode 数组
  • 经过 ContextNodeopen 函数,在 /dev/__properties__/ 中创立新的特点文件
  • 调用 MapSerialPropertyArea 函数,构建一个特别文件

接下来逐渐剖析:

bool ContextsSerialized::InitializeProperties() {
  if (!property_info_area_file_.LoadDefaultPath()) {
    return false;
  }
  if (!InitializeContextNodes()) {
    FreeAndUnmap();
    return false;
  }
  return true;
}
  • LoadDefaultPath/dev/__properties__/property_info 文件加载进内存
  • InitializeContextNodes 函数初始化一个 ContextNode 数组

咱们先看看 LoadDefaultPath 的详细完成:

bool PropertyInfoAreaFile::LoadDefaultPath() {
  return LoadPath("/dev/__properties__/property_info");
}
// 接着调用 LoadPath 函数
// 把 /dev/__properties__/property_info 映射到内存 mmap_base_ 中
// filename: "/dev/__properties__/property_info"
bool PropertyInfoAreaFile::LoadPath(const char* filename) {
  int fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
  struct stat fd_stat;
  if (fstat(fd, &fd_stat) < 0) {
    close(fd);
    return false;
  }
  if ((fd_stat.st_uid != 0) || (fd_stat.st_gid != 0) ||
      ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) ||
      (fd_stat.st_size < static_cast<off_t>(sizeof(PropertyInfoArea)))) {
    close(fd);
    return false;
  }
  auto mmap_size = fd_stat.st_size;
  void* map_result = mmap(nullptr, mmap_size, PROT_READ, MAP_SHARED, fd, 0);
  if (map_result == MAP_FAILED) {
    close(fd);
    return false;
  }
  auto property_info_area = reinterpret_cast<PropertyInfoArea*>(map_result);
  if (property_info_area->minimum_supported_version() > 1 ||
      property_info_area->size() != mmap_size) {
    munmap(map_result, mmap_size);
    close(fd);
    return false;
  }
  close(fd);
  mmap_base_ = map_result;
  mmap_size_ = mmap_size;
  return true;
}

全体逻辑很简略:

  • 翻开 /dev/__properties__/property_info 文件
  • mmap 映射这个文件到内存
  • 将映射区内存地址强转为办理类 PropertyInfoArea指针,经过这个指针就能够读写 /dev/__properties__/property_info 文件中的数据了
  • 把映射区地址保存到成员变量 mmap_base_ 中

接下来看 InitializeContextNodes() 函数:

// 分配一块内存,里边保存 ContextNode 数组
// 把 /dev/__properties__/property_info 文件中的部分信息写入上面的数组
bool ContextsSerialized::InitializeContextNodes() {
  auto num_context_nodes = property_info_area_file_->num_contexts();
  auto context_nodes_mmap_size = sizeof(ContextNode) * num_context_nodes;
  // We want to avoid malloc in system properties, so we take an anonymous map instead (b/31659220).
  void* const map_result = mmap(nullptr, context_nodes_mmap_size, PROT_READ | PROT_WRITE,
                                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (map_result == MAP_FAILED) {
    return false;
  }
  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, map_result, context_nodes_mmap_size,
        "System property context nodes");
  context_nodes_ = reinterpret_cast<ContextNode*>(map_result);
  num_context_nodes_ = num_context_nodes;
  contxt_nodes_mmap_size_ = context_nodes_mmap_size;
e
  for (size_t i = 0; i < num_context_nodes; ++i) {
    new (&context_nodes_[i]) ContextNode(property_info_area_file_->context(i), filename_);
  }
  return true;
}

这儿经过 mmap 了一块匿名内存,然后经过一个 for 循环,将这块内存初始化为一个 ContextNode 的数组结构,每个 ContextNode 目标中的信息保存了一个安全上下文信息和文件途径信息,其间安全上下文信息来自 property_info_area_file_ 目标。

完结了上面的作业,就会经过一个 for 循环,调用每个 ContextNode 目标的 Open 函数了:

bool ContextNode::Open(bool access_rw, bool* fsetxattr_failed) {
  lock_.lock();
  if (pa_) {
    lock_.unlock();
    return true;
  }
  char filename[PROP_FILENAME_MAX];
  // 构建文件的完好途径
  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", filename_, context_);
  if (len < 0 || len >= PROP_FILENAME_MAX) {
    lock_.unlock();
    return false;
  }
  if (access_rw) { // 走这个分支
    pa_ = prop_area::map_prop_area_rw(filename, context_, fsetxattr_failed);
  } else {
    pa_ = prop_area::map_prop_area(filename);
  }
  lock_.unlock();
  return pa_;
}

这儿会构建一个文件途径 filename ,其结构便是 /dev/__properties__/ + 安全上下文,接着就调用 map_prop_area_rw 函数,map_prop_area_rw 函数是一个类中的 static 函数,直接经过类名调用:

prop_area* 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 (fd < 0) {
    if (errno == EACCES) {
      /* for consistency with the case where the process has already
       * mapped the page in and segfaults when trying to write to it
       */
      abort();
    }
    return nullptr;
  }
    // 设置文件安全上下文
  if (context) {
    if (fsetxattr(fd, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0) != 0) {
      async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                            "fsetxattr failed to set context (%s) for "%s"", context, filename);
      if (fsetxattr_failed) {
        *fsetxattr_failed = true;
      }
    }
  }
  if (ftruncate(fd, PA_SIZE) < 0) {
    close(fd);
    return nullptr;
  }
  pa_size_ = PA_SIZE;
  pa_data_size_ = pa_size_ - sizeof(prop_area);
    // 把文件映射到内存
  void* const memory_area = mmap(nullptr, pa_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (memory_area == MAP_FAILED) {
    close(fd);
    return nullptr;
  }
    // 运用 prop_area 办理文件映射的内存
  prop_area* pa = new (memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
  close(fd);
  return pa;
}

map_prop_area_rw 函数中:

  • 创立一个文件,文件名是函数传入的 context 安全上下文
  • 将文件的安全上下文设置为函数参数 context,与文件名相同
  • 把新创立的文件映射到内存,并把这块内存转成这块内存对应的办理类 prop_area 的指针。函数回来后,该指针保存在 ContextNode 的成员 _pa 中。

prop_area 用于办理特点文件映射的内存区域,这儿先简略了解一下。

// 目标内存办理类
class prop_area {
 public:
  static prop_area* map_prop_area_rw(const char* filename, const char* context,
                                     bool* fsetxattr_failed);
  static prop_area* map_prop_area(const char* filename);
  static void unmap_prop_area(prop_area** pa) {
    if (*pa) {
      munmap(*pa, pa_size_);
      *pa = nullptr;
    }
  }
  prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) {
    atomic_init(&serial_, 0u);
    memset(reserved_, 0, sizeof(reserved_));
    // Allocate enough space for the root node.
    bytes_used_ = sizeof(prop_bt);
  }
  const prop_info* find(const char* name);
  bool add(const char* name, unsigned int namelen, const char* value, unsigned int valuelen);
  bool foreach (void (*propfn)(const prop_info* pi, void* cookie), void* cookie);
  atomic_uint_least32_t* serial() {
    return &serial_;
  }
  uint32_t magic() const {
    return magic_;
  }
  uint32_t version() const {
    return version_;
  }
 private:
  static prop_area* map_fd_ro(const int fd);
  void* allocate_obj(const size_t size, uint_least32_t* const off);
  prop_bt* new_prop_bt(const char* name, uint32_t namelen, uint_least32_t* const off);
  prop_info* new_prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen,
                           uint_least32_t* const off);
  void* to_prop_obj(uint_least32_t off);
  prop_bt* to_prop_bt(atomic_uint_least32_t* off_p);
  prop_info* to_prop_info(atomic_uint_least32_t* off_p);
  prop_bt* root_node();
  prop_bt* find_prop_bt(prop_bt* const bt, const char* name, uint32_t namelen, bool alloc_if_needed);
  const prop_info* find_property(prop_bt* const trie, const char* name, uint32_t namelen,
                                 const char* value, uint32_t valuelen, bool alloc_if_needed);
  bool foreach_property(prop_bt* const trie, void (*propfn)(const prop_info* pi, void* cookie),
                        void* cookie);
  // The original design doesn't include pa_size or pa_data_size in the prop_area struct itself.
  // Since we'll need to be backwards compatible with that design, we don't gain much by adding it
  // now, especially since we don't have any plans to make different property areas different sizes,
  // and thus we share these two variables among all instances.
  static size_t pa_size_;
  static size_t pa_data_size_;
  uint32_t bytes_used_;
  atomic_uint_least32_t serial_;
  uint32_t magic_;
  uint32_t version_;
  uint32_t reserved_[28];
  char data_[0];
  BIONIC_DISALLOW_COPY_AND_ASSIGN(prop_area);
};

先看成员变量,首要是一些头信息,最后是一个 0 长数组,代表一个地址,剩余的便是配套的函数操作了,这个一般用到的时分再剖析。

最后会调用到 MapSerialPropertyArea 函数,创立一个特别文件 /dev/__properties__/properties_serial


bool ContextsSerialized::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed) {
  char filename[PROP_FILENAME_MAX];
  // 一个特别文件 /dev/__properties__/properties_serial
  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/properties_serial", filename_);
  if (len < 0 || len >= PROP_FILENAME_MAX) {
    serial_prop_area_ = nullptr;
    return false;
  }
  if (access_rw) { //走这个分支
    serial_prop_area_ =
        prop_area::map_prop_area_rw(filename, "u:object_r:properties_serial:s0", fsetxattr_failed);
  } else {
    serial_prop_area_ = prop_area::map_prop_area(filename);
  }
  return serial_prop_area_;
}

目前为止,这个程序的结构如下图所示:

特点体系源码剖析二

4. 特点 key value 对的加载进程

自此,特点文件都创立好并初始化结束,那么特点的 key-value 对,怎么保存进特点文件呢?

Android 平台把特点文件经过 mmap 映射到内存中,运用二叉/字典混合树的结构来保存 key-value 对。

映射结构如下:

特点体系源码剖析二

树的结构如下:

特点体系源码剖析二

树有两种节点 prop_bt 和 prop_info:

struct prop_bt {
  // name 长度
  uint32_t namelen;
  //各个指针,实践存储的是相对当时内存区域起点的偏移值
  atomic_uint_least32_t prop;
  atomic_uint_least32_t left;
  atomic_uint_least32_t right;
  atomic_uint_least32_t children;
  // 0 长数组,代表一个指针
  char name[0];
  prop_bt(const char* name, const uint32_t name_length) {
    this->namelen = name_length;
    memcpy(this->name, name, name_length);
    this->name[name_length] = '';
  }
 private:
  BIONIC_DISALLOW_COPY_AND_ASSIGN(prop_bt);
};
// 通常 prop_bt 的 prop 指针指向一个 prop_info 目标
// 首要用于保存完好的特点名与特点值
struct prop_info {
  constexpr static uint32_t kLongFlag = 1 << 16;
  constexpr static size_t kLongLegacyErrorBufferSize = 56;
 public:
  atomic_uint_least32_t serial;
  // 联合体用于保存特点名
  union {
    char value[PROP_VALUE_MAX];
    struct {
      char error_message[kLongLegacyErrorBufferSize];
      uint32_t offset;
    } long_property;
  };
  // 特点名,'/0' 结束
  char name[0];
  bool is_long() const {
    return (load_const_atomic(&serial, memory_order_relaxed) & kLongFlag) != 0;
  }
  const char* long_value() const {
    return reinterpret_cast<const char*>(this) + long_property.offset;
  }
  prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen);
  prop_info(const char* name, uint32_t namelen, uint32_t long_offset);
 private:
  BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(prop_info);
};

init 进程接下来的使命便是加载特点值到这些特点文件中,init 进程会调用 export_kernel_boot_props 和 property_load_boot_defaults 两个函数来完结这个使命,咱们逐个剖析:

static void export_kernel_boot_props() {
    constexpr const char* UNSET = "";
    struct {
        const char *src_prop;
        const char *dst_prop;
        const char *default_value;
    } prop_map[] = {
        { "ro.boot.serialno",   "ro.serialno",   UNSET, },
        { "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", },
    };
    for (const auto& prop : prop_map) {
        std::string value = GetProperty(prop.src_prop, prop.default_value);
        if (value != UNSET)
            property_set(prop.dst_prop, value);
    }
}

export_kernel_boot_props() 函数的首要作用是把 Kernel 启动相关的特点写入特点体系。

这儿先调用 GetProperty 获取特点值,如果没有值则调用 property_set 来增加值。体系初始化时,还没有写入数据,GetProperty 获取的特点值一定是空。所以咱们先看 property_set 的详细完成:

// system/core/init/property_service.cpp
const std::string kInitContext = "u:r:init:s0";
// property_set 是一个函数指针,指向 InitPropertySet
uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;
uint32_t InitPropertySet(const std::string& name, const std::string& value) {
    // 做一些详细判别
    if (StartsWith(name, "ctl.")) {
        LOG(ERROR) << "InitPropertySet: Do not set ctl. properties from init; call the Service "
                      "functions directly";
        return PROP_ERROR_INVALID_NAME;
    }
    if (name == "selinux.restorecon_recursive") {
        LOG(ERROR) << "InitPropertySet: Do not set selinux.restorecon_recursive from init; use the "
                      "restorecon builtin directly";
        return PROP_ERROR_INVALID_NAME;
    }
    uint32_t result = 0;
    ucred cr = {.pid = 1, .uid = 0, .gid = 0};
    std::string error;
    // 中心是这个
    result = HandlePropertySet(name, value, kInitContext.c_str(), cr, &error);
    if (result != PROP_SUCCESS) {
        LOG(ERROR) << "Init cannot set '" << name << "' to '" << value << "': " << error;
    }
    return result;
}

开端会对特点值做一些判别,遇到不合规的特点名,直接打 log 报错回来。中心功用完成终究会调用到 HandlePropertySet 函数:

// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr, std::string* error) {
    // selinux 的权限查看
    if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
        return ret;
    }
    if (StartsWith(name, "ctl.")) {
        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }
    // sys.powerctl is a special property that is used to make the device reboot.  We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") {
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std::string process_cmdline;
        std::string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
    }
    if (name == "selinux.restorecon_recursive") {
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }
    // 
    return PropertySet(name, value, error);
}

一堆的操作,终究中心是调用 PropertySet:

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
    size_t valuelen = value.size();
    if (!IsLegalPropertyName(name)) {
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }
    if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
        *error = "Property value too long";
        return PROP_ERROR_INVALID_VALUE;
    }
    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
        *error = "Value is not a UTF8 encoded string";
        return PROP_ERROR_INVALID_VALUE;
    }
    // 查找特点
    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    if (pi != nullptr) {
        // ro.* properties are actually "write-once".
        if (StartsWith(name, "ro.")) {
            *error = "Read-only property was already set";
            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) {
            *error = "__system_property_add failed";
            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 && StartsWith(name, "persist.")) {
        WritePersistentProperty(name, value);
    }
    property_changed(name, value);
    return PROP_SUCCESS;
}

先经过 __system_property_find 去找,有没有这个特点值,明显这儿是没有的,回来空,走 else 分支,调用 __system_property_add 增加特点值:

__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int __system_property_add(const char* name, unsigned int namelen, const char* value,
                          unsigned int valuelen) {
  return system_properties.Add(name, namelen, value, valuelen);
}
int SystemProperties::Add(const char* name, unsigned int namelen, const char* value,
                          unsigned int valuelen) {
  if (valuelen >= PROP_VALUE_MAX && !is_read_only(name)) {
    return -1;
  }
  if (namelen < 1) {
    return -1;
  }
  if (!initialized_) {
    return -1;
  }
  // 获取 /dev/__properties__/properties_serial 文件对应的办理类 prop_area
  prop_area* serial_pa = contexts_->GetSerialPropArea();
  if (serial_pa == nullptr) {
    return -1;
  }
  // ContextsSerialized
  prop_area* pa = contexts_->GetPropAreaForName(name);
  if (!pa) {
    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Access denied adding property "%s"", name);
    return -1;
  }
  bool ret = pa->add(name, namelen, value, valuelen);
  if (!ret) {
    return -1;
  }
  // There is only a single mutator, but we want to make sure that
  // updates are visible to a reader waiting for the update.
  atomic_store_explicit(serial_pa->serial(),
                        atomic_load_explicit(serial_pa->serial(), memory_order_relaxed) + 1,
                        memory_order_release);
  __futex_wake(serial_pa->serial(), INT32_MAX);
  return 0;
}

这儿经过 ContextsSerialized 目标的 GetPropAreaForName 函数获取到特点对应的 prop_area 目标,其内部完成很简略,先经过 name 获取到对应的 index,在经过 index 找到对应的 ContextNode 目标,ContextNode 目标中保存着有特点文件对应的 prop_area 目标。经过 prop_area 目标就能够操作对应的特点文件了。

prop_area* ContextsSerialized::GetPropAreaForName(const char* name) {
  // name 和 安全上下文值相同,从 /dev/__properties__/property_info 文件中的 contexts_ 中拿到 index
  uint32_t index;
  property_info_area_file_->GetPropertyInfoIndexes(name, &index, nullptr);
  if (index == ~0u || index >= num_context_nodes_) {
    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Could not find context for property "%s"",
                          name);
    return nullptr;
  }
  // 拿到 index 对应的 ContextNode
  auto* context_node = &context_nodes_[index];
  if (!context_node->pa()) {
    // We explicitly do not check no_access_ in this case because unlike the
    // case of foreach(), we want to generate an selinux audit for each
    // non-permitted property access in this function.
    context_node->Open(false, nullptr);
  }
  // 拿到特点  文件对应的 prop_area
  return context_node->pa();
}

接着再调用 prop_area 目标的 add 函数增加特点值:

bool prop_area::add(const char* name, unsigned int namelen, const char* value,
                    unsigned int valuelen) {
  return find_property(root_node(), name, namelen, value, valuelen, true);
}
// data_ 数据区 0 位置,强转为 prop_bt*
// prop_bt 结构简略,直接写,省去序列化操作
inline prop_bt* prop_area::root_node() {
  return reinterpret_cast<prop_bt*>(to_prop_obj(0));
}

进一步骤用 find_property 函数

const prop_info* prop_area::find_property(prop _bt* const trie, const char* name, uint32_t namelen,
                                          const char* value, uint32_t valuelen,
                                          bool alloc_if_needed) {
  if (!trie) return nullptr;
  const char* remaining_name = name;
  prop_bt* current = trie;
  while (true) {
    const char* sep = strchr(remaining_name, '.');
    const bool want_subtree = (sep != nullptr);
    const uint32_t substr_size = (want_subtree) ? sep - remaining_name : strlen(remaining_name);
    if (!substr_size) {
      return nullptr;
    }
    prop_bt* root = nullptr;
    uint_least32_t children_offset = atomic_load_explicit(&current->children, memory_order_relaxed);
    if (children_offset != 0) {
      root = to_prop_bt(&current->children);
    } else if (alloc_if_needed) { 
      uint_least32_t new_offset;
      root = new_prop_bt(remaining_name, substr_size, &new_offset);
      if (root) {
        atomic_store_explicit(&current->children, new_offset, memory_order_release);
      }
    }
    if (!root) {
      return nullptr;
    }
    // 实践刺进数据
    current = find_prop_bt(root, remaining_name, substr_size, alloc_if_needed);
    if (!current) {
      return nullptr;
    }
    if (!want_subtree) break;
    remaining_name = sep + 1;
  }
  uint_least32_t prop_offset = atomic_load_explicit(&current->prop, memory_order_relaxed);
  if (prop_offset != 0) {
    return to_prop_info(&current->prop);
  } else if (alloc_if_needed) {
    uint_least32_t new_offset;
    prop_info* new_info = new_prop_info( , namelen, value, valuelen, &new_offset);
    if (new_info) {
      atomic_store_explicit(&current->prop, new_offset, memory_order_release);
    }
    return new_info;
  } else {
    return nullptr;
  }
}
prop_bt* prop_area::find_prop_bt(prop_bt* const bt, const char* name, uint32_t namelen,
                                 bool alloc_if_needed) {
  prop_bt* current = bt;
  while (true) {
    if (!current) {
      return nullptr;
    }
    const int ret = cmp_prop_name(name, namelen, current->name, current->namelen);
    if (ret == 0) {
      return current;
    }
    if (ret < 0) {
      uint_least32_t left_offset = atomic_load_explicit(&current->left, memory_order_relaxed);
      if (left_offset != 0) {
        current = to_prop_bt(&current->left);
      } else {
        if (!alloc_if_needed) {
          return nullptr;
        }
        uint_least32_t new_offset;
        prop_bt* new_bt = new_prop_bt(name, namelen, &new_offset);
        if (new_bt) {
          atomic_store_explicit(&current->left, new_offset, memory_order_release);
        }
        return new_bt;
      }
    } else {
      uint_least32_t right_offset = atomic_load_explicit(&current->right, memory_order_relaxed);
      if (right_offset != 0) {
        current = to_prop_bt(&current->right);
      } else {
        if (!alloc_if_needed) {
          return nullptr;
        }
        uint_least32_t new_offset;
        prop_bt* new_bt = new_prop_bt(name, namelen, &new_offset);
        if (new_bt) {
          atomic_store_explicit(&current->right, new_offset, memory_order_release);
        }
        return new_bt;
      }
    }
  }
}

这儿实践会依照本节一最初给出的混合树的结构来创立结构体并做树的刺进操作,这个不是咱们的要点,有兴趣的同学能够自己捋一下代码。