请稍侯

内存管理 —— ION

18 November 2017

ION 是当前 Android 流行的内存分配管理机制,在多媒体部分中使用的最多,例如从 Camera 到 Display,从 Mediaserver 到 Surfaceflinger,都会利用 ION 进行内存分配管理。 ION 的前任是 PMEM,关于 PMEM 我在 M030/M04X 项目中有接触过,后来由于 PMEM 的一些局限性,Google 推出了 ION 来取代 PMEM,当前 ION 已经融合到 Linux 主线,被广泛使用。 对于魅族,从 M65 项目开始,相机的内存分配管理就已经利用 ION 进行了,本文会结合 M65、M76 和 M86 项目开发介绍下我对 ION 内存管理机制的理解和感悟。

ION 基本概念

ION,最显著的特点是它可以被用户空间的进程之间或者内核空间的模块之间进行内存共享,而且这种共享可以是零拷贝的。在实际使用中,ION 和 VIDEOBUF2、DMA-BUF、V4L2 等结合的很紧密。本文主要介绍 ION,其它子系统感兴趣的话后续会陆续进行介绍。

ION 是在各种 heaps 上分配内存,通过 ion_buffer 来描述所分配的内存。

下图展示了 ION 的基本框架。图中 PID1、PID2、PID3 表示用户空间进程。ION core 表示 ION 核心层,它提供设备创建、注册等服务,同时提供统一的接口给用户使用。ION Driver 利用 ION core 对相应功能进行实现,可以说它是具体平台相关的,例如 SAMSUNG 平台、QUALCOMM 平台和 MTK 平台都会依据自己的特性开发相应的 ION Driver。

ION_ARCHI

上图虽然描述的是用户空间进程使用 ION 的情形,但实际上,在内核空间同样可以直接使用 ION 来分配、管理内存。例如 M76、M86 平台的相机驱动,都有直接使用 ION 分配和管理内存。

主要数据结构

数据结构是程序设计的基础,代码看的多了,其实可以从数据结构看出其能提供的基本功能和大致用法。 为了抓住纲领,本文将抓住 ION 的主要数据结构进行介绍。

ion_device

ion_device 是 ION 很重要很基础的数据结构,用 struct ion_device 结构体描述,一般在一个系统中只有一个本实例。例如在 M86 中,就是在 exynos_ion_v2.c 的 exynos_ion_probe() 函数中创建了系统唯一的本实例。

struct ion_device {
	struct miscdevice dev;
	struct rb_root buffers;
	struct mutex buffer_lock;
	struct rw_semaphore lock;
	struct plist_head heaps;
	long (*custom_ioctl)(struct ion_client *client, unsigned int cmd,
			     unsigned long arg);
	struct rb_root clients;
	struct dentry *debug_root;
	struct dentry *heaps_debug_root;
	struct dentry *clients_debug_root;
	struct semaphore vm_sem;
	atomic_t page_idx;
	struct vm_struct *reserved_vm_area;
	pte_t **pte;
	...
};

struct ion_device 其实是 ION 的核心结构,不过由于对于使用 ION 的用户而言是屏蔽的,即如果是单纯使用 ION,不需要直接和本结构打交道。但是想完全的理解 ION,需要对其有所了解。 struct ion_device 是由 ion_device_create() [ion.c] 分配、初始化。

  • dev 成员,是 struct miscdevice 类型,所以可想而知 ION 是作为 MISC 设备注册进系统的。从这点还可以看出来,用户空间使用 ION 时必定需要使用 open 啊,ioctl 啊系统调用。事实也正是如此。
  • heaps 成员,在 M86 使用过的 KERNEL LINUX 3.10 中是 struct plist_head 类型,但是在此之前并不是此类型,例如在 M65 使用过的 KERNEL LINUX 3.4 中是 struct rb_root 类型。可见随着 KERNEL 和 ION 的演进,struct ion_device 的实现会有所改变。本字段管理的是属于本 struct ion_device 的所有 struct ion_heap 实例。
  • clients 成员,是 struct rb_root 类型,struct rb_root 是红黑树,属于二叉树的一种。本字段管理的是 struct ion_client 实例。

ion_client

struct ion_client 是由 ion_client_create() [ion.c] 创建,在创建时必须指定上文提到的 struct ion_device 实例。

struct ion_client {
	struct rb_node node;
	struct ion_device *dev;
	struct rb_root handles;
	struct idr idr;
	struct mutex lock;
	const char *name;
	char *display_name;
	int display_serial;
	struct task_struct *task;
	pid_t pid;
	struct dentry *debug_root;
};
  • node 成员,是 struct rb_node 结构类型,用于将本 struct ion_client 实例加入到 struct ion_device,具体的是 struct ion_device::clients。
  • device 成员,是 struct ion_device 指针结构类型,指向所属的 struct ion_device 实例。
  • handles 成员,是 struct rb_root 结构类型,管理其所拥有的 handle,即 struct ion_handle 实例。一个 struct ion_handle 实例表示一个 buffer,即 struct ion_buffer 实例。而 struct ion_buffer 就是从 heap,即 struct ion_heap 中分配的内存。

ion_heap

struct ion_heap 表示 ION 中的重要概念 heap。系统会通过链表或者红黑树,这取决与你所使用的 KERNEL 版本,来管理所有的 heap,这些 heap 可用 struct ion_device::heaps 字段来寻找。

struct ion_heap {
	struct plist_node node;
	struct ion_device *dev;
	enum ion_heap_type type;
	struct ion_heap_ops *ops;
	unsigned long flags;
	unsigned int id;
	const char *name;
	struct shrinker shrinker;
	struct list_head free_list;
	size_t free_list_size;
	spinlock_t free_lock;
	wait_queue_head_t waitqueue;
	struct task_struct *task;

	int (*debug_show)(struct ion_heap *heap, struct seq_file *, void *);
};
  • node 成员,是 struct plist_node 结构,用于将本 heap 实例加入到 struct ion_device 所管理的链表中,详情可以参见 ion_device_add_heap() [ion.c] 函数。
  • dev 成员,是 struct ion_device 结构体指针类型,用于指示本 heap 挂在哪一个 struct ion_device 实例下了。
  • type 成员,是 enum ion_heap_type 类型,用于表示本 heap 属于哪种类型。用户在使用 ION 分配内存时需要指定 heap 的种类。关于本字段,后续会结合使用方法进行更详细的介绍。
  • ops 成员,是 struct ion_heap_ops 类型,它很重要!本字段提供的回调函数是用于从本 heap 中分配内存时所时用的。请参见 ion_buffer_create() [ion.c],它会调用 struct ion_heap_ops::allocate() 等回调函数。
	struct ion_heap_ops {
		int (*allocate)(struct ion_heap *heap,
				struct ion_buffer *buffer, unsigned long len,
				unsigned long align, unsigned long flags);
		void (*free)(struct ion_buffer *buffer);
		int (*phys)(struct ion_heap *heap, struct ion_buffer *buffer,
			    ion_phys_addr_t *addr, size_t *len);
		struct sg_table * (*map_dma)(struct ion_heap *heap,
					     struct ion_buffer *buffer);
		void (*unmap_dma)(struct ion_heap *heap, struct ion_buffer *buffer);
		void * (*map_kernel)(struct ion_heap *heap, struct ion_buffer *buffer);
		void (*unmap_kernel)(struct ion_heap *heap, struct ion_buffer *buffer);
		int (*map_user)(struct ion_heap *mapper, struct ion_buffer *buffer,
				struct vm_area_struct *vma);
		int (*shrink)(struct ion_heap *heap, gfp_t gfp_mask, int nr_to_scan);
		void (*preload) (struct ion_heap *heap, unsigned int count,
				 unsigned int flags, struct ion_preload_object obj[]);
	};
  • id 成员,也很重要,它可表示优先级,在分配内存时选择哪一个 heap 有关,必须唯一。

ion_handle

struct ion_handle 其实就是表示 buffer,用户空间常用它来表示 buffer。本结构通过 ion_handle_create() [ion.c] 分配、初始化。

struct ion_handle {
	struct kref ref;
	struct ion_client *client;
	struct ion_buffer *buffer;
	struct rb_node node;
	unsigned int kmap_cnt;
	int id;
};
  • ref 成员,是 struct kref 结构类型,它在内核中被广泛的用来表示引用计数。ion_handle 的创建和销毁都与其有关,下文会有介绍。
  • client 成员,是 struct ion_client 指针类型,指向其所述的 ion_client 实例。
  • buffer 成员,是 struct ion_buffer 指针类型,指向真正的 buffer 所在,它可以说是 stuct ion_handle 的核心成员了。

下面就来介绍 struct ion_buffer 结构。在分配内存时,也是先通过 ion_buffer_create() 创建 ion_buffer 实例,然后交给 ion_handle_create() 创建 ion_handle 实例。

ion_buffer

struct ion_buffer 很重要,通过 ION 分配的内存就是通过它表示的。它和上面提到的 ion_handle 的区别主要在于一个是用户空间使用的,一个是内核空间使用的。即虽然常用的接口函数中使用的是 struct ion_handle,但实际上真正表示内存的其实是 struct ion_buffer。

struct ion_buffer {
	struct kref ref;
	...
	struct ion_device *dev;
	struct ion_heap *heap;
	unsigned long flags;
	unsigned long private_flags;
	size_t size;
	union {
		void *priv_virt;
		ion_phys_addr_t priv_phys;
	};
	struct mutex lock;
	int kmap_cnt;
	void *vaddr;
	int dmap_cnt;
	struct sg_table *sg_table;
	struct page **pages;
	struct list_head vmas;
	struct list_head iovas;
	...
};
  • ref 成员,是 struct kref 结构实例,维护了本 ion_buffer 的引用计数。当引用计数为 0 时会释放该 buffer,即 struct ion_heap_ops::free 会被调用。分配用 ION_IOC_ALLOC 型 ioctl 系统调用,相应的释放用 ION_IOC_FREE 型 ioctl 系统调用。
  • size 成员,当然是本 buffer 所表示的空间的大小,用字节表示。
  • priv_virt 成员,是所分配内存的虚拟地址啦,它常与 struct sg_table,或者封装它的结构,有关。它不是我们在内核中读写时所需的内核虚拟地址啦,内核虚拟地址使用 vaddr 成员来表示的。一般而言,物理内存不连续的,使用本字段;否则使用下面的 priv_phys 字段,如 struct ion_heap_ops contig_heap_ops。
  • priv_phys 成员,表示所分配的内存的物理地址。它适用于分配的物理内存是连续的 ion heap。这种连续的物理内存:在将其映射到用户空间时,即获取用户空间虚拟地址,可以使用 remap_pfn_range() [memory.c] 这个方便的接口;在将其映射到内核空间时,即获取内核虚拟地址,可以使用 vmap() [vmalloc.c] 这个方便的接口。例子详见 struct ion_heap_ops contig_heap_ops [exynos_ion.c]。priv_virt 成员和 priv_phys 成员组成了一个联合体,其实都表示地址,只不过不同的场景下具体用的不一样而已。
  • kmap_cnt 成员,记录本 buffer 被映射到内核空间的次数。
  • vaddr 成员,是本 buffer 对应的内核虚拟地址。当 kmap_cnt 不为 0 时有效。可以通过 ion_map_kernel() [ion.c] 来获取本 buffer 对应的内核虚拟地址。ion_map_kernel() [ion.c] 实际上调用的是相应 struct ion_heap_ops::map_kernel 回调函数获取相应的虚拟地址的。
  • dmap_cnt 成员,记录本 buffer 被 mapped for DMA 的次数。
  • sg_table 成员,是 struct sg_table 结构体类型的指针。本字段与 DMA 操作有关,而且仅仅在 dmap_cnt 成员变量不为 0 时是有效的。可以通过 ion_buffer_create() [ion.c] 来初始化本成员变量,该函数实际上是调用相应 ion_heap 所属的 struct ion_heap_ops::map_dma 回调函数获取本字段的值的。
  • dirty 成员,表示 bitmask。即以位图表示本 buffer 的哪一个 page 是 dirty 的,即不能直接用于 DMA。dirty 表示 DMA 的不一致性,即 CPU 缓存中的内容与内存中的实际内容不一样。

事实上,ION 涉及到的数据结构还有很多,这里列举的都是一些非常重要的。 下图展示了上文介绍到的数据结构的基本关系。

ION_RELAT

重要函数分析

函数对数据进行处理,完成特定的任务,体现算法的具体实现。

ion_device_create

前面分析 ION 的一些核心数据结构时曾经指出,ION 会注册进 MISC 设备,这样用户空间就可以像使用 MISC 设备一样使用 ION 进行内存分配了。 先来看 ION 是如何注册进 MISC 子系统的。

struct ion_device *ion_device_create(long (*custom_ioctl)
				     (struct ion_client *client,
				      unsigned int cmd,
				      unsigned long arg))
{
	struct ion_device *idev;
	int ret;
	/*
	* 分配 struct ion_device 实例
	*/
	idev = kzalloc(sizeof(struct ion_device), GFP_KERNEL);
	...
	/*
	* 如前面所说,dev 成员是 struct miscdevice 结构体类型,
	* 这里初始化其相关字段,名字指定为 ”ion”,所以提供
	* 给用户空间调用的设备节点名为 /dev/ion。
	* 用户空间操作该设备节点时,ION 驱动中响应的函数集位于
	* ion_fops
	*/
	idev->dev.minor = MISC_DYNAMIC_MINOR;
	idev->dev.name = "ion";
	idev->dev.fops = &ion_fops;
	idev->dev.parent = NULL;
	ret = misc_register(&idev->dev);
	...
	/*
	* 注册调试信息接口
	*/
	idev->debug_root = debugfs_create_dir("ion", NULL);
	...
	idev->heaps_debug_root = debugfs_create_dir("heaps", idev->debug_root);
	...
	idev->clients_debug_root = debugfs_create_dir("clients",
						idev->debug_root);
	...

debugfs_done:

	idev->custom_ioctl = custom_ioctl;
	idev->buffers = RB_ROOT;
	mutex_init(&idev->buffer_lock);
	init_rwsem(&idev->lock);
	plist_head_init(&idev->heaps);
	idev->clients = RB_ROOT;
	...
	/* backup of ion device: assumes there is only one ion device */
	g_idev = idev;
	return idev;
}

本函数最重要的是分配并初始化了核心 struct ion_device 实例,并将其和 MISC 设备结合起来,这样用户空间就可以通过 open()、ioctl() 等系统调用使用它了。

ion_open

用户空间要想使用 ION 进行内存分配,首先必须对设备节点 /dev/ion 进行 open() 系统调用。

static int ion_open(struct inode *inode, struct file *file)
{
	struct miscdevice *miscdev = file->private_data;
	struct ion_device *dev = container_of(miscdev, struct ion_device, dev);
	struct ion_client *client;
	char debug_name[64];
	
	snprintf(debug_name, 64, "%u", task_pid_nr(current->group_leader));
	/*
	* 创建struct ion_client 实例
	*/
	client = ion_client_create(dev, debug_name);
	...
	file->private_data = client;

	return 0;
}

ion_open() 函数最重要的作用就是创建了 struct ion_client 实例。这样,后续就可以利用 ioctl 系统调用从其中分配内存了。

ION 系统提供的 ioctl 类型有很多,常用的有 ION_IOC_ALLOC、ION_IOC_FREE、ION_IOC_SHARE 和 ION_IOC_IMPORT 等等。

#define ION_IOC_ALLOC		_IOWR(ION_IOC_MAGIC, 0, struct ion_allocation_data)
#define ION_IOC_FREE		_IOWR(ION_IOC_MAGIC, 1, struct ion_handle_data)
#define ION_IOC_SHARE		_IOWR(ION_IOC_MAGIC, 4, struct ion_fd_data)

下面就抽出几个典型的进行分析。

ion_alloc

这是当用户空间执行 ION_IOC_ALLOC 型 ioctl() 系统调用时所执行的。

struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
			     size_t align, unsigned int heap_id_mask,
			     unsigned int flags)
{
	struct ion_handle *handle;
	struct ion_device *dev = client->dev;
	struct ion_buffer *buffer = NULL;
	struct ion_heap *heap;
	int ret;
	...
	/*
	* len 是用户空间想分配的内存大小,驱动中会将其进行页对齐
	*/
	len = PAGE_ALIGN(len);
	...
	down_read(&dev->lock);
	/*
	* 用户空间会指定其想从哪种 heap 分配内存,ION 驱动
	* 会对其进行检查并找到最合适的。
	*/
	heap_id_mask = ion_parse_heap_id(heap_id_mask, flags);
	/*
	* 从系统所有的 heap 中找到最合适的 heap 并分配内存,
	* 创建 struct ion_buffer 实例。
	*/
	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;
		buffer = ion_buffer_create(heap, dev, len, align, flags);
		if (!IS_ERR(buffer))
			break;
	}
	up_read(&dev->lock);
	...
	/*
	* 在对创建的 ion_buffer 实例进行了一系列的 sanity 检查后
	* 利用其创建 struct ion_handle 实例。
	*/
	handle = ion_handle_create(client, buffer);
	...
	mutex_lock(&client->lock);
	/*
	* 将 struct ion_handle 实例加入到其所属的 struct ion_client
	*/
	ret = ion_handle_add(client, handle);
	mutex_unlock(&client->lock);
	...
	return handle;
}

ion_free

这是当用户空间执行 ION_IOC_FREE 型 ioctl() 系统调用时所执行的。

void ion_free(struct ion_client *client, struct ion_handle *handle)
{
	bool valid_handle;

	BUG_ON(client != handle->client);

	mutex_lock(&client->lock);
	valid_handle = ion_handle_validate(client, handle);

	if (!valid_handle) {
		WARN(1, "%s: invalid handle passed to free.\n", __func__);
		mutex_unlock(&client->lock);
		return;
	}
	mutex_unlock(&client->lock);
	ion_handle_put(client, handle);
}

此函数比较简单,重点就是通过 ion_handle_put() 来对上文提到的 struct ion_handle::ref 这个 reference count 减一,当 ref 减到 0 时,就会调用 ion_handle_destroy() 来销毁 ion_handle 实例。

从前文的分析可知,用户空间在利用 ION 分配内存时,需要指定具体的 heap mask,即告知 ION 想从哪种 heap 分配内存。 下面就来介绍下。

HEAP 种类

以下是通过 ION 分配内存时,可能会使用到的 heap mask。

#define ION_HEAP_SYSTEM_MASK            (1 << 0)
#define ION_HEAP_SYSTEM_CONTIG_MASK     (1 << 1)
#define ION_HEAP_EXYNOS_CONTIG_MASK     (1 << 4)
#define ION_HEAP_EXYNOS_MASK            (1 << 5)

以 M65 项目为例,系统定义了 4 种 heap,见 dev-ion.c。

ION_HEAP_TYPE_SYSTEM

本 heap 的名字为:”ion_noncontig_heap”。相应的 heap mask 为 1,即 ION_HEAP_SYSTEM_MASK。 在本 struct ion_heap 上分配内存的操作集是: struct ion_heap_ops system_heap_ops [ion_system_heap.c] 其内存可以从 HIGHMEM 中分配,所以其物理内存可能不连续。 在调试 M65 CAMERA 的 HDR 功能时,一开始发现其写入速度很慢很慢,经过调查,后来在通过 ION 分配内存时指定 ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC | ION_FLAG_PRESERVE_KMAP 标志后速度得到明显提高。

ION_HEAP_TYPE_SYSTEM_CONTIG

本 heap 的名字为:”ion_contig_heap”,相应的 heap mask 为 2,即 ION_HEAP_SYSTEM_CONTIG_MASK。 在本 struct ion_heap 上分配内存的操作集是: struct ion_heap_ops kmalloc_ops [ion_system_heap.c] 其内存分配 allocate 回调函数为:ion_system_contig_heap_allocate() [ion_system_heap.c],该函数很简单,就是利用 kzalloc() 分配内存。

ION_HEAP_TYPE_EXYNOS

本 heap 的名字为:”exynos_noncontig_heap”,相应的 heap mask 为 32,即 ION_HEAP_EXYNOS_MASK。 在本 struct ion_heap 上分配内存的操作集是: struct ion_heap_ops vmheap_ops [exynos_ion.c] 其内存可以从 HIGHMEM 中分配,所以其物理内存不一定连续。详情可以见其 allocate 回调函数 ion_exynos_heap_allocate() [exynos_ion.c]。

ION_HEAP_TYPE_EXYNOS_CONTIG

本 heap 的名字为:”exynos_contig_heap”。相应的 heap mask 为 16,即 ION_HEAP_EXYNOS_CONTIG_MASK。 在本 struct ion_heap 上分配内存的操作集是: struct ion_heap_ops contig_heap_ops [exynos_ion.c] 内存由 CMA 分配,所以可以保证其物理地址是连续的。详情可以参见其 allocate 回调函数 ion_exynos_contig_heap_allocate() [exynos_ion.c]。 因为本函数是利用 CMA 分配内存,所以可以推测肯定有地方预留了 CMA 所需的物理内存。分析代码后可以发现,这个地方就位于 mach-m65.c 文件。 M65 的相机驱动使用了本 ION_HEAP_TYPE_EXYNOS_CONTIG 类型的 heap 来分配内存。 按道理,当用户空间获取了 struct ion_handle 实例后,就已经完成了 ION 内存的分配任务。但实际上,为了在不同的进程间,甚至在用户空间和内核空间共享这段内存使用,用户空间还通常需要调用 ION_IOC_SHARE 型ioctl(),获取 ion buffer 相关的 fd,这就和 dma_buf 子系统联系起来了。

int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle)
{
	struct dma_buf *dmabuf;
	int fd;
	/*
	* 前面已经说过 struct ion_handle 其实就是对 struct ion_buffer
	* 的封装,这里利用 struct ion_client 实例和 struct ion_handle 实例
	* 创建了一个 dma_buf。
	*/
	dmabuf = ion_share_dma_buf(client, handle);
	...
	/*
	* 重要,这样就将 struct ion_buffer 对应的内存转化
	* 为文件描述符了。将文件描述符传递给其它进程或者
	* 传给内核空间,其它进程或者内核空间就可以使用其内存了
	*/
	fd = dma_buf_fd(dmabuf, O_CLOEXEC);
	...
	return fd;
}

到这里,ION 使用过程中涉及到的重要函数都已经介绍完全。当然要完全理解这些函数的细节,需要用户对 dma_buffer 有一定的了解。对 dma_buffer 的介绍不属于本文的范围,有兴趣的话可以参见我写的其它相关文档。

结语

关于 Android ION 内存管理机制的介绍就到这里。本文先介绍了什么是 ION,为什么要用 ION。ION 是为了解决内存碎片管理而引入的通用内存管理器,用于取代 PMEM 机制。然后介绍了下 ION 中的重要数据结构,对 struct ion_device、struct ion_client、struct ion_heap、struct ion_handle 和 struct ion_buffer 进行了详细的介绍,并对它们之间的关系进行了阐述。接着,从使用 ION 的场景出发,介绍了一些重要的函数,例如 ion_alloc(),并以实际的相机开发为例介绍了系统中各个 ion_heap 的种类和各自的内存特性。本文还对进程之间、内核空间和用户空间之间的内存共享进行了介绍。

参考资料

作者信息
瞿稻 bio photo

Qudao

本作品由 Qudao 创作,并由本站发表。未经授权,禁止转载。