什么是carveout heap
- carveout heap從代碼中給的解釋來看,是reserved預留的物理內存來實現的,這些內存buddy系統是沒辦法看到和管理到的
- carveout heap中的內存通過自建通用內存分配器gen_pool,使用bitmap來管理申請和釋放
- 比如多數平台是在dts中配置保留的物理內存,將該內存專門用來作為carveout heap,ion_platform_heap結構體就是用來指示平台配置的
carvout heap創建
- ion_carveout_heap_create()函數:
- ion_carveout_heap_create接口是ION提供的創建carveout heap的接口,不同平台需要主動調用該接口來創建heap
- 其實現主要是將平台預留的物理內存放入gen_pool中
struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data)
{
struct ion_carveout_heap *carveout_heap;
int ret;
struct page *page;
size_t size;
//根據傳遞過來的base物理基地址轉換成page(所有的物理內存都對應有一個page在表示)
page = pfn_to_page(PFN_DOWN(heap_data->base));
size = heap_data->size;
//將該內存塊做清零處理:通過將page映射到vmalloc虛擬地址端,調用memset執行清零。
ret = ion_heap_pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL));
if (ret)
return ERR_PTR(ret);
carveout_heap = kzalloc(sizeof(*carveout_heap), GFP_KERNEL);
if (!carveout_heap)
return ERR_PTR(-ENOMEM);
//創建內存池對象,PAGE_SHIFT表示pool中最小分配為4K大小
carveout_heap->pool = gen_pool_create(PAGE_SHIFT, -1);
if (!carveout_heap->pool) {
kfree(carveout_heap);
return ERR_PTR(-ENOMEM);
}
carveout_heap->base = heap_data->base;
//將該內存塊(base到base+size之間)加入到pool中,通過bitmap來表示使用和釋放
gen_pool_add(carveout_heap->pool, carveout_heap->base, heap_data->size,
-1);
carveout_heap->heap.ops = &carveout_heap_ops;
carveout_heap->heap.type = ION_HEAP_TYPE_CARVEOUT;
carveout_heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
//返回ion_heap對象,該對象最后由平台自己add到ion dev的heaps鏈表中
return &carveout_heap->heap;
}
- gen_pool創建:gen_pool_create()函數
gen_pool看起來還挺簡單,它通過chunk來管理不同內存塊,min_alloc_order表示bitmap中一個bit代表多大內存,比如我們這里是PAGE_SHIFT=12,那么就是4K
algo是分配算法,其實就是bitmap的查找算法,其實就是調用bitmap_find_next_zero_area()函數
data目前沒什么用,可能用來存放私有數據
struct gen_pool {
spinlock_t lock;
struct list_head chunks; /* list of chunks in this pool */
int min_alloc_order; /* minimum allocation order */
genpool_algo_t algo; /* allocation function */
void *data;
const char *name;
};
- chunk直譯就是大塊,其實就表示內存塊
avail當前內存塊的可用內存大小,按字節算
phys_addr按理說應該是跟start_addr一樣,但從代碼中被設置為-1
start_addr表示起始物理地址
end_addr結束物理地址
bit[0] 表示bitmap
/*
* General purpose special memory pool chunk descriptor.
*/
struct gen_pool_chunk {
struct list_head next_chunk; /* next chunk in pool */
atomic_t avail;
phys_addr_t phys_addr; /* physical starting address of memory chunk */
unsigned long start_addr; /* start address of memory chunk */
unsigned long end_addr; /* end address of memory chunk (inclusive) */
unsigned long bits[0]; /* bitmap for allocating memory chunk */
};
- gen_pool創建
struct gen_pool *gen_pool_create(int min_alloc_order, int nid)
{
struct gen_pool *pool;
pool = kmalloc_node(sizeof(struct gen_pool), GFP_KERNEL, nid);
if (pool != NULL) {
spin_lock_init(&pool->lock);
INIT_LIST_HEAD(&pool->chunks);
pool->min_alloc_order = min_alloc_order;
pool->algo = gen_pool_first_fit; //初始化查找算法,起始就是bitmap的查找
pool->data = NULL;
pool->name = NULL;
}
return pool;
}
- 將內存加入到gen_pool中,中間會創建chunk來存放和管理該內存
- gen_pool_add()->gen_pool_add_virt()函數:
int gen_pool_add_virt(struct gen_pool *pool, unsigned long virt, phys_addr_t phys,
size_t size, int nid)
{
struct gen_pool_chunk *chunk;
//根據一個bit代表多大內存,計算出當前內存需要多少個bit
int nbits = size >> pool->min_alloc_order;
//計算chunk結構體大小+需要表示內存大小的bit位數轉換成long型變量,總共需要多少內存
int nbytes = sizeof(struct gen_pool_chunk) +
BITS_TO_LONGS(nbits) * sizeof(long);
//申請chunk對象,使用nbytes
chunk = kzalloc_node(nbytes, GFP_KERNEL, nid);
if (unlikely(chunk == NULL))
return -ENOMEM;
//初始化chunk中的內存信息,包括內存起始地址,結束地址,avail可用內存大小(size是字節)
chunk->phys_addr = phys;
chunk->start_addr = virt;
chunk->end_addr = virt + size - 1;
atomic_set(&chunk->avail, size);
spin_lock(&pool->lock);
//最后將當前chunk加入到gen_pool鏈表中
list_add_rcu(&chunk->next_chunk, &pool->chunks);
spin_unlock(&pool->lock);
return 0;
}
- 創建完成
carvout heap分配
- ion_carveout_heap_allocate()函數實現分配:
static int ion_carveout_heap_allocate(struct ion_heap *heap,
struct ion_buffer *buffer,
unsigned long size,
unsigned long flags)
{
struct sg_table *table;
phys_addr_t paddr;
int ret;
table = kmalloc(sizeof(*table), GFP_KERNEL);
if (!table)
return -ENOMEM;
//我們看到通過ion方式申請到的內存都是通過sg_table來組織
ret = sg_alloc_table(table, 1, GFP_KERNEL);
if (ret)
goto err_free;
//關鍵函數,申請內存,這里申請過來的是物理地址,因為gen_pool中存放的就是物理地址,通過bitmap來管理
paddr = ion_carveout_allocate(heap, size);
if (paddr == ION_CARVEOUT_ALLOCATE_FAIL) {
ret = -ENOMEM;
goto err_free_table;
}
//將申請到的內存加入到sg_table中
//生成的buffer最終都在ion_alloc總入口處轉換成dma_buf,並最終轉換成fd句柄返回給用戶層
sg_set_page(table->sgl, pfn_to_page(PFN_DOWN(paddr)), size, 0);
buffer->sg_table = table;
return 0;
err_free_table:
sg_free_table(table);
err_free:
kfree(table);
return ret;
}
- 調用ion_carveout_allocate()->gen_pool_alloc()->gen_pool_alloc_algo()真正實現分配:
unsigned long gen_pool_alloc_algo(struct gen_pool *pool, size_t size,
genpool_algo_t algo, void *data)
{
struct gen_pool_chunk *chunk;
unsigned long addr = 0;
int order = pool->min_alloc_order;
int nbits, start_bit, end_bit, remain;
#ifndef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
BUG_ON(in_nmi());
#endif
if (size == 0)
return 0;
//假如一個bit代表4K的內存(當前是4K),總共需要多少個bit才能表示用戶申請的內存size
nbits = (size + (1UL << order) - 1) >> order;
rcu_read_lock();
//遍歷當前gen_pool中所有的chunk內存塊(一般ION都只創建一塊),找到bitmap中連續nbits個bit為0的位
list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) {
if (size > atomic_read(&chunk->avail))
continue;
start_bit = 0;
//計算當前chunk中末位bit的下標
end_bit = chunk_size(chunk) >> order;
retry:
//開始查找,algo算法實際在創建gen_pool時已經指定,其實就是在bits中查找滿足要求的位
start_bit = algo(chunk->bits, end_bit, start_bit,
nbits, data, pool);
if (start_bit >= end_bit)
continue;
//對找到的滿足要求的bits位置1,表示分配出去
remain = bitmap_set_ll(chunk->bits, start_bit, nbits);
if (remain) {
remain = bitmap_clear_ll(chunk->bits, start_bit,
nbits - remain);
BUG_ON(remain);
goto retry;
}
//然后計算出分配到的內存實際的起始物理地址,start_addr+偏移地址
addr = chunk->start_addr + ((unsigned long)start_bit << order);
size = nbits << order;
//更新當前chunk中可用的物理內存大小
atomic_sub(size, &chunk->avail);
break;
}
rcu_read_unlock();
//返回申請到的內存起始物理地址
return addr;
}
- 分配完成
carvout heap釋放
- 釋放主要調用ion_carveout_free()->gen_pool_free()函數:
void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size)
{
struct gen_pool_chunk *chunk;
int order = pool->min_alloc_order;
int start_bit, nbits, remain;
#ifndef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
BUG_ON(in_nmi());
#endif
//計算出需要釋放的內存大小轉換成bit代表的內存,總共需要多少個bit來表示
//比如釋放5K內存,由於order是4K,那么轉換出來就是2個bit,也就是8K
//不過,由於在分配時已經按order對齊,所以一般傳遞進來的內存應該也是order對齊的
nbits = (size + (1UL << order) - 1) >> order;
rcu_read_lock();
list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) {
//遍歷所有的chunk,找到當前要釋放的內存物理地址所屬的chunk
//通過物理地址范圍確定所屬chunk
if (addr >= chunk->start_addr && addr <= chunk->end_addr) {
//開始釋放
BUG_ON(addr + size - 1 > chunk->end_addr);
//計算釋放內存的地址對應的bitmap的起始bit
start_bit = (addr - chunk->start_addr) >> order;
//從起始bit的連續nbits個位置0,表示釋放
remain = bitmap_clear_ll(chunk->bits, start_bit, nbits);
BUG_ON(remain);
//計算釋放的內存,並更新chunk中的avail可用內存大小
size = nbits << order;
atomic_add(size, &chunk->avail);
rcu_read_unlock();
return;
}
}
rcu_read_unlock();
BUG();
}
- 釋放完成
