Android SystemProperties系统属性详解
16lz
2021-01-23
Systemproperties类在android.os下,但这个类是隐藏的,上层程序开发无法直接使用,用Java的反射机制就可以了。
Java代码中创建与修改android属性用Systemproperties.set(name, value),获取android属性用Systemproperties.get(name),
Native代码中通过property_get(const char *key, char *value, const char *default_value)/property_set(const char *name, const char *value)来读取和设置属性。需要注意的是android属性的名称是有一定的格式要求的,前缀必须用system\core\init\property_service.c中定义的前缀(后面会详细列出),进行系统属性设置的程序也必须有system或root权限,如何将android程序的权限提升到system权限呢?方法是这个样子滴:
1、在AndroidManifest.xml中,加入android:sharedUserId="android.uid.system"。
2、在Android.mk中,设置LOCAL_CERTIFICATE := platform。
注意:使用property_get/property_set这两个API必须要包含头文件cutils/properties.h和链接libcutils库。
Shell脚本中Android提供了命令行工具setprop和getprop来设置和获取属性。用法举例:getprop sys.settingkeys.disabled,setprop sys.settingkeys.disabled 1
用法就讲完了,怎么样,很简单吧?下面我们具体来看一下读取和设置系统属性的流程:
读取属性:
native_get()[SystemProperties.java]
property_get()[android_os_SystemProperties.cpp]-->__system_property_read()[system_properties.c]
\bionic\libc\bionic\system_properties.c中:
native_set()[SystemProperties.java]
__system_property_set()[system_properties.c ]-->send_prop_msg()[system_properties.c ]
服务器端:
\system\core\init\Init.c
bionic/libc/include/sys/_system_properties.h
1、property_init
初始化共享内存空间,即:将文件(/dev/__properties__)映射为共享进程空间内存,使其可以与操作内存方式一致,将__system_property_area__指向系统属性共享内存区域,每个进程都会使用此变量。
加载属性配置文件(/default.prop),并写入到共享内存中。
2、start_property_service
加载属性配置文件,加载的属性将覆盖原先的值,这些属性加载之后,最后加载的属性会被保持在/data/property中,还会写到共享内存中。
顺序是:
/system/build.prop
/system/default.prop
/data/local.prop
接着创建socket property_service,并且监听客户端的消息。
3、handle_property_set_fd
main()里面有个死循环,不停的去取出消息,然后使用handle_property_set_fd处理,该函数做的事情是:如果接收到PROP_MSG_SETPROP消息,
如果是以ctl.开头消息,那么就是用来启动和关闭service的,关闭/启动服务是在新的进程中进行的,会把共享内存的fd加入到环境变量中。
例如:
// start boot animation
property_set("ctl.start", "bootanim");
当然在init.rc中表明服务是否在开机时启动也是可以的,如:
service adbd /sbin/adbd
class core
disabled //不自动启动
如果不是以ctl.开头的消息,那就是设置系统属性值了。
为了方便理解,上一张图片:
(图片来自网络)
从图中我们可以看出Android属性系统由有三个进程,一组属性文件和一块共享内存组成。这块共享内存保存着系统中所有的属性记录,只有Property service能写这块共享内存,并且Property service负责将属性文件中的属性记录加载到共享内存中。
属性读取进程(property consumer)把这块共享内存映射到自己的进程空间,然后直接读取它。
属性设置进程(property setter)也加载这块共享到他的进程空间,但是他不能直接写这块共享内存。当他需要增加或者修改属性的时候,通过Unix Socket发生属性给Property service,Property service将代表设置进程写入共享内存和属性文件。
Property service运行于init进程中。init进程首先创建一块共享内存,并把他的句柄fd存放在这块内存中,init进程通过mmap带MAP_SHARE标志的系统调用,把这块内存映射到他的虚拟空间中,最终这块内存所有的更新将会被所有映射这块共享内存的进程看到。共享内存句柄fd和共享内存大小存储在系统环境变量“ANDROID_PROPERTY_WORKSPACE”中,所有的进程包括属性设置进程和属性读取进程都将通过这个系统环境变量获得共享内存的句柄fd和大小,然后把这块内存映射到他们自己的虚拟空间。然后,init进程将会从以下文件中加载属性:
/default.prop
/system/build.prop
/system/default.prop
/data/local.prop
下一步是启动Property service。这步中,将会创建一个Unix Socket服务器,这个Socket有一个闻名的名称“/dev/socket/property_service”。最后init进入死循环,等待socket的连接请求。
在读取进程中,当它初始化libc库的时候,将会获得属性系统共享内存的句柄和大小(bionic/libc/bionic/libc_init_common.c __libc_init_common函数)。并把这块共享内存映射到自己的进程虚拟空间中(bionic/libc/bionic/system_properties.c __system_properties_init函数)。这样读取进程将会向访问普通内存一样访问属性系统的共享内存了。
最后,来一个扩展:
如果属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。
如果属性名称以“persist.”开头,当设置这个属性时,其值也将写入/data/property 。
如果属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自动设置,以加入到最后修改的属性名。(这是很巧妙的。 netresolve模块的使用这个属性来追踪在net.*属性上的任何变化。)
属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。
参考文档:
http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html
http://blog.csdn.net/ameyume/article/details/8056492
Java代码中创建与修改android属性用Systemproperties.set(name, value),获取android属性用Systemproperties.get(name),
Native代码中通过property_get(const char *key, char *value, const char *default_value)/property_set(const char *name, const char *value)来读取和设置属性。需要注意的是android属性的名称是有一定的格式要求的,前缀必须用system\core\init\property_service.c中定义的前缀(后面会详细列出),进行系统属性设置的程序也必须有system或root权限,如何将android程序的权限提升到system权限呢?方法是这个样子滴:
1、在AndroidManifest.xml中,加入android:sharedUserId="android.uid.system"。
2、在Android.mk中,设置LOCAL_CERTIFICATE := platform。
注意:使用property_get/property_set这两个API必须要包含头文件cutils/properties.h和链接libcutils库。
Shell脚本中Android提供了命令行工具setprop和getprop来设置和获取属性。用法举例:getprop sys.settingkeys.disabled,setprop sys.settingkeys.disabled 1
用法就讲完了,怎么样,很简单吧?下面我们具体来看一下读取和设置系统属性的流程:
读取属性:
native_get()[SystemProperties.java]
property_get()[android_os_SystemProperties.cpp]-->__system_property_read()[system_properties.c]
\bionic\libc\bionic\system_properties.c中:
int __system_property_get(const char *name, char *value){ //数据已经存储在共享内存中,通过__system_property_area__ 即可获取到,之后等待读取完返回 const prop_info *pi = __system_property_find(name); return __system_property_read(pi, 0, value);//阻塞式读取}
设置属性(客户端): native_set()[SystemProperties.java]
__system_property_set()[system_properties.c ]-->send_prop_msg()[system_properties.c ]
int __system_property_set(const char *key, const char *value){ msg.cmd = PROP_MSG_SETPROP; strlcpy(msg.name, key, sizeof msg.name); strlcpy(msg.value, value, sizeof msg.value); err = send_prop_msg(&msg);}//发送消息通知property_service去启动服务或者设置属性static int send_prop_msg(prop_msg *msg){ //与/dev/socket/property_service通信 s = socket(AF_LOCAL, SOCK_STREAM, 0); connect(s, (struct sockaddr *) &addr, alen) send(s, msg, sizeof(prop_msg), 0) close(s);}
可以看出读取属性比较简单,就是通过__system_property_read直接读取共享内存中的数据即可,而设置属性则是通过客户端发送socket消息让property_service去启动服务或者设置属性。 服务器端:
\system\core\init\Init.c
int main(int argc, char **argv){ //加入到action queue队列 queue_builtin_action(property_service_init_action, "property_service_init");...//将属性系统初始化函数加入action queue queue_builtin_action(property_init_action, "property_init");... for(;;) //执行action queue队列 //接收通过socket向property service发送的数据 nr = poll(ufds, fd_count, timeout); …… handle_property_set_fd();}static int property_service_init_action(int nargs, char **args){ start_property_service();}void property_changed(const char *name, const char *value){ if (property_triggers_enabled) { queue_property_triggers(name, value); drain_action_queue(); }}
\system\core\init\property_service.c: void property_init(bool load_defaults){ //初始化共享内存空间 init_property_area(); //加载属性文件 load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);}
\bionic\libc\bionic\system_properties.c
static int init_property_area(void){ //创建匿名内存空间PA_SIZE = 32768 init_workspace(&pa_workspace, PA_SIZE) //将内存区域分成两部分:属性系统基本信息和属性键值对 pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); //初始化属性系统信息 pa = pa_workspace.data; memset(pa, 0, PA_SIZE); pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* plug into the lib property services 每个进程都会使用此变量,指向系统属性共享内存区域,访问系统属性,很重要*/ __system_property_area__ = pa;}static int init_workspace(workspace *w, size_t size){ //dev is a tmpfs是一种虚拟内存文件系统 int fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600); //将文件映射为共享进程空间内存 使其可以与操作内存方式一致 void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); //删除文件 fd = open("/dev/__properties__", O_RDONLY); unlink("/dev/__properties__"); //保存fd size 将作为环境变量传递给每个进程 w->data = data; w->size = size; w->fd = fd;}static void load_properties_from_file(const char *fn){ //读取系统属性键值对数据写入到共享内存中 data = read_file(fn, &sz); load_properties(data);}void start_property_service(void){ //加载属性配置文件,加载的属性将覆盖原先的值。这些属性加载之后,最后加载的属性会被保持在/data/property中。 load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE); load_persistent_properties(); //创建socket资源 并绑定 fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); //监听 listen(fd, 8);}void handle_property_set_fd(){ //等待建立通信 s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size); //获取套接字相关信息 uid gid getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size); //接收属性设置请求消息 recv(s, &msg, sizeof(msg), 0); //处理消息 switch(msg.cmd) { case PROP_MSG_SETPROP: //通过设置系统属性 处理ctl.开头消息 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); } } break; } close(s);}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 if (!strcmp(msg,"restart")) { msg_stop(arg); msg_start(arg); } }static void msg_start(const char *name){ service_start(svc, args);}void service_start(struct service *svc, const char *dynamic_args){ //创建进程 pid = fork(); if (pid == 0) { if (properties_inited()) { //获取系统属性空间文件描述 get_property_workspace(&fd, &sz); //dup最小的可用文件描述符 sprintf(tmp, "%d,%d", dup(fd), sz); //加入ANDROID_PROPERTY_WORKSPACE环境变量到ENV //包含共享内存fd add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); } //执行程序 传递环境变量ENV execve(svc->args[0], (char**) svc->args, (char**) ENV) //设置Service系统属性 notify_service_state(svc->name, "running"); }}void get_property_workspace(int *fd, int *sz){ *fd = pa_workspace.fd; *sz = pa_workspace.size;}
在bionic\libc\bionic\libc_init_dynamic.c中://将系统属性内存空间映射到当前进程虚拟空间,进程在启动时,会加载动态库bionic libc库:void __attribute__((constructor)) __libc_preinit(void); void __libc_preinit(void){ __libc_init_common(elfdata);}void __libc_init_common(uintptr_t *elfdata){ __system_properties_init();}int __system_properties_init(void){ prop_area *pa; int s, fd; unsigned sz; char *env; //获取环境变量ANDROID_PROPERTY_WORKSPACE //与上面init进程中设置对应 env = getenv("ANDROID_PROPERTY_WORKSPACE"); //共享内存文件描述符 内存大小 fd = atoi(env); sz = atoi(env + 1); //将文件描述符映射到当前进程虚拟空间内存,实现共享内存 pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0); //全局变量指向共享系统属性内存首地址 __system_property_area__ = pa;}
static int check_perms(const char *name, unsigned int uid, unsigned int gid){ //进行权限检测 for (i = 0; property_perms[i].prefix; i++) { int tmp; if (strncmp(property_perms[i].prefix, name, strlen(property_perms[i].prefix)) == 0) { if ((uid && property_perms[i].uid == uid) || (gid && property_perms[i].gid == gid)) { return 1; } } } return 0;}property_perms[] = { { "net.rmnet0.", AID_RADIO, 0 }, { "net.gprs.", AID_RADIO, 0 }, { "net.ppp", AID_RADIO, 0 }, { "ril.", AID_RADIO, 0 }, { "gsm.", AID_RADIO, 0 }, { "persist.radio", AID_RADIO, 0 }, { "net.dns", AID_RADIO, 0 }, { "net.", AID_SYSTEM, 0 }, { "dev.", AID_SYSTEM, 0 }, { "runtime.", AID_SYSTEM, 0 }, { "hw.", AID_SYSTEM, 0 }, { "sys.", AID_SYSTEM, 0 }, { "service.", AID_SYSTEM, 0 }, { "wlan.", AID_SYSTEM, 0 }, { "dhcp.", AID_SYSTEM, 0 }, { "dhcp.", AID_DHCP, 0 }, { ".", AID_SYSTEM, 0 }, { ".", AID_VPN, 0 }, { "debug.", AID_SHELL, 0 }, { "log.", AID_SHELL, 0 }, { "service.adb.root", AID_SHELL, 0 }, { "persist.sys.", AID_SYSTEM, 0 }, { "persist.service.", AID_SYSTEM, 0 }, { "persist.security.", AID_SYSTEM, 0 }, { NULL, 0, 0 }};int property_set(const char *name, const char *value){property_changed(name, value);return 0;}
以上使用到的相关的宏定义在这里: bionic/libc/include/sys/_system_properties.h
#define PROP_SERVICE_NAME "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"
还有这里 system/core/include/private/android_filesystem_config.h
#define AID_SYSTEM 1000 /* system server */#define AID_RADIO 1001 /* telephony subsystem, RIL */#define AID_DHCP 1014 /* dhcp client */#define AID_VPN 1016 /* system */#define AID_SHELL 2000 /* adb and debug shell user */
代码有点长,具体来解释下这段代码都做了神马~
Init main()1、property_init
初始化共享内存空间,即:将文件(/dev/__properties__)映射为共享进程空间内存,使其可以与操作内存方式一致,将__system_property_area__指向系统属性共享内存区域,每个进程都会使用此变量。
加载属性配置文件(/default.prop),并写入到共享内存中。
2、start_property_service
加载属性配置文件,加载的属性将覆盖原先的值,这些属性加载之后,最后加载的属性会被保持在/data/property中,还会写到共享内存中。
顺序是:
/system/build.prop
/system/default.prop
/data/local.prop
接着创建socket property_service,并且监听客户端的消息。
3、handle_property_set_fd
main()里面有个死循环,不停的去取出消息,然后使用handle_property_set_fd处理,该函数做的事情是:如果接收到PROP_MSG_SETPROP消息,
如果是以ctl.开头消息,那么就是用来启动和关闭service的,关闭/启动服务是在新的进程中进行的,会把共享内存的fd加入到环境变量中。
例如:
// start boot animation
property_set("ctl.start", "bootanim");
当然在init.rc中表明服务是否在开机时启动也是可以的,如:
service adbd /sbin/adbd
class core
disabled //不自动启动
如果不是以ctl.开头的消息,那就是设置系统属性值了。
为了方便理解,上一张图片:
(图片来自网络)
从图中我们可以看出Android属性系统由有三个进程,一组属性文件和一块共享内存组成。这块共享内存保存着系统中所有的属性记录,只有Property service能写这块共享内存,并且Property service负责将属性文件中的属性记录加载到共享内存中。
属性读取进程(property consumer)把这块共享内存映射到自己的进程空间,然后直接读取它。
属性设置进程(property setter)也加载这块共享到他的进程空间,但是他不能直接写这块共享内存。当他需要增加或者修改属性的时候,通过Unix Socket发生属性给Property service,Property service将代表设置进程写入共享内存和属性文件。
Property service运行于init进程中。init进程首先创建一块共享内存,并把他的句柄fd存放在这块内存中,init进程通过mmap带MAP_SHARE标志的系统调用,把这块内存映射到他的虚拟空间中,最终这块内存所有的更新将会被所有映射这块共享内存的进程看到。共享内存句柄fd和共享内存大小存储在系统环境变量“ANDROID_PROPERTY_WORKSPACE”中,所有的进程包括属性设置进程和属性读取进程都将通过这个系统环境变量获得共享内存的句柄fd和大小,然后把这块内存映射到他们自己的虚拟空间。然后,init进程将会从以下文件中加载属性:
/default.prop
/system/build.prop
/system/default.prop
/data/local.prop
下一步是启动Property service。这步中,将会创建一个Unix Socket服务器,这个Socket有一个闻名的名称“/dev/socket/property_service”。最后init进入死循环,等待socket的连接请求。
在读取进程中,当它初始化libc库的时候,将会获得属性系统共享内存的句柄和大小(bionic/libc/bionic/libc_init_common.c __libc_init_common函数)。并把这块共享内存映射到自己的进程虚拟空间中(bionic/libc/bionic/system_properties.c __system_properties_init函数)。这样读取进程将会向访问普通内存一样访问属性系统的共享内存了。
最后,来一个扩展:
如果属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。
如果属性名称以“persist.”开头,当设置这个属性时,其值也将写入/data/property 。
如果属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自动设置,以加入到最后修改的属性名。(这是很巧妙的。 netresolve模块的使用这个属性来追踪在net.*属性上的任何变化。)
属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。
参考文档:
http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html
http://blog.csdn.net/ameyume/article/details/8056492
更多相关文章
- android RelativeLayout 适用场景和属性
- android加载字体内存泄漏的处理方法
- Android 禁止带有home属性的APP安装
- 布局动画 属性初始 android:animateLayoutChanges
- Android进程保活(常驻内存)
- android init进程解析init.rc过程