(Android系统)android property浅析

android property,相信各位android平台的开发人员用到的不会少,但是property的具体机制大家可能知道的不多,这里利用空闲时间大致了解了一些,特此分享跟大家,如有谬误,欢迎指正

android 1号进程进程init进程在开机的时候就会调用property_init函数,至于init是怎么起来的,这里不是重点,所以暂时先不介绍,property_init的具体flow如下:

system/core/init/init.c

void property_init(void)
{
    init_property_area();
}

system/core/init/property_service.c

static int init_property_area(void)
{
    if (property_area_inited)
        return -1;

    if(__system_property_area_init())
        return -1;

    if(init_workspace(&pa_workspace, 0))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

    property_area_inited = 1;
    return 0;
}

bionic/libc/bionic/system_properties.c

int __system_property_area_init()
{
    return map_prop_area_rw();
}

把property_filename映射到共享内存,之所以要使用共享内存是因为其他进程也需要使用property,这个是property的最基本功能,注意这里有一个全局变量__system_property_area__很重要,这个是以后所有property的root,也就是说通过这个变量就可以遍历其他property

具体property file的路径如下:

[email protected]:~/kitkat2_git/bionic$ grep property_filename * -nr
libc/bionic/system_properties.c:111:static char property_filename[PATH_MAX] = PROP_FILENAME;
[email protected]:~/kitkat2_git/bionic$ grep PROP_FILENAME * -nr
libc/include/sys/_system_properties.h:44:#define PROP_FILENAME "/dev/__properties__"

bionic/libc/bionic/system_properties.c

static int map_prop_area_rw()
{
    prop_area *pa;
    int fd;
    int ret;

    /* dev is a tmpfs that we can use to carve a shared workspace
     * out of, so let's do that...
     */
    fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |
            O_EXCL, 0444);
    if (fd < 0) {
        if (errno == EACCES) {
            /* for consistency with the case where the process has already
             * mapped the page in and segfaults when trying to write to it
             */
            abort();
        }
        return -1;
    }

    ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
    if (ret < 0)
        goto out;

    if (ftruncate(fd, PA_SIZE) < 0)
        goto out;

    pa_size = PA_SIZE;
    pa_data_size = pa_size - sizeof(prop_area);
    compat_mode = false;

    pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(pa == MAP_FAILED)
        goto out;

    memset(pa, 0, pa_size);
    pa->magic = PROP_AREA_MAGIC;
    pa->version = PROP_AREA_VERSION;
    /* reserve root node */
    pa->bytes_used = sizeof(prop_bt);

    /* plug into the lib property services */
    __system_property_area__ = pa;

    close(fd);
    return 0;

out:
    close(fd);
    return -1;
}

property init之后就需要加载boot所需的default property,跟property相关的文件定义如下:

[email protected]:~/kitkat2_git/bionic$ grep PROP_PATH_SYSTEM_BUILD * -nr
libc/include/sys/_system_properties.h:82:#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
[email protected]:~/kitkat2_git/bionic$ grep PROP_PATH_SYSTEM_DEFAULT * -nr
libc/include/sys/_system_properties.h:83:#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"
[email protected]:~/kitkat2_git/bionic$ grep PROP_PATH_RAMDISK_DEFAULT * -nr
libc/include/sys/_system_properties.h:81:#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"

system/core/init/init.c

void property_load_boot_defaults(void)
{
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}

system/core/init/property_service.c

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

read file的作用就是把file的内容读到buffer里面,然后确保以‘/0‘结尾

system/core/init/util.c

void *read_file(const char *fn, unsigned *_sz)
{
    char *data;
    int sz;
    int fd;
    struct stat sb;

    data = 0;
    fd = open(fn, O_RDONLY);
    if(fd < 0) return 0;

    // for security reasons, disallow world-writable
    // or group-writable files
    if (fstat(fd, &sb) < 0) {
        ERROR("fstat failed for '%s'\n", fn);
        goto oops;
    }
    if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
        ERROR("skipping insecure file '%s'\n", fn);
        goto oops;
    }

    sz = lseek(fd, 0, SEEK_END);
    if(sz < 0) goto oops;

    if(lseek(fd, 0, SEEK_SET) != 0) goto oops;

    data = (char*) malloc(sz + 2);
    if(data == 0) goto oops;

    if(read(fd, data, sz) != sz) goto oops;
    close(fd);
    data[sz] = '\n';
    data[sz+1] = 0;
    if(_sz) *_sz = sz;
    return data;

oops:
    close(fd);
    if(data != 0) free(data);
    return 0;
}

把buffer的内容按指定格式解析出来,然后用property_set设置到系统中,具体property_set的流程后续给出具体解释

system/core/init/property_service.c

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

init的main里面接下来会跑property的service,这个service会首先加载system build和system defaule两个property文件,然后创建socket用来监听bionic 里面system_properties.c发送过来的事件,关于事件的解析后续会进行分析

[email protected]:~/kitkat2_git/bionic$ grep  PROP_SERVICE_NAME * -nr
libc/include/sys/_system_properties.h:43:#define PROP_SERVICE_NAME "property_service"
void start_property_service(void)
{
    int fd;

    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    load_override_properties();
    /* 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;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    listen(fd, 8);
    property_set_fd = fd;
}
[email protected]:~/kitkat2_git/system/core$ grep ANDROID_SOCKET_DIR * -nr
include/cutils/sockets.h:33:#define ANDROID_SOCKET_DIR		"/dev/socket"

这也就是说会建立一个/dev/socket/property_service的socket,然后listen这个socket

int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{
    struct sockaddr_un addr;
    int fd, ret;
    char *secon;

    fd = socket(PF_UNIX, type, 0);
    if (fd < 0) {
        ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
        return -1;
    }

    memset(&addr, 0 , sizeof(addr));
    addr.sun_family = AF_UNIX;
    snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
             name);

    ret = unlink(addr.sun_path);
    if (ret != 0 && errno != ENOENT) {
        ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
        goto out_close;
    }

    secon = NULL;
    if (sehandle) {
        ret = selabel_lookup(sehandle, &secon, addr.sun_path, S_IFSOCK);
        if (ret == 0)
            setfscreatecon(secon);
    }

    ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
    if (ret) {
        ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno));
        goto out_unlink;
    }

    setfscreatecon(NULL);
    freecon(secon);

    chown(addr.sun_path, uid, gid);
    chmod(addr.sun_path, perm);

    INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
         addr.sun_path, perm, uid, gid);

    return fd;

out_unlink:
    unlink(addr.sun_path);
out_close:
    close(fd);
    return -1;
}

init的main接下来会把建立起来的socket加到poll的列表中,如果有数据进来的时候就去调用handle_property_set_fd

system/core/init/init.c

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

if (ufds[i].revents == POLLIN) {
	if (ufds[i].fd == get_property_set_fd())
		handle_property_set_fd();

可以看到handle_property_set_fd会去读取property_set_fd接受到的信息,这个socket就是前面start_property_service里面创建的socket哈,非常的关键,收到的msg如果不是指定size的,大致判断为不是system_properties.c发送过来的,直接drop掉,如果满足条件接下来判断msg的cmd,如果是PROP_MSG_SETPROP,且是以“ctl.”开头的,那就就检查property name和permission是否符合要求,符合要求的话就调用handle_control_message去做具体的处理

system/core/init/property_service.c

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);
    char * source_ctx = NULL;

    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }

    /* Check socket options here */
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        ERROR("Unable to receive socket options\n");
        return;
    }

    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0));
    if(r != sizeof(prop_msg)) {
        ERROR("sys_prop: mis-match msg size received: %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;

        if (!is_legal_property_name(msg.name, strlen(msg.name))) {
            ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
            close(s);
            return;
        }

        getpeercon(s, &source_ctx);

        if(memcmp(msg.name,"ctl.",4) == 0) {
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
            if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) {
                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, source_ctx)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }

            // Note: bionic's property client code assumes that the
            // property server will not close the socket until *AFTER*
            // the property is written to memory.
            close(s);
        }
        freecon(source_ctx);
        break;

    default:
        close(s);
        break;
    }
}

关于具体的参数和权限检查如下:

property name检查:

static bool is_legal_property_name(const char* name, size_t namelen)
{
    size_t i;
    bool previous_was_dot = false;
    if (namelen >= PROP_NAME_MAX) return false;
    if (namelen < 1) return false;
    if (name[0] == '.') return false;
    if (name[namelen - 1] == '.') return false;

    /* Only allow alphanumeric, plus '.', '-', or '_' */
    /* Don't allow ".." to appear in a property name */
    for (i = 0; i < namelen; i++) {
        if (name[i] == '.') {
            if (previous_was_dot == true) return false;
            previous_was_dot = true;
            continue;
        }
        previous_was_dot = false;
        if (name[i] == '_' || name[i] == '-') continue;
        if (name[i] >= 'a' && name[i] <= 'z') continue;
        if (name[i] >= 'A' && name[i] <= 'Z') continue;
        if (name[i] >= '0' && name[i] <= '9') continue;
        return false;
    }

    return true;
}

permission检查:

static int check_control_perms(const char *name, unsigned int uid, unsigned int gid, char *sctx) {
    int i;
    if (uid == AID_SYSTEM || uid == AID_ROOT)
      return check_control_mac_perms(name, sctx);

    /* Search the ACL */
    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 check_control_mac_perms(name, sctx);
            }
        }
    }
    return 0;
}
static int check_control_mac_perms(const char *name, char *sctx)
{
    /*
     *  Create a name prefix out of ctl.<service name>
     *  The new prefix allows the use of the existing
     *  property service backend labeling while avoiding
     *  mislabels based on true property prefixes.
     */
    char ctl_name[PROP_VALUE_MAX+4];
    int ret = snprintf(ctl_name, sizeof(ctl_name), "ctl.%s", name);

    if (ret < 0 || (size_t) ret >= sizeof(ctl_name))
        return 0;

    return check_mac_perms(ctl_name, sctx);
}
static int check_mac_perms(const char *name, char *sctx)
{
    if (is_selinux_enabled() <= 0)
        return 1;

    char *tctx = NULL;
    const char *class = "property_service";
    const char *perm = "set";
    int result = 0;

    if (!sctx)
        goto err;

    if (!sehandle_prop)
        goto err;

    if (selabel_lookup(sehandle_prop, &tctx, name, 1) != 0)
        goto err;

    if (selinux_check_access(sctx, tctx, class, perm, name) == 0)
        result = 1;

    freecon(tctx);
 err:
    return result;
}

如果不是"ctl."开头的property name的话,走这个flow来检测permission

static int check_perms(const char *name, unsigned int uid, unsigned int gid, char *sctx)
{
    int i;
    unsigned int app_id;

    if(!strncmp(name, "ro.", 3))
        name +=3;

    if (uid == 0)
        return check_mac_perms(name, sctx);

    app_id = multiuser_get_app_id(uid);
    if (app_id == AID_BLUETOOTH) {
        uid = app_id;
    }

    for (i = 0; property_perms[i].prefix; i++) {
        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 check_mac_perms(name, sctx);
            }
        }
    }

    return 0;
}

一系列检查完毕之后就需要去handle对应的msg了,具体的流程如下:

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 if (!strcmp(msg,"restart")) {
        msg_restart(arg);
    } else {
        ERROR("unknown control msg '%s'\n", msg);
    }
}

这里只说ctl.start的流程,其他的应该差不多

static void msg_start(const char *name)
{
    struct service *svc = NULL;
    char *tmp = NULL;
    char *args = NULL;

    if (!strchr(name, ':'))
        svc = service_find_by_name(name);
    else {
        tmp = strdup(name);
        if (tmp) {
            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);
}

可以看到ctl.start去起一个service,具体的流程如下:

void service_start(struct service *svc, const char *dynamic_args)
{
    struct stat s;
    pid_t pid;
    int needs_console;
    int n;
    char *scon = NULL;
    int rc;

        /* 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_RESTART));
    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;
    }

    if (is_selinux_enabled() > 0) {
        if (svc->seclabel) {
            scon = strdup(svc->seclabel);
            if (!scon) {
                ERROR("Out of memory while starting '%s'\n", svc->name);
                return;
            }
        } else {
            char *mycon = NULL, *fcon = NULL;

            INFO("computing context for service '%s'\n", svc->args[0]);
            rc = getcon(&mycon);
            if (rc < 0) {
                ERROR("could not get context while starting '%s'\n", svc->name);
                return;
            }

            rc = getfilecon(svc->args[0], &fcon);
            if (rc < 0) {
                ERROR("could not get context while starting '%s'\n", svc->name);
                freecon(mycon);
                return;
            }

            rc = security_compute_create(mycon, fcon, string_to_security_class("process"), &scon);
            freecon(mycon);
            freecon(fcon);
            if (rc < 0) {
                ERROR("could not get context while starting '%s'\n", svc->name);
                return;
            }
        }
    }
    NOTICE("starting '%s'\n", svc->name);

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

        setsockcreatecon(scon);

        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));
            int s = create_socket(si->name, socket_type,
                                  si->perm, si->uid, si->gid);
            if (s >= 0) {
                publish_socket(si->name, s);
            }
        }

        freecon(scon);
        scon = NULL;
        setsockcreatecon(NULL);

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

#if 0
        for (n = 0; svc->args[n]; n++) {
            INFO("args[%d] = '%s'\n", n, svc->args[n]);
        }
        for (n = 0; ENV[n]; n++) {
            INFO("env[%d] = '%s'\n", n, ENV[n]);
        }
#endif

        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);
            }
        }
        if (svc->seclabel) {
            if (is_selinux_enabled() > 0 && setexeccon(svc->seclabel) < 0) {
                ERROR("cannot setexeccon('%s'): %s\n", svc->seclabel, strerror(errno));
                _exit(127);
            }
        }

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

    freecon(scon);

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

上面的code的确很冗长,但是请把注意力放在fork+execve组合上,这对组合的出现也就是说明通过service来启动一个process的最终目的达到了,在启动完service之后别忘记设置service的运行状态,具体流程如下:

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

上面只说到了socket收到以"ctl."开头的property name的设置的flow,其实这种用法主要是我们service来起process,如果是非“ctl.”开头的property name呢?这个才是我们用key=value pair对来设置property的主要用法,关于property_set的具体flow有两种

方法一:包含libcutils里面的properties.h头文件,使用里面的property_set方法,这个是我们最常用的方法,但是这个最终还是会跑到方法二

方法二:使用__system_property_find+__system_property_update(__system_property_add)的组合方法把具体的property设置到具体的mmap所对应的共享内存中,这个才是最终的方法

因为方法一有包含方法二,所以我们先从方法一开始讲:

libcutils里面关于property_set的具体实现如下:

system/core/libcutils/properties.c

int property_set(const char *key, const char *value)
{
    return __system_property_set(key, value);
}

其实还是会跑到bionic里面的__system_property_set,这里有看到prop_msg的类型,这个前面用sizeof(prop_msg)来大致检测socket收到的msg是否合法,另外msg.cmd的值是PROP_MSG_SETPROP,这个也是property_service.c的handle_property_set_fd里面要检测的哈

/bionic/libc/bionic/system_properties.c

int __system_property_set(const char *key, const char *value)
{
    int err;
    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;

    memset(&msg, 0, sizeof msg);
    msg.cmd = PROP_MSG_SETPROP;
    strlcpy(msg.name, key, sizeof msg.name);
    strlcpy(msg.value, value, sizeof msg.value);

    err = send_prop_msg(&msg);
    if(err < 0) {
        return err;
    }

    return 0;
}

在发送msg之前,我们先确定一下要发送给哪个socket:

[email protected]:~/kitkat2_git/bionic$ grep property_service_socket * -nr
libc/bionic/system_properties.c:110:static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
[email protected]:~/kitkat2_git/bionic$ grep PROP_SERVICE_NAME * -nr
libc/include/sys/_system_properties.h:43:#define PROP_SERVICE_NAME "property_service"

这个连接起来就是/dev/socket/property_service,这个就是之前start_property_service所创建的socket哈,原来bionic的system_properties.c里面的__system_property_set不是具体设置property的地方,property的设置接下来会跑到property_service.c里面

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;

    s = socket(AF_LOCAL, SOCK_STREAM, 0);
    if(s < 0) {
        return result;
    }

    memset(&addr, 0, sizeof(addr));
    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;

    if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) {
        close(s);
        return result;
    }

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

property_service.c收到这个msg之后,会根据msg.name和msg.value来设置具体的property,在这里需要说明一下,如果根据name能够找到对应的prop_info信息,那么就去update对应的value值,如果找不到对应的prop_info信息,那么就去add一个prop_info,具体的flow如下:

int property_set(const char *name, const char *value)
{
    prop_info *pi;
    int ret;

    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);

    if (!is_legal_property_name(name, namelen)) return -1;
    if (valuelen >= PROP_VALUE_MAX) 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;

        __system_property_update(pi, value, valuelen);
    } else {
        ret = __system_property_add(name, namelen, value, valuelen);
        if (ret < 0) {
            ERROR("Failed to set '%s'='%s'\n", name, value);
            return ret;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    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);
    } else if (strcmp("selinux.reload_policy", name) == 0 &&
               strcmp("1", value) == 0) {
        selinux_reload_policy();
    }
    property_changed(name, value);
    return 0;
}
const prop_info *__system_property_find(const char *name)
{
    if (__predict_false(compat_mode)) {
        return __system_property_find_compat(name);
    }
    return find_property(root_node(), name, strlen(name), NULL, 0, false);
}

到了这里有必要说一下property在共享内存里面的具体结构:

/*

* Properties are stored in a hybrid trie/binary tree structure.

* Each property‘s name is delimited at ‘.‘ characters, and the tokens are put

* into a trie structure.  Siblings at each level of the trie are stored in a

* binary tree.  For instance, "ro.secure"="1" could be stored as follows:

*

* +-----+   children    +----+   children    +--------+

* |     |-------------->| ro |-------------->| secure |

* +-----+               +----+               +--------+

*                       /    \                /   |

*                 left /      \ right   left /    |  prop   +===========+

*                     v        v            v     +-------->| ro.secure |

*                  +-----+   +-----+     +-----+            +-----------+

*                  | net |   | sys |     | com |            |     1     |

*                  +-----+   +-----+     +-----+            +===========+

*/

可以看到ro.secure=1在内存里面的组成图如上,这里我有一个疑问,就是net节点应该在ro节点的right子树上,疑问的解释待会给出

关于这个find_property,其实他是在遍历property树,具体的内容我大致解释一下:

首先以.做分隔符,解释出property name里面的第一个元素,一般第一个元素是ro,net,sys之类的,然后根据property name中是否还有.作为是否还需要继续循环的条件,也就是说没有.的情况下就是最后一轮的find了,接下来计算出解析出来的元素的长度substr_size,这个size是要跟后续所遍历的节点的name length做对比的,接下来根据root的childred来到第一个节点,根据图来看应该是ro节点,然后这里会调用find_prop_bt,find_prop_bt会找到name对应的prop_bt,然后根据want_subtree来确认是否还需要find,如果不需要的话就停止find,如果这个prop是存在的就返回,如果这个prop不存在的话,就根据alloc_if_needed参数来确定是否需要创建,如果不需要创建就返回NULL

static const prop_info *find_property(prop_bt *trie, const char *name,
        uint8_t namelen, const char *value, uint8_t valuelen,
        bool alloc_if_needed)
{
    const char *remaining_name = name;

    while (true) {
        char *sep = strchr(remaining_name, '.');
        bool want_subtree = (sep != NULL);
        uint8_t substr_size;

        prop_bt *root;

        if (want_subtree) {
            substr_size = sep - remaining_name;
        } else {
            substr_size = strlen(remaining_name);
        }

        if (!substr_size)
            return NULL;

        if (trie->children) {
            root = to_prop_obj(trie->children);
        } else if (alloc_if_needed) {
            root = new_prop_bt(remaining_name, substr_size, &trie->children);
        } else {
            root = NULL;
        }

        if (!root)
            return NULL;

        trie = find_prop_bt(root, remaining_name, substr_size, alloc_if_needed);
        if (!trie)
            return NULL;

        if (!want_subtree)
            break;

        remaining_name = sep + 1;
    }

    if (trie->prop) {
        return to_prop_obj(trie->prop);
    } else if (alloc_if_needed) {
        return new_prop_info(name, namelen, value, valuelen, &trie->prop);
    } else {
        return NULL;
    }
}

接下来追查一下find_prop_bt的flow,我们先看一下return的情况,可以看到要么return bt,要么return NULL,return bt的情况是cmp_prop_name的返回值ret是0,return NULL的原因是ret小于0的时候没有left节点且不需要alloc,或者是ret大于0的时候没有没有right节点且不需要alloc,否则ret小于0的时候bt跳到left节点进行下一次find,或者ret大于0的时候跳到right节点进行下一次find

static prop_bt *find_prop_bt(prop_bt *bt, const char *name, uint8_t namelen,
        bool alloc_if_needed)
{
    while (true) {
        int ret;
        if (!bt)
            return bt;
        ret = cmp_prop_name(name, namelen, bt->name, bt->namelen);

        if (ret == 0) {
            return bt;
        } else if (ret < 0) {
            if (bt->left) {
                bt = to_prop_obj(bt->left);
            } else {
                if (!alloc_if_needed)
                   return NULL;

                bt = new_prop_bt(name, namelen, &bt->left);
            }
        } else {
            if (bt->right) {
                bt = to_prop_obj(bt->right);
            } else {
                if (!alloc_if_needed)
                   return NULL;

                bt = new_prop_bt(name, namelen, &bt->right);
            }
        }
    }
}

关于cmp_prop_name的flow如下:

我前面有说到,我对android原生提供的property结构图有一些疑问,在于property在创建的时候,ro节点最先创建,然后在创建net节点的时候,substr_size是3,one是net,到这里就是one_len是3,two_len(bt->namelen)是2,two(bt->name)是ro,这里可以看到cmp_prop_name的结果是大于0的,也就是说当时在创建net property的时候也应该是在ro的right节点,而不是left,这里的分析可能有问题,麻烦各位网友帮忙double
check一下

</pre><pre name="code" class="cpp">static int cmp_prop_name(const char *one, uint8_t one_len, const char *two,
        uint8_t two_len)
{
    if (one_len < two_len)
        return -1;
    else if (one_len > two_len)
        return 1;
    else
        return strncmp(one, two, one_len);
}

这里的__system_property_area__就是前面init的时候mmap所创建的那段共享内存,这个是遍历property结构的基础

static void *to_prop_obj(prop_off_t off)
{
    if (off > pa_data_size)
        return NULL;

    return __system_property_area__->data + off;
}

前面的内容是name在property结构中是存在的,那么property_set就会直接设置value到对应的节点,如果name在property结构中是不存在的,就需要创建对应的property结构,具体的flow如下:

因为__system_property_area__是共享内存的起始地址,那么__system_property_area__加上一定的offset就可以得到new出来的prop obj的指针

static void *new_prop_obj(size_t size, prop_off_t *off)
{
    prop_area *pa = __system_property_area__;
    size = ALIGN(size, sizeof(uint32_t));

    if (pa->bytes_used + size > pa_data_size)
        return NULL;                                                                                                               

    *off = pa->bytes_used;
    __system_property_area__->bytes_used += size;
    return __system_property_area__->data + *off;
}

new出prop obj之后根据name去设置bt->name的值,具体value的值后续会给出设置的方式

static prop_bt *new_prop_bt(const char *name, uint8_t namelen, prop_off_t *off)
{
    prop_off_t off_tmp;
    prop_bt *bt = new_prop_obj(sizeof(prop_bt) + namelen + 1, &off_tmp);
    if (bt) {
        memcpy(bt->name, name, namelen);
        bt->name[namelen] = '\0';
        bt->namelen = namelen;
        ANDROID_MEMBAR_FULL();
        *off = off_tmp;
    }

    return bt;
}

到这里就根据prop name找到了对应prop info的节点,这个是后续property_set来设置具体的value项的基础

接下来跟一下具体set value的flow

int __system_property_update(prop_info *pi, const char *value, unsigned int len)
{
    prop_area *pa = __system_property_area__;

    if (len >= PROP_VALUE_MAX)
        return -1;

    pi->serial = pi->serial | 1;
    ANDROID_MEMBAR_FULL();
    memcpy(pi->value, value, len + 1);
    ANDROID_MEMBAR_FULL();
    pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff);
    __futex_wake(&pi->serial, INT32_MAX);

    pa->serial++;
    __futex_wake(&pa->serial, INT32_MAX);

    return 0;
}

这里我们可以看到memcpy(pi->value, value, len + 1);,这就是把用户的value设置到了prop info这个节点里面了

如果对应name的prop info是不存在的呢?这里就需要使用__system_property_add来增加对应的prop info信息了

int __system_property_add(const char *name, unsigned int namelen,
            const char *value, unsigned int valuelen)
{
    prop_area *pa = __system_property_area__;
    const prop_info *pi;

    if (namelen >= PROP_NAME_MAX)
        return -1;
    if (valuelen >= PROP_VALUE_MAX)
        return -1;
    if (namelen < 1)
        return -1;

    pi = find_property(root_node(), name, namelen, value, valuelen, true);
    if (!pi)
        return -1;

    pa->serial++;
    __futex_wake(&pa->serial, INT32_MAX);
    return 0;
}

同样会走find_property的flow,但是跟单纯的find有一些不一样的是最后一个参数是true,也就是说拥有创建prop info的权限,到这里我们就把property_set的整个flow(查找name之后set和直接创建两种情况)走完了

property_set的flow走完之后我们来走一下property_get的flow

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

拿到prop info之后根据prop info读出value的值

int __system_property_read(const prop_info *pi, char *name, char *value)
{
    unsigned serial, len;

    if (__predict_false(compat_mode)) {
        return __system_property_read_compat(pi, name, value);
    }

    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);
        ANDROID_MEMBAR_FULL();
        if(serial == pi->serial) {
            if(name != 0) {
                strcpy(name, pi->name);
            }
            return len;
        }
    }
}

到此为止property的整个flow就走完了,可以发现property的最终存储实体是在/dev/__properties__文件mmap对应的共享内存中,由于这块内存是共享的,所以其他进程也可以访问,另外指向这块内存的指针是一个全局变量,所以可以直接拿到指向包含property信息的指针

一般property_set的流程是根据name拿到prop info,prop info之所以能拿到是因为__system_property_area__全局变量的存在,这个全局变量可以遍历property信息的节点,所以也就找到包含对应name的prop info,如果这个prop info不存在那么就看情况决定是否创建,拿到prop info之后就可以用property_set所传递的参数来填充prop info的value字段,这个是在__system_property_update里面做的,前面有讲到的

property_get就简单了,用跟propert_set相同的方式去遍历property的节点,拿到包含对应name的prop info,然后读出prop info的value字段返回给上层

这里面最后一个比较重要的地方就是prop info在共享内存中的结构,这个是前面property遍历的规则,大致如前面图里面所展示的,大家可以根据find_prop_bt函数大致理清楚节点的left和right是怎么确定的,节点的children则是property的下一个元素,每个元素之间用.来隔开,到此为止android property的大致内容就讲完了,谢谢!

(Android系统)android property浅析

时间: 2024-10-08 06:20:48

(Android系统)android property浅析的相关文章

理解Android系统(一)

理解Android系统 Android 是业界流行的开源移动平台,受到广泛关注并为多个手机制造商作为手机的操作系统平台.由于它的开放性,市面上又出现了它的很多改良定制版本.且广泛的应用在手机.汽车.电脑等领域.因此,研究其安全架构及权限控制机制具有非常的重要性. 本章从 Android 层次化安全架构入手,详细地介绍 Android 平台的安全架构及其权限控制机制,涵盖 Android 应用程序权限申请方法等,并从源代码实现层面来解析该机制. 1.1 系统的层级架构 Android架构,其实就是

Android系统Google Maps开发实例浅析

Google Map(谷歌地图)是Google公司提供的电子地图服务.包括了三种视图:矢量地图.卫星图片.地形地图.对于Android系统来说,可以利用Google提供的地图服务来开发自己的一些应用.Google Map的服务体现在两个方面:地图API和位置API.使用Android Maps API(地图API)和Android Location API(定位API)可以轻松实现实用而且强大的功能. 我的位置:“我的位置”在地图上显示你的当前位置(通常在 1000 米范围之内).即使没有 GP

(Android系统)android log机制浅析

在android下面debug,最主要的方式就是用logcat抓log了,我们可能有尝试过使用printf来打印,当然结果是不行的,这里有时间就看了一下android平台下log的flow,在此做个笔记以作记录 我们一般使用ALOGD来打印log,所以这里就跟一下ALOGD的flow system/core/include/log/log.h system/core/include/log/log.h #ifndef ALOGD #define ALOGD(...) ((void)ALOG(LO

React Native Android Gradle 编译流程浅析

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 前面已经发车了一篇<React Native Android 从学车到补胎和成功发车经历>,接着就该好好琢磨一下 React Native 周边了,没看第一篇的可以先去看看:这里我们先从 React Native 的 Android 编译来简单揭晓一下 React Native 在集成的过程中到底干了哪些不可告人的坏事:由于我们项目准备以 Gradle 形式接入

【Android 系统开发】 Android 系统启动流程简介

Android 系统启动总结 : Android 系统启动分底层 Linux 内核启动 和 应用系统启动; -- 底层系统启动 : 系统上电, bootloader 启动, linux kernel 启动, init 进程启动; -- 应用系统启动 : init 进程启动关键的进程如 Zygote 进程 和 System Server 等系统服务, 之后进入 Home 界面; 一. Android 底层系统启动流程(Bootloader Kernel init) 1. 系统上电 执行 ROM 引

Android系统Recovery工作原理之使用update.zip升级过程分析(一)

通过分析update.zip包在具体Android系统升级的过程,来理解Android系统中Recovery模式服务的工作原理.我们先从update.zip包的制作开始,然后是Android系统的启动模式分析,Recovery工作原理,如何从我们上层开始选择system update到重启到Recovery服务,以及在Recovery服务中具体怎样处理update.zip包升级的,我们的安装脚本updater-script怎样被解析并执行的等一系列问题.分析过程中所用的Android源码是gin

Android系统Recovery工作原理之使用update.zip升级过程分析(六)---Recovery服务流程细节【转】

本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465439  Android系统Recovery工作原理之使用update.zip升级过程分析(六)---Recovery服务流程细节            Recovery服务毫无疑问是Recovery启动模式中最核心的部分.它完成Recovery模式所有的工作.Recovery程序对应的源码文件位于:/gingerbread0919/bootable/recovery/recovery

React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 有了前面<React Native Android 从学车到补胎和成功发车经历>和<React Native Android Gradle 编译流程浅析>两篇文章的学习我们 React Native 已经能够基本接入处理一些事情了,那接下来的事情就是渐渐理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码

[转载]起动service保存android系统log( logcat服务)

原文链接:http://www.myexception.cn/android/1904013.html 启动service保存android系统log 作为android开发工程师,出现了BUG是否苦于没有log而苦恼万分呢,以下敝人提供一套自动保存log的方法,供大家借鉴学习: 首先,在产品目录的init.XXX.rc文件中,添加相应的service, # start log service start logd on property:service.logcat.enable=1 star