[Android源碼解析]Property之十月懷胎到茁壯成長所涉及的方方面面

         其實在網上講Property的文章還是蠻多的,不過源碼級分析的倒是不多,曉東正好做好了一個項目,其中涉及到了Property的一些內容,折騰了一段時間,心想不如來讀讀源碼,看看究竟是怎麼回事。

1property內存區域的申請

         在網上通常都是這樣開始講的“屬性服務運行於init進程中。init進程首先創建一個共享內存區域,並保存一個指向該區域的描述符fd[1]這一段從代碼中如何來看呢,首先找到initmain函數:(system/core/init/init.c文件中),有這樣一句話:

         queue_builtin_action(property_init_action, "property_init");

         大概的意思就是把property_init_action加入到action queue中,後面會來調用這個action。所以,我們自然而然的就會去看property_init_action的操作:

static int property_init_action(int nargs, char **args)
{
    bool load_defaults = true;

INFO("property init\n");
//只要不是關機充電的模式,我們都把load_defaults置爲true
    if (!strcmp(bootmode, "charger"))
        load_defaults = false;
	//property的初始化
    property_init(load_defaults);
    return 0;
}

void property_init(bool load_defaults)
{
	//初始化property的內存區域,就是上面傳說的共享內存區域?
init_property_area();
//若是需要load,這裏會load  PROP_PATH_RAMDISK_DEFAULT
// #define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
//所以網上盛傳的先加載哪個文件,再加載哪個文件都是有前提的,就是首先不是在關機充電模式。哈哈~~,不過的確是得先加載這個文件default.prop
if (load_defaults)
//這裏加載對應文件中的property內容,詳細分析見1.2
        load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}

static int init_property_area(void)
{
    prop_area *pa;
//這是個全局變量,不過也就只有這個函數會賦值,所以開始不會有問題,再重複進來就會報錯了
    if(pa_info_array)
        return -1;
	//初始化內存區域,詳細分析見1.1
    if(init_workspace(&pa_workspace, PA_SIZE))
        return -1;
	//設置FD_CLOEXEC,大概的意思就是在excel執行時關閉
    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
	//這裏如圖1所示
    pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);

pa = pa_workspace.data;
//把整個shared memory都初始化爲0
memset(pa, 0, PA_SIZE);
//設置magic和version
    pa->magic = PROP_AREA_MAGIC;
    pa->version = PROP_AREA_VERSION;

        /* plug into the lib property services */
	//這裏把__system_property_area__也指過去了
__system_property_area__ = pa;
	//設置inited的狀態位
    property_area_inited = 1;
    return 0;
}


1 property內存區域示意圖[1]

1.1 共享內存區域的初始化

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...
         */
//其實就是打開__properties__的設備節點。這個就是在內核中實現的了,具體的分析曉東有機會再和大家一起分析
    fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
    if (fd < 0)
        return -1;
	//改變文件的大小爲size
    if (ftruncate(fd, size) < 0)
        goto out;
//這裏實現的就是“init進程將該區域通過使用了MAP_SHARED標誌的mmap映射至它自身的虛擬地址空間,這樣,任何對於該區域的更新對於所有進程都是可見的[1]”
    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(data == MAP_FAILED)
        goto out;

close(fd);
//重新打開爲readonly
    fd = open("/dev/__properties__", O_RDONLY);
    if (fd < 0)
        return -1;

    //remove this for BLCR
    //unlink("/dev/__properties__");
//這裏賦值data,size和fd參數
    w->data = data;
    w->size = size;
    w->fd = fd;
    return 0;

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

1.2 default.prop爲例詳解property文件的加載

假設一個default.prop的文件內容如下:

#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.secure=1
ro.allow.mock.location=0
ro.debuggable=1
persist.sys.usb.config=mtp,adb
static void load_properties_from_file(const char *fn)
{
    char *data;
    unsigned sz;
//讀出文件中的內容,保存到data所指向的一段內存中,size是sz
    data = read_file(fn, &sz);

if(data != 0) {
//加載對應的value key對
        load_properties(data);
        free(data);
    }
}
static void load_properties(char *data)
{
    char *key, *value, *eol, *sol, *tmp;

sol = data;
//這個while循環就是一個key和value的解析過程了,大概的意思就是把=號前的保存到key中,把=號後的內容保存到value中
    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和我們真正調用的property_set可不是一回事哦,當然最終我們仍然會調用到這個函數。具體見最後的分析
int property_set(const char *name, const char *value)
{
    prop_area *pa;
    prop_info *pi;
//得到name和value的長度
    int namelen = strlen(name);
    int valuelen = strlen(value);
//這裏property的name和value都是有最大長度的哦,name是32,value是92,所以大家在寫自己的name和value的時候,不要超過這個長度哦
    if(namelen >= PROP_NAME_MAX) return -1;
    if(valuelen >= PROP_VALUE_MAX) return -1;
    if(namelen < 1) return -1;
//去prop_info中找一下是否已經有了同名的
    pi = (prop_info*) __system_property_find(name);

if(pi != 0) {
//若是有這個name,假如是以ro開頭,則不修改,直接返回
        /* ro.* properties may NEVER be modified once set */
        if(!strncmp(name, "ro.", 3)) return -1;
	//否則就需要update新的value
        pa = __system_property_area__;
        update_prop_info(pi, value, valuelen);
        pa->serial++;
        __futex_wake(&pa->serial, INT32_MAX);
    } else {
        pa = __system_property_area__;
//首先看一下pa的count是不是已經到max了
        if(pa->count == PA_COUNT_MAX) return -1;
		//這裏知道後面的pa_info,然後保存對應的name和value
        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));
		//count++
        pa->count++;
        pa->serial++;
        __futex_wake(&pa->serial, INT32_MAX);
}
    /* If name starts with "net." treat as a DNS property. */
//若是以net.開頭的,把它作爲一個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.
        */
//同時需要改變net.change的值,net.change本身就在上面直接返回了
        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.
         */
//先寫到temp文件中,暫時不要寫到disk
        write_persistent_property(name, value);
    }
    property_changed(name, value);
    return 0;
}

void property_changed(const char *name, const char *value)
{
//這個參數會在queue_property_triggers_action中調用,目前還是沒有設置的,所以就先這樣
    if (property_triggers_enabled)
        queue_property_triggers(name, value);
}


總得來說property_init_action就是申請共享內存區域,然後load default.prop文件。 

2"set_init_properties"設置初始化的property

property_init_action之後和property相關的就是set_init_properties_action

queue_builtin_action(set_init_properties_action, "set_init_properties");

所以我們接着來分析這個函數:

static int set_init_properties_action(int nargs, char **args)
{
    char tmp[PROP_VALUE_MAX];
	//得到kernel的cmd line的參數
    if (qemu[0])
        import_kernel_cmdline(1, import_kernel_nv);
	//若是工廠模式,設幾個ro的property,有人說這裏是ro的參數,不能只讀不能改嗎,哈哈,你若是仔細看了前面的property_set的函數就會發現,其實這裏若是第一次,還是可以寫的,相當於初始化後就不能改了。
    if (!strcmp(bootmode,"factory"))
        property_set("ro.factorytest", "1");
    else if (!strcmp(bootmode,"factory2"))
        property_set("ro.factorytest", "2");
    else
        property_set("ro.factorytest", "0");
//設置下面這些ro參數的值
    property_set("ro.serialno", serialno[0] ? serialno : "");
    property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
    property_set("ro.baseband", baseband[0] ? baseband : "unknown");
    property_set("ro.carrier", carrier[0] ? carrier : "unknown");
    property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");

    property_set("ro.hardware", hardware);
    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
    property_set("ro.revision", tmp);
    return 0;
}


這個函數就是根據kernelcmdline參數等設置對應的一些ro參數的值,和我們的關係不是很大,知道就可以了。

3property_service_init_action之初始化property service

這個action就是緊接着上面的一些操作了:

queue_builtin_action(property_service_init_action, "property_service_init");

該函數就是初始化property service

static int property_service_init_action(int nargs, char **args)
{
    /* read any property files on system or data and
     * fire up the property service.  This must happen
     * after the ro.foo properties are set above so
     * that /data/local.prop cannot interfere with them.
     */
//啓動property的service
    start_property_service();
    return 0;
}
void start_property_service(void)
{
    int fd;
//加載下面三個文件:"/system/build.prop","/system/default.prop",/data/local.prop"。
//這裏就是網上流傳的4個文件的加載順序的說法,從這裏就可以看到了。
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
    /* Read persistent properties after all default values have been loaded. */
//這裏就是所有的default 值都初始化好了,就加載persistent的property了,還記得前面我們把persisten的property寫到一個文件中去的麼?這裏就再從裏面讀出來好了
    load_persistent_properties();
//創建一個socket,這裏就是盛傳的“在這一步中,一個unix domain socket服務被創建”[1]
    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了
    listen(fd, 8);
	//賦值給property_set_fd
    property_set_fd = fd;
}


這裏主要的工作就是啓動property service,加載了剩下的三個文件,同時新建了一個socket,並且監聽了他的內容。我想下面的內容就可以猜到了,就是對這個socket的數據進行處理了。

4socket有數據後的處理

init.c的最後會有一個循環,用來不停的處理它所監聽的socket。代碼如下:

for(;;){
……
//若是property fd那邊有數據,就處理這邊的數據
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();
……
}
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);

//accept數據
    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }
//得到socket的options
    /* Check socket options here */
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        ERROR("Unable to recieve socket options\n");
        return;
    }
	//recv數據
    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) {
//這裏就是處理我們調用的property_set了,所以,理解了吧,我們調用property_set的時候,其實就是通過socket發送這個msg過來而已。
    case PROP_MSG_SETPROP:
//得到對應的name和value值
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;
//這裏處理ctl.start,ctl.stop等等
        if(memcmp(msg.name,"ctl.",4) == 0) {
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
//檢查value對應的uid和gid,若是root或者system就可以直接處理,而不需要檢查,這就是官大的好處理問題啊。哈哈~~
            if (check_control_perms(msg.value, cr.uid, cr.gid)) {
//根據start,stop還是restart來進行service的對應的處理,我們就不詳細關注了哦
                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 {
//這裏同樣會check permission,不同的是system沒有特權了,只有root纔有特權
            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);
            }
//這裏就是和bionic 那邊進行通信的,就是寫好了,這裏關閉。然後bionic那邊進行監聽。這裏有個問題,就是我們若是找不到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);
        }
        break;

    default:
        close(s);
        break;
    }
}


這裏就是通過socket來得到對應msg進行對應的處理。完成property_set的真正操作。然後通過close來通知client那邊這裏ok了。

5、真正property_set的實現

其實上面我們也是通過代碼猜測一下收到的msg處理狀況,並沒有去看我們調用property_set的流程,這裏我們簡單看一下,他位於/bionic/libc/bionic/system_properties.c文件中:

我們調用property_set最終會調用到這個函數:

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;

    memset(&msg, 0, sizeof msg);
//這裏就是我們收到的msg吧
    msg.cmd = PROP_MSG_SETPROP;
    strlcpy(msg.name, key, sizeof msg.name);
    strlcpy(msg.value, value, sizeof msg.value);
//發送這個msg
    err = send_prop_msg(&msg);
    if(err < 0) {
        return err;
    }

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

    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;
//connect
    if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen) < 0)) {
        close(s);
        return result;
    }
//Send,哈哈
    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;
        r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
        if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {
            result = 0;
        } else {
//就是這個地方太壞了,超時了,他也說是ok的。所以,哎~~~大家使用的時候自求多福吧,這段代碼太。。。。
            // 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;
}


因此,client端就是真的調用socket,然後send對應的msg,接着等待迴應,反正都是ok的。他是不會出錯,我就曾今遇到沒有寫進去,但是返回值仍然是對的情況。害的我查了很久,這段代碼寫的。。。。不吐槽了。。。。

 

至此,所有property相關的內容都分析好了,您還有什麼疑問麼?

 

參考文章:

[1]http://blog.csdn.net/jackyu613/article/details/6136620

 

若您覺得該文章對您有幫助,請在下面用鼠標輕輕按一下“頂”,哈哈~~·
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章