1. 程式人生 > >mmap那些事之android property實現

mmap那些事之android property實現

mmap的概論

mmap的一大應用就是將核心空間的一段記憶體對映到各個應用程式的各自的應用地址空間中,然後各個應用程式都可以訪問這段記憶體空間,這就是所謂的記憶體共享實現程序間的資訊的互動。類似於核心的讀寫鎖一樣,應用程序對共享記憶體的訪問分為兩種:一種是讀,一種是寫。所有程序的讀可以同時併發的訪問同一個記憶體地址,但寫跟讀是互斥的,即我在讀某個記憶體地址的時候,不能有寫的操作,寫操作相對於讀操作有更高的優先權。並且所有程序對同一個地址的寫操作都是互斥的。所以共享記憶體的實現關鍵是訪問的同步控制。

那麼android的property的實現則是利用mmap實現記憶體共享的一個經典應用例項。android的property為了實現各程序對共享記憶體寫操作的同步,他規定所有對屬性變數的寫操作請求都會通過socket通訊傳送至init後臺服務,由init後臺服務來處理所有程序的屬性變數寫請求,這樣儘管各個程序可以隨意甚至併發的呼叫設定屬性變數的介面,但實際對共享記憶體進行寫操作時,則只會有init後臺服務一個入口,在這個init後臺服務入口裡,實現序列化的寫操作,這樣就保證了對共享記憶體的寫操作的互斥。

至於屬性讀跟屬性寫的互斥則會在後面的程式碼中有詳細介紹

android的property的實現

android的屬性系統對應的共享記憶體所屬的檔案是/dev/__properties__,在講詳細實現之前,我們先看下該段共享記憶體對映到各個應用程式的地址空間的分佈情況。先看/init程序,執行命令:cat /proc/1/maps


從上面黑色高亮部分,我們看出,這段共享記憶體對映到/init程序的[b6fef000,b6ff7000]地址空間,該地址空間是屬於應用空間(<3G),並且對映的空間大小是32KB(0x8000).我們應該容易想到,所有使用或支援android 屬性的程序,都應該對映檔案/dev/__properties__對應的共享記憶體

,為了證實這個問題,我們可以看下netd程序的地址空間分佈,執行:cat /proc/77(netd pid)/maps:


從以上對映分佈情況,我們可以看出,/dev/__properties__被對映到[402c9000,402d1000]虛擬地址空間,大小同樣是32KB

再看busybox中的sh程序的maps,則發現並沒有/dev/__properties__檔案的對映,所以busybox的sh是不支援android的屬性操作的。


再看android的shell程序/system/bin/sh,他就是包含了對/dev/__properties__檔案的對映,所以and'r'oid的shell(即ash)是支援屬性操作的。


那麼應用程式是在什麼地方對/dev/__properties__檔案進行對映的呢?

在這之前先說明下共享記憶體的資料結構,先上圖:



主要有三個重要的資料結構:

static workspace pa_workspace


static prop_info *pa_info_array;


extern prop_area *__system_property_area__;


他們之間的關係如上圖所示,__system_property_area__是共享記憶體的頭,長度為8個長字,toc資料的構成為(4個位元組):高位位元組是屬性名字的長度,低三位位元組是對應的屬性info結構相對共享記憶體開始抵制的偏移量。pa_info_array是屬性info的陣列,每個成員的長度是固定為128bytes,所以整個屬性空間位元組長度為:

/* (8 header words + 247 toc words) = 1020 bytes */ /* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */

在應用空間第一次對映共享記憶體的程式碼如下:


line184展開如下:


至此init程序已經建立了與/dev/__properties__檔案所擁有的共享記憶體的關聯,這個時候/dev/__properties__檔案在核心空間只是為該init程序分配了一段大小為32KB的虛擬地址,由於還沒有實際的屬性寫操作發生,所以這個時候還未分配實際的實體記憶體跟這段虛擬地址對應起來,關於這個過程的詳細資訊,我以後會詳細的解說。

由於當前只是為init程序建立了共享記憶體的對映,當前我更關心的是,其他程序是怎麼樣來對映這段共享記憶體的呢?

問題的關鍵在system/core/init/init.c檔案中的void service_start(struct service *svc, const char *dynamic_args)函式的如下片段:


line322 將共享記憶體對應的檔案控制代碼和共享記憶體大小儲存在ANDROID_PROPERTY_WORKSPACE環境變數中。這樣所有init程序的子程序都可以取得這兩個值,而這兩個值為其他程序對映這塊共享記憶體,已經提供足夠條件。

那麼其他程序是如何使用這兩個值的呢?見如下程式碼段:


以上函式在c庫被載入的時候就會被呼叫,所以所有依賴於c庫且是init程序的子程序(或子子程序)的應用程序都可以針對屬性系統進行操作。說到這,回到之前我們看到的busybox的shell為什麼沒有對映這個共享記憶體,因為它沒有依賴android的libc庫,所以不支援android的屬性操作。

前面說過,屬性的多個讀操作可以併發進行,但讀操作和寫操作則只能序列化的進行。詳細說明如下,首先見屬性的寫操作如下:


關鍵是上面的line379行,展開如下:


需要注意的是,上面在開始更新屬性值時,會將每個屬性對應的serial值的bit0置成1,操作完後會將serial+1,這樣的話,屬性寫操作完成後,serial值總是一個偶數,並且更新完成後,該serial值相對於更新前的serial值>=1

結合屬性的讀操作函式:


結合上面函式的註釋,應該不難明白屬性讀操作跟屬性寫操作是如何實現互斥的。

下一篇,我將詳細講下tmpfs檔案系統下的/dev/__properties__檔案是如何響應mmap系統呼叫的。