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系統調用的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章