Android 系统属性SystemProperty分析

Android System Property



属性变更的请求时init事件循环处理的另一个事件,在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。属性由键(key)与值(value)构成,其表现形式为“键=值”。在Linux系统中,属性服务主要用来设置环境变量,提供各进程访问设定的环境变量值。在Android平台中,在访问属性值时,添加了访问权限控制,增强了访问的安全性。系统中所有运行中的进程都可以访问属性值,但仅有init进程才能修改属性值。其他进程修改属性值时,必须向init进程提出请求,最终由init进程负责修改属性值。在此过程中,init进程会先检查各属性的访问权限,而后再修改属性值,当属性值更改后,若定义在init.rc文件中的某个特定条件得到满足,则与此条件相匹配的动作就会发生,每个动作都有一个触发器,决定动作的执行时间,记录在“on property”关键字后的命令即被执行。

属性设计框架

属性系统是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.<服务名>“属性中 。客户端应用程序可以轮询那个属性值,以确定结果。

如何读取/设置属性

1、  native code

当编写本地应用程序时,可以使用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
    on property:ro.kernel.qemu=1
       start adbd
    on property:persist.service.adb.enable=1
       start adbd
    on property:persist.service.adb.enable=0
       stop adbd
这样,如果你设置persist.service.adb.enable为1 ,"init"守护程序就知道需要采取行动:开启adbd服务。
2、  java code

在Java包中提供有System.getProperty和System.setProperty方法。但值得注意的是,尽管这两个API在语义上等同native函数,但其将数据存储于完全不同的位置。实际上,dalvik VM使用一个哈希表来存储这些属性。所以,用这两个API存储的属性是独立的,不能存取native属性,反之亦然。

然而Android有一个内部隐藏类(@hide,对SDK不可见)android.os.SystemProperties来操纵native属性。其通过jni来存取native属性库。

3、  shell脚本

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的最大允许长度。

属性服务启动过程

android之Init进程启动过程源码分析一文中介绍了整个Init进程的启动流程,init进程的主要任务之一就是启动Android系统的属性服务,接下来将详细介绍属性服务的启动流程:
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 属性系统的整个流程:



1)初始化属性存储空间

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字节,用于保存属性值。在属性域完成初始化之后,就会从指定的文件中读取初始化值,并设置属性值。
属性系统的存储空间分配图如下:

2)初始化系统属性
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函数来设置解析得到的系统属性,属性的设置过程在接下来将进行详细分析!
3)属性服务端设置属性
在前面的属性系统的设计框架中介绍了属性的正真设置是在Init进程进行的,通过属性服务来设置相关系统属性,或者控制相关进程的运行状态,如下图所示:

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;
    }
}

1.服务状态控制属性设置
以ctl开头的属性是服务运行状态控制属性,对于这类属性,根据ctl后面的start或stop来控制服务进程的启动与停止,在启动或停止服务进程前首先会检查control_perms数组中的服务是否具有相应权限:

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);
}

2.系统属性设置
在设置系统属性前,依然要检查property_perms属性数组中的属性是否有指定的权限
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:<key> = <value>” 的形式给出,当某个条件相关的键值被设定后,与该条件相关的触发器就会被触发。

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);
    }
}
获取系统属性
因为在属性服务启动时就已经将系统属性从属性文件中加载到共享内存中,因此系统属性的读取其实是从属性系统的匿名共享内存中读取的。
\frameworks\base\core\jni\android_os_SystemProperties.cpp
static jstring SystemProperties_getS(JNIEnv *env, jobject clazz,
                                      jstring keyJ)
{
    return SystemProperties_getSS(env, clazz, keyJ, NULL);
}
调用另一个JNI函数来实现
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;
}
进程启动后已经将系统属性数据读取到相应的共享内存中,保存在全局变量__system_property_area__;
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;
}

对于客户端进程来说这样就算完成了属性的设置,其实属性的设置工作被转交给了init进程的属性系统来完成的,init进程的属性系统在前面中已经详细介绍了。Android的属性系统到此基本上就介绍完了。


  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值