您的位置:首页 > 移动开发 > Android开发

Android属性系统Property service设定分析

2012-11-17 14:25 423 查看

Android的属性Property系统

http://blog.csdn.net/yinlijun2004/article/details/6981954

一直想研究一下android的属性系统,刚好最近一个项目告一段落,可以开始研究一下相关代码。

按照我的理解,Android属性分为两个部分

1、一个部分是系统属性,一般与虚拟机相关的一些属性,

代码位置

dalvik/libcore/luni-kernel/src/main/java/java/lang/System.java

dalvik/libcore/luni/src/main/java/java/util/Properties.java

dalvik/vm/Properties.c

虚拟机有一些默认属性,例如os.arch, java.boot.class.path等,只加载一次。

来看一些这种属性的加载过程,以Settings.java中的VNC属性为例

[java]
view plaincopy

<span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;"><span style="font-family:monospace;"><span style="white-space: pre;"> private DialogInterface.OnClickListener mVncDisableListener = new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
System.setProperty("vncserver.enable", "0");
System.setProperty("vncserver.password", "");

}
};
</span></span></span></span>

看System.java的代码

[cpp]
view plaincopy

public static String setProperty(String prop, String value) {
if (prop.length() == 0) {
throw new IllegalArgumentException();
}
SecurityManager secMgr = System.getSecurityManager();
if (secMgr != null) {
secMgr.checkPermission(new PropertyPermission(prop, "write"));
}
return (String)internalGetProperties().setProperty(prop, value);
}

首先会对该线程执行写权限的检查,然后才设置属性

在internalGetProperties方法里面,会加载虚拟机默认属性。

[java]
view plaincopy

static Properties internalGetProperties() {
if (System.systemProperties == null) {
SystemProperties props = new SystemProperties();
props.preInit();
props.postInit();
System.systemProperties = props;
}

return systemProperties;
}

这里的SystemProperties只是内部类,跟android.os.SystemProperties不是同一个类。

[java]
view plaincopy

class SystemProperties extends Properties {
// Dummy, just to make the compiler happy.

native void preInit();

native void postInit();
}

它继承了Properties,两个JNI接口在dalvik/vm/native/java_lang_SystemProperties.c中注册,preInit调用本地到本地dvmCreateDefaultProperties函数,该函数就负责加载刚才说的虚拟机默认属性。

[cpp]
view plaincopy

static void Dalvik_java_lang_SystemProperties_preInit(const u4* args,
JValue* pResult)
{
dvmCreateDefaultProperties((Object*) args[0]);
RETURN_VOID();
}

也就是说System.setProperty调用到Properties.setProperty,

[java]
view plaincopy

public Object setProperty(String name, String value) {
return put(name, value);
}

Properties是继承Hashtable的

[java]
view plaincopy

public class Properties extends Hashtable<Object, Object>

这样,就完成设置属性的动作,获取的动作类似,最后从哈希表中根据key拿到value,整个过程比较简单。
可以看到这套属性系统只适合一些不会变化,或者很少变的属性,如果你希望你的属性改变之后能触发某些实践,例如init.rc脚本中的动作,那就要用到另外一套属性系统了。

2、剩下一部分是常规属性。

它的实现原理跟刚才的hash表不一样,是讲属性保存在一块共享内存之中,该共享内存的大小由环境变量ANDROID_PROPERTY_WORKSPACE决定

代码位置:

frameworks/base/core/java/android/os/SystemProperties.java

frameworks/base/core/jni/android_os_SystemProperties.cpp

system/core/init/property_service.c

bionic/libc/bionic/system_properties.c

写属性的过程:

SystemProperties.java

[java]
view plaincopy

public static void set(String key, String val) {
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
if (val != null && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("val.length > " +
PROP_VALUE_MAX);
}
native_set(key, val);
}

value值只支持String类型,而get重载了各种类型的value
这些方法调用jni

[java]
view plaincopy

private static native String native_get(String key);
private static native String native_get(String key, String def);
private static native int native_get_int(String key, int def);
private static native long native_get_long(String key, long def);
private static native boolean native_get_boolean(String key, boolean def);
private static native void native_set(String key, String def);

这些jni在frameworks/base/core/jniandroid_os_SystemProperties.cpp注册

[java]
view plaincopy

static 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 },
{ "native_get_long", "(Ljava/lang/String;J)J",
(void*) SystemProperties_get_long },
{ "native_get_boolean", "(Ljava/lang/String;Z)Z",
(void*) SystemProperties_get_boolean },
{ "native_set", "(Ljava/lang/String;Ljava/lang/String;)V",
(void*) SystemProperties_set },
};

其中SystemProperties_set方法调用到property_service.c中的

[cpp]
view plaincopy

int property_set(const char *name, const char *value)

在property_set中的流程是这样的
首相,通过

[cpp]
view plaincopy

pi = (prop_info*) __system_property_find(name);

找到对应的键值对,prop_info在bionic/libc/include/sys/_system_properties.h有定义

[cpp]
view plaincopy

struct prop_area {
unsigned volatile count;
unsigned volatile serial;
unsigned magic;
unsigned version;
unsigned reserved[4];
unsigned toc[1];
};

#define SERIAL_VALUE_LEN(serial) ((serial) >> 24)
#define SERIAL_DIRTY(serial) ((serial) & 1)

struct prop_info {
char name[PROP_NAME_MAX];
unsigned volatile serial;
char value[PROP_VALUE_MAX];
};

来看看__system_property_find的实现,该函数位于system_properties.c中

[cpp]
view plaincopy

const prop_info *__system_property_find(const char *name)
{
prop_area *pa = __system_property_area__;
unsigned count = pa->count;
unsigned *toc = pa->toc;
unsigned len = strlen(name);
prop_info *pi;

while(count--) {
unsigned entry = *toc++;
if(TOC_NAME_LEN(entry) != len) continue;

pi = TOC_TO_INFO(pa, entry);
if(memcmp(name, pi->name, len)) continue;

return pi;
}

return 0;
}

这个函数就是找出键值对,看看TOC_NAME_LEN和TOC_TO_INFO的定义,

[cpp]
view plaincopy

#define TOC_NAME_LEN(toc) ((toc) >> 24)
#define TOC_TO_INFO(area, toc) ((prop_info*) (((char*) area) + ((toc) & 0xFFFFFF)))

因此toc的高8位保存的是属性名长度,低24位保存属性键值对的地址,

再看__system_property_area__了,这是个全局变量,在system_properties.c的__system_properties_init函数中初始化

该函数读取ANDROID_PROPERTY_WORKSPACE环境变量,格式为:fd,size

然后利用mmap将"fd"处的内容,映射"size"大小,赋给__system_property_area__。

如果匹配成功,看看property_set是怎么做的

[cpp]
view plaincopy

if(pi != 0) {
/* ro.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;

pa = __system_property_area__;
update_prop_info(pi, value, valuelen);
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}

注意pa->serial++,它的修饰符包含一个volatile,这样做是确保每一次针对属性系统的改动都能得到处理。

看看update_prop_info

[cpp]
view plaincopy

static void update_prop_info(prop_info *pi, const char *value, unsigned len)
{
pi->serial = pi->serial | 1;
memcpy(pi->value, value, len + 1);
pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff);
__futex_wake(π->serial, INT32_MAX);
}

首先讲针对该格式的修改序列号+1,然后保存属性值,最后调用__futex_wake触发一个系统调用,在atomics_x86.c中是这样写的

[cpp]
view plaincopy

int __futex_wake(volatile void *ftx, int count)
{
int ret;
asm volatile (
"int $0x80;"
: "=a" (ret)
: "0" (FUTEX_SYSCALL),
"b" (ftx),
"c" (FUTEX_WAKE),
"d" (count)
);
return ret;
}

具体是什么意思待研究。
接下来,就是property_set执行如下语句

[cpp]
view plaincopy

property_changed(name, value);

property_changed在system/core/init/init.c中有定义

[cpp]
view plaincopy

void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled) {
queue_property_triggers(name, value);
drain_action_queue();
}
}

property_triggers_enabled在执行main函数里面设定。

[cpp]
view plaincopy

void queue_property_triggers(const char *name, const char *value)
{
struct listnode *node;
struct action *act;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
if (!strncmp(act->name, "property:", strlen("property:"))) {
const char *test = act->name + strlen("property:");
int name_length = strlen(name);

if (!strncmp(name, test, name_length) &&
test[name_length] == '=' &&
!strcmp(test + name_length + 1, value)) {
action_add_queue_tail(act);
}
}
}
}

这个函数讲action_list中的所有关心该属性的动作都串到act中,action_list应该是在解析初始化脚本文件的时候生成的。

[cpp]
view plaincopy

void drain_action_queue(void)
{
struct listnode *node;
struct command *cmd;
struct action *act;
int ret;

while ((act = action_remove_queue_head())) {
INFO("processing action %p (%s)\n", act, act->name);
list_for_each(node, &act->commands) {
cmd = node_to_item(node, struct command, clist);
ret = cmd->func(cmd->nargs, cmd->args);
INFO("command '%s' r=%d\n", cmd->args[0], ret);
}
}
}

这个函数负责触发各个回调函数。

脚本文件的解析由system/core/init/parser.c完成,来看init.c的main函数有如下语句

[cpp]
view plaincopy

get_hardware_name();
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
parse_config_file(tmp);

在parser.c里面

[cpp]
view plaincopy

int parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;

parse_config(fn, data);
DUMP();
return 0;
}

parse_config_file读入脚本文件,并且进行解析。

-------------------------------------------------------------------------------------------------------------------------------------
http://cd.tarena.com.cn/gpx/201204/2579.html
在Window中有个注册表的东东,可以存储一些类似key:value的键值对,而在android平台上也有类似的机制叫做属性服务(Property service)进行初始化,设置及修改和查询的功能,adb shell命令使用 setprop 及 getprop 可以看到。

问题:

SurfaceFlinger启动后线程调用readyToRun函数时设定有一个属性值:

status_t SurfaceFlinger::readyToRun()

{

LOGI( "SurfaceFlinger's main thread ready to run. "

"Initializing graphics H/W...");

...

/*

* We're now ready to accept clients...

*/

// start boot animation

property_set("ctl.start", "bootanim");

return NO_ERROR;

}

是如何启动bootanim这个服务的呢?bootanim就是开机动画的一个单独进程,在init.rc中以 service bootanim /system/bin/bootanimation 作为一个服务启动,为了使用图形系统功能必须等待SurfaceFlinger启动后才能执行,这里就利用属性服务作为进程同步之用法。

下面我们就这个流程进行一个简单梳理:

一、属性客户端流程

property_set("ctl.start", "bootanim"); 这就是启动触发点!!!

-->

property_set @ /system/core/libcutils/properties.c

int property_set(const char *key, const char *value)

{

msg.cmd = PROP_MSG_SETPROP;

strcpy((char*) msg.name, key);

strcpy((char*) msg.value, value);

return send_prop_msg(&msg);

}

-->

这里就是通过一个普通的TCP(SOCK_STREAM)套接字进行通讯

static int send_prop_msg(prop_msg *msg)

{

s = socket_local_client(PROP_SERVICE_NAME,

ANDROID_SOCKET_NAMESPACE_RESERVED,

SOCK_STREAM);

if(s < 0) return -1;

while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {

if((errno == EINTR) || (errno == EAGAIN)) continue;

break;

}

close(s);

return r;

}

二、服务端是如何监听并实现注程

main @ /system/core/init/init.c

int main(int argc, char **argv)

{

int property_set_fd = -1;

/* read any property files on system or data and

* fire up the property service. This must happen

* after the ro.foo properties are set above so

* that /data/local.prop cannot interfere with them.

*/

property_set_fd = start_property_service();

// 将 property_set_fd 设定到poll监听队列

ufds[0].fd = device_fd;

ufds[0].events = POLLIN;

ufds[1].fd = property_set_fd;

ufds[1].events = POLLIN;

for(;;) {

...

nr = poll(ufds, fd_count, timeout);

// 监听到有属性服务请求需要处理

if (ufds[1].revents == POLLIN)

handle_property_set_fd(property_set_fd);

...

}

return 0;

}

先看一下 start_property_service 如何实现的?

int start_property_service(void)

{

int fd;

load_properties_from_file(PROP_PATH_SYSTEM_BUILD);

load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);

load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);

/* Read persistent properties after all default values have been loaded. */

load_persistent_properties();

fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);

if(fd < 0) return -1;

fcntl(fd, F_SETFD, FD_CLOEXEC);

fcntl(fd, F_SETFL, O_NONBLOCK);

listen(fd, 8);

return fd;

}

ok,明白了吧,创建了一个SOCK_STREAM套接字并进入监听listen状态

handle_property_set_fd @ /system/core/init/property_service.c

void handle_property_set_fd(int fd)

{

//1、接收socket请求连接

if ((s = accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {

return;

}

//2、收取属性请求数

r = recv(s, &msg, sizeof(msg), 0);

close(s);

//3、处理属情请求数据【 】

switch(msg.cmd) {

case PROP_MSG_SETPROP:

...

if(memcmp(msg.name,"ctl.",4) == 0) {

if (check_control_perms(msg.value, cr.uid, cr.gid)) {

handle_control_message((char*) msg.name + 4, (char*) msg.value);

}

}else {

if (check_perms(msg.name, cr.uid, cr.gid)) {

property_set((char*) msg.name, (char*) msg.value);

}

}

}

}

由于请求消息是:ctl.start 则执行 handle_control_message 这个函数:

handle_control_message @ /system/core/init/init.c

void handle_control_message(const char *msg, const char *arg)

{

if (!strcmp(msg,"start")) {

msg_start(arg);

} else if (!strcmp(msg,"stop")) {

msg_stop(arg);

} else {

ERROR("unknown control msg '%s'\n", msg);

}

}

static void msg_start(const char *name)

{

svc = service_find_by_name(name);

...

service_start(svc, args);

}

static void msg_stop(const char *name)

{

struct service *svc = service_find_by_name(name);

service_stop(svc);

}

看下上面的代码大家应该明白了吧,就是请求ServiceManager服务进行启动或停止某个服务,这里就是那是 bootanim 服务了。

还有一点为何 bootanim 在init.rc 脚本中没有开机就启动呢?请见 init.rc 脚本:

service bootanim /system/bin/bootanimation

user graphics

group graphics

disabled

oneshot

看到 disabled 没有,这个关键字是在添加到 service_list 双键表时使用:

#define SVC_DISABLED 0x01 /* do not autostart with class */

#define SVC_ONESHOT 0x02 /* do not restart on exit */

#define SVC_RUNNING 0x04 /* currently active */

#define SVC_RESTARTING 0x08 /* waiting to restart */

#define SVC_CONSOLE 0x10 /* requires console */

#define SVC_CRITICAL 0x20 /* will reboot into recovery if keeps crashing */

static void parse_line_service(struct parse_state *state, int nargs, char **args)

{

kw = lookup_keyword(args[0]);

switch (kw) {

case K_disabled:

svc->flags |= SVC_DISABLED;

break;

...

}

而在执行 service_start 及 service_stop 时都会判定这个 flags 值:

void service_stop(struct service *svc)

{

/* if the service has not yet started, prevent

* it from auto-starting with its class

*/

svc->flags |= SVC_DISABLED;

...

}

初始启动Service流程:

int do_class_start(int nargs, char **args)

{

/* Starting a class does not start services

* which are explicitly disabled. They must

* be started individually.

*/

service_for_each_class(args[1], service_start_if_not_disabled);

return 0;

}

static void service_start_if_not_disabled(struct service *svc)

{

if (!(svc->flags & SVC_DISABLED)) {

service_start(svc, NULL);

}

}

这里会决定这个 Service 是否初始开机启机,通过这个 SVC_DISABLED flag即可判定。

还有一个补充说明一下: 】

ctr.start和ctr.stop系统属性?

每一项服务必须在/init.rc中定义.Android系统启动时,init守护进程将解析init.rc和启动属性服务,属性“ ctl.start ”和“ ctl.stop ”是用来启动和停止服务的。一旦收到设置“ ctrl.start ”属性的请求,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入“ init.svc.<服务名>“属性中 。客户端应用程序可以轮询那个属性值,以确定结果。

基本常用代码写法:

static const char DAEMON_NAME[] = "dhcpcd";

static const char DAEMON_PROP_NAME[] = "init.svc.dhcpcd";

int dhcp_stop(const char *interface)

{

char result_prop_name[PROPERTY_KEY_MAX];

const char *ctrl_prop = "ctl.stop";

const char *desired_status = "stopped";

...

/* Stop the daemon and wait until it's reported to be stopped */

property_set(ctrl_prop, DAEMON_NAME);

if (wait_for_property(DAEMON_PROP_NAME, desired_status, 5) < 0) {

return -1;

}

}

这里对于 wait_for_property 的状态信息说明一下:

请注意执行 service_start 函数最后会执行如下语句:

void service_start(struct service *svc, const char *dynamic_args)

{

...

notify_service_state(svc->name, "running");

}

static void notify_service_state(const char *name, const char *state)

{

char pname[PROP_NAME_MAX];

int len = strlen(name);

if ((len + 10) > PROP_NAME_MAX)

return;

// 通过这里组成 init.svc.xxx 属性名称,并调用 property_set 设定其状态完成

snprintf(pname, sizeof(pname), "init.svc.%s", name);

property_set(pname, state);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: