iOS標準庫中常用資料結構和演算法之記憶體池

歐陽大哥2013發表於2019-05-05

上一篇:iOS標準庫中常用資料結構和演算法之位串

⛲️記憶體池

記憶體池提供了記憶體的複用和持久的儲存功能。設想一個場景,當你分配了一塊大記憶體並且填寫了內容,但是你又不是經常去訪問這塊記憶體。這樣的記憶體利用率將不高,而且無法複用。而如果是採用記憶體池則可以很輕鬆解決這個問題:你只需要從記憶體池中申請這塊記憶體,設定完內容後當不需要用時你可以將這塊記憶體放入記憶體池中,供其他地方在申請時進行復用,而當你再次需要時則只需要重新申請即可。記憶體池提供了記憶體分配編號而且設定髒標誌的概念,當你把分配的記憶體放入記憶體池並設定髒標誌後,系統就會在適當的時候將這塊記憶體的內容寫回到磁碟,這樣當你再次根據記憶體編號來訪問記憶體時,系統就又會從磁碟中將內容讀取到記憶體中去。

功能:在iOS中提供了一套記憶體池管理的API,你可以用這套API來實現上述的功能,而且系統內部很多功能也是藉助記憶體池來實現記憶體複用和磁碟儲存的。

標頭檔案: #include <mpool.h>

平臺: BSD系統,linux系統

一、記憶體池的建立、同步和關閉

功能:建立和關閉一個記憶體池物件並和磁碟檔案繫結以便進行同步操作。

函式簽名


//建立一個記憶體池物件
 MPOOL * mpool_open(void *key, int fd, pgno_t pagesize, pgno_t maxcache);

//將記憶體池中的髒資料同步寫回到磁碟檔案中
int mpool_sync(MPOOL *mp);

//關閉和銷燬記憶體池物件。
int mpool_close(MPOOL *mp);

複製程式碼

引數

key:[in] 保留欄位,暫時沒有用處,傳遞NULL即可。

fd:[in] 記憶體池關聯的磁碟檔案控制程式碼,檔案控制程式碼需要用open函式來開啟。

pagesize:[in] 記憶體池中每次申請和分配的記憶體的尺寸大小,單位是位元組。

maxcache:[in] 記憶體池中記憶體頁的最大快取數量。如果池中快取的記憶體數量超過了最大快取的數量就會複用已經存在的記憶體,而不是每次都分配新的記憶體。 return:[out] 返回一個新建立的記憶體池物件,其他兩個函式成功返回0,失敗返回非0.

描述

  1. 記憶體池中的記憶體的分配和獲取是以頁為單位進行的,每次分配的頁的尺寸大小由pagesize指定,同時記憶體池也指定了最大的快取頁數量maxcache。每次從記憶體池中分配一頁記憶體時,除了會返回分配的記憶體地址外,還會返回這一頁記憶體的編號。這個編號對於記憶體池中的記憶體頁來說是唯一的。因為記憶體池中的記憶體是可以被複用的,因此就有可能是不同的編號的記憶體頁所得到的記憶體地址是相同的。

  2. 每一個記憶體池物件都會要和一個檔案關聯起來,以便能夠實現記憶體資料的永久儲存和記憶體的複用。檔案控制程式碼必須用open函式來開啟,比如下面的例子:

 int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);
複製程式碼
  1. 當我們不需要使用某個記憶體頁時或者記憶體頁的內容有改動則我們需要將這個記憶體頁放入回記憶體池中,並將頁標誌為髒標誌。這樣系統就會在適當的時候將此記憶體頁的資料寫回到磁碟檔案中,同時此記憶體頁也會在後續被重複利用。

  2. 當我們想將所有設定為髒標誌的記憶體頁立即寫入磁碟時則需要呼叫mpool_sync函式進行同步處理。

  3. 當我們不再需要記憶體池時,則可以通過mpool_close來關閉記憶體池物件,需要注意的是關閉記憶體池並不會將記憶體中的資料回寫到磁碟中去。

二、記憶體池中記憶體的獲取

功能: 從記憶體池中申請分配一頁新的記憶體或者獲取已有快取中的記憶體。

函式簽名:

//從記憶體池中申請分配一頁新的記憶體
void *  mpool_new(MPOOL *mp, pgno_t *pgnoaddr);
//根據記憶體編號頁獲取對應的記憶體。
void * mpool_get(MPOOL *mp, pgno_t pgno, u_int flags);
複製程式碼

引數:

mp:[in] 記憶體池物件。

pgnoaddr:[out] 用於mpool_new函式,用於儲存新分配的記憶體頁編號。

pngno:[in] 用於mpool_get函式,指定要獲取的記憶體頁的編號。

flags:[in] 此引數暫時無用。

return:[out] 返回分配或者獲取的記憶體地址。如果分配或者獲取失敗則返回NULL。

描述:

  1. 無論是new還是get每次從記憶體池裡面分配或者獲取的記憶體頁的大小都是由上述mpool_open函式中的pagesize引數指定的大小。
  2. 系統內部分配的記憶體是用calloc函式實現的,但是我們不需要手動呼叫free來對記憶體進行釋放處理。
  3. 每個記憶體頁都有一個唯一的頁編號,而且每次分配的頁編號也會一直遞增下去。
  4. mpool_new函式申請分配新的記憶體時,如果當前快取中的記憶體頁小於maxcache數量則總是分配新的記憶體,只有當快取數量大於maxcache時才會從現有的快取中尋找一頁可以被重複利用的記憶體頁,如果沒有可以重複利用的頁面,則會繼續分配新的記憶體頁。
  5. mpool_get函式則根據記憶體頁的編號獲取對應的記憶體頁。如果編號不存在則返回NULL。需要注意的是一般在獲取了某一頁記憶體後,不要進行重複獲取操作,否則在DEBUG狀態下會返回異常。另外一個情況是有可能相同的頁編號下兩次獲取的記憶體地址是不一樣的,因為系統實現內部有記憶體複用的機制。

三、記憶體池中記憶體的放回

功能:將分配或者申請的記憶體頁放回到記憶體池中去,以便進行重複利用。

函式簽名:

int  mpool_put(MPOOL *mp, void *pgaddr, u_int flags);

複製程式碼

引數:

mp: [in] 記憶體池物件。

pgaddr:[in] 要放入快取的記憶體頁地址。這個地址由mpool_get/new兩個函式返回。

flags:[in] 放回的屬性,一般設定為0或者MPOOL_DIRTY。

return:[in] 函式呼叫成功返回0,失敗返回非0

描述

  1. 這個函式用來將記憶體頁放入回記憶體池快取中,以便對記憶體進行重複利用。當將某個記憶體地址放入回快取後,將不能再次訪問這個記憶體地址了。如果要想繼續訪問記憶體中的資料則需要藉助上述的mpool_get/new函式來重新獲取。
  2. flags:屬性如果指定為0時,表明放棄這次記憶體中的內容的修改,系統不會將記憶體中的內容寫入到磁碟中,而只是將記憶體放入快取中供其他地方重複使用。而如果設定為MPOOL_DIRTY時,則表明將這頁記憶體中的資料設定為髒標誌,除了同樣將記憶體放入快取中重複利用外,則會在適當的時候將記憶體中的資料寫入到磁碟中,以便下次進行讀取。

四、記憶體池磁碟讀寫通知

功能:註冊回撥函式,當某頁記憶體要寫回到磁碟或者要從磁碟中讀取時就會呼叫指定的回撥函式。

函式簽名:

void mpool_filter(MPOOL *mp, void (*pgin)(void *, pgno_t, void *),
         void (*pgout)(void *, pgno_t, void *), void *pgcookie);
複製程式碼

引數:

mp:[in] 記憶體池物件.

pgin: [in]: 回撥函式,當某個記憶體頁的資料需要從磁碟讀取時,會在讀取完成後呼叫這個回撥函式。

pgout:[in]: 回撥函式,當某個記憶體頁的資料要到磁碟時,會在寫入完成後呼叫這個回撥函式。

pgcookie: [in] 上述兩個回撥函式的附加引數。

描述

因為記憶體池中的記憶體頁會進行復用,以及會在適當的時候將內容同步到磁碟中,或者從磁碟中將內容讀取到記憶體中,因此可以藉助這個函式來監控這些磁碟檔案和記憶體之間的讀寫操作。pgin和pgout函式的格式定義如下:

//pgin和pgout回撥函式的格式。
//pgcookie:是mpool_filter函式中傳入的引數。
//pgno: 要進行讀寫的記憶體頁編號
//pageaddr: 要進行讀寫的記憶體地址。
void (*pgcallback)(void *pgcookie, pgno_t pgno, void *pageaddr);

複製程式碼

五、示例程式碼

 //建立並開啟一個檔案。
 int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);

//建立一個記憶體池物件,每頁的記憶體100個位元組,最大的快取數量為4
 MPOOL *pool = mpool_open(NULL, fd, 100, 4);

   
//從記憶體池中分配一個新的記憶體頁,這裡對返回的記憶體填寫資料。
 pgno_t pidx1, pidx2 = 0;
 char *mem1 =  (char*)mpool_new(pool, &pidx1);
 memcpy(mem1, "aaa", 4);
    
 char *mem2 = (char*)mpool_new(pool, &pidx2);
 memcpy(mem2, "bbb", 4);
    
//將分配的記憶體mem1放回記憶體池中,但是內容不儲存到磁碟
 mpool_put(pool, mem1, 0);
//將分配的記憶體mem2放回記憶體池中,但是內容儲存到磁碟。
 mpool_put(pool, mem2, MPOOL_DIRTY);
    
//經過上面的操作後mem1,mem2將不能繼續再訪問了,需要訪問時需要再次呼叫mpool_get。   
mem1 = (char*)mpool_get(pool, pidx1, 0);
mem2 =   (char*)mpool_get(pool, pidx2, 0);

//上面的mem1和mem2可能和前面的new返回的地址是不一樣的。因此在記憶體池中不能通過地址來做唯一比較,而應該將編號來進行比較。
       
//將所有設定為髒標誌的記憶體也寫回到磁碟中去。
 mpool_sync(pool);

 mpool_close(pool);  //關閉記憶體池。

 close(fd);  //關閉檔案。

複製程式碼

記憶體池為iOS系統底層開發提供了一個非常重要的能力,我們可以好好利用記憶體池來對記憶體進行管理,以及一些需要進行持久化的資料也可以藉助記憶體池來進行儲存,通過記憶體池提高記憶體的重複利用率。

相關文章