属性系统是android的一个重要特性。它作为一个服务运行,管理系统配置和状态。每个属性是一个键值对(key/value pair),其类型都是字符串。属性被大量使用在Android系统中,用来记录系统设置或进程之间的信息交换。属性是在整个系统中全局可见的。每个进程可以get/set属性。
在系统初始化时,Android将分配一个共享内存区来存储的属性。这些是由“init”守护进程完成的,其源代码位于:device/system/init。“init”守护进程将启动一个属性服务。属性服务在“init”守护进程中运行。每一个客户端想要设置属性时,必须连接属性服务,再向其发送信息。属性服务将会在共享内存区中修改和创建属性。任何客户端想获得属性信息,可以从共享内存直接读取。这提高了读取性能。
属性系统的上层架构如下图所示
图中有3个进程、一组永久属性文件和一块共享内存区域。共享内存区域是所有属性记录的存储所在。只有property服务进程才可以写入共享内存区域,它负责从永久文件中加载属性记录并将它们保存在共享内存中。
consumer进程将共享内存加载到其自身的虚拟地址空间并直接访问这些属性。setter进程同样将共享内存加载到其自身的虚拟地址空间,但其不能直接写该内存。当setter试图增加或者更新一个属性时,它将该属性通过unix domain socket发送至property服务。property服务代表setter进程将该属性写入共享内存和永久文件中。
property服务运行于init进程中。init进程首先创建一个共享内存区域,并保存一个指向该区域的描述符fd。init进程将该区域通过使用了MAP_SHARED标志的mmap映射至它自身的虚拟地址空间,这样,任何对于该区域的更新对于所有进程都是可见的。fd和区域大小被存储在一个名为ANDROID_PROPERTY_WORKSPACE的变量中。任何其他进程,比如consumer和setter将使用这个变量来获得fd和尺寸,这样它们就能mmap这个区域到它们自身的虚拟地址空间中。该共享内存区域如下图所示。
当启动属性服务时,将从以下文件中加载默认属性:
/ default.prop
/system/build.prop
/system/default.prop
/data/local.prop
属性将会以上述顺序加载。后加载的属性将覆盖原先的值。
启动property服务。在这一步中,一个unix domain socket服务被创建。此socket的路径是/dev/socket/property_service,该路径对于其他客户端进程是熟知的。最后,init进程调用poll来等待该socket上的连接事件。在consumer一边,当它初始化libc(bionic/libc/bionic/libc_common.c __libc_init_common函数),它将从环境变量中返回fd和尺寸,并映射共享内存到其自身的地址空间(bionic/libc/bionic/system_properties.c __system_properties_init函数)。在这之后,libcutils可以想读取普通内存那样为consumer读取属性。目前,属性是不能够被删除的。也就是说,一旦添加了一个属性,它将不能够被删除,其键也不能够被改变。
几种特殊的属性:
1.ro.属性,它表示只读属性,它一旦被设置就不能被修改;
2.net.属性,顾名思义,就是与网络相关的属性,net.属性中有一个特殊的属性:net.change,它记录了每一次最新设置和更新的net.属性,也就是每次设置和更新net.属性时则会自动的更新net.change属性,net.change属性的value就是这个被设置或者更新的net属性的name。例如我们更新了属性net.bt.name的值,由于net有属性发生了变化,那么属性服务就会自动更新net.change,将其值设置为net.bt.name。
3.persist.属性,以文件的形式保存在/data/property路径下。persist.属性由于将其保存在了用户空间中,所以在property_init中是不能对其更新的,只能将其更新过程交给用户来处理。
4.ctl.属性,虽然是以属性的形式来进行设置,其实它的目的是为了启动或关闭它指定的service
属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。每一项服务必须在/init.rc中定义.系统启动时,init守护进程将解析init.rc和启动属性服务。一旦收到设置“ ctrl.start ”属性的请求,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入“ init.svc.<服务名>“属性中 。客户端应用程序可以轮询那个属性值,以确定结果。
当编写本地应用程序时,可以使用property_get和property_set这两个API来读取/设置属性。要使用它们,我们需要include cutils/properties.h,并链接libcutils库。
int property_get(const char *key, char *value, const char *default_value);
int property_set(const char *key, const char *value);
默认情况下,设置属性只会使"init"守护程序写入共享内存,它不会执行任何脚本或二进制程序。但是,您可以将您的想要的实现的操作与init.rc中某个属性的变化相关联.例如,在默认的init.rc中有:
# adbd on at boot in emulator在Java包中提供有System.getProperty和System.setProperty方法。但值得注意的是,尽管这两个API在语义上等同native函数,但其将数据存储于完全不同的位置。实际上,dalvik VM使用一个哈希表来存储这些属性。所以,用这两个API存储的属性是独立的,不能存取native属性,反之亦然。
然而Android有一个内部隐藏类(@hide,对SDK不可见)android.os.SystemProperties来操纵native属性。其通过jni来存取native属性库。
Android toolbox程序提供了两个工具: setprop和getprop获取和设置属性。其使用方法:
getprop <属性名>
setprop <属性名><<属性值>
可以通过命令adb shell :
getprop查看手机上所有属性状态值。
或者 getprop init.svc.bootanim制定查看某个属性状态
使用setprop init.svc.bootanim start 设置某个属性的状态
property_service.c:
1)PA_COUNT_MAX指定了系统(共享内存区域中)最多能存储多少个属性。
2)PROP_NAME_MAX指定了一个属性的key最大允许长度;
3)PROP_VALUE_MAX则指定了value的最大允许长度。
int main(int argc, char **argv)
{
.............
//初始化属性的存储空间
property_init();
.............
is_charger = !strcmp(bootmode, "charger");
//如果是充电模式,则加载/default.prop属性文件
INFO("property init\n");
if (!is_charger)
property_load_boot_defaults();
//属性初始化
queue_builtin_action(property_service_init_action, "property_service_init");
/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
for(;;) {
int nr, i, timeout = -1;
execute_one_command();
restart_processes();
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
.................
//轮训等待
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
//处理属性事件
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
.............
}
}
}
return 0;
}
Android 属性系统的整个流程:
property_init()函数用来初始化属性域,首先在内存中开辟一块共享区域,而后将其用着匿名共享内存,外部进程可以访问这块共享内存域,获取属性值,但它们不能通过直接访问共享内存域的方式来更改属性值。一个进程若想更改属性值,必须先向init进程提交属性变更请求,由init进程更改共享内存中的属性值。
void property_init(void)
{
init_property_area(); //初始化属性存储区域
}
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
//初始化工作空间,大小为32768
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
//设置工作空间句柄属性值
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
//设置pa_info_array的值为属性工作空间的数据区地址加偏移1024
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
//设置属性区域为工作空间的数据区
pa = pa_workspace.data;
//设置属性区域的值为0
memset(pa, 0, PA_SIZE);
//设置属性的幻数
pa->magic = PROP_AREA_MAGIC;
//设置属性的版本
pa->version = PROP_AREA_VERSION;
//将全局变量__system_property_area__指向属性空间
__system_property_area__ = pa;
property_area_inited = 1;
return 0;
}
属性共享内存区初始化
static int init_workspace(workspace *w, size_t size)
{
void *data;
int fd;
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
//打开"/dev/__properties__"设备文件
fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
if (fd < 0)
return -1;
//设置"/dev/__properties__"设备文件大小
if (ftruncate(fd, size) < 0)
goto out;
//将设备文件映射到共享内存空间
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED)
goto out;
close(fd);
fd = open("/dev/__properties__", O_RDONLY);
if (fd < 0)
return -1;
//删除"/dev/__properties__"设备文件
unlink("/dev/__properties__");
w->data = data;
w->size = size;
w->fd = fd;
return 0;
out:
close(fd);
return -1;
}
属性域的起始1024字节作为属性域头,用来保存管理属性表所需要的一些值,其余的31616个字节空间被划分成247块,每块大小为128字节,用于保存属性值。在属性域完成初始化之后,就会从指定的文件中读取初始化值,并设置属性值。
queue_builtin_action(property_service_init_action, "property_service_init");
static int property_service_init_action(int nargs, char **args)
{
//必须在ro.foo属性被设置后,读取属性文件并启动属性服务
start_property_service();
return 0;
}
start_property_service()函数在创建套接字之前,先读取存储在各个属性文件中的属性,并设置属性值。当所有系统初始属性值设置完毕后,load_persistent_properties()函数开始读取保存在/data/property目录下的属性值。该目录中保存着系统运行中其他进程新生成的属性值或更改的属性值,属性的key用作文件名,value保存在文件中。在属性初始值设置完毕后,就会创建名为/dev/socket/property_service的套接字。
void start_property_service(void)
{
int fd;
//加载"/system/build.prop"属性文件
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
//加载"/system/default.prop"属性文件
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
#ifdef ALLOW_LOCAL_PROP_OVERRIDE
//加载 "/data/local.prop"属性文件
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
#endif
/* 读取持久属性 */
load_persistent_properties();
//创建名为"property_service"的socket
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd < 0) return;
//设置socket的句柄属性
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
//启动socket监听
listen(fd, 8);
//设置property_set_fd为监听socket的句柄值
property_set_fd = fd;
}
初始化系统属性的工作主要是从属性文件中读取系统属性到属性存储空间,因为属性存储空间是一块匿名共享内存,所有的进程都可以访问,这样就加快了属性的读取速度;同时创建并启动属性服务端socket;从属性文件中读取系统属性的实现如下:
static void load_properties_from_file(const char *fn)
{
char *data;
unsigned sz;
//读取文件,返回文件内容
data = read_file(fn, &sz);
if(data != 0) {
//解析文件内容,并设置相应属性
load_properties(data);
free(data);
}
}
属性文件的部分内容如下:
ro.com.android.dataroaming=false
persist.msms.phone_count=2
persist.blcr.enable=0
persist.msms.phone_default=0
dalvik.vm.heapstartsize=5m
dalvik.vm.heapgrowthlimit=64m
属性文件内容解析:
static void load_properties(char *data)
{
char *key, *value, *eol, *sol, *tmp;
sol = data;
//解析属性内容,
while((eol = strchr(sol, '\n'))) {
key = sol;
*eol++ = 0;
sol = eol;
value = strchr(key, '=');
if(value == 0) continue;
*value++ = 0;
while(isspace(*key)) key++;
if(*key == '#') continue;
tmp = value - 2;
while((tmp > key) && isspace(*tmp)) *tmp-- = 0;
while(isspace(*value)) value++;
tmp = eol - 2;
while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
//属性设置
property_set(key, value);
}
}
最后调用property_set函数来设置解析得到的系统属性,属性的设置过程在接下来将进行详细分析!
Init进程通过poll系统调用来监控ufds句柄池中的事件,当某一进程需要设置某一属性时,将通过socket向Init进程发起请求,请求过程在接下来分析,当Init接收到请求时,poll系统调用返回,并根据传过来的系统属性及属性值设置相应的属性,设置过程如下:
void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
int res;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
//接收客户端的socket连接
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* 检查socket权限属性 */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to recieve socket options\n");
return;
}
//从socket中接收来之客户端的消息
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size recieved: %d expected: %d errno: %d\n",r, sizeof(prop_msg), errno);
close(s);
return;
}
//根据消息命令码区分消息
switch(msg.cmd) {
//设置属性
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
//判断消息名称是否以"ctl."开头
if(memcmp(msg.name,"ctl.",4) == 0) {
//关闭socket
close(s);
//根据msg.value查找对应的service,并检查启动权限
if (check_control_perms(msg.value, cr.uid, cr.gid)) {
//控制对应服务的运行状态
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
//检查判断属性的权限
if (check_perms(msg.name, cr.uid, cr.gid)) {
//设置属性
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",cr.uid, msg.name);
}
//关闭socket
close(s);
}
break;
default:
//关闭socket
close(s);
break;
}
}
static int check_control_perms(const char *name, unsigned int uid, unsigned int gid) {
int i;
//system /root用户直接有权限
if (uid == AID_SYSTEM || uid == AID_ROOT)
return 1;
//查询用户名单,判断是否存在表中并具有对应权限
for (i = 0; control_perms[i].service; i++) {
if (strcmp(control_perms[i].service, name) == 0) {
if ((uid && control_perms[i].uid == uid) ||
(gid && control_perms[i].gid == gid)) {
return 1;
}
}
}
return 0;
}
需要指定权限的服务存放在数组control_perms中:
control_perms[] = {
{ "dumpstate",AID_SHELL, AID_LOG },
{ "ril-daemon",AID_RADIO, AID_RADIO },
{NULL, 0, 0 }
};
如果想要应用有权限启动/关闭某Native Service:需要具有system/root权限
比如要停止zygote进程,可以使用property_set("ctl.stop", "zygote")来实现,进程运行状态是通过handle_control_message来控制的:
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);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}
对于进程的启动,使用msg_start来完成:
static void msg_start(const char *name)
{
struct service *svc;
char *tmp = NULL;
char *args = NULL;
//是否包含':'
if (!strchr(name, ':'))
//根据服务名查找服务
svc = service_find_by_name(name);
else {
tmp = strdup(name);
args = strchr(tmp, ':');
*args = '\0';
args++;
svc = service_find_by_name(tmp);
}
if (svc) {
//启动服务
service_start(svc, args);
} else {
ERROR("no such service '%s'\n", name);
}
if (tmp)
free(tmp);
}
首先根据服务名称从service_list链表中查找对应的service,然后通过调用service_start函数来启动:
void service_start(struct service *svc, const char *dynamic_args)
{
struct stat s;
pid_t pid;
int needs_console;
int n;
/* starting a service removes it from the disabled or reset
* state and immediately takes it out of the restarting
* state if it was in there
*/
svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET));
svc->time_started = 0;
/* running processes require no additional work -- if
* they're in the process of exiting, we've ensured
* that they will immediately restart on exit, unless
* they are ONESHOT
*/
if (svc->flags & SVC_RUNNING) {
return;
}
needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
if (needs_console && (!have_console)) {
ERROR("service '%s' requires console\n", svc->name);
svc->flags |= SVC_DISABLED;
return;
}
if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) {
ERROR("service '%s' must be one-shot to use dynamic args, disabling\n",
svc->args[0]);
svc->flags |= SVC_DISABLED;
return;
}
NOTICE("starting '%s'\n", svc->name);
//fork出新的进程
pid = fork();
//新进程执行服务内容
if (pid == 0) {
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
umask(077);
//判断属性系统是否初始化
if (properties_inited()) {
//获取属性空间的句柄和大小
get_property_workspace(&fd, &sz);
//将属性空间的句柄和大小格式化成字符串并添加到环境变量ANDROID_PROPERTY_WORKSPACE
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
//添加其他环境变量
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
//如果服务支持socket通信,创建socket
for (si = svc->sockets; si; si = si->next) {
int socket_type = (!strcmp(si->type, "stream") ? SOCK_STREAM :(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
//创建socket
int s = create_socket(si->name, socket_type,si->perm, si->uid, si->gid);
//将创建的socket句柄以名为ANDROID_SOCKET_xxx添加到环境变量中
if (s >= 0) {
publish_socket(si->name, s);
}
}
if (svc->ioprio_class != IoSchedClass_NONE) {
if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) {
ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));
}
}
//如果需要控制台,打开控制台
if (needs_console) {
setsid();
open_console();
} else {
zap_stdio();
}
setpgid(0, getpid());
/* as requested, set our gid, supplemental gids, and uid */
if (svc->gid) {
if (setgid(svc->gid) != 0) {
ERROR("setgid failed: %s\n", strerror(errno));
_exit(127);
}
}
if (svc->nr_supp_gids) {
if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) {
ERROR("setgroups failed: %s\n", strerror(errno));
_exit(127);
}
}
if (svc->uid) {
if (setuid(svc->uid) != 0) {
ERROR("setuid failed: %s\n", strerror(errno));
_exit(127);
}
}
//调用exec函数启动新的程序
if (!dynamic_args) {
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
} else {
char *arg_ptrs[INIT_PARSER_MAXARGS+1];
int arg_idx = svc->nargs;
char *tmp = strdup(dynamic_args);
char *next = tmp;
char *bword;
/* Copy the static arguments */
memcpy(arg_ptrs, svc->args, (svc->nargs * sizeof(char *)));
while((bword = strsep(&next, " "))) {
arg_ptrs[arg_idx++] = bword;
if (arg_idx == INIT_PARSER_MAXARGS)
break;
}
arg_ptrs[arg_idx] = '\0';
execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);
}
_exit(127);
}
if (pid < 0) {
ERROR("failed to start '%s'\n", svc->name);
svc->pid = 0;
return;
}
//设置服务进程状态
svc->time_started = gettime();
svc->pid = pid;
svc->flags |= SVC_RUNNING;
//如果属性初始化了,更新服务的运行状态属性
if (properties_inited())
notify_service_state(svc->name, "running");
}
以属性的方式记录服务的运行状态:
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;
//格式化服务对应属性名称
snprintf(pname, sizeof(pname), "init.svc.%s", name);
//设置对应服务的运行状态
property_set(pname, state);
}
static int check_perms(const char *name, unsigned int uid, unsigned int gid)
{
int i;
if (uid == 0)
return 1;
if(!strncmp(name, "ro.", 3))
name +=3;
//遍历property_perms数组,判断该数组内的属性是否拥有相应的权限
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中
property_perms[] = {
{ "net.rmnet0.", AID_RADIO, 0 },
{ "net.gprs.", AID_RADIO, 0 },
{ "net.ppp", AID_RADIO, 0 },
{ "net.qmi", AID_RADIO, 0 },
{ "net.lte", AID_RADIO, 0 },
{ "net.cdma", AID_RADIO, 0 },
{ "ril.", AID_RADIO, 0 },
{ "gsm.", AID_RADIO, 0 },
{ "persist.radio", AID_RADIO, 0 },
{ "persist.msms", AID_RADIO, 0 },
{ "net.dns", AID_RADIO, 0 },
{ "sys.usb.config", 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 },
{ "debug.", AID_SYSTEM, 0 },
{ "debug.", AID_SHELL, 0 },
{ "log.", AID_SHELL, 0 },
{ "service.adb.root", AID_SHELL, 0 },
{ "service.adb.tcp.port", AID_SHELL, 0 },
{ "persist.sys.", AID_SYSTEM, 0 },
{ "persist.service.", AID_SYSTEM, 0 },
{ "persist.security.", AID_SYSTEM, 0 },
#ifdef BOARD_HAVE_BLUETOOTH_BCM
{ "service.brcm.bt.", AID_BLUETOOTH, 0 },
{ "service.brcm.bt.", AID_SYSTEM, 0 },
{ "persist.service.brcm.bt.", AID_BLUETOOTH, 0 },
{ "persist.service.brcm.bt.", AID_SYSTEM, 0 },
{ "brcm.fm_state", AID_SYSTEM, 0 },
{ "brcm.fm_state", AID_BLUETOOTH, 0 },
{ "brcm.bt_state", AID_SYSTEM, 0 },
{ "brcm.bt_state", AID_BLUETOOTH, 0 },
#endif
{ NULL, 0, 0 }
};
通过property_set函数来设置系统属性,这里的property_set函数是property_service.c中定义的函数,请仔细区分接下来客户进程设置属性的函数property_set,他们是完全不相同的函数调用:
int property_set(const char *name, const char *value)
{
prop_area *pa;
prop_info *pi;
int namelen = strlen(name);
int valuelen = strlen(value);
//属性名和属性值长度检查
if(namelen >= PROP_NAME_MAX) return -1;
if(valuelen >= PROP_VALUE_MAX) return -1;
if(namelen < 1) return -1;
//根据名称从属性系统中查找对应的属性信息
pi = (prop_info*) __system_property_find(name);
//如果该属性已存在,则更新该属性
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);
//插入新的属性
} else {
pa = __system_property_area__;
if(pa->count == PA_COUNT_MAX) return -1;
pi = pa_info_array + pa->count;
pi->serial = (valuelen << 24);
memcpy(pi->name, name, namelen + 1);
memcpy(pi->value, value, valuelen + 1);
pa->toc[pa->count] =(namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
pa->count++;
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}
/* 以.net开头的属性是DNS属性 */
if (strncmp("net.", name, strlen("net.")) == 0) {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
/*对于持久性属性*/
} else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
}
//执行属性更改触发的动作
property_changed(name, value);
return 0;
}
property_set()函数,更改属性值,并调用property_changed()函数处理在init.rc文件中记录着的某个属性改变后要采取的动作,动作执行条件以“on property:
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled)
queue_property_triggers(name, value);
}
例如:
# adbd on at boot in emulator
on property:ro.kernel.qemu=1
start adbd
当属性ro.kernel.qemu=1时,启动adb服务,queue_property_triggers()函数处理属性触发的动作:
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) ||
!strcmp(test + name_length + 1, "*"))) {
action_add_queue_tail(act);
}
}
}
}
遍历action_list链表,查找以property:属性名命名形式的action,并添加到action_queue队列尾。
共享内存空间fd size作为环境变量传递给新创建进程,将系统属性内存空间映射到当前进程虚拟空间:进程在启动时,会加载动态库bionic libc库:
\bionic\libc\bionic\libc_init_dynamic.c中:
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;
}
framework通过SystemProperties接口操作系统属性,SystemProperties通过JNI调用访问系统属性。
\frameworks\base\core\java\android\os\ SystemProperties.java:
public class SystemProperties
{
//JNI
private static native String native_get(String key, String def);
private static native void native_set(String key, String def);
public static String get(String key, String def) {
return native_get(key, def);
}
public static void set(String key, String val) {
native_set(key, val);
}
}
static jstring SystemProperties_getS(JNIEnv *env, jobject clazz,
jstring keyJ)
{
return SystemProperties_getSS(env, clazz, keyJ, NULL);
}
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;
}
int property_get(const char *key, char *value, const char *default_value)
{
int len;
len = __system_property_get(key, value);//获取属性值
if(len > 0) {
return len;
}
if(default_value) { //如果读取到的属性值的长度为0,则使用默认值
len = strlen(default_value);
memcpy(value, default_value, len + 1);
}
return len;
}
int __system_property_get(const char *name, char *value)
{
const prop_info *pi = __system_property_find(name);//根据属性名称从属性共享内存中查找属性信息
if(pi != 0) {
return __system_property_read(pi, 0, value);//读取属性值
} else {
value[0] = 0;
return 0;
}
}
const prop_info *__system_property_find(const char *name)
{
prop_area *pa = __system_property_area__;//数据已经存储在内存中__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;
}
int __system_property_read(const prop_info *pi, char *name, char *value)
{
unsigned serial, len;
for(;;) {
serial = pi->serial;
while(SERIAL_DIRTY(serial)) {
__futex_wait((volatile void *)&pi->serial, serial, 0);
serial = pi->serial;
}
len = SERIAL_VALUE_LEN(serial);
memcpy(value, pi->value, len + 1);
if(serial == pi->serial) {
if(name != 0) {
strcpy(name, pi->name);
}
return len;
}
}
}
static void SystemProperties_set(JNIEnv *env, jobject clazz,
jstring keyJ, jstring valJ)
{
int err;
const char* key;
const char* val;
if (keyJ == NULL) {
jniThrowNullPointerException(env, "key must not be null.");
return ;
}
//获取属性的键
key = env->GetStringUTFChars(keyJ, NULL);
//获取属性的默认值
if (valJ == NULL) {
val = ""; /* NULL pointer not allowed here */
} else {
val = env->GetStringUTFChars(valJ, NULL);
}
err = property_set(key, val);//设置属性,这里的property_set函数与property_service.c中的property_set 是完全不同的,一个在服务端,一个在客户进程
env->ReleaseStringUTFChars(keyJ, key);
if (valJ != NULL) {
env->ReleaseStringUTFChars(valJ, val);
}
if (err < 0) {
jniThrowException(env, "java/lang/RuntimeException","failed to set system property");
}
}
int property_set(const char *key, const char *value)
{
return __system_property_set(key, value);
}
int __system_property_set(const char *key, const char *value)
{
int err;
int tries = 0;
int update_seen = 0;
prop_msg msg;
if(key == 0) return -1;
if(value == 0) value = "";
//判断属性的键与值的长度是否符合要求
if(strlen(key) >= PROP_NAME_MAX) return -1;
if(strlen(value) >= PROP_VALUE_MAX) return -1;
//创建并清空一个prop_msg
memset(&msg, 0, sizeof msg);
//设置消息命令码
msg.cmd = PROP_MSG_SETPROP;
//将属性的键值存放到msg中
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
//通过socket向init进程的属性服务发送消息
err = send_prop_msg(&msg);
if(err < 0) {
return err;
}
return 0;
}
通过名为"property_service"的socket向Android属性服务发送消息,Android属性服务驻留在init进程中,当消息发出后,init进程接收到属性服务socket的消息,并处理调用handle_property_set_fd函数来真正设置属性,属性服务设置属性的过程在属性服务端设置属性小节中已经介绍了。
static int send_prop_msg(prop_msg *msg)
{
struct pollfd pollfds[1];
struct sockaddr_un addr;
socklen_t alen;
size_t namelen;
int s;
int r;
int result = -1;
//创建一个socket
s = socket(AF_LOCAL, SOCK_STREAM, 0);
if(s < 0) {
return result;
}
//设置socket地址
memset(&addr, 0, sizeof(addr));
// /dev/socket/property_service
namelen = strlen(property_service_socket);
strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path);
addr.sun_family = AF_LOCAL;
alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
//连接init进程中的属性服务端
if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen) < 0)) {
close(s);
return result;
}
//通过socket发送消息
r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));
if(r == sizeof(prop_msg)) {
// We successfully wrote to the property server but now we
// wait for the property server to finish its work. It
// acknowledges its completion by closing the socket so we
// poll here (on nothing), waiting for the socket to close.
// If you 'adb shell setprop foo bar' you'll see the POLLHUP
// once the socket closes. Out of paranoia we cap our poll
// at 250 ms.
pollfds[0].fd = s;
pollfds[0].events = 0;
//poll等待
r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {
result = 0;
} else {
// Ignore the timeout and treat it like a success anyway.
// The init process is single-threaded and its property
// service is sometimes slow to respond (perhaps it's off
// starting a child process or something) and thus this
// times out and the caller thinks it failed, even though
// it's still getting around to it. So we fake it here,
// mostly for ctl.* properties, but we do try and wait 250
// ms so callers who do read-after-write can reliably see
// what they've written. Most of the time.
// TODO: fix the system properties design.
result = 0;
}
}
close(s);
return result;
}