Android的系统属性Property整体上看,是键值对保存, 即Key -- Value方式。在系统运行过程中,Property是以字典树的方式存储内存中。
Property机制的运作机理可以汇总成一下几点:
1) 系统一启动就会从若干属性脚本文件中加载属性内容;
2) 系统中的所有属性(key/value)会存入同一块共享内存中;
3) 系统中的各个进程会将这块共享内存映射到自己的内存空间,这样就可以直接读取属性内容了;
4) 系统中只有一个实体可以设置、修改属性值,它就是属性服务(Property Service);
5) 不同进程只可以通过socket方式,向属性服务发出修改属性值的请求,而不能直接修改属性值;
6) 共享内存中的键值内容会以一种字典树的形式进行组织。
项目中的一些字段如
"ro.product.brand=$PRODUCT_BRAND" 品牌名
等等ro开头字段是在build/make/tools/buildinfo.sh中定义同时由build/core/Makefile解析完成后生成build.prop。
BUILD_FINGERPRINT也是在Makefile 拼接而成
# build.prop
INSTALLED_BUILD_PROP_TARGET := $(TARGET_OUT)/build.prop
ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_BUILD_PROP_TARGET)
$BUILD_FINGERPRINT在Makefile 由如下拼接而成
$(PRODUCT_BRAND)/$(XXX_PRODUCT_NAME)/$(XXX_PRODUCT_DEVICE):$(PLATFORM_VERSION)/$(BUILD_ID)/$(FINGERPRINT_BUILD_VERNO):$(TARGET_BUILD_VARIANT)/$(BUILD_VERSION_TAGS)
Makefile也把$(TARGET_DEVICE_DIR)/system.prop的内容追加到build.prop中。
还会收集ADDITIONAL_BUILD_PROPERTIES中的属性,追加到build.prop中,
ADDITIONAL_BUILD_PROPERTIES又会收集PRODUCT_PROPERTY_OVERRIDES中定义的属性。
BUILDINFO_SH := build/tools/buildinfo.sh
bash $(BUILDINFO_SH) >> $@
...... $(hide) $(foreach file,$(system_prop_file), \
if [ -f "$(file)" ]; then \
echo "#" >> $@; \
echo Target buildinfo from: "$(file)"; \
echo "# from $(file)" >> $@; \
echo "#" >> $@; \
cat $(file) >> $@; \
fi;)
$(if $(FINAL_BUILD_PROPERTIES), \
$(hide) echo >> $@; \
echo "#" >> $@; \
echo "# ADDITIONAL_BUILD_PROPERTIES" >> $@; \
echo "#" >> $@; )
$(hide) $(foreach line,$(FINAL_BUILD_PROPERTIES), \
echo "$(line)" >> $@;)
.............
kernel中init进程启动
内核引导时,会在用户空间创建一个名为init的特殊进程,这个是所有进程之父(系统中所有进程不是init直接创建的,就是其子孙进程创建的)。对应的程序文件为“/sbin/init”。 实现init进程启动的代码位于kernel-4.4/init/main.c中
static int __ref kernel_init(void *unused){
..................
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
........................
}
build.prop生成之后就需要通过加载来实现读取其中的参数,property的初始化过程如下:
system/core/init/init.cpp中
int main(int argc, char** argv) {
......
初始化
property_init();
.......
property_load_boot_defaults();
export_oem_lock_status();
启动属性服务
start_property_service();
....
}
system/core/init/property_service.cpp
void property_init() {
if (__system_property_area_init()) {
LOG(ERROR) << "Failed to initialize property area";
exit(1);
}
}
erty_area_init() 存在于bionic/libc/bionic/system_properties.cpp初始化属性共享内存。
bionic/libc/include/sys/_system_properties.h
:#define PROP_SERVICE_NAME "property_service"
void start_property_service() {
property_set("ro.property_service.version", "2");
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr, sehandle);
if (property_set_fd == -1) {
PLOG(ERROR) << "start_property_service socket creation failed";
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
通过load properties这样的函数,将一些文件里面的property加载到内存,然后创建了一个socket,
其名为 "/dev/socket/property_service”
然后调用listen监听这个socket,并将创建的socket描述符赋值给全局变量property_set_fd。
加载由build/core/Makefile中生成的prop配置文件
void load_system_props() {
load_properties_from_file("/system/build.prop", NULL);
load_properties_from_file("/odm/build.prop", NULL);
load_properties_from_file("/vendor/build.prop", NULL);
load_properties_from_file("/factory/factory.prop", "ro.*");
load_recovery_id_prop();
}
在init.rc中启动加载properties,对应property_service.cpp中load_system_props()
system/core/rootdir/init.rc
单一的init*.rc,被拆分,服务根据其二进制文件的位置(/system,/vendor)定义到对应分区的etc/init目录中,每个服务一个rc文件。与该服务相关的触发器、操作等也定义在同一rc文件中。 /system/etc/init,包含系统核心服务的定义,如SurfaceFlinger、MediaServer、Logcatd等。/vendor/etc/init, SOC厂商针对SOC核心功能定义的一些服务。比如高通、MTK某一款SOC的相关的服务。
Init.rc头部还包含了如下rc文件
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
在init.rc中分成三个部分(Section),而每一部分的开头需要指定on(Actions)、service(Services)或import。也就是说,每一个Actions, import或 Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和 Options在第一个Section之前被定义,它们将被忽略。Actions和Services的名称必须唯一。如果有两个或多个Actions或Services拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。
init进程在init.cpp的main函数中将service, on, import设置为三个Section
Parser& parser = Parser::GetInstance();
parser.AddSectionParser("service", std::make_unique(&sm));
parser.AddSectionParser("on", std::make_unique(&am));
parser.AddSectionParser("import", std::make_unique(&parser));
Action的格式如下:
on
在init.cpp中设置的trigger有early-init, init, late-init等, 当trigger被触发时就执行command
触发器(trigger)用来描述一个触发条件,当这个触发条件满足时可以执行动作.
触发器 |
描述 |
boot |
当init程序执行,并载入/init.conf文件时触发. |
|
当属性名对应的值设置为指定值时触发. |
device-added- |
当添加设备时触发. |
device-removed- |
当设备移除时触发. |
service-exited- |
当指定的服务退出时触发. |
early-init |
初始化之前触发 |
late-init |
初始化之后触发 |
init |
初始化时触发 |
命令(commands)
命令 |
描述 |
exec |
执行指定路径下的程序,并传递参数. |
export |
设置全局环境参数,此参数被设置后对所有进程都有效. |
ifup |
使指定的网络接口"上线",相当激活指定的网络接口 |
import |
导入一个额外的init配置文件. |
hostname |
设置主机名 |
chdir |
改变工作目录. |
chmod |
改变指定文件的读取权限. |
chown |
改变指定文件的拥有都和组名的属性. |
chroot |
改变进行的根目录. |
class_start |
启动指定类属的所有服务,如果服务已经启动,则不再重复启动. |
class_stop |
停止指定类属的所胡服务. |
domainname |
设置域名 |
insmod |
安装模块到指定路径. |
mkdir |
用指定参数创建一个目录,在默认情况下,创建的目录读取权限为755.用户名为root,组名为root. |
mount |
类似于linux的mount指令 |
setkey |
TBD(To Be Determined),待定. |
setprop |
设置属性及对应的值. |
setrlimit |
设置资源的rlimit(资源限制),不懂就百度一下rlimit |
start |
如果指定的服务未启动,则启动它. |
stop |
如果指定的服务当前正在运行,则停止它. |
symlink |
创建一个符号链接. |
sysclktz |
设置系统基准时间. |
trigger |
Trigger an event. Used to queue an action from another action.. |
write |
往指定的文件写字符串. |
Services格式如下:
service
...
选项(options)是用来修改服务的。它们影响如何及何时运行这个服务.
选项 |
描述 |
critical |
据设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式。 |
disabled |
服务不会自动运行,必须显式地通过服务器来启动。 |
setenv |
设置环境变量 |
socket |
在/dev/socket/下创建一个unix domain的socket,并传递创建的文件描述符fd给服务进程.其中type必须为dgram或stream,seqpacket.用户名和组名默认为0 |
user |
在执行此服务之前先切换用户名。当前默认为root. |
group |
类似于user,切换组名 |
oneshot |
当此服务退出时不会自动重启. |
class |
给服务指定一个类属,这样方便操作多个服务同时启动或停止.默认情况下为default. |
onrestart |
当服务重启时执行一条指令, |
late-init中又分了多个trigger,
post-fs会启动logd
load_system_props 加载系统属性
system/core/rootdir/init.rc
on late-init
trigger post-fs
on post-fs
# Load properties from
# /system/build.prop,
# /odm/build.prop,
# /vendor/build.prop and
# /factory/factory.prop
load_system_props
# start essential services
property_service.cpp中load_properties_from_file加载prop文件字段
// Filter is used to decide which properties to load: NULL loads all keys,
// "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
static bool load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
std::string err;
if (!ReadFile(filename, &data, &err)) {
PLOG(WARNING) << "Couldn't load property file: " << err;
return false;
}
data.push_back('\n');
load_properties(&data[0], filter);
LOG(VERBOSE) << "(Loading properties from " << filename << " took " << t << ".)";
return true;
}
system/init/util.cpp中的读取prop文件
bool ReadFile(const std::string& path, std::string* content, std::string* err) {{
.......
if (!android::base::ReadFdToString(fd, content)) {
*err = "Unable to read '" + path + "': " + strerror(errno);
return false;
}
.......
}
load_properties会逐行分析传来的buffer并且过滤点其中的#注释,解析出行内的key、value部分,并调用property_set(),将key、value设置进系统的属性共享内存去。
在load_properties中
if (!strncmp(key, "import ", 7) && flen == 0) {...
fn = key + 7;
while (isspace(*fn)) fn++;
key = strchr(fn, ' ');
if (key) {
*key++ = 0;
while (isspace(*key)) key++;
}
load_properties_from_file(fn, key);
...}
有这么一句可以看出在build.prop中或者其他加载文件中添加 import xxx.prop可以将信息导入,实现ro的动态替换改写。
property_set()是在bionic/libc/bionic/system_properties.cpp实现的具体操作
uint32_t property_set(const std::string& name, const std::string& value) {
if (name == "selinux.restorecon_recursive") {
return PropertySetAsync(name, value, RestoreconRecursiveAsync);
}
return PropertySetImpl(name, value);
}
PropertySetImpl中检测到是ro.开头的则会返回只读,然后更新字段。
static uint32_t PropertySetImpl(const std::string& name, const std::string& value) {
........
prop_info* pi = (prop_info*) __system_property_find(name.c_str());
if (pi != nullptr) {
// ro.* properties are actually "write-once".
if (android::base::StartsWith(name, "ro.")) {
LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
<< "property already set";
return PROP_ERROR_READ_ONLY_PROPERTY;
}
__system_property_update(pi, value.c_str(), valuelen);
}
...........
}
bionic/libc/include/sys/system_properties.h
#define PROP_NAME_MAX 32
#define PROP_VALUE_MAX 92
bionic/libc/bionic/system_properties.cpp static constexpr size_t PA_SIZE = 128 * 1024;
static prop_area* map_prop_area_rw(const char* filename, const char* context, bool* fsetxattr_failed) {
........ 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);
........ }
可以算出来prop至少的个数是128*1024/(32+92) = 1036
简介结构位于bionic/libc/bionic/system_properties.cpp中
/*
* Properties are stored in a hybrid trie/binary tree structure.
* Each property's name is delimited at '.' characters, and the tokens are put
* into a trie structure. Siblings at each level of the trie are stored in a
* binary tree. For instance, "ro.secure"="1" could be stored as follows:
*
*/
Property是以字典树的方式存储内存中。
字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。从空字符串的根开始,往下遍历到某个节点,确定了对应的字符串,也就是说,任意一个节点的所有子孙都具备相同的前缀。
字典树与字典很相似,当你要查一个单词是不是在字典树中,首先看单词的第一个字母是不是在字典的第一层,如果不在,说明字典树里没有该单词,如果在就在该字母的孩子节点里找是不是有单词的第二个字母,没有说明没有该单词,有的话用同样的方法继续查找.字典树不仅可以用来储存字母,也可以储存数字等其它数据。
Trie树的基本性质可以归纳为:
Trie树有一些特性:
比如我们创建一个字典树包含下列单词:
inn, int, at, age, adv, ant
非常的类似于新华字典的拼音查词的顺序,比如查“生”就要从sh声母开始找e再找n再找g。
字典树简介链接:https://blog.csdn.net/thesprit/article/details/52065241
Property本身的信息是使用prop_info这个结构体描述的
struct prop_info {
atomic_uint_least32_t serial;
// we need to keep this buffer around because the property
// value can be modified whereas name is constant.
char value[PROP_VALUE_MAX];
char name[0];
prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen) {
memcpy(this->name, name, namelen);
this->name[namelen] = '\0';
atomic_init(&this->serial, valuelen << 24);
memcpy(this->value, value, valuelen);
this->value[valuelen] = '\0';
}
而存储时的信息是用prop_bt描述
// Represents a node in the trie.
struct prop_bt {
uint32_t namelen;
atomic_uint_least32_t prop;
atomic_uint_least32_t left;
atomic_uint_least32_t right;
atomic_uint_least32_t children;
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] = '\0';
}
private:
DISALLOW_COPY_AND_ASSIGN(prop_bt);
};
有如下三个疑问
在存储过程中,如何把从socket接收到的信息到生成prop_info结构体的过程是怎样的?
在存储时如何把结构体prop_info和结构体prop_bt是如何存储的?
在修改已有的属性时,如何从内存中找到所需要的属性的?
2. 如何从结构体prop_bt转化到prop_info的?to_prop_obj()函数 prop_bt是局部信息,prop_info是整体信息,find_property() 函数完成这个工作,从根节点开始查找,如果没有找到的话,不要再当前函数中为prop_info申请空间。先把要设置属性的名字开始分段,以‘.’为分隔符,查找是否存在对应的节点是否存在,如果存在,那就找名字第二部分对应的节点是否存在,以此类推。如果都存在的话,那么就说明这个属性已经存在了,这时候通过to_prop_obj()函数,把其对应的信息构造成prop_info返回。如果属性名字中,有其中一部分没有找到的话,那么就要申请内存空间,创建属性,并存储。
3. 如何从结构体prop_info转化到prop_bt的?
find_prop_bt() 函数完成这个工作。无论是添加属性,更新现有属性都是通过find_property()这个函数中的分发去处理的。把属性中目标名字和节点中的name做比较,按照字母升序排列,如果节点中的名字靠后,那么返回值是 < 0;反之,返回值 > 0; 如果属性中目标名字和节点的名字一样的话,那么返回值就是0. 这样比较后,如果返回值是大于0,那么就去属性字典树的右侧查找;反之,就去字典树的左侧查找。如果返回值是等于0, 那么意味着找到了此节点。
当默认的配置属性被加载后,则可以通过SystemProperties类来查询关键字属性值
frameworks/base/core/jni/android_os_SystemProperties.cpp
static const JNINativeMethod method_table[] = {
{ "native_get", "(Ljava/lang/String;)Ljava/lang/String;",
(void*) SystemProperties_getS },
{ "native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
(void*) SystemProperties_getSS },
{ "native_get_int", "(Ljava/lang/String;I)I",
(void*) SystemProperties_get_int },
.....
.....
}
int register_android_os_SystemProperties(JNIEnv *env)
{
return RegisterMethodsOrDie(env, "android/os/SystemProperties", method_table,
NELEM(method_table));
}
此jni的函数与frameworks/base/core/java/android/os/SystemProperties.java中的native方法匹配。
而register_android_os_SystemProperties该函数注册于
frameworks/base/core/jni/AndroidRuntime.cpp虚拟机中。
将对于native_get的指向引导到了SystemProperties_getS函数而其内部跳转最终跳转到SystemProperties_getSS。
static jstring SystemProperties_getS(JNIEnv *env, jobject clazz,jstring keyJ){
return SystemProperties_getSS(env, clazz, keyJ, NULL);
}
SystemProperties_getSS的方法为:
static jstring SystemProperties_getSS(JNIEnv *env, jobject clazz,
jstring keyJ, jstring defJ)
{
int len;
const char* key;
char buf[PROPERTY_VALUE_MAX];
jstring rvJ = NULL;
if (keyJ == NULL) {
jniThrowNullPointerException(env, "key must not be null.");
goto error;
}
key = env->GetStringUTFChars(keyJ, NULL);
len = property_get(key, buf, "");
if ((len <= 0) && (defJ != NULL)) {
rvJ = defJ;
} else if (len >= 0) {
rvJ = env->NewStringUTF(buf);
} else {
rvJ = env->NewStringUTF("");
}
env->ReleaseStringUTFChars(keyJ, key);
error:
return rvJ;
}
在frameworks/base/core/java/android/os/SystemProperties.java中
private static native String native_get(String key);
private static native String native_get(String key, String def);
public static String get(String key) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
/**
* Get the value for the given key.
* @return if the key isn't found, return def if it isn't null, or an empty string otherwise
*/
public static String get(String key, String def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key, def);
}
最后应用层或者frameworks读取ro.字段获取所需要的值.