Android Framework------之Property子系統


概述

  Property是Android系統中一個重要的概念,在Android系統內,主要用於系統配置,以及不同服務間的簡單信息分享。比如設備名字,藍牙名字,編譯信息,網絡dns地址,以及其他的一些基本信息。 除了簡單的信息分享外,還有個功能是啟動和停止系統服務。 通過設置ctl.start.xxx屬性,來啟動某個屬性,或者設置service.xxx.exit來停止服務。

  Android的系統屬性Property整體上看,是鍵值對保存, 即Key -- Value方式。在系統運行過程中,Property是以字典樹的方式存儲內存中。但是也有一些固定的,不能修改的屬性是存儲在磁盤文件中。這些文件中存儲的屬性,在運行過程中是不會有交互使用的。只是在Android系統啟動之初把這些固定的,不能修改的屬性加載到內存中。 之所以選擇字典樹作為Property存儲的數據結構,主要原因可能是trie這種數據結構的特點:把具有相同前綴的字符存儲在同一個節點中,后續不同的字符存儲為此節點的子節點。這種存儲方式,能夠是字符的查找速度更快。

  Property服務的啟動時在init進程中進行的。在init進程初始化其他服務的同時,盡量早地在啟動property服務。 
下面簡單介紹Property啟動的過程流程,大致分為如下:

1. property_init();【system/core/init/init.c】 
2. property_load_boot_defaults();【system/core/init/init.c】 
3. property_service_init_action;【system/core/init/init.c】 
4. load_all_props; 【system/core/rootdir/init.rc】 
5. queue_property_triggers_action;【system/core/init/init.c】 
6. handle_property_set_fd();【system/core/init/init.c】

      對於property模塊而言,啟動完成后,就是在一個死循環中,不停地檢查是否有他服務設置屬性,如果有的話,接收和處理設置的屬性。在閱讀源碼過程中,有個概念值得提一下:內存屏障(memory barrier),以下來自百科, 也稱內存柵欄,內存柵障,屏障指令等, 是一類同步屏障指令,使得CPU或編譯器在對內存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行后才可以開始執行此點之后的操作。

property模塊的數據結構

Property的存儲的模型大致如下:

 +-----+   children    +----+   children    +--------+
 |     |-------------->| ro |-------------->| secure |
 +-----+               +----+               +--------+
                       /    \                /   |
                 left /      \ right   left /    |  prop   +===========+
                     v        v            v     +-------->| ro.secure |
                  +-----+   +-----+     +-----+            +-----------+
                  | net |   | sys |     | com |            |     1     |
                  +-----+   +-----+     +-----+            +===========+

根據字典樹的存儲方式,property相關的重要數據結構都是和字典樹相關,有如下幾個:

 

 struct prop_bt

  描述每個節點的信息,對應的就是屬性名字中的某一部分名字的信息。這些信息包括此節點字符串,左右兄弟節點地址,子節點地址。同時在結構體聲明時,也同時聲明了prop_bt節點是不能賦值的,只有在構造的時候才能給這些信息初始化。不能賦值操作是通過宏 DISALLOW_COPY_AND_ASSIGN(prop_bt)來實現的。

 

struct prop_area {}

  描述Property所在使用內存的信息,包括property的版本信息,和使用空間。

 

struct prop_info {}

  描述某一個property的信息,比如屬性[sys.boot_completed]:[1], 這個數據結構就是描述這個屬性的整體信息的,包括屬性名,屬性值,以及屬性值得長度。在這個結構體中,有個屬性是serial,這個屬性用來表示屬性值的長度。 serial用關鍵字volatile修飾,這個值是經常在變化的,每次要使用這個值得時候,都要從內存去讀取,而不是從register或cache讀取。 另外,個人認為,這個屬性除了表示屬性值的長度外,還是個同步鎖,同時只允許一個進程對此屬性進行操作。

    總結一下,以屬性[sys.boot_completed]:[1]為例,屬性名字sys.boot_completed以‘.’為分隔符分成兩部分,需要兩個prop_bt去存儲。最后,在保存值得時候創建prop_info去保存[sys.boot_completed]:[1]。最后把prop_info的地址放入最后一個節點prop_bt的prop中。

下面稍微具體點來介紹Property這個模塊

先從流程開始介紹: 
1.property_init();【system/core/init/init.c】 
在Android啟動時, 在Init進程中很早就被調用了,主要作用就是初始化Property的使用的內存空間。初始化的方式就是創建一個tmfs文件系統文件/dev/properties,然后把這個文件一共享內存的方式映射到內存中,映射到內存的大小是128KB。最后,在libc中導出了這塊共享內存的地址。這樣,就有兩種方式可以與Property進行讀寫操作了,一種是就是通過文件讀寫;另外就是內存共享讀寫。使用property的地址對property進行讀寫主要是property_service,運行在init進程中。這個過程對應的代碼如下:

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

    if(__system_property_area_init()) //[bionic/libc/bionic/system_properties.cpp]
        return -1;

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

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);//fork a process, but this fd will be closed

    property_area_inited = 1;
    return 0;
}

 

2. property_load_boot_defaults();【system/core/init/init.c】

在init.rc文件進行解析之前,就要加載此函數。因為default.prop文件中包含了一些重要屬性**———— 要查———— **。 這個函數主要是加載Android系統根目錄的/default.prop文件。這個文件的生成過程在【/build/core/Makefile】中INSTALLED_DEFAULT_PROP_TARGET模塊。加載主要過程簡述如下: 
打開/default.prop文件, 讀取過程中以行為單位進行分析(對應代碼是while ((eol = strchr(sol, '\n'))) {**}), 
然后找出‘=’兩側的內容,然后調用 property_set(key, value)完成屬性賦值過程。property_set的實現如下:

 

  

3. property_service_init_action;【system/core/init/init.c】 
首先此函數是由queue_builtin_action(property_service_init_action, "property_service_init");調用觸發的, 
這個調用是發生在init.rc文件開始解析之后,並且已經執行完成了early-init, init系列動作之后進行的。但是這個這個函數執行是發生在after-init之前進行的。其實在第一步成功執行之后,就已經可以開始進行屬性的設置和查找。只不過,在這個函數之前發生的設置屬性操作,基本來自於default.prop 和 init.rc。從這兩個地方過來的屬性設置,都是init進程解析文件,主動發起的設置,發生在init進程中。所以可以直接對Property所在的內存空間直接進行讀寫,添加或修改屬性。這個函數的主要功能就是創建一個socket,然后其他進程想要設置屬性的時候,就可以通過socket通信,把想要設置的屬性發送過來,然后交由init進程代為設置。

void start_property_service(void)
{
    int fd;

    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);
    if(fd < 0) return;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    listen(fd, 8);
    property_set_fd = fd;
}

這個函數內容比較簡潔,雖然函數名字的意思是啟動property服務,但是實際上property服務並不是一個單獨的進程中,而是運行在當前進程--init中。這個函數執行完成后,會在/dev/socket/創建一個socket文件/dev/socket/property_service。以后其它進程中可以通過這個socket文件與init進程通信,通信的內容是要init進程代為設置property屬性。后面會說明這個通信過程。

 

4. load_all_props; 【system/core/rootdir/init.rc】 

這個動作在函數的代碼中是找不到的。它是init.rc中定義的一個命令,在late-init之后才會被觸發。觸發過程如下:

on load_all_props_action
    load_all_props
...
on late-init
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger post-fs-data

    # Load properties from /system/ + /factory after fs mount. Place
    # this in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_all_props_action
    ...

這個動作也是從文件中加載一些系統屬性,和第二步中加載/default.prop方式一樣,只不過這次加載的這些屬性是要在/system, /factory文件系統掛載之后才能進行的。具體過程可以參考第二步.

5. queue_property_triggers_action;【system/core/init/init.c】 
當init進程執行到這兒的時候,所有系統必要的屬性都已經基本設置完成。這里會逐一trigger監聽屬性的操作。和第二步中的最后一個動作的道理一樣。

6. handle_property_set_fd();【system/core/init/init.c】 
這個函數是Property模塊中最為重要的函數之一。因為所有其它進程想要設置屬性,都是通過socket通信進行的,當init進程中監聽到有新的消息發送過來時,就會調用此函數。init進程是通過Linux的poll機制去監聽Property通信用的socket的文件。

    for(;;) {
...
        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;
        }
        ...
        nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
            continue;

        for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents & POLLIN) {
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();
...
            }
        }
    }

在init進程執行到最后,開啟了一個無限循環,在這個循環過程中使用poll機制監聽了一共三個文件,而Property得socket通信文件/dev/socket/property_service就是其中之一。當有socket有新的消息過來時,就會調用handle_property_set_fd();函數。整個過程如上面這段代碼所述。handle_property_set_fd函數的流程如下:

 

 

對於在init進程中設置屬性過程基本如此。但是對於其它進程是如何把要設置的屬性轉為消息,並通過socket發送到init進程中的這個過程還沒有接觸到,在下面的內容property與其他模塊的通信方式和通信對象中會說明

Property的查找

由前面的敘述,我們已經知道,Property的存儲是用字典樹這種數據結構來存儲的。使用字典樹存儲的目的就是為了查找速度更快,那么我們先看些Property從字典樹中的查找過程:

 

 

 

在Property模塊中的一些注意事項:

問題1: Property本身的信息是使用prop_info這個結構體描述的,而存儲時的信息是用prop_bt描述的,那么在存儲過程中,如何把從socket接收到的信息到生成prop_info結構體的過程是怎樣的?在存儲時如何把結構體prop_info,和結構體prop_bt是如何存儲的?在修改已有的屬性時,如何從內存中找到所需要的屬性的? 
答:在解答上面一系列的問題之前,我們先看過函數find_property()的流程: 這個問題可以分如下幾個小點回答:

  1. 如何把socket信息轉化為結構體prop_info的? 
       從socket發送過來的信息,封裝成了msg結構體。這個結構體中包含了想要進行的操作是設置屬性,還有>設置屬性所需要的key,value值。屬性設置所需要的兩個變量都有了,下面創建屬性信息prop_info結構體,和存儲屬性結構體prop_bt都是property服務的工作了。

  2. 如何從結構體prop_bt轉化到prop_info的?to_prop_obj()函數 prop_bt是局部信息,prop_info是整體信息。說法不太好 
       find_property() 函數完成這個工作。【bionic/libc/bionic/system_properties.cpp】 
    從根節點開始查找,如果沒有找到的話,不要再當前函數中為prop_info申請空間。先把要設置屬性的名字開始分段,以‘.’為分隔符,查找是否存在對應的節點是否存在,如果存在,那就找名字第二部分對應的節點是否存在,以此類推。如果都存在的話,那么就說明這個屬性已經存在了,這時候通過to_prop_obj()函數,把其對應的信息構造成prop_info返回。如果屬性名字中,有其中一部分沒有找到的話,那么就要申請內存空間,創建屬性,並存儲。

  3. 如何從結構體prop_info轉化到prop_bt的? 
       find_prop_bt() 函數完成這個工作。【bionic/libc/bionic/system_properties.cpp】 
    流程圖 
    無論是添加屬性,更新現有屬性都是通過find_property()這個函數中的分發去處理的。【bionic/libc/bionic>/system_properties.cpp】 
    把屬性中目標名字和節點中的name做比較,按照字母升序排列,如果節點中的名字靠后,那么返回值是 < 0;反之,返回值 > 0; 如果屬性中目標名字和節點的名字一樣的話,那么返回值就是0. 這樣比較后,如果返回值是大於0,那么就去屬性字典樹的右側查找;反之,就去字典樹的左側查找。如果返回值是等於0, 那么意味着找到了此節點。

__system_property_add和__system_property_find的區別就是在查找節點過程中,如果不存在想要找的節點,是否要創建新的節點。__system_property_add是要創建。__system_property_find是不創建。

Property與其他模塊的通信方式和通信對象

      在Android系統中,和屬性相關的操作只有兩個:查找屬性,讀取屬性和設置屬性。沒有刪除屬性操作。另外,查找屬性發生在讀取屬性和設置屬性的過程中,其它模塊一般不會直接使用查找操作。在常用的讀取屬性和設置屬性這兩個操作中,讀取屬性是不涉及到進程間通信的。前面我們也說到了,Property屬性是存儲在內存中,並且進行了內存共享映射,所以在讀取屬性的時候,是直接從共享內存中讀取的,就發生在當前進程中,沒有用到進程間通信。而用到進程間通信的是設置屬性操作。因為在設置屬性時,可能會啟動其它的服務,也可能會修改某些系統配置,這都需要進行權限檢查,所以不允許進程直接對屬性設置,而是通過socket通信,把要設置的屬性發送給init進程,由init進程代為進行屬性設置。 
     下面以設置屬性為例,補充完成上面第六步中handle_property_set_fd()中接收消息之前的操作。 假設我們想要設置一個屬性property_set("test.test.test", "1"), 首先找到property_set函數system/core/libcutils/properties.c 文件中。對於Java層中進行的屬性設置,最終會通過JNI調用此函數,詳細過程不再述說。property_set會調用Android系統庫libc中的__system_property_set()函數在bionic/libc/bionic/system_properties.cpp文件中,在__system_property_set()函數中,把要設置的屬性封裝成為msg后,就開始通過socket發送這個消息。發送消息的函數是send_prop_msg(&msg);也在同一個文件。這個函數就是通信的重點。在send_prop_msg()函數中,主要是創建socket,並通過這個socket發送消息。所創建的socket要想和init進程通信,就必須知道init進程段接收端的socket文件。Android系統中,Property接收端socket文件是默認的路徑/dev/socket/property_service文件。這個文件是由前面第三步中create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);創建。在create_socket()函數是Android系統自定義的函數,默認的就是在/dev/socket/目錄下創建指定的socket文件。到這兒,當前進程就找到了Property在init進程中接收端的socket文件,就可以通過socket發送信息,進行屬性設置了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM