在init进程启动的第二阶段,调用PropertyInit 对属性系统进行初始化
c
int SecondStageMain(int argc, char** argv) {
//省略
PropertyInit();
//省略
}
PropertyInit函数在system\core\init\property_service.cpp 中实现
c
void PropertyInit() {
//省略
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); //1
CreateSerializedPropertyInfo();//2
if (__system_property_area_init()) {//3
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
ProcessKernelDt(); //4
ProcessKernelCmdline();//5
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
ExportKernelBootProps();//6
PropertyLoadBootDefaults();//7
}
注释1处在dev下创建__properties__文件夹。注释2处会收集读取各个分区下的property_contexts文件,将读取到的信息系列化之后,写到/dev/properties /property_info文件中。注释3处会读取/dev/properties/property_info文件构建ContextNode 数组,并在/dev/__properties__目录下,创建属性文件.注释4处处理内核dts中的属性信息。注释5处理cmdline中的属性信息。注释6导出一些属性给另外的属性赋值。注释7加载系统默认的属性文件。
接下来一项一项的来分析
CreateSerializedPropertyInfo
c
void CreateSerializedPropertyInfo() {
auto property_infos = std::vector<PropertyInfoEntry>();
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 (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
&property_infos);
}
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);
}
}
//省略
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;
}
constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
PLOG(ERROR) << "Unable to write serialized property infos to file";
}
selinux_android_restorecon(kPropertyInfosPath, 0);
- 初始化property_infos 动态数组
- 调用LoadPropertyInfoFromFile分别读取各目录下的property_contexts文件,将读取到的信息构建PropertyInfoEntry并放入property_infos 数组
- BuildTrie对数组进行系列化(构建字典树并对字典树进行TrieBuilerNode 对象系列化),
- 将系列化后的信息通过WriteStringToFile写进/dev/properties/property_info文件
__system_property_area_init
__system_property_area_init的实现在bionic\libc\bionic\system_property_api.cpp文件中
c
//#define PROP_FILENAME "/dev/__properties__"
int __system_property_area_init() {
bool fsetxattr_failed = false;
return system_properties.AreaInit(PROP_FILENAME, &fsetxattr_failed) && !fsetxattr_failed ? 0 : -1;
}
AreaInit函数实现在bionic\libc\system_properties\system_properties.cpp文件中
c
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;
}
先初始化一个ContextsSerialized对象,然后调用其Initialize函数。注意第一个参数。为true时,则会创建所有的/dev/properties /property_info/u:object_r:*:s0属性安全上下文文件,并在mmap时具有可读写权限。为false时,则不会创建文件且在映射时,只有可读的权限。
接着看一下Initialize函数,实现在bionic\libc\system_properties\contexts_serialized.cpp文件中
c
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文件,并映射一块内存,然后通过一个 for 循环,将这块内存初始化为一个 ContextNode 的数组结构,每个 ContextNode 对象中的信息保存了一个安全上下文信息和文件路径信息。
完成上面的工作之后,通过for循环,调用每个ContextNode 的Open函数,在Open函数中,会根据属性安全上下文创建属性文件,如dev/properties /u:object_r:hwservicemanager_prop:s0,并映射它,将映射的地址保存在ContextNode 中。需要说明的是,相同安全上下文的不同属性,都会存放在同一片共享内存中,内存的名字就是其安全上下文(比如属性的安全上下文名字为adbd_config_prop,则存放在dev/properties/u:object_r:adbd_config_prop:s0文件中)。
c
ls -lh dev/__properties__/
-r--r--r-- 1 root root 128K 2017-01-01 20:00 u:object_r:adbd_config_prop:s0
-r--r--r-- 1 root root 128K 2017-01-01 20:00 u:object_r:adbd_prop:s0
-r--r--r-- 1 root root 128K 2017-01-01 20:00 u:object_r:apexd_prop:s0
-r--r--r-- 1 root root 128K 2017-01-01 20:00 u:object_r:apk_verity_prop:s0
-r--r--r-- 1 root root 128K 2017-01-01 20:00 u:object_r:audio_prop:s0
ProcessKernelDt
c
static void ProcessKernelDt() {
if (!is_android_dt_value_expected("compatible", "android,firmware")) {
return;
}
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(get_android_dt_dir().c_str()), closedir);
if (!dir) return;
std::string dt_file;
struct dirent* dp;
while ((dp = readdir(dir.get())) != NULL) {
if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") ||
!strcmp(dp->d_name, "name")) {
continue;
}
std::string file_name = get_android_dt_dir() + dp->d_name;
android::base::ReadFileToString(file_name, &dt_file);
std::replace(dt_file.begin(), dt_file.end(), ',', '.');
InitPropertySet("ro.boot."s + dp->d_name, dt_file);
}
}
首先检查compatible属性的值是"android,firmware"如果不是,函数直接返回。如果是的话,再打开目录下的dts文件,并读取其内容。其中 get_android_dt_dir是在cmdline中指定
c
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
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
if (key == "androidboot.android_dt_dir") {
android_dt_dir = value;
}
});
LOG(INFO) << "Using Android DT directory " << android_dt_dir;
return android_dt_dir;
}
// FIXME: The same logic is duplicated in system/core/fs_mgr/
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;
}
比如androidboot.android_dt_dir目录下有cpu.dts和dispaly.dts,满足条件的话,则会将两个文件的内容分别设置ro.boot.cpu和ro.boot.dispaly
ProcessKernelCmdline
c
static void ProcessKernelCmdline() {
bool for_emulator = false;
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
if (key == "qemu") {
for_emulator = true;
} else if (StartsWith(key, "androidboot.")) {
InitPropertySet("ro.boot." + key.substr(12), value);
}
});
if (for_emulator) {
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
// In the emulator, export any kernel option with the "ro.kernel." prefix.
InitPropertySet("ro.kernel." + key, value);
});
}
}
解析cmdline中的数据设置其属性。如:androidboot.mode=normal 会转化成:ro.boot.mode=normal
ExportKernelBootProps
c
static void ExportKernelBootProps() {
constexpr const char* UNSET = "";
struct {
const char* src_prop;
const char* dst_prop;
const char* default_value;
} prop_map[] = {
// clang-format off
{ "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", },
// clang-format on
};
for (const auto& prop : prop_map) {
std::string value = GetProperty(prop.src_prop, prop.default_value);
if (value != UNSET) InitPropertySet(prop.dst_prop, value);
}
}
如果系统中有ro.boot.serialno这个属性,则将其值也设置给ro.serialno
PropertyLoadBootDefaults
加载系统默认的属性文件。参考初识Android 属性
总结
在初始化属性系统时,会加载各个分区的property_contexts文件,并进行TrieBuilerNode 对象系列化,然后将系列化的信息写入到dev/properties /propert_info文件中。根据该文件信息(name,安全上下文),在dev/properties/目录下创建内存文件,并进行映射,地址保存在ContextNode 中。创建的内存文件的名字是属性的安全上下文。ContextNode 和TrieBuilerNode 是一一对应的。内存创建好之后,还会根据dts,cmdline中的信息,生成属性。最后加载各个分区默认的属性文件。
以下为简略的逻辑图,每个内存文件为128k