Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> (Android系統)android property淺析

(Android系統)android property淺析

編輯:關於Android編程

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的路徑如下:

cheny.le@cheny-desktop:~/kitkat2_git/bionic$ grep property_filename * -nr
libc/bionic/system_properties.c:111:static char property_filename[PATH_MAX] = PROP_FILENAME;
cheny.le@cheny-desktop:~/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相關的文件定義如下:
cheny.le@cheny-desktop:~/kitkat2_git/bionic$ grep PROP_PATH_SYSTEM_BUILD * -nr
libc/include/sys/_system_properties.h:82:#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
cheny.le@cheny-desktop:~/kitkat2_git/bionic$ grep PROP_PATH_SYSTEM_DEFAULT * -nr
libc/include/sys/_system_properties.h:83:#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"
cheny.le@cheny-desktop:~/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發送過來的事件,關於事件的解析後續會進行分析
cheny.le@cheny-desktop:~/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;
}


cheny.le@cheny-desktop:~/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.
     *  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:
cheny.le@cheny-desktop:~/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;
cheny.le@cheny-desktop:~/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一下
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的大致內容就講完了,謝謝!
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved