深刻講解Android Property機制

深刻講解Android Property機制java


侯亮node


1      概述

     Android系統(本文以Android 4.4爲準)的屬性(Property)機制有點兒相似Windows系統的註冊表,其中的每一個屬性被組織成簡單的鍵值對(key/value)供外界使用。linux

     咱們能夠經過在adb shell裏敲入getprop命令來獲取當前系統的全部屬性內容,並且,咱們還能夠敲入相似「getprop 屬性名」的命令來獲取特定屬性的值。另外,設置屬性值的方法也很簡單,只需敲入「setprop 屬性名 新值」命令便可。android

但是問題在於咱們不想只認識到這個層次,咱們但願瞭解更多一些Property機制的運做機理,而這纔是本文關心的重點。程序員

 

     說白了,Property機制的運做機理能夠彙總成如下幾句話:
1)  系統一啓動就會從若干屬性腳本文件中加載屬性內容;
2)  系統中的全部屬性(key/value)會存入同一塊共享內存中;
3)  系統中的各個進程會將這塊共享內存映射到本身的內存空間,這樣就能夠直接讀取屬性內容了;
4)  系統中只有一個實體能夠設置、修改屬性值,它就是屬性服務(Property Service);
5)  不一樣進程只能夠經過socket方式,向屬性服務發出修改屬性值的請求,而不能直接修改屬性值;
6)  共享內存中的鍵值內容會以一種字典樹的形式進行組織。shell

 

Property機制的示意圖以下:數據結構

 

 

 

2      Property Service

 

2.1  init進程裏的Property Service

Property Service實體實際上是在init進程裏啓動的。咱們知道,init是Linux系統中用戶空間的第一個進程。它負責建立系統中最關鍵的幾個子進程,好比zygote等等。在本節中,咱們主要關心init進程是如何啓動Property Service的。app

 

咱們查看core/init/Init.c文件,能夠看到init進程的main()函數,它裏面和property相關的關鍵動做有:
1)間接調用__system_property_area_init():打開屬性共享內存,並記入__system_property_area變量;
2)間接調用init_workspace():只讀打開屬性共享內存,並記入環境變量;
3)根據init.rc,異步激發property_service_init_action(),該函數中會:
    l  加載若干屬性文本文件,將具體屬性、屬性值記入屬性共享內存;
    l  建立並監聽socket;
4)根據init.rc,異步激發queue_property_triggers_action(),將剛剛加載的屬性對應的激發動做,推入action列表。異步

 

main()中的調用關係以下:socket

 

 

2.1.1   初始化屬性共享內存

     咱們能夠看到,在init進程的main()函數裏,展轉打開了一個內存文件「/dev/__properties__」,並把它設定爲128KB大小,接着調用mmap()將這塊內存映射到init進程空間了。這個內存的首地址被記錄在__system_property_area__全局變量裏,之後每添加或修改一個屬性,都會基於這個__system_property_area__變量來計算位置。

 

      初始化屬性內存塊時,爲何要兩次open那個/dev/__properties__文件呢?我想緣由是這樣的:第一次open的句柄,最終是給屬性服務本身用的,因此須要有讀寫權限;而第二次open的句柄,會被記入pa_workspace.fd,並在合適時機添加進環境變量,供其餘進程使用,所以只能具備讀取權限。

 

第一次open時,執行的代碼以下: 
fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444); 
傳給open()的參數標識裏指明瞭O_RDWR,表示用「讀寫方式」打開文件。另外O_NOFOLLOW標識主要是爲了防止咱們打開「符號連接」,不過咱們知道,__properties__文件並非符號連接,因此固然能夠成功open。O_CLOEXEC標識是爲了保證一種獨佔性,也就是說當init進程打開這個文件時,此時就算其餘進程也open這個文件,也會在調用exec執行新程序時自動關閉該文件句柄。O_EXCL標識和O_CREATE標識配合起來,表示若是文件不存在,則建立之,而若是文件已經存在,那麼open就會失敗。第一次open動做後,會給__system_property_area__賦值,而後程序會當即close剛打開的句柄。

 

第二次open動做發生在接下來的init_workspace()函數裏。此時會再一次打開__properties__文件,此次倒是以只讀模式打開的: 
int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW); 
打開的句柄記錄在pa_workspace.fd處,之後每當init進程執行socket命令,並調用service_start()時,會執行相似下面的句子:

[cpp]  view plain  copy
 
  1. get_property_workspace(&fd, &sz);   // 讀取pa_workspace.fd  
  2. sprintf(tmp, "%d,%d", dup(fd), sz);  
  3. add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);  
說白了就是把 pa_workspace.fd 的句柄記入一個名叫「 ANDROID_PROPERTY_WORKSPACE 」的環境變量去。

 

 

 

 

【system/core/init/Init.c】

[cpp]  view plain  copy
 
  1. /* add_environment - add "key=value" to the current environment */  
  2. int add_environment(const char *key, const char *val)  
  3. {  
  4.     int n;  
  5.   
  6.     for (n = 0; n < 31; n++) {  
  7.         if (!ENV[n]) {  
  8.             size_t len = strlen(key) + strlen(val) + 2;  
  9.             char *entry = malloc(len);  
  10.             snprintf(entry, len, "%s=%s", key, val);  
  11.             ENV[n] = entry;  
  12.             return 0;  
  13.         }  
  14.     }  
  15.   
  16.     return 1;  
  17. }  

這個環境變量在往後有可能被其餘進程拿來用,從而將屬性內存區映射到本身的內存空間去,這個後文會細說。

         接下來,main()函數在設置好屬性內存塊以後,會調用queue_builtin_action()函數向內部的action_list列表添加action節點。關於這部分的詳情,可參考其餘講述Android啓動機制的文檔,這裏再也不贅述。咱們只需知道,後續,系統會在合適時機回調「由queue_builtin_action()的參數」所指定的property_service_init_action()函數就能夠了。

 

2.1.2   初始化屬性服務

property_service_init_action()函數只是在簡單調用start_property_service()而已,後者的代碼以下:

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. void start_property_service(void)  
  2. {  
  3.     int fd;  
  4.   
  5.     load_properties_from_file(PROP_PATH_SYSTEM_BUILD);  
  6.     load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);  
  7.   
  8.     /* Read vendor-specific property runtime overrides. */  
  9.     vendor_load_properties();  
  10.   
  11.     load_override_properties();  
  12.     /* Read persistent properties after all default values have been loaded. */  
  13.     load_persistent_properties();  
  14.   
  15.     fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);  
  16.     if(fd < 0) return;  
  17.     fcntl(fd, F_SETFD, FD_CLOEXEC);  
  18.     fcntl(fd, F_SETFL, O_NONBLOCK);  
  19.   
  20.     listen(fd, 8);  
  21.     property_set_fd = fd;  
  22. }  

其主要動做無非是加載若干屬性文件,而後建立並監聽一個socket接口。

 

2.1.2.1  加載屬性文本文件

start_property_service()函數首先會調用load_properties_from_file()函數,嘗試加載一些屬性腳本文件,並將其中的內容寫入屬性內存塊裏。從代碼裏能夠看到,主要加載的文件有: 
l  /system/build.prop 
l  /system/default.prop(該文件不必定存在) 
l  /data/local.prop 
l  /data/property目錄裏的若干腳本

load_properties_from_file()函數的代碼以下:

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. static void load_properties_from_file(const char *fn)  
  2. {  
  3.     char *data;  
  4.     unsigned sz;  
  5.   
  6.     data = read_file(fn, &sz);  
  7.   
  8.     if(data != 0) {  
  9.         load_properties(data);  
  10.         free(data);  
  11.     }  
  12. }  

其中調用的read_file()函數很簡單,只是把文件內容的全部字節讀入一個buffer,並在內容最後添加兩個字節:’\n’和0。

 

        接着調用的load_properties()函數,會逐行分析傳來的buffer,解析出行內的key、value部分,並調用property_set(),將key、value設置進系統的屬性共享內存去。

        咱們繪製出property_service_init_action()函數的調用關係圖,以下:


2.1.2.2  建立socket接口

在加載動做完成後,start_property_service ()會建立一個socket接口,並監聽這個接口。

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);  
  2. if(fd < 0) return;  
  3. fcntl(fd, F_SETFD, FD_CLOEXEC);  
  4. fcntl(fd, F_SETFL, O_NONBLOCK);  
  5. listen(fd, 8);  
  6. property_set_fd = fd;  

這個socket是專門用來監聽其餘進程發來的「修改」屬性值的命令的,它被設置成「非阻塞」(O_NONBLOCK)的socket。

2.1.3   初始化屬性後的觸發動做

既然在上一小節的property_service_init_action()動做中,系統已經把必要的屬性都加載好了,那麼如今就能夠遍歷剛生成的action_list,看看哪一個剛加載好的屬性能夠進一步觸發連鎖動做。這就是init進程裏爲何有兩次和屬性相關的queue_builtin_action()的緣由。

【system/core/init/Init.c】

[cpp]  view plain  copy
 
  1. static int queue_property_triggers_action(int nargs, char **args)  
  2. {  
  3.     queue_all_property_triggers();  
  4.     /* enable property triggers */  
  5.     property_triggers_enabled = 1;  
  6.     return 0;  
  7. }  

【system/core/init/Init_parser.c】

[cpp]  view plain  copy
 
  1. void queue_all_property_triggers()  
  2. {  
  3.     struct listnode *node;  
  4.     struct action *act;  
  5.     list_for_each(node, &action_list) {  
  6.         act = node_to_item(node, struct action, alist);  
  7.         if (!strncmp(act->name, "property:", strlen("property:"))) {  
  8.             /* parse property name and value 
  9.                syntax is property:<name>=<value> */  
  10.             const char* name = act->name + strlen("property:");  
  11.             const char* equals = strchr(name, '=');  
  12.             if (equals) {  
  13.                 char prop_name[PROP_NAME_MAX + 1];  
  14.                 char value[PROP_VALUE_MAX];  
  15.                 int length = equals - name;  
  16.                 if (length > PROP_NAME_MAX) {  
  17.                     ERROR("property name too long in trigger %s", act->name);  
  18.                 } else {  
  19.                     memcpy(prop_name, name, length);  
  20.                     prop_name[length] = 0;  
  21.   
  22.                     /* does the property exist, and match the trigger value? */  
  23.                     property_get(prop_name, value);  
  24.                     if (!strcmp(equals + 1, value) ||!strcmp(equals + 1, "*")) {  
  25.                         action_add_queue_tail(act);  
  26.                     }  
  27.                 }  
  28.             }  
  29.         }  
  30.     }  
  31. }  

這段代碼是說,當獲取的屬性名和屬性值,與當初init.rc裏記錄的某action的激發條件匹配時,就把該action插入執行隊列的尾部(action_add_queue_tail(act))。

 

2.2  init進程循環監聽socket

         如今再回過頭看init進程,其main()函數的最後,咱們能夠看到一個for(;;)循環,不斷監聽外界發來的命令,包括設置屬性的命令。

【system/core/init/Init.c】

[cpp]  view plain  copy
 
  1. for(;;) {  
  2.     . . . . . .  
  3.     . . . . . .  
  4.     nr = poll(ufds, fd_count, timeout);  
  5.     if (nr <= 0)  
  6.         continue;  
  7.   
  8.     for (i = 0; i < fd_count; i++) {  
  9.         if (ufds[i].revents == POLLIN) {  
  10.             if (ufds[i].fd == get_property_set_fd())  
  11.                 handle_property_set_fd();  
  12.             else if (ufds[i].fd == get_keychord_fd())  
  13.                 handle_keychord();  
  14.             else if (ufds[i].fd == get_signal_fd())  
  15.                 handle_signal();  
  16.         }  
  17.     }  
  18. }  

 

2.2.1   處理「ctl.」命令

         當從socket收到「設置屬性」的命令後,會調用上面的handle_property_set_fd()函數,代碼截選以下:

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. void handle_property_set_fd()  
  2. {  
  3.     prop_msg msg;  
  4.     . . . . . .  
  5.     if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {  
  6.         return;  
  7.     }  
  8.     . . . . . .   
  9.     switch(msg.cmd) {  
  10.     case PROP_MSG_SETPROP:  
  11.         msg.name[PROP_NAME_MAX-1] = 0;  
  12.         msg.value[PROP_VALUE_MAX-1] = 0;  
  13.         . . . . . .  
  14.         if(memcmp(msg.name,"ctl.",4) == 0) {  
  15.             . . . . . .  
  16.             if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) {  
  17.                 handle_control_message((char*) msg.name + 4, (char*) msg.value);  
  18.             } else {  
  19.                 ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",  
  20.                         msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);  
  21.             }  
  22.         } else {  
  23.             if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {  
  24.                 property_set((char*) msg.name, (char*) msg.value);  
  25.             } else {  
  26.                 ERROR("sys_prop: permission denied uid:%d  name:%s\n",  
  27.                       cr.uid, msg.name);  
  28.             }  
  29.             . . . . . .  
  30.             close(s);  
  31.         }  
  32.         . . . . . .  
  33.         break;  
  34.     . . . . . .  
  35.     }  
  36. }<span style="font-family:宋體;margin: 0px; padding: 0px;"></span>  

        看到了嗎?設置屬性時,一開始就把屬性名和屬性值的長度都限制了。

[cpp]  view plain  copy
 
  1. #define PROP_NAME_MAX   32  
  2. #define PROP_VALUE_MAX  92  

也就是說,有意義的部分的最大字節數分別爲31字節和91字節,最後一個字節先被強制設爲0了。

 

2.2.1.1  check_control_perms()

        對於普通屬性而言,主要是調用property_set()來設置屬性值,可是有一類特殊屬性是以「ctl.」開頭的,它們本質上是一些控制命令,好比啓動某個系統服務。這種控制命令需調用handle_control_message()來處理。

固然,並非隨便誰均可以發出這種控制命令的,也就是說,不是誰均可以成功設置以「ctl.」開頭的特殊屬性。handle_property_set_fd()會先調用check_control_perms()來檢查發起方是否具備相應的權限。

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. static int check_control_perms(const char *name, unsigned int uid, unsigned int gid, char *sctx) {  
  2.   
  3.     int i;  
  4.     if (uid == AID_SYSTEM || uid == AID_ROOT)  
  5.       return check_control_mac_perms(name, sctx);  
  6.   
  7.     /* Search the ACL */  
  8.     for (i = 0; control_perms[i].service; i++) {  
  9.         if (strcmp(control_perms[i].service, name) == 0) {  
  10.             if ((uid && control_perms[i].uid == uid) ||  
  11.                 (gid && control_perms[i].gid == gid)) {  
  12.                 return check_control_mac_perms(name, sctx);  
  13.             }  
  14.         }  
  15.     }  
  16.     return 0;  
  17. }  

能夠看到,若是設置方的uid是AID_SYSTEM或者AID_ROOT,那麼通常都是具備權限的。而若是uid是其餘值,那麼就得查control_perms表了,這個表的定義以下:

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. /* 
  2.  * White list of UID that are allowed to start/stop services. 
  3.  * Currently there are no user apps that require. 
  4.  */  
  5. struct {  
  6.     const char *service;  
  7.     unsigned int uid;  
  8.     unsigned int gid;  
  9. } control_perms[] = {  
  10.     { "dumpstate",AID_SHELL, AID_LOG },  
  11.     { "ril-daemon",AID_RADIO, AID_RADIO },  
  12.      {NULL, 0, 0 }  
  13. };  

uid爲AID_SHELL的進程能夠啓動、中止dumpstate服務,uid爲AID_RADIO的進程能夠啓動、中止ril-daemon服務。

 

2.2.1.2  handle_control_message()

         在經過權限檢查以後,就能夠調用handle_control_message()來處理控制命令了:

【system/core/init/Init.c】

[cpp]  view plain  copy
 
  1. void handle_control_message(const char *msg, const char *arg)  
  2. {  
  3.     if (!strcmp(msg,"start")) {  
  4.         msg_start(arg);  
  5.     } else if (!strcmp(msg,"stop")) {  
  6.         msg_stop(arg);  
  7.     } else if (!strcmp(msg,"restart")) {  
  8.         msg_restart(arg);  
  9.     } else {  
  10.         ERROR("unknown control msg '%s'\n", msg);  
  11.     }  
  12. }  

 

    假設從socket發來的命令是「ctl.start」,那麼就會走到msg_start(arg)。

[cpp]  view plain  copy
 
  1. static void msg_start(const char *name)  
  2. {  
  3.     struct service *svc = NULL;  
  4.     char *tmp = NULL;  
  5.     char *args = NULL;  
  6.   
  7.     if (!strchr(name, ':'))  
  8.         svc = service_find_by_name(name);  
  9.     else {  
  10.         tmp = strdup(name);  
  11.         if (tmp) {  
  12.             args = strchr(tmp, ':');  
  13.             *args = '\0';  
  14.             args++;  
  15.   
  16.             svc = service_find_by_name(tmp);  
  17.         }  
  18.     }  
  19.   
  20.     if (svc) {  
  21.         service_start(svc, args);  
  22.     } else {  
  23.         ERROR("no such service '%s'\n", name);  
  24.     }  
  25.     if (tmp)  
  26.         free(tmp);  
  27. }  

這裏啓動的service基本上都是在init.rc裏說明的系統service。好比netd:

 

咱們知道,init進程在分析init.rc文件時,會造成一個service鏈表,如今msg_start()就是從這個service鏈表裏去查找相應名稱的service節點的。找到節點後,再調用service_start(svc, args)。

 

service_start()經常會fork一個子進程,而後爲它設置環境變量(ANDROID_PROPERTY_WORKSPACE):

[cpp]  view plain  copy
 
  1. void service_start(struct service *svc, const char *dynamic_args)  
  2. {  
  3.     . . . . . .  
  4.     . . . . . .  
  5.     pid = fork();  
  6.   
  7.     if (pid == 0) {  
  8.         struct socketinfo *si;  
  9.         struct svcenvinfo *ei;  
  10.         char tmp[32];  
  11.         int fd, sz;  
  12.   
  13.         umask(077);  
  14.         if (properties_inited()) {  
  15.             get_property_workspace(&fd, &sz);  
  16.             sprintf(tmp, "%d,%d", dup(fd), sz);  
  17.             add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);  
  18.         }  
  19.   
  20.         for (ei = svc->envvars; ei; ei = ei->next)  
  21.             add_environment(ei->name, ei->value);  
  22.     . . . . . .  
其中 get_property_workspace() 的代碼以下: 
[cpp]  view plain  copy
 
  1. void get_property_workspace(int *fd, int *sz)  
  2. {  
  3.     *fd = pa_workspace.fd;  
  4.     *sz = pa_workspace.size;  
  5. }  

你們還記得前文闡述init_workspace()時,把打開的句柄記入pa_workspace.fd的句子吧,如今就是在用這個句柄。

 

一切準備好後,service_start()會調用execve(),執行svc->args[0]所指定的可執行文件,而後還要再寫個屬性值:

[cpp]  view plain  copy
 
  1. void service_start(struct service *svc, const char *dynamic_args)  
  2. {  
  3.     . . . . . .  
  4.     . . . . . .  
  5.     execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);  
  6.     . . . . . .  
  7.     . . . . . .  
  8.     svc->time_started = gettime();  
  9.     svc->pid = pid;  
  10.     svc->flags |= SVC_RUNNING;  
  11.   
  12.     if (properties_inited())  
  13.         notify_service_state(svc->name, "running");  
  14. }  
其中的notify_service_state()的代碼以下:
[cpp]  view plain  copy
 
  1. void notify_service_state(const char *name, const char *state)  
  2. {  
  3.     char pname[PROP_NAME_MAX];  
  4.     int len = strlen(name);  
  5.     if ((len + 10) > PROP_NAME_MAX)  
  6.         return;  
  7.     snprintf(pname, sizeof(pname), "init.svc.%s", name);  
  8.     property_set(pname, state);  
  9. }  
 

通常狀況下,這種在init.rc裏記錄的系統service的名字都不會超過22個字節,加上「init.svc.」前綴也不會超過31個字節,因此每次啓動service,都會修改相應的屬性。好比netd服務,一旦它被啓動,就會將init.svc.netd屬性的值設爲「running」。

以上是handle_control_message()處理「ctl.start」命令時的狀況,相應地還有處理「ctl.stop」命令的狀況,此時會調用到msg_stop()。

【system/core/init/Init.c】

[cpp]  view plain  copy
 
  1. static void msg_stop(const char *name)  
  2. {  
  3.     struct service *svc = service_find_by_name(name);  
  4.   
  5.     if (svc) {  
  6.         service_stop(svc);  
  7.     } else {  
  8.         ERROR("no such service '%s'\n", name);  
  9.     }  
  10. }  

 

[cpp]  view plain  copy
 
  1. void service_stop(struct service *svc)  
  2. {  
  3.     service_stop_or_reset(svc, SVC_DISABLED);  
  4. }  

 

[cpp]  view plain  copy
 
  1. static void service_stop_or_reset(struct service *svc, int how)  
  2. {  
  3.     /* The service is still SVC_RUNNING until its process exits, but if it has 
  4.      * already exited it shoudn't attempt a restart yet. */  
  5.     svc->flags &= (~SVC_RESTARTING);  
  6.   
  7.     if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {  
  8.         /* Hrm, an illegal flag.  Default to SVC_DISABLED */  
  9.         how = SVC_DISABLED;  
  10.     }  
  11.         /* if the service has not yet started, prevent 
  12.          * it from auto-starting with its class 
  13.          */  
  14.     if (how == SVC_RESET) {  
  15.         svc->flags |= (svc->flags & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET;  
  16.     } else {  
  17.         svc->flags |= how;  
  18.     }  
  19.   
  20.     if (svc->pid) {  
  21.         NOTICE("service '%s' is being killed\n", svc->name);  
  22.         kill(-svc->pid, SIGKILL);  
  23.         notify_service_state(svc->name, "stopping");  
  24.     } else {  
  25.         notify_service_state(svc->name, "stopped");  
  26.     }  
  27. }  

能夠看到,中止一個service時,主要是調用kill( )來殺死服務子進程,並將init.svc.xxx屬性值設爲stopping。

       OK,終於把init進程裏,處理「ctl.」命令的部分講完了,下面咱們接着看init進程處理普通屬性的部分。

 

2.2.2   處理屬性設置命令

咱們仍是先回到前文init進程處理屬性設置動做的地方:

[cpp]  view plain  copy
 
  1. void handle_property_set_fd()  
  2. {  
  3.         . . . . . .  
  4.         if(memcmp(msg.name,"ctl.",4) == 0) {  
  5.             . . . . . .  
  6.         } else {  
  7.             if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {  
  8.                 property_set((char*) msg.name, (char*) msg.value);  
  9.             } else {  
  10.                 ERROR("sys_prop: permission denied uid:%d  name:%s\n",  
  11.                       cr.uid, msg.name);  
  12.             }  
  13.             . . . . . .  
  14.             close(s);  
  15.         }  
  16.         . . . . . .  
  17.         break;  
  18.     . . . . . .  
  19.     }  
  20. }  

 

2.2.2.1  check_perms()

要設置普通屬性,也是要具備必定權限哩。請看上面的 check_perms() 一句。該函數的代碼以下:
[cpp]  view plain  copy
 
  1. static int check_perms(const char *name, unsigned int uid, unsigned int gid, char *sctx)  
  2. {  
  3.     int i;  
  4.     unsigned int app_id;  
  5.   
  6.     if(!strncmp(name, "ro.", 3))  
  7.         name +=3;  
  8.   
  9.     if (uid == 0)  
  10.         return check_mac_perms(name, sctx);  
  11.   
  12.     app_id = multiuser_get_app_id(uid);  
  13.     if (app_id == AID_BLUETOOTH) {  
  14.         uid = app_id;  
  15.     }  
  16.   
  17.     for (i = 0; property_perms[i].prefix; i++) {  
  18.         if (strncmp(property_perms[i].prefix, name,  
  19.                     strlen(property_perms[i].prefix)) == 0) {  
  20.             if ((uid && property_perms[i].uid == uid) ||  
  21.                 (gid && property_perms[i].gid == gid)) {  
  22.   
  23.                 return check_mac_perms(name, sctx);  
  24.             }  
  25.         }  
  26.     }  
  27.   
  28.     return 0;  
  29. }  

主要也是在查表,property_perms表的定義以下:

 

      這其實很容易理解,好比要設置「sys.」打頭的系統屬性,進程的uid就必須是AID_SYSTEM,不然阿貓阿狗都能設置系統屬性,豈不糟糕。


2.2.2.2  property_set()

權限檢查經過以後,就能夠真正設置屬性了。在前文「概述」一節中,咱們已經說過,只有Property Service(即init進程)能夠寫入屬性值,而普通進程最多隻能經過socket向Property Service發出設置新屬性值的請求,最終還得靠Property Service來寫。那麼咱們就來看看Property Service裏具體是怎麼寫的。

         整體說來,property_set()會作以下工做:

1)  判斷待設置的屬性名是否合法; 
2)  盡力從「屬性共享內存」中找到匹配的prop_info節點,若是能找到,就調用__system_property_update(),固然若是屬性是以「ro.」打頭的,說明這是個只讀屬性,此時不會update的;若是找不到,則調用__system_property_add()添加屬性節點。 
3)  在update或add動做以後,還須要作一些善後處理。好比,若是改動的是「net.」開頭的屬性,那麼須要從新設置一下net.change屬性,屬性值爲剛剛設置的屬性名字。 
4)  若是要設置persist屬性的話,只有在系統將全部的默認persist屬性都加載完畢後,才能設置成功。persist屬性應該是那種會存入可持久化文件的屬性,這樣,系統在下次啓動後,能夠將該屬性的初始值設置爲系統上次關閉時的值。
5)  若是將「selinux.reload_policy」屬性設爲「1」了,那麼會進一步調用selinux_reload_policy()。這個意味着要從新加載SEAndroid策略。 
6)  最後還需調用property_changed()函數,其內部會執行init.rc中指定的那些和property同名的action。

【core/init/Property_service.c】

[cpp]  view plain  copy
 
  1. int property_set(const char *name, const char *value)  
  2. {  
  3.     . . . . . .  
  4.     . . . . . .  
  5.     pi = (prop_info*) __system_property_find(name);  
  6.   
  7.     if(pi != 0) {  
  8.         if(!strncmp(name, "ro.", 3)) return -1;  
  9.         __system_property_update(pi, value, valuelen);  
  10.     } else {  
  11.         ret = __system_property_add(name, namelen, value, valuelen);  
  12.         . . . . . .  
  13.     }  
  14.       
  15.     if (strncmp("net.", name, strlen("net.")) == 0)  {  
  16.         if (strcmp("net.change", name) == 0) {  
  17.             return 0;  
  18.         }  
  19.         property_set("net.change", name);  
  20.     } else if (persistent_properties_loaded &&  
  21.             strncmp("persist.", name, strlen("persist.")) == 0) {  
  22.         write_persistent_property(name, value);  
  23.     } else if (strcmp("selinux.reload_policy", name) == 0 &&  
  24.                strcmp("1", value) == 0) {  
  25.         selinux_reload_policy();  
  26.     }  
  27.     property_changed(name, value);  
  28.     return 0;  
  29. }  

         一開始固然要先找到「但願設置的目標屬性」在共享內存裏對應的prop_info節點啦,後續關於__system_property_update()和__system_property_add()的操做,主要都是在操做該prop_info節點,代碼比較簡單。prop_info的詳細內容咱們會在下文闡述,這裏先跳過。

         若是能夠找到prop_info節點,就儘可能將這個屬性的值更新一下,除非是遇到「ro.」屬性,這種屬性是隻讀的,固然不能set。若是找不到prop_info節點,此時會爲這個新屬性建立若干字典樹節點,包括最終的prop_info葉子。

         屬性寫入完畢後,還要調用property_changed(),作一些善後處理:

【system/core/init/Init.c】

[cpp]  view plain  copy
 
  1. void property_changed(const char *name, const char *value)  
  2. {  
  3.     if (property_triggers_enabled)  
  4.         queue_property_triggers(name, value);  
  5. }  

【 system/core/init/Init_parser.c 】 

[cpp]  view plain  copy
 
  1. void queue_property_triggers(const char *name, const char *value)  
  2. {  
  3.     struct listnode *node;  
  4.     struct action *act;  
  5.     list_for_each(node, &action_list) {  
  6.         act = node_to_item(node, struct action, alist);  
  7.         if (!strncmp(act->name, "property:", strlen("property:"))) {  
  8.             const char *test = act->name + strlen("property:");  
  9.             int name_length = strlen(name);  
  10.   
  11.             if (!strncmp(name, test, name_length) &&  
  12.                     test[name_length] == '=' &&  
  13.                     (!strcmp(test + name_length + 1, value) ||  
  14.                      !strcmp(test + name_length + 1, "*"))) {  
  15.                 action_add_queue_tail(act);  
  16.             }  
  17.         }  
  18.     }  
  19. }  

 

[cpp]  view plain  copy
 
  1. void action_add_queue_tail(struct action *act)  
  2. {  
  3.     if (list_empty(&act->qlist)) {  
  4.         list_add_tail(&action_queue, &act->qlist);  
  5.     }  
  6. }  

從代碼能夠看出,當某個屬性修改以後, Property Service 會遍歷一遍 action_list 列表,找到其中匹配的 action 節點,並將之添加進 action_queue 隊列。之因此會有 if (list_empty(&act->qlist)) 判斷,是爲了防止重複添加。下面是 init.rc 腳本中的一個片斷: 

【system/core/rootdir/init.rc】 

這幾個就是和property相關的action,其餘相關的action還有很多,咱們就不列了。咱們以第一個action爲例來講明。若是咱們修改了vold.decrypt屬性的值,那麼queue_property_triggers()搜索action_list時,就能找到一個名爲「property:vold.decrypt=trigger_reset_main」的action節點,此時的邏輯無非是比較「冒號後的名字」、「賦值號後的值」,是否分別和queue_property_triggers()的name、value參數匹配,若是匹配,就把這個action節點添加進action_queue隊列裏。

3      客戶進程訪問屬性的機制

3.1  映射「屬性共享內存」的時機

如今有一個問題必須先提出來,那就是「屬性共享內存」是在什麼時刻映射進用戶進程空間的?總不會無緣無故地就能夠成功調用property_get()吧。其實,爲了讓你們方便地調用property_get(),屬性機制的設計者的確是用了一點兒小技巧,下面咱們就來看看細節。

3.1.1   靜態加載時的初始化

在前文介紹Init進程初始化屬性共享內存時,調用了一個叫作__system_property_area_init()的函數:

【bionic/libc/bionic/System_properties.c】

[cpp]  view plain  copy
 
  1. int __system_property_area_init()  
  2. {  
  3.     return map_prop_area_rw();  
  4. }  

它映射時須要的是讀寫權限。而對普通進程而言,只有讀權限,固然不可能調用__system_property_area_init()了。其實在System_properties.c文件中,咱們還能夠找到另外一個長得挺像的初始化函數——__system_properties_init():

[cpp]  view plain  copy
 
  1. int __system_properties_init()  
  2. {  
  3.     return map_prop_area();  
  4. }  

它調用的map_prop_area()會把屬性共享內存,以只讀模式映射到用戶進程空間:

[cpp]  view plain  copy
 
  1. static int map_prop_area()  
  2. {  
  3.     fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);  
  4.     . . . . . .  
  5.     if ((fd < 0) && (errno == ENOENT)) {  
  6.         fd = get_fd_from_env();    
  7.         fromFile = false;  
  8.     }  
  9.   
  10.     . . . . . .  
  11.     pa_size = fd_stat.st_size;  
  12.     pa_data_size = pa_size - sizeof(prop_area);  
  13.     prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);  
  14.     . . . . . .  
  15.     result = 0;  
  16.     __system_property_area__ = pa;  
  17.     . . . . . .  
  18.   
  19.     return result;  
  20. }  

其中調用的get_fd_from_env()的代碼以下:

[cpp]  view plain  copy
 
  1. static int get_fd_from_env(void)  
  2. {  
  3.     char *env = getenv("ANDROID_PROPERTY_WORKSPACE");  
  4.     if (!env) {  
  5.         return -1;  
  6.     }  
  7.     return atoi(env);  
  8. }  

哇,終於看到讀取「ANDROID_PROPERTY_WORKSPACE」環境變量的地方啦。不過呢,它的重要性彷佛並無咱們一開始想的那麼大。在map_prop_area()函數裏分明寫着,只有在open()屬性文件不成功的狀況下,纔會嘗試從環境變量中讀取文件句柄,而通常都會open成功的。無論文件句柄fd是怎麼獲得的吧,反正能映射成空間地址就行。映射後的空間地址,仍然會記錄在__system_property_area__全局變量中。

 

         如今咱們只需找到調用__system_properties_init()的源頭就能夠了。通過查找,咱們發現__libc_init_common()會調用它,代碼以下:

【bionic/libc/bionic/Libc_init_common.cpp】

[cpp]  view plain  copy
 
  1. void __libc_init_common(KernelArgumentBlock& args) {  
  2.   . . . . . .  
  3.   . . . . . .  
  4.   _pthread_internal_add(main_thread);  
  5.   __system_properties_init(); // Requires 'environ'.  
  6. }  

這個函數但是在bionic目錄裏的,小技巧已經用到C庫裏啦。

         __libc_init_common()又會被__libc_init()調用:

【bionic/libc/bionic/Libc_init_static.cpp】

[cpp]  view plain  copy
 
  1. __noreturn void __libc_init(void* raw_args,  
  2.                             void (*onexit)(void),  
  3.                             int (*slingshot)(int, char**, char**),  
  4.                             structors_array_t const * const structors) {  
  5.   KernelArgumentBlock args(raw_args);  
  6.   __libc_init_tls(args);  
  7.   __libc_init_common(args);  
  8.   . . . . . .  
  9.   . . . . . .  
  10.   call_array(structors->preinit_array);  
  11.   call_array(structors->init_array);  
  12.   . . . . . .  
  13.   exit(slingshot(args.argc, args.argv, args.envp));  
  14. }  

        當一個用戶進程被調用起來時,內核會先調用到C運行期庫(crtbegin)層次來初始化運行期環境,在這個階段就會調用到__libc_init(),然後纔會間接調用到C程序員熟悉的main()函數。可見屬性共享內存在執行main()函數以前就已經映射好了。

3.1.2   動態加載時的初始化

除了__libc_init()中會調用__libc_init_common(),還有一處會調用。

【bionic/libc/bionic/Libc_init_dynamic.cpp】

[cpp]  view plain  copy
 
  1. __attribute__((constructor)) static void __libc_preinit() {  
  2.   . . . . . .  
  3.   __libc_init_common(*args);  
  4.   . . . . . .  
  5.   pthread_debug_init();  
  6.   malloc_debug_init();  
  7. }  

請你們注意函數名那一行起始處的__attribute__((constructor))屬性,這是GCC的一個特有屬性。被這種屬性修飾的函數會被放置在特殊的代碼段中。這樣,當動態連接器一加載libc.so時,會盡早執行__libc_preinit()函數。這樣一來,動態庫裏也能夠放心調用property_get()了。

 

3.2  讀取屬性值

下面咱們來集中精力研究讀取屬性值的部分。咱們在前文留下過一個尾巴,當時對屬性共享內存塊裏的prop_info節點,只作了很是簡略的說起,如今咱們就來細說它。

 

說白了,屬性共享內存中的內容,其實被組織成一棵字典樹。內存塊的第一個節點是個特殊的總述節點,類型爲prop_area。緊隨其後的就是字典樹的「樹枝」和「樹葉」了,樹枝以prop_bt表達,樹葉以prop_info表達。咱們讀取或設置屬性值時,最終都只是在操做「葉子」節點而已。

 

3.2.1   「屬性共享內存」裏的數據結構

 

 【bionic/libc/bionic/System_properties.c】

[cpp]  view plain  copy
 
  1. struct prop_area {  
  2.     unsigned bytes_used;  
  3.     unsigned volatile serial;  
  4.     unsigned magic;  
  5.     unsigned version;  
  6.     unsigned reserved[28];  
  7.     char data[0];  
  8. };  
  9.   
  10. typedef struct prop_area prop_area;  
  11.   
  12. struct prop_info {  
  13.     unsigned volatile serial;  
  14.     char value[PROP_VALUE_MAX];  
  15.     char name[0];  
  16. };  
  17.   
  18. typedef struct prop_info prop_info;  

 

[cpp]  view plain  copy
 
  1. typedef volatile uint32_t prop_off_t;  
  2. struct prop_bt {  
  3.     uint8_t namelen;  
  4.     uint8_t reserved[3];  
  5.   
  6.     prop_off_t prop;  
  7.   
  8.     prop_off_t left;  
  9.     prop_off_t right;  
  10.   
  11.     prop_off_t children;  
  12.   
  13.     char name[0];  
  14. };  
  15.   
  16. typedef struct prop_bt prop_bt;  

 

      如今的問題是,這棵樹是如何組織其枝葉的?System_properties.c文件中,有一段註釋,給出了一個不算太清楚的示意圖,截取以下:

 

看過這張圖後,各位同窗搞清楚了嗎?反正我一開始沒有搞清楚,後來只好研究代碼,如今算是知道一點兒了,詳情以下: 
l  一開始的prop_area節點嚴格地說並不屬於字典樹,可是它表明着屬性共享內存塊的起始; 
l  緊接着prop_area節點,須要有一個空白的prop_bt節點。這個是必須的噢,在前文說明init進程的main()函數的調用關係圖中,咱們表達了這個概念:

這個就是空節點; 
l  屬性名將以‘.’符號爲分割符,被分割開來。好比ro.secure屬性名就會被分割成「ro」和「secure」兩部分,並且每一個部分用一個prop_bt節點表達。 
l  屬性名中的這種‘.’關係被表示爲父子關係,因此「ro」節點的children域,會指向「secure」節點。可是請注意,一個節點只有一個children域,若是它還有其餘孩子,那些孩子將會和第一個子節點(好比secure節點)組成一棵二叉樹。 
l  當一個屬性名對應的「字典樹枝」都已經造成好後,會另外建立一個prop_info節點,專門表示這個屬性,該節點就是「字典樹葉」。

 

下面咱們畫幾張圖來講明問題。好比咱們如今手頭有3個屬性,分別爲 
ro.abc.def 
ro.hhh.def 
sys.os.ccc

咱們依此順序設置屬性,就會造成下面這樣的樹:

 

 

其中天藍色塊表示prop_area節點,桔黃色塊表示prop_bt節點,淺綠色塊表示prop_info節點。簡單地說,父節點的children域,只指代其第一個子節點。後續從屬於同一父節點的兄弟子節點,會被組織成一棵二叉子樹,該二叉子樹的根就是父節點的第一個子節點。咱們用藍色箭頭來表示二叉子樹的關係,在代碼中對應prop_bt的left、right域。這麼說來,以不一樣順序添加屬性,其實會致使最終獲得的字典樹在形態上發生些許變化。

 

         prop_bt節點的name域只記錄「樹枝」的名字,好比「ro」、「abc」、「def」等等,而prop_info節點的name域記錄的則是屬性的全名,好比「ro.abc.def」。

 

         如今咱們向上面這棵字典樹中再添加一個rs.ppp.qqq屬性,會造成以下字典樹:

「rs」節點之因此在那個位置,是基於strcmp()的計算結果。「rs」字符串比「ro」字符串大,因此進一步和「ro」的right節點(即「sys」節點)比對,「rs」又比「sys」小,因此在「sys」節點的left枝上創建了新節點。

 

         以上是畫成字典樹的樣子,它表示的是一種邏輯關係。而在實際的「屬性共享內存」中,這些節點基本上是緊湊排列的,大致上會造成下面這樣的排列關係:

 

         說到這裏,你們應該已經比較清楚屬性共享內存塊是怎麼組織的吧。有了這種大體思路,再去看相應的代碼,相信你們會輕鬆一點兒。

 

3.2.2   property_get()

在讀取具體屬性值時,最終會調用到property_get()函數,該函數的調用關係以下:

說白了就是先從字典樹中找到感興趣的prop_info葉子,而後把葉子裏的值讀出來。

4      Java層的封裝

接下來咱們再說說屬性機制裏Java層的封裝。這部分比較簡單,由於它主要只是在簡單包裝C語言層次的函數。

 

Java層使用的屬性機制被封裝在SystemProperties中:

【frameworks/base/core/java/android/os/SystemProperties.java】

[java]  view plain  copy
 
  1. public class SystemProperties  
  2. {  
  3.     public static final int PROP_NAME_MAX = 31;  
  4.     public static final int PROP_VALUE_MAX = 91;  
  5.   
  6.     private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();  
  7.   
  8.     private static native String native_get(String key);  
  9.     private static native String native_get(String key, String def);  
  10.     private static native int native_get_int(String key, int def);  
  11.     private static native long native_get_long(String key, long def);  
  12.     private static native boolean native_get_boolean(String key, boolean def);  
  13.     private static native void native_set(String key, String def);  
  14.     private static native void native_add_change_callback();  
  15.       
  16. /** 
  17.      * Get the value for the given key. 
  18.      * @return an empty string if the key isn't found 
  19.      * @throws IllegalArgumentException if the key exceeds 32 characters 
  20.      */  
  21.     public static String get(String key) {  
  22.         if (key.length() > PROP_NAME_MAX) {  
  23.             throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);  
  24.         }  
  25.         return native_get(key);  
  26.     }  
  27.     . . . . . .  
  28.     . . . . . .  

 

         咱們就以上面的get()成員函數爲例來講明,它基本上只是在調用native_get()函數而已,該函數對應的C語言函數能夠從下表查到,就是那個SystemProperties_getS():

【frameworks/base/core/jni/android_os_SystemProperties.cpp】

[cpp]  view plain  copy
 
  1. static JNINativeMethod method_table[] = {  
  2.     { "native_get", "(Ljava/lang/String;)Ljava/lang/String;",  
  3.       (void*) SystemProperties_getS },  
  4.     { "native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",  
  5.       (void*) SystemProperties_getSS },  
  6.     { "native_get_int", "(Ljava/lang/String;I)I",  
  7.       (void*) SystemProperties_get_int },  
  8.     { "native_get_long", "(Ljava/lang/String;J)J",  
  9.       (void*) SystemProperties_get_long },  
  10.     { "native_get_boolean", "(Ljava/lang/String;Z)Z",  
  11.       (void*) SystemProperties_get_boolean },  
  12.     { "native_set", "(Ljava/lang/String;Ljava/lang/String;)V",  
  13.       (void*) SystemProperties_set },  
  14.     { "native_add_change_callback", "()V",  
  15.       (void*) SystemProperties_add_change_callback },  
  16. };  

 

【frameworks/base/core/jni/android_os_SystemProperties.cpp】

[cpp]  view plain  copy
 
  1. static jstring SystemProperties_getS(JNIEnv *env, jobject clazz,  
  2.                                       jstring keyJ)  
  3. {  
  4.     return SystemProperties_getSS(env, clazz, keyJ, NULL);  
  5. }  
  6.   
  7. static jstring SystemProperties_getSS(JNIEnv *env, jobject clazz,  
  8.                                       jstring keyJ, jstring defJ)  
  9. {  
  10.     int len;  
  11.     const char* key;  
  12.     char buf[PROPERTY_VALUE_MAX];  
  13.     jstring rvJ = NULL;  
  14.   
  15.     if (keyJ == NULL) {  
  16.         jniThrowNullPointerException(env, "key must not be null.");  
  17.         goto error;  
  18.     }  
  19.   
  20.     key = env->GetStringUTFChars(keyJ, NULL);  
  21.   
  22.     len = property_get(key, buf, "");  
  23.     if ((len <= 0) && (defJ != NULL)) {  
  24.         rvJ = defJ;  
  25.     } else if (len >= 0) {  
  26.         rvJ = env->NewStringUTF(buf);  
  27.     } else {  
  28.         rvJ = env->NewStringUTF("");  
  29.     }  
  30.   
  31.     env->ReleaseStringUTFChars(keyJ, key);  
  32.   
  33. error:  
  34.     return rvJ;  
  35. }  

最終調用的仍是property_get()函數。

5      尾聲

至此,有關Android屬性機制的大致機理就講解完畢了,但願對你們有點兒幫助。

轉自http://blog.csdn.net/codefly/article/details/48379239

相關文章
相關標籤/搜索