【內存管理】ION內存管理器淺析(system heap)(基於linux 4.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;
	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)
{
	mutex_lock(&pool->mutex);
	//判斷page是否為highmem中分配的
	if (PageHighMem(page)) {
		list_add_tail(&page->lru, &pool->high_items);
		pool->high_count++;
	} else {
		list_add_tail(&page->lru, &pool->low_items);
		pool->low_count++;
	}
	mutex_unlock(&pool->mutex);
	return 0;
}

釋放內存到buddy

  • 已經在pool中的page,通過系統內存回收shrink機制回到buddy
  • ion_system_heap_shrink()函數:
static int ion_system_heap_shrink(struct ion_heap *heap, gfp_t gfp_mask,
				  int nr_to_scan)
{
	struct ion_page_pool *uncached_pool;
	struct ion_page_pool *cached_pool;
	struct ion_system_heap *sys_heap;
	int nr_total = 0;
	int i, nr_freed;
	int only_scan = 0;

	sys_heap = container_of(heap, struct ion_system_heap, heap);

	if (!nr_to_scan)
		only_scan = 1;
	//根據不同的order遍歷
	for (i = 0; i < NUM_ORDERS; i++) {
		uncached_pool = sys_heap->uncached_pools[i];
		cached_pool = sys_heap->cached_pools[i];

		if (only_scan) { //掃描
			nr_total += ion_page_pool_shrink(uncached_pool,
							 gfp_mask,
							 nr_to_scan);

			nr_total += ion_page_pool_shrink(cached_pool,
							 gfp_mask,
							 nr_to_scan);
		} else { //回收
			nr_freed = ion_page_pool_shrink(uncached_pool,
							gfp_mask,
							nr_to_scan);
			nr_to_scan -= nr_freed;
			nr_total += nr_freed;
			if (nr_to_scan <= 0)
				break;
			nr_freed = ion_page_pool_shrink(cached_pool,
							gfp_mask,
							nr_to_scan);
			nr_to_scan -= nr_freed;
			nr_total += nr_freed;
			if (nr_to_scan <= 0)
				break;
		}
	}
	return nr_total;
}
  • 關鍵函數ion_page_pool_shrink():

根據傳入的pool以及回收的數量,在指定的pool中掃描回收
回收時主要就是將page從pool鏈表中拿下來,然后調用系統free_pages接口,將page掛到buddy的free_area中。

int ion_page_pool_shrink(struct ion_page_pool *pool, gfp_t gfp_mask,
			 int nr_to_scan)
{
	int freed = 0;
	bool high;

	if (current_is_kswapd())
		high = true;
	else
		high = !!(gfp_mask & __GFP_HIGHMEM);
	//只掃描,返回page數量
	if (nr_to_scan == 0)
		return ion_page_pool_total(pool, high);
	//回收
	//先將low的內存進行回收,如果沒有low就從high中回收
	//回收的內存達到要求的nr_to_scan后停止,或者把pool中的全部回收完了也停止
	while (freed < nr_to_scan) {
		struct page *page;

		mutex_lock(&pool->mutex);
		if (pool->low_count) {
			page = ion_page_pool_remove(pool, false);
		} else if (high && pool->high_count) {
			page = ion_page_pool_remove(pool, true);
		} else {
			mutex_unlock(&pool->mutex);
			break;
		}
		mutex_unlock(&pool->mutex);
		ion_page_pool_free_pages(pool, page);
		freed += (1 << pool->order);
	}
	//返回回收的數量
	return freed;
}

后記

  • 最后,system heap的大致分配流程到此,其中我覺得比較重要的是,ion分配的頁已經不在buddy,而是由自己來做管理。同時里面涉及到通過sg_table來管理離散的物理內存,並且最終ion_alloc函數中將ion_buffer轉換成dma_buf,同時使用dma_buf的fd作為返回給用戶的文件句柄。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM