zephyr 系統--- 內存池使用方法

內存池(memory pool) 是一個內核對象,它允許從指定的內存區域上動態地分配內存塊(memory block)。同一個內存池中的內存塊的大小是不固定的,這樣可以減小由於不同的應用程序需要爲大小不同的數據結構分配不同的存儲空間所造成的浪費。內存池使用“夥伴(buddy)內存分配”算法,它可以高效地將大塊內存分割爲小塊內存。此外,它還可以在最大限度減小內存碎片的前提下,高效地分配和釋放不小不同的內存塊。

概念

可以定義任意數量的內存池。每個內存池通過其內存地址進行引用。

內存池的關鍵屬性包括:

  • 塊最小尺寸:以字節爲單位。大於等於 4X 字節,其 0。
  • 塊最大尺寸:以字節爲單位。等於 “塊最小尺寸”*4^Y,其中 Y 大於等於 0。
  • 最大尺寸塊的數量:大於 0。
  • buffer:內存池的塊的實際內存區域。它必須大於等於 “塊最大尺寸”*“最大尺寸塊的數量”。

內存片的 buffer 必須 N 字節對齊,其中 N 是大於 2 的 2 的整數次冪(例如 4,8,16…)。爲了保證 buffer 中的所有內存塊都對齊到這個邊界,塊的大小必須是 N 的整數倍。

當線程需要內存塊時,它只需要從一個內存池中申請。申請成功後,由線程提供的塊描述符的 data 字段表示該內存塊的起始地址。當線程使用完內存塊後,它必須將其釋放給內存池,讓其可以重複利用。

如果沒有找到所期望的內存塊,線程可以等待,直到某個塊可用。多個線程可以同時等待某個空的內存池;當某個內存塊可用時,它會被分配給優先級最高的、等待時間最久的線程使用。

與堆不同的是,如果有需要,可以定義多個內存片。例如不同的應用程序可以利用不同的內存池。這樣可以阻止某個應用程序“綁架”所有資源。

內部操作

內存池的 buffer 是一個數組,數組的元素的大小是塊的最大尺寸,這樣能保證塊與塊之間沒有空間被浪費。每個“第 0 級”的塊是一個 quad-block,(如果有需要)可以被分爲四個小的大小相等的“第 1 級”塊。類似地,每個第 1 級塊也是 quad-block,也可以被分爲四個小的大小相等的“第 2 級”塊。依次類推。因此,每個內存塊都可以遞歸地分爲四分之一的小塊,知道小塊的尺寸不滿足塊最小尺寸。

內存池通過一個叫做 塊集(block set) 的數據結構來跟蹤它的 buffer 空間的分區情況。內存池爲所支持的每一個劃分等級或者每一個塊尺寸都維持了一個塊集。每個塊集使用一個叫做 quad-block 狀態 的數據結構的數組來跟蹤它所關聯的尺寸的所有空閒塊。

當應用程序請求一個內存塊時,內存池首先會判斷最小塊的尺寸是否滿足請求,並檢查其相應的塊集。如果塊集包含有一個空閒塊,它會將該塊標記爲以使用,然後分配過程就結束了。如果該塊集不包含空閒塊,內存池將嘗試將一個更大尺寸的空閒塊分類成小的塊,或者將小塊合併爲大的塊。如果不能創建這樣的塊,則分配失敗。

內存池的塊合併和分裂過程是非常高效的,但是它採用的是遞歸算法,因此很容易產生顯著的開銷。此外,合併算法不能將大小不同的相鄰塊結合在一起,也不能合併不屬於同一個父 quad-block 的尺寸相同的相鄰塊。因此,使用內存池時依然會遇到碎片問題。

當應用程序釋放一個已分配的內存塊時,僅僅會在該內存塊所關聯塊集中將其標記爲空閒。內存池不會嘗試合併最近釋放的塊,這樣的好處是可以很方便地在其已存在的組織上進行重分配。

實現

定義內存池

使用類型爲 struct k_mem_pool 的變量可以定義一個內存池。不過,由於內存池也需要大量的尺寸可變的數據結構來代表它的塊集合和它的 quad-block 的狀態,內核不支持在運行時動態地定義內存池。內存池只能使用 K_MEM_POOL_DEFINE 在編譯時進行定義和初始化。

下面的代碼定義並初始化了一個內存池,這個內存池有三個大小爲 4096 字節的塊。這些塊也可以被劃分爲最小爲 64 字節的 4 字節對齊的子塊。(也就是說,內存池支持的塊大小是 4096、1024、256 和 64 字節。)注意,該宏定義了內存池的所有數據結構和它的 buffer。

K_MEM_POOL_DEFINE(my_pool, 64, 4096, 3, 4);

分配內存塊

函數 k_mem_pool_alloc() 用於分配內存塊。

下面的代碼會先等待 100 毫秒,以拿到一個 200 字節的可以內存塊,然後將其填充爲零。如果沒有獲得合適的內存塊,代碼會打印一個警告信息。

注意,應用程序實際會接收到一個大小爲 256 字節的內存塊,因爲這是內存池所支持的最接近的尺寸。

struct k_mem_block block;
if (k_mem_pool_alloc(&my_pool, &block, 200, 100) == 0)) {
memset(block.data, 0, 200);
...
} else {
printf("Memory allocation time-out");
}

釋放內存塊

函數 k_mem_pool_free() 用於釋放內存塊。

下面的代碼基於上面的例程之上,它申請了 75 字節的內存塊,並在不再使用時釋放。(基於安全考慮,實際上會從堆內存池使用 256 字節的內存塊。)

struct k_mem_block block;
k_mem_pool_alloc(&my_pool, &block, 75, K_FOREVER);
... /* use memory block */
k_mem_pool_free(&block);

建議的用法

當需要分配大小不固定的內存時,可以使用內存池。

當一個線程需要給另一個線程發送大量的數據時,可以使用內存池,這樣可以避免不必要的數據拷貝。

API

頭文件 kernel.h 中提供瞭如下的內存池 API:

  • K_MEM_POOL_DEFINE
  • k_mem_pool_alloc()
  • k_mem_pool_free()
  • http://zephyrproject.cn/zh-cn/docs/latest/k_memory_pool.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章