缘由:
在应用层两次调用 open_adf 接口后,整个系统就 crash了,经过log分析发现,在 kernel 第二次ION申请内存的时候,出了问题。那什么是ION?怎么玩的?于是准备一探究竟。
什么是 ION?
ION 是google android系统在4.0后引入的一种内存管理技术。详细链接如下:
The Android ION memory allocator
用在哪些地方?
应用层和kernel层都可以使用。应用层比如 camera、display、audio等系统。同时,ION 可以在两个进程间进行内存共享。
如何用?
对于kernel层,直接调用 ION 框架中相关的函数接口就可以;对于应用层,通过访问平台提供的 ION 驱动设备节点 “/dev/ion” 来进行操作。
相关概念
ion_client
:申请内存的对象,一般一个进程代表一个 client。以应用层为例,打开一次 “/dev/ion” 节点,就产生一个 client。
ion_heap
:内存缓冲池,存放通过伙伴系统申请的各种页面。heap 在系统开机阶段由平台ION驱动调用ION框架中相关接口函数创建。为了分类管理申请的页面,heap 也是多个种类。
ion_buffer
:申请的内存页面,存放在 heap 的 pool 中。
ion_handle
:存放申请到的 buffer 的句柄
简要理解
首先,在设备树中,SPRD 的 ION 配置部分,先指定了平台上的 heap相关信息,包括 type、id、name;如下:
ion {
compatible = "sprd,ion";
#address-cells = <1>;
#size-cells = <0>;
heap@0 {
reg = <0>;
label = "system";
type = <0>;
};
heap@3 {
reg = <3>;
label = "carveout_fb";
type = <2>;
memory-region = <&fb_reserved>;
};
};
其次,在代码中,SPRD ION 驱动,一是调用ION的 ion_device_create
接口函数,创建设备驱动节点“/dev/ion”,并注册相关的 ion 操作 ioctl 文件接口:
idev = ion_device_create(&sprd_ion_ioctl);
接着,调用 ION 的 ion_heap_create
接口函数,按照 ION 设备树里面的配置,创建各种 heap。注意,此时的 heap 中是空的,并没有内存页面存在。heap 中页面,是后续慢慢的存放进去的。(根据heap的type不同,这里的过程稍微不一致,以下以 ION_HEAP_TYPE_SYSTEM
为例)。
通过 ION 申请内存的接口是 ion_alloc
。该函数会去指定的 ion_heap
中查,如果 ion_heap
的 pool 中有空闲且符合要求的页面,则直接取用该页面;如果没有,则需要通过伙伴(buddy)系统的alloc_pages
去获取新的页面,至于如何通过伙伴系统去获取内存,此处略过不表。ion_alloc 申请的内存空间,最后会将句柄放置到 ion_handle
中,返回给 ion_client
。
而在 client 不再使用当前页面时,就需要释放。释放分两种情况,一是需要立即归还给系统的页面,二是可以暂时不归还给系统的页面。如果是需要立即规划给系统的页面,则需要调用 ion_page_pool_free_immediate
函数,最后走到 __free_pages
流程上;如果是可以暂时不归还给系统的页面,则调用了 ion_page_pool_free
函数,最后将这些页面存入指定的 heap 中,并对存入的页面进行统计、归纳整理,如果下一次再有 ion alloc 的需求发出,则alloc会先查询 heap 中的页面是否符合要求,如果有符合要求的页面,就直接使用了,不再通过伙伴系统去申请新的页面。
这里需要做一个对比,就是从伙伴系统申请页面,以及站在伙伴系统的基础上,直接从 ion heap 中获取页面,它们的速度肯定是不一样的,则效率也是不一样的。毕竟,产生一次缺页中断或者重新做一次物理内存块到虚拟页面的映射,这都是需要时间的。而 ion heap 中的页面,就像一个数组一样,申请一次,每次用的时候,最多采用 memset 清零就好。
代码考察
以下考察下 ION_HEAP_TYPE_SYSTEM
类型的 alloc 和 free 的过程。
针对 ION_HEAP_TYPE_SYSTEM 类型的 type, ion_alloc 最终会调用 ion_system_heap_allocate
接口。
struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
size_t align, unsigned int heap_id_mask,
unsigned int flags)
{
buffer = ion_buffer_create(heap, dev, len, align, flags);
}
static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
struct ion_device *dev,
unsigned long len,
unsigned long align,
unsigned long flags)
{
ret = heap->ops->allocate(heap, buffer, len, align, flags);
}
上面的 allocate
注册地方如下:
static struct ion_heap_ops system_heap_ops = {
.allocate = ion_system_heap_allocate,
};
则可以继续考察 ion_system_heap_allocate
的实现(代码片段):
static int ion_system_heap_allocate(struct ion_heap *heap,
struct ion_buffer *buffer,
unsigned long size, unsigned long align,
unsigned long flags)
{
//....
unsigned long size_remaining = PAGE_ALIGN(size);
//.....
INIT_LIST_HEAD(&pages);
INIT_LIST_HEAD(&pages_from_pool);
while (size_remaining > 0) {
info = alloc_largest_available(sys_heap, buffer, size_remaining,
max_order);
if (!info)
goto free_pages;
sz = (1 << info->order) * PAGE_SIZE;
if (info->from_pool) {
pool_sz += sz;
list_add_tail(&info->list, &pages_from_pool);
} else {
int index;
for (index = 0; index < num_orders; index++) {
if (info->order == orders[index]) {
buddy_orders[index]++;
break;
}
}
buddy_sz += sz;
list_add_tail(&info->list, &pages);
}
size_remaining -= sz;
max_order = info->order;
i++;
}
//....
table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
if (!table)
goto free_pages;
if (sg_alloc_table(table, i, GFP_KERNEL))
goto free_table;
sg = table->sgl;
do {
info = list_first_entry_or_null(&pages, struct page_info, list);
tmp_info = list_first_entry_or_null(&pages_from_pool,
struct page_info, list);
if (info && tmp_info) {
if (info->order >= tmp_info->order)
set_sg_info(info, sg);
else
set_sg_info(tmp_info, sg);
} else if (info) {
set_sg_info(info, sg);
} else if (tmp_info) {
set_sg_info(tmp_info, sg);
} else {
BUG();
}
sg = sg_next(sg);
} while (sg);
buffer->priv_virt = table;
return 0;
}
在该函数中,首选要对需要申请的内存大小,进行一个内存对齐操作:
unsigned long size_remaining = PAGE_ALIGN(size);
所谓的内存对齐,也可以理解成内存大小的提升。众所周知,内存分配中,最小的计量是页(page)。在操作系统中,以32bit为例,一页为4KB(4096 byte),比如要申请一个7KB大小的页面,那么此时怎么做?先申请一个4KB,这没问题,但是后面的3KB从哪里申请?系统中最小的页面都是4KB,不可再分割,属于原子级别了。那么,此时,客户要7KB,系统就给它8KB好了。从这里也可以看出,内存分配是需要管理的,否则,不恰当的内存分配,就会导致内存浪费。
while (size_remaining > 0) {
info = alloc_largest_available(sys_heap, buffer, size_remaining,
max_order);
if (!info)
goto free_pages;
sz = (1 << info->order) * PAGE_SIZE;
//...
size_remaining -= sz;
max_order = info->order;
i++;
}
从以上这个循环中可以看到,用户需要的内存,可能不是一次性就分配完成的,继续考察下 alloc_largest_available
函数:
static struct page_info *alloc_largest_available(struct ion_system_heap *heap,
struct ion_buffer *buffer,
unsigned long size,
unsigned int max_order)
{
struct page *page;
struct page_info *info;
int i;
bool from_pool;
for (i = 0; i < num_orders; i++) {
if (size < order_to_size(orders[i]))
continue;
if (max_order < orders[i])
continue;
page = alloc_buffer_page(heap, buffer, orders[i], &from_pool);
if (!page)
continue;
info = kmalloc(sizeof(*info), GFP_KERNEL);
if (info) {
info->page = page;
info->order = orders[i];
info->from_pool = from_pool;
}
return info;
}
return NULL;
}
这里有一个阶(orders)的概念。这里划分了三个阶:
static const unsigned int orders[] = {8, 4, 0};
每一个阶对应的内存大小为:
static inline unsigned int order_to_size(int order)
{
return PAGE_SIZE << order;
}
那么,按一个 PAGE 的大小为 4KB 来算,则如下:
4096 << 8 = 1048576 Byte = 1024KB = 1M
4096 << 4 = 65536 Byte = 64KB
4096 << 0 = 4096 Byte = 4KB
for (i = 0; i < num_orders; i++) {
if (size < order_to_size(orders[i]))
continue;
if (max_order < orders[i])
continue;
page = alloc_buffer_page(heap, buffer, orders[i], &from_pool);
}
这里首先选择一个合适的 order,比如用户需要一个 大于1M 的内存,则 order 就选择 8,也就是 order[0] ,在1M上面去获取内存;但是如果客户只需要一个4KB的页面,是否可以在1M或者64KB的阶上面去分配呢?从内存的尺寸上是可以的,但是从碎片内存的管理上来考虑,并不建议这样操作。
选定了orders后,继续考察 alloc_buffer_page
函数:
static struct page *alloc_buffer_page(struct ion_system_heap *heap,
struct ion_buffer *buffer,
unsigned long order,
bool *from_pool)
{
bool cached = ion_buffer_cached(buffer);
struct ion_page_pool *pool;
struct page *page;
if (!cached)
pool = heap->uncached_pools[order_to_index(order)];
else
pool = heap->cached_pools[order_to_index(order)];
page = ion_page_pool_alloc(pool, buffer->flags, from_pool);
return page;
}
这里的意思是,一个 heap中有两种 pool,一个是 cached,一个是uncached,需要确定从哪一类 pool 中去获取内存,至于cached和uncached的区别,暂时没有搞明白。继续 ion_page_pool_alloc
函数
struct page *ion_page_pool_alloc(struct ion_page_pool *pool,
unsigned long buffer_flag,
bool *from_pool)
{
struct page *page = NULL;
BUG_ON(!pool);
mutex_lock(&pool->mutex);
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);
if (!page) {
page = ion_page_pool_alloc_pages(pool, buffer_flag);
*from_pool = false;
} else {
*from_pool = true;
}
return page;
}
在 ion_page_pool_alloc
函数中,先确定当前 pool 里面是否有合适的页面;如果没有,则转向 buddy 系统进行分配获取:
if (pool->high_count)
page = ion_page_pool_remove(pool, true);
else if (pool->low_count)
page = ion_page_pool_remove(pool, false);
查看 pool 中是否有合适的页面,这里的 ion_page_pool_remove
的名字极具困扰,它的意思是把一个空闲页面从 pool 里面移除占用了:
static struct page *ion_page_pool_remove(struct ion_page_pool *pool, bool high)
{
struct page *page;
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--;
}
list_del(&page->lru);
return page;
在 ion_page_pool_alloc
中,如果 pool 里面没有合适的页面,则分配流程走向了伙伴系统:
page = ion_page_pool_alloc_pages(pool, buffer_flag);
*from_pool = false;
static void *ion_page_pool_alloc_pages(struct ion_page_pool *pool,
unsigned long buffer_flag)
{
struct page *page = NULL;
if (buffer_flag & ION_FLAG_NOCLEAR)
page = alloc_pages(pool->gfp_mask & (~__GFP_ZERO), pool->order);
else
page = alloc_pages(pool->gfp_mask, pool->order);
if (!page)
return NULL;
ion_pages_sync_for_device(pool->dev, page, PAGE_SIZE << pool->order,
DMA_BIDIRECTIONAL);
return page;
}
回到 ion_system_heap_allocate
函数。
假如用户需要一个 10M 的内存空间,则 ion_system_heap_allocate
函数中,就调用 10次 alloc_largest_available
函数。每次获取的内存页面,都需要标明是从 pool 里面获取的,还是从 buddy 里面获取的:
if (info->from_pool) { //从 pool 里面获取的
pool_sz += sz;
list_add_tail(&info->list, &pages_from_pool);
} else { //从buddy伙伴系统里面分配的
int index;
for (index = 0; index < num_orders; index++) {
if (info->order == orders[index]) {
buddy_orders[index]++;
break;
}
}
buddy_sz += sz;
list_add_tail(&info->list, &pages);
}
内存申请完毕,通过物理内存散列表(scatterlist)的方式进行管理:
table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
if (!table)
goto free_pages;
if (sg_alloc_table(table, i, GFP_KERNEL))
goto free_table;
sg = table->sgl;
do {
info = list_first_entry_or_null(&pages, struct page_info, list);
tmp_info = list_first_entry_or_null(&pages_from_pool,
struct page_info, list);
if (info && tmp_info) {
if (info->order >= tmp_info->order)
set_sg_info(info, sg);
else
set_sg_info(tmp_info, sg);
} else if (info) {
set_sg_info(info, sg);
} else if (tmp_info) {
set_sg_info(tmp_info, sg);
} else {
BUG();
}
sg = sg_next(sg);
} while (sg);
buffer->priv_virt = table;
关于 scatterlist 的内容,参考: Linux kernel scatterlist API介绍
ION 的 alloc 过程到此就结束了。下面继续考察一下 free 的过程。free 的过程,从 ION ioctl 的 free cmd 命令开始:
static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
union {
struct ion_fd_data fd;
struct ion_allocation_data allocation;
struct ion_handle_data handle;
struct ion_custom_data custom;
} data;
switch (cmd) {
case ION_IOC_ALLOC:
break;
//....
case ION_IOC_FREE:
{
ion_free_nolock(client, handle);
break;
}
free的过程稍微比较复杂,包括 handle 的解除、内核映射的解除、内核引用的归还等动作,此处略去不表,只看最后如何将一个空闲的页面,放入指定 heap 的 pool 中。
static void ion_free_nolock(struct ion_client *client, struct ion_handle *handle)
{
ion_handle_put_nolock(handle);
}
static int ion_handle_put_nolock(struct ion_handle *handle)
{
int ret;
ret = kref_put(&handle->ref, ion_handle_destroy);
return ret;
}
static void ion_handle_destroy(struct kref *kref)
{
ion_buffer_put(buffer);
}
static int ion_buffer_put(struct ion_buffer *buffer)
{
return kref_put(&buffer->ref, _ion_buffer_destroy);
}
static void _ion_buffer_destroy(struct kref *kref)
{
if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
ion_heap_freelist_add(heap, buffer);
else
ion_buffer_destroy(buffer);
}
void ion_buffer_destroy(struct ion_buffer *buffer)
{
if (WARN_ON(buffer->kmap_cnt > 0))
buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
buffer->heap->ops->unmap_dma(buffer->heap, buffer);
buffer->heap->ops->free(buffer);
vfree(buffer->pages);
kfree(buffer);
}
buffer->heap->ops->free(buffer);
调用的 free 函数,即是在 system_heap_ops
里面注册的 ion_system_heap_free
函数:
static struct ion_heap_ops system_heap_ops = {
.allocate = ion_system_heap_allocate,
.free = ion_system_heap_free,
.map_dma = ion_system_heap_map_dma,
.unmap_dma = ion_system_heap_unmap_dma,
.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_free
函数:
static void ion_system_heap_free(struct ion_buffer *buffer)
{
for_each_sg(table->sgl, sg, table->nents, i)
free_buffer_page(sys_heap, buffer, sg_page(sg));
sg_free_table(table);
}
alloc 的过程使用到了 scatterlist ,free 过程也会使用到 scatterlist。调用 free_buffer_page
进行处理:
static void free_buffer_page(struct ion_system_heap *heap,
struct ion_buffer *buffer, struct page *page)
{
unsigned int order = compound_order(page);
bool cached = ion_buffer_cached(buffer);
struct ion_page_pool *pool;
if (cached)
pool = heap->cached_pools[order_to_index(order)];
else
pool = heap->uncached_pools[order_to_index(order)];
if (buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE)
ion_page_pool_free_immediate(pool, page);
else
ion_page_pool_free(pool, page);
}
一样的,先确定内存页面,归还到哪类 pool 中:
if (cached)
pool = heap->cached_pools[order_to_index(order)];
else
pool = heap->uncached_pools[order_to_index(order)];
然后确定,该页面是立刻归还给系统,还是可以放入 pool 中:
if (buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE)
ion_page_pool_free_immediate(pool, page); // 立刻归还
else
ion_page_pool_free(pool, page); //放入 pool
如果是立刻归还,则:
void ion_page_pool_free_immediate(struct ion_page_pool *pool, struct page *page)
{
ion_page_pool_free_pages(pool, page);
}
//.........
static void ion_page_pool_free_pages(struct ion_page_pool *pool,
struct page *page)
{
if (!(pool->is_cache & ION_FLAG_CACHED))
ion_page_pool_free_set_cache_policy(pool, page);
__free_pages(page, pool->order);
}
如果可以放入 pool:
void ion_page_pool_free(struct ion_page_pool *pool, struct page *page)
{
int ret;
BUG_ON(pool->order != compound_order(page));
ret = ion_page_pool_add(pool, page); //pool 里面计数改变,并加入
}
static int ion_page_pool_add(struct ion_page_pool *pool, struct page *page)
{
mutex_lock(&pool->mutex);
if (PageHighMem(page)) {
list_add_tail(&page->lru, &pool->high_items); // 页面的 lru 添加到链表,方便下次寻找
pool->high_count++; // 增加引用
} else {
list_add_tail(&page->lru, &pool->low_items);
pool->low_count++; //增加引用
}
mutex_unlock(&pool->mutex);
return 0;
}
就这样,不再使用的空闲内存页面,就做了妥当的处理。free 的考察就此结束。
资料参考
android ion --system heap(个人理解,不确定完全对)
Android-ION
The Android ION memory allocator
linux物理内存管理-伙伴系统
Linux kernel scatterlist API介绍
Linux内核scatterlist用法
页面置换算法(FIFO,LRU,OPT)