Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [Android源碼解析]Property之十月懷胎到茁壯成長所涉及的方方面面

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

編輯:關於Android編程

      其實在網上講Property的文章還是蠻多的,不過源碼級分析的倒是不多,曉東正好做好了一個項目,其中涉及到了Property的一些內容,折騰了一段時間,心想不如來讀讀源碼,看看究竟是怎麼回事。   1、property內存區域的申請            在網上通常都是這樣開始講的“屬性服務運行於init進程中。init進程首先創建一個共享內存區域,並保存一個指向該區域的描述符fd。”[1]這一段從代碼中如何來看呢,首先找到init的main函數:(system/core/init/init.c文件中),有這樣一句話:            queue_builtin_action(property_init_action, "property_init");            大概的意思就是把property_init_action加入到action queue中,後面會來調用這個action。所以,我們自然而然的就會去看property_init_action的操作:   [cpp]   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"   <span style="color:#000000;">//所以網上盛傳的先加載哪個文件,再加載哪個文件都是有前提的,就是首先不是在關機充電模式。哈哈~~,不過的確是得先加載這個文件default.prop   if (load_defaults)   </span>//這裡加載對應文件中的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 共享內存區域的初始化   [cpp]   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;   <span style="color:#000000;">//這裡實現的就是“init進程將該區域通過使用了MAP_SHARED標志的mmap映射至它自身的虛擬地址空間,這樣,任何對於該區域的更新對於所有進程都是可見的[1]”   </span>    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的文件內容如下:   [plain]   #   # ADDITIONAL_DEFAULT_PROPERTIES   #   ro.secure=1   ro.allow.mock.location=0   ro.debuggable=1   persist.sys.usb.config=mtp,adb   [cpp]   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);       }   }   <span style="color:#000000;">//提醒一下,這裡的property_set和我們真正調用的property_set可不是一回事哦,當然最終我們仍然會調用到這個函數。具體見最後的分析   </span>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);   <span style="color:#000000;">//這裡property的name和value都是有最大長度的哦,name是32,value是92,所以大家在寫自己的name和value的時候,不要超過這個長度哦   </span>    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");   所以我們接著來分析這個函數:   [cpp]   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;   }     這個函數就是根據kernel的cmdline參數等設置對應的一些ro參數的值,和我們的關系不是很大,知道就可以了。   3、property_service_init_action之初始化property service   這個action就是緊接著上面的一些操作了:   queue_builtin_action(property_service_init_action, "property_service_init");   該函數就是初始化property service   [cpp]  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;   <span style="color:#000000;">//加載下面三個文件:"/system/build.prop","/system/default.prop",/data/local.prop"。   //這裡就是網上流傳的4個文件的加載順序的說法,從這裡就可以看到了。   </span>    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的數據進行處理了。   4、socket有數據後的處理   在init.c的最後會有一個循環,用來不停的處理它所監聽的socket。代碼如下:   [cpp]   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) {   <span style="color:#000000;">//這裡就是處理我們調用的property_set了,所以,理解了吧,我們調用property_set的時候,其實就是通過socket發送這個msg過來而已。   </span>    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最終會調用到這個函數:   [cpp]   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 {   <span style="color:#000000;">//就是這個地方太壞了,超時了,他也說是ok的。所以,哎~~~大家使用的時候自求多福吧,這段代碼太。。。。   </span>            // 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;   }    
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved