Android系统注册表(prop属性)的研究与分析

在程序开发的时候,会使用很多的环境变量,有时候会遇到以下几种情况:

1.该变量在多处需要使用,并且是跨进程或者跨线程的。

2.该环境变量一般只需读取一次,不需要频繁保存。

3.这个变量信息在关机重启后任然可以保存。

对于这些需求,通常的做法是将这些信息保存到一个文件中,通过对该文件的读写来提取和保存信息,这些信息的数据量都比较小。这种方法是可以的,但是不是很系统完善,而且当需要读取信息时都需要进行一次文件的io操作,这就很费时和浪费系统资源;还有一种情况,就是一个变量信息,开机启动的时只需从flash中读取一次,在系统运行时很少对它进行修改;如果保存到文件,每次读取都要进行一次IO操作,如果保存不当很容易出错,所以这种信息保存到内存更显得合适。

Window中有注册表这样完善的模块对少量配置信息进行存储,使用起来安全、方便、快捷,android中是否也有类似的接口呢?答案是肯定的,那就是Prop模块(目前还没有更合适的名字)Prop模块存储着系统运行的很多配置信息,当程序运行时需要某种系统状态时,会到该模块中进行读取和寻找。Prop模块本质上来说,是系统运行时内存中保存的一块数据区,读写数据都是对这一块区域进行操作;好处是读写速度快,数据跨进程共享,缺点是突然断电会丢失数据;当然Prop也能保存数据,这个在后面提到。

系统中的一些有用的配置信息:

本分基于android4.2源码进行分析,Android的启动后,在property_service.cproperty_init中完成prop的初始化。系统中存在着几个文件,如build.propdefault.prop等,这些文件在系统构建时候生成的,里面包含很多系统的配置。系统开机时回去加载这些文件中的信息并保存到prop模块(内存)中去,以便其它程序进行读取和使用。

例如在build.prop中有如下信息:

dalvik.vm.heapstartsize=5m

dalvik.vm.heapgrowthlimit=96m

dalvik.vm.heapsize=256m

dalvik.vm.heaptargetutilization=0.75

dalvik.vm.heapminfree=512k

dalvik.vm.heapmaxfree=2m

虚拟机的堆栈大小以及其它属性。

persist.sys.timezone=Asia/Shanghai

时区信息。

ro.build.version.codename=REL

ro.build.version.release=4.2.2

ro.build.date=Fri Dec 26 15:56:10 UTC 2014

ro.build.date.utc=1419609370

软件版本构建信息。

ro.product.cpu.abi=armeabi-v7a

ro.product.cpu.abi2=armeabi

ro.product.manufacturer=unknown

ro.product.locale.language=en

ro.product.locale.region=US

Cpu信息,默认语言设置。

dalvik.vm.stack-trace-file=/data/anr/traces.txt

虚拟机的调试信息保存。

 

以上动作都是在init.rc中完成的,该过程会调用property_service.c中的start_property_service函数,在该函数中完成以下文件的加载:

#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"

#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"

#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"

#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"

同时会调用load_persistent_properties函数,该函数会/data/property/中寻找用户的保存设置。

Env环境变量中有一个ANDROID_PROPERTY_WORKSPACE变量,该变量中存储着prop内存区域的大小,如果想知道详细,可查阅init_property_area函数。

Prop模块对应的Java接口:

Prop模块是保存少量的全局共享信息,其保存的数据具有信息量少,跨进程共享数据等特性;每一条信息包含两个属性,键名和键名对应的键值,例如:

ro.product.locale.language=en

Ro.product.locale.language”表示本产品本地语言,表示该条信息的名字,“en”表示该条信息的取值为英文,这样任何一个应用程序就知道本机使用的语言情况。在接口设计时也需要有两个参数,namevalue(键名和键值),方法有setget,例如:

Set(String name,String value); String Get(String name);

当然,无论上层怎么设计,在C底层键名name和键值value都是以char数组进行保存的,因为设计者并不知道传入namevalue的数据大小。

android.os.SystemProperties类中对prop模块进行了封装,该类使用SetGet直接进行设置和获取,当然这些java接口最终还是调用系统接口完成的。在jni层有一个property_service.c文件,文件中有对应的实际处理接口,这些接口即可以给java调用,也可以一些系统命令使用(例如setpropgetprop命令就是调用这些接口方法)SystemProperties类中的get方法没有什么限制,但是set方法就有权限的限制,应用程序是不能随便使用set接口的。

SystemProperties类的访问必须要有系统权限,并且应用的uid必须是系统id1000或者为root:0。因为setget操作不同,set时该操作建立了一个socket管道通过发cmd出去完成的,服务端接收cmd同时比较权限,关键代码如下:

    if (uid == AID_SYSTEM || uid == AID_ROOT)

      return check_control_mac_perms(name, sctx);

只有权限是AID_SYSTEM(系统ID)AID_ROOT(root用户ID)才能通过验证;而get没有权限检查,不过试想也正常,如果谁都能进行修改,那这黑客也太好当了。当然个人觉得,对于set的设计使用权限验证无可厚非,但对于查看系统属性这样的功能(get),应该还可以进行细分,比如有些属性是不重要的,任何进程和用户都可以读取使用;当然有些敏感的数据在指定读取权限时也可以进行指定(由于时间有限本人没有更深入的细读,也不知道android系统是否完成了这些功能)

设置键值名时需要注意的地方:

在进行设置时,包含两个参数,变量名和变量值,形如:[[key]]: [[value]]。如果原来没有对应的key值,那么就会在该模块中创建一个新的键值,否则覆盖原有键值。对于键值名在设计时最好按规范书写,比如“类名.模块.用途”,这样清晰可记而且不容易冲突。另外,如果属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。这个判断动作是在property_service.c中的property_set函数中完成的:

if(!strncmp(name, "ro.", 3)) return -1;

如果是以“persist.”开头,当设置这个属性时,其值也将写入/data/property/目录中,键值名就是该属性名,下次开机重新加载和读取该属性;该文件中的load_persistent_properties函数就是用来完成该功能。特别的属性名以“net.change”开头那么其值中必须以“net.”开头,例如键值名为[net.change]: 那么键值为[net.qtaguid_enabled],这个设置目前还没想到有什么作用。

Shell中对应的prop操作命令:

androidshell中也有对应的命令进行操作,有如下三个命令:

getprop [keyname]

Keyname为需要获取的键值名,如果没有参数则打印全部的键值信息。

setprop [keyname] [value]

Keyname为需要获取的键值名,value为设置的值,这个值为字符串。

watchprops

监听系统属性的变化,如果期间系统的属性发生变化则把变化的值显示出来。

init.rc中也使用setprop来设置一些属性状态。


   [本文是基于android4.2源码进行分析]

没有更多推荐了,返回首页