SPRD ION 代码阅读理解---笔记

缘由:
在应用层两次调用 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介绍

Linux内核scatterlist用法

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)

发布了28 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/q1075355798/article/details/103488951