【内存管理】ION内存管理器浅析(systemheap)(基于
linux4.14)
什么是ION
ION具体不知道是什么的缩写,只知道是android系统上google引⼊的内存管理⽅式,为了实现⽤户与内核间数据共享时零拷贝。多⽤于多媒体,⽐如camera和display,graphic。
ION是⼀个内存管理器,管理不同type的内存堆(heap),⽽不同的type的内存⼜通过不同的内存分配器来分配,⽐如cma、
kmalloc、vmalloc。
ION中不同type的heap
enum ion_heap_type {
ION_HEAP_TYPE_SYSTEM,
ION_HEAP_TYPE_SYSTEM_CONTIG,
ION_HEAP_TYPE_CARVEOUT,
ION_HEAP_TYPE_CHUNK,
ION_HEAP_TYPE_DMA,
ION_HEAP_TYPE_CUSTOM, /*
};
ION_HEAP_TYPE_SYSTEM:头⽂件中说是通过vmalloc分配,代码中看是直接通过alloc_pages分配的,对应⽂件
ion_system_heap.c。
ION_HEAP_TYPE_SYSTEM_CONTIG:通过kmalloc进⾏分配,对应⽂件ion_system_heap.c
ION_HEAP_TYPE_DMA:从代码中看是对接的cma分配器,对应⽂件ion_cma_heap.c
ION_HEAP_TYPE_CARVEOUT:对应⽂件ion_carveout_heap.c
ION_HEAP_TYPE_CHUNK:对应⽂件ion_chunk_heap.c
ION分配(以system heap为例)
⽤户层打开/dev/ion,并通过ioctl调⽤传递分配内存需要的参数,主要是:
struct ion_allocation_data {
__u64 len; //需要分配的字节数
__u32 heap_id_mask; //需要从哪个heap中分配,heap id是在每个heap添加到ion dev时⾃动增长的,从0开始。
__u32 flags; //
__u32 fd; //分配后的内存转换成dma-buf的fd⽂件句柄
__u32 unused;
};
内核中ioctl调⽤ion_alloc函数进⾏分配:
long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
....省略
union ion_ioctl_arg data;
....省略
switch (cmd) {
case ION_IOC_ALLOC:
{
int fd;
//调⽤ion_alloc,传⼊⽤户层参数。分配成功则返回dma_buf转换后的fd到⽤户层。
fd = ion_alloc(data.allocation.len,
data.allocation.heap_id_mask,
data.allocation.flags);
if (fd < 0)
return fd;
//将fd赋值给⽤户层
data.allocation.fd = fd;
break;
}
case ION_IOC_HEAP_QUERY:
ret = ion_query_heaps(&data.query);
break;
default:
return -ENOTTY;
}
....省略
return ret;
}
ion_alloc函数:
从ion dev设备中查⽤户想从哪个heap中分配内存
分配成功后,将ion_buffer转换并导出成dma_buf,再讲dma_buf转换成⽂件fd
int ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags)
{
struct ion_device *dev = internal_dev;
struct ion_buffer *buffer = NULL;
struct ion_heap *heap;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
int fd;
struct dma_buf *dmabuf;
pr_debug("%s: len %zu heap_id_mask %u flags %x\n", __func__,
len, heap_id_mask, flags);
/*
* len是⽤户传递过来的长度,这⾥需要页对齐。少于⼀个page的⼤⼩就按⼀个page给,⽐如传递过来是5字节
* 经过page_align后,返回的值就是4096.
*/
len = PAGE_ALIGN(len);
if (!len)
return -EINVAL;
down_read(&dev->lock);
/*
* ion设备的heaps链表中,链接了ion设备等的所有heaps,⽐如system heap、cma heap、carveout heap等
* 通过遍历所有的heap,到匹配上⽤户传递过来的参数中指定的heap(⽐如heap id),主要就是heap id。heap id在每个heap添加到ion dev时⾃动分配的。 * 表⽰中哪个heap去分配
*/
plist_for_each_entry(heap, &dev->heaps, node) {
/* if the caller didn't specify this heap id */
if (!((1 << heap->id) & heap_id_mask))
continue;
/*
* 到了⽤户需要的heap,⽐如system heap
*/
buffer = ion_buffer_create(heap, dev, len, flags);
if (!IS_ERR(buffer))
break;
}
up_read(&dev->lock);
if (!buffer)
return -ENODEV;
if (IS_ERR(buffer))
return PTR_ERR(buffer);
//将分配到的buffer导出为dma buf格式
exp_info.ops = &dma_buf_ops;
exp_info.size = buffer->size;
exp_info.flags = O_RDWR;
exp_info.priv = buffer; //保留ion_buffer
//导出为dma buf
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
_ion_buffer_destroy(buffer);
return PTR_ERR(dmabuf);
}
//将dma buf转换成file fd⽂件句柄,返回给⽤户空间
fd = dma_buf_fd(dmabuf, O_CLOEXEC);
if (fd < 0)
dma_buf_put(dmabuf);
return fd;
}
假如选中的heap为system heap(ION_HEAP_TYPE_SYSTEM),那么接下来会调⽤它的allocate函数:
static struct ion_heap_ops system_heap_ops = {
.allocate = ion_system_heap_allocate,
.free = ion_system_heap_free,
.map_kernel = ion_heap_map_kernel,
.unmap_kernel = ion_heap_unmap_kernel,
.map_user = ion_heap_map_user,
.shrink = ion_system_heap_shrink,
};
ion_system_heap_allocate()分配函数:
system heap中管理着两类pool,分别是cache和uncache,⽽每⼀类⼜分为不同order,总共3个order,8/4/0.
分配前,会先将size转换成页对齐⼤⼩,⽐如5字节,那么对齐后就是4096字节
分配时,会从三种order中选中最接近分配要求但是不超过size的order。⽐如分配18页,那么会分配1个order为4,2个
order为1的页。
分配成功后,会将分配后的离散的物理页通过sg_table组织起来,具体是将page地址存放在scatterlist->page_link中,同时将page彻底从buddy系统中脱离出来。
static int ion_system_heap_allocate(struct ion_heap *heap,
struct ion_buffer *buffer,
unsigned long size,
unsigned long flags)
{
struct ion_system_heap *sys_heap = container_of(heap,
struct ion_system_heap,
heap);
struct sg_table *table;
struct scatterlist *sg;
struct list_head pages;
struct page *page, *tmp_page;
int i = 0;
unsigned long size_remaining = PAGE_ALIGN(size);
unsigned int max_order = orders[0];
if (size / PAGE_SIZE > totalram_pages / 2)
return -ENOMEM;
INIT_LIST_HEAD(&pages);
/
/假如传递过来的size为4096(在ion_alloc经过对齐了),再次经过页⾯对齐,此时的size_remaining也为4096
while (size_remaining > 0) {
//分配内存,system heap中管理着两类pool,根据pool中连续page页的order数,⼜分为6个pool,分别是order为
//8、 4、 0,定义在int orders[] = {8, 4, 0};中
page = alloc_largest_available(sys_heap, buffer, size_remaining,
max_order);
if (!page)
goto free_pages;
//分配成功,将page页加⼊到pages list中
list_add_tail(&page->lru, &pages);
size_remaining -= PAGE_SIZE << compound_order(page);
max_order = compound_order(page);
i++;
}
//将分配成功的page放⼊sg_table中
table = kmalloc(sizeof(*table), GFP_KERNEL);
if (!table)
goto free_pages;
//总共分配了多少次(i次)连续的内存块,那么就分配i个scatterlist⽤来关联物理buffer
if (sg_alloc_table(table, i, GFP_KERNEL))
goto free_table;
sg = table->sgl;
//将内存块与scatterlist关联起来,⽐如上⾯分配了⼀个16页,2个⼀页,总共3个物理连续的内存buffer,
//将他们分别放⼊scatterlist的page_link中,实际是将page结构体的地址存放在其中。
//接着将page->lru从前⾯的pages链表中删除。此时的page就只有sg中能到了,在buddy中已经不到了。
list_for_each_entry_safe(page, tmp_page, &pages, lru) {
sg_set_page(sg, page, PAGE_SIZE << compound_order(page), 0);
sg = sg_next(sg);
list_del(&page->lru);
}
//最后将sg_table赋值,其中就包含刚分配的内存页
buffer->sg_table = table;
return 0;
free_table:
kfree(table);
free_pages:
list_for_each_entry_safe(page, tmp_page, &pages, lru)
free_buffer_page(sys_heap, buffer, page);
return -ENOMEM;
}
alloc_largest_available()函数:
到匹配分配⼤⼩的order,并调⽤alloc_buffer_page实际去分配。
static struct page *alloc_largest_available(struct ion_system_heap *heap,
struct ion_buffer *buffer,
unsigned long size,
unsigned int max_order)
{
struct page *page;
system的头文件int i;
//system heap中有三种order⼤⼩的内存块,分别是8、4、0,对应2^8页...
//此处到⼩于分配size的最⼤order,⽐如size是18*4K(18个page),那么这⾥就会选择到order 4 ,先分配⼀个16个page返回,
//再次进⼊该函数,size变成2*4k(2个page),会选择order为0,分配⼀个page返回。
//第三次进⼊该函数,size变成1*4k(1个page),此时会选择order为0,分配⼀个page返回。
for (i = 0; i < NUM_ORDERS; i++) {
if (size < order_to_size(orders[i]))
continue;
if (max_order < orders[i])
continue;
//实际分配:先从pool中分配,如果没有就从buddy中分配。
page = alloc_buffer_page(heap, buffer, orders[i]);
if (!page)
continue;
return page;
}
return NULL;
}
alloc_buffer_page()函数:
实际分配时,根据⽤户空间传递过来的flags决定是从cached还是uncached中分配(实际我也没太明⽩这两个的区别)。
对应的pool具体是哪个order,之前已经⽐较出来。
static struct page *alloc_buffer_page(struct ion_system_heap *heap,
struct ion_buffer *buffer,
unsigned long order)
{
bool cached = ion_buffer_cached(buffer);
struct ion_page_pool *pool;
struct page *page;
//system heap⾃⼰定义了两个pool,⼀个cached⼀个uncached
//每个pool对应不同order,当前是8/4/0三种
if (!cached)
pool = heap->uncached_pools[order_to_index(order)];
else
pool = heap->cached_pools[order_to_index(order)];
//从pool中分配内存,分配时先从high链表中分配,没有就从low中分配,还没有就从buddy中分配。
page = ion_page_pool_alloc(pool);
return page;
}
ion_page_pool_alloc()函数分配:
⾸先从pool中的high内存链表中分配,如果没有就从low链表中分配
再者,如果当前pool中都没有内存,那么就从buddy中分配
struct page *ion_page_pool_alloc(struct ion_page_pool *pool)
{
struct page *page = NULL;
BUG_ON(!pool);
mutex_lock(&pool->mutex);
//分配时,先从pool中分配
//pool中⼜分为highmem和lowmem
//先从high中去分配
if (pool->high_count)
page = ion_page_pool_remove(pool, true);
else if (pool->low_count)
page = ion_page_pool_remove(pool, false);
mutex_unlock(&pool->mutex);
//如果没有分配成功,那么就从伙伴系统中分配。
//但是这⾥分配后并没有加⼊到pool中。
if (!page)
page = ion_page_pool_alloc_pages(pool);
return page;
}
ion_page_pool_remove()函数:
如果pool中有内存,那么就从pool的对应链表中获取page,获取后将page从链表中移除。
static struct page *ion_page_pool_remove(struct ion_page_pool *pool, bool high)
{
struct page *page;
//如果是从high中申请,那么就从high_items链表中拿出page(可能是order为8/4/0),并且high_count减1.
//这⾥可以看到,page被加⼊到high_items或者low_items中,是通过page->lru
if (high) {
BUG_ON(!pool->high_count);
page = list_first_entry(&pool->high_items, struct page, lru);
pool->high_count--;
} else {
BUG_ON(!pool->low_count);
page = list_first_entry(&pool->low_items, struct page, lru);
pool->low_count--;
}
//将page从low或者high链表中移除。
list_del(&page->lru);
return page;
}
ion_page_pool_alloc_pages函数:
如果pool中没有的话,就调⽤alloc_pages()从buddy中分配内存
static void *ion_page_pool_alloc_pages(struct ion_page_pool *pool)
{
struct page *page = alloc_pages(pool->gfp_mask, pool->order);
if (!page)
return NULL;
return page;
}
释放内存(system heap)到pool
释放内存,会调⽤free接⼝,将ion_buffer中通过sg_table组织起来的page加到pool中。当然如果在分配时,已经指定了flags static void ion_system_heap_free(struct ion_buffer *buffer)
{
struct ion_system_heap *sys_heap = container_of(buffer->heap,
struct ion_system_heap,
heap);
struct sg_table *table = buffer->sg_table;
struct scatterlist *sg;
int i;
/* zero the buffer before goto page pool */
if (!(buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE))
ion_heap_buffer_zero(buffer);
//遍历ion_buffer中通过sg_table组织的page,并将这些page返回到pool中,并不直接给buddy。
for_each_sg(table->sgl, sg, table->nents, i)
free_buffer_page(sys_heap, buffer, sg_page(sg));
sg_free_table(table);
kfree(table);
}
*关键函数:free_buffer_page()
通过是否设置ION_PRIV_FLAG_SHRINKER_FREE标志来确认释放时直接给buddy还是先放到pool中
static void free_buffer_page(struct ion_system_heap *heap,
struct ion_buffer *buffer, struct page *page)
{
struct ion_page_pool *pool;
unsigned int order = compound_order(page);
bool cached = ion_buffer_cached(buffer);
/
* go to system */
//如果指定了ION_PRIV_FLAG_SHRINKER_FREE标志,则直接返还给buddy
if (buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE) {
__free_pages(page, order);
return;
}
//⼀般来说都是返还给pool中,直到系统内存不⾜时,
//kswapd回收内存调⽤shrink回调,将pool中的内存回收到buddy中
if (!cached)
pool = heap->uncached_pools[order_to_index(order)];
else
pool = heap->cached_pools[order_to_index(order)];
//返还到pool中
ion_page_pool_free(pool, page);
}
ion_page_pool_free()->ion_page_pool_add()函数,返还到pool中。
返还给pool,也是先确定具体返还到的order、high或low
返还给pool后,最终要回到buddy中,需要通过系统回收接⼝shrink进⾏主动回收
static int ion_page_pool_add(struct ion_page_pool *pool, struct page *page)
{
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论