ION内存管理器介绍

1. ION介绍

ION是google在Android4.0为了解决内存碎片化管理而引入的通用内存管理器,用来支持不同的内存分配机制,如CARVOUT(PMEM),物理连续内存(kmalloc),虚拟地址连续但物理地址不连续内存(vmalloc),IOMMU等。

内核版本:linux-4.9

2. ION框架

在这里插入图片描述
名词解释:

ion client:ion的使用者,用户空间和内核驱动要使用ion的buffer,必须先创建一个client,一个client可以有多个ion buffer,用struct ion_buffer结构表示。

ion handle:将ion buffer抽象出来,通过ion handle来管理该buffer,一般用户直接拿到的都是handle,而不是buffer,ion handle用struct ion_handle表示。

ion head:用来表示内存分配的相关信息,包括id,type,name等,用struct ion_heap表示,在调用ion_device_create时指定。

各个数据结构关系如下图所示:

2.1 ION的数据结构

在这里插入图片描述

2.2 ION的数据结构介绍

ion_device : ion设备,一个平台只能创建一个,属于misc设备,vexpress_ion_probe中调用ion_device_create创建.

struct ion_device {
	struct miscdevice dev;		//混杂设备
	struct rb_root buffers;		//连接通过该ion_device创建的所有ion_buffer的树
	struct mutex buffer_lock;
	struct rw_semaphore lock;
	struct plist_head heaps;		//该ion_device所支持的ion_heap
	long (*custom_ioctl)(struct ion_client *client, unsigned int cmd,
				    unsigned long arg);		//用户指定的ioctl函数,当使用ION_IOC_CUSTOM命令时,会由ion_ioctl()委托给custom_ioctl
	struct rb_root clients;				//连接该ion_device下的所有ion_client的树,ion_client可以在用户空间通过ion_open创建,也可以在驱动通过ion_client_create创建
	struct dentry *debug_root;
	struct dentry *heaps_debug_root;
	struct dentry *clients_debug_root;
	int heap_cnt;							//ion heaps的数目
};

ion_client : ion的使用者,用户空间和内核空间要使用ION的buffer,必须先创建一个client,一个client可以有多个buffer,用struct ion_buffer表示。

struct ion_client {
	struct rb_node node;			//挂在ion_device->clients树的节点
	struct ion_device *dev;		//指向所属的ion_device
	struct rb_root handles;		//管理该client下所有ion_handle的树
	struct idr idr;
	struct mutex lock;			//保护该client管理的handles树的互斥锁,修改该handles时需持锁
	const char *name;
	char *display_name;
	int display_serial;
	struct task_struct *task;
	pid_t pid;
	struct dentry *debug_root;
};

ion_handle :访问ion_buffer的句柄.一个ion_buffer可能被不同ion_client共享, ion_client通过ion_handle来访问ion_buffer.

struct ion_handle {
	struct kref ref;					     //该ion_handle的引用计数
	struct ion_client *client;		//指向所属的ion_client
	struct ion_buffer *buffer;		//指向所关联的ion_buffer
	struct rb_node node;					//用于添加到ion_client->handles树的节点
	unsigned int kmap_cnt;				//该handle map到kernel的次数. 实际是对ion_buffer->kmap_cnt的封装
	int id;							   //由ion_client->idr分配的唯一id,每个ion_handle有且只有一个独立id
};

ion_buffer:ion分配的buffer的元数据. ion_device分配的所有buffer, 记录在ion_device->buffers链上

struct ion_buffer {
	struct kref ref;										 //ion_buffer的引用计数
	union {
		struct rb_node node;							//用于添加到ion_device->buffers树的节点
		struct list_head list;
	};
	struct ion_device *dev;						 //指向所属的ion_device
	struct ion_heap *heap;						  //指向所属的ion_heap
	unsigned long flags;						    //分配ion_buffer指定的flag
	unsigned long private_flags;
	size_t size;									//buffer的大小
	void *priv_virt;								//ion_buffer管理的对应ion_heap的私有数据
	struct mutex lock;
	int kmap_cnt;				//映射给内核的次数. 第一次时, 才真正调用heap->ops->map_kernel, 此后只增加kmap_cnt计数
	void *vaddr;			 //映射给内核的虚拟地址
	int dmap_cnt;			//dma映射次数
	struct sg_table *sg_table;	//dma映射得到的散列表
	struct page **pages;        //保存sg_table中内存对应的页帧信息,用于同步cache用
	struct list_head vmas;		//记录ion_bufer对应的内存区域,用户空间使用mmap系统调用时将相应内存区域加入该链表
	/* used to track orphaned buffers */
	int handle_count;			//对于共享buffer,用于记录多少个ion_handle关联到该buffer
	char task_comm[TASK_COMM_LEN];
	pid_t pid;
};

ion_heap:ion堆,用于管理各种类型内存的分配,释放,映射,解除映射等操作的抽象对象

struct ion_heap {
	struct plist_node node;					//用于添加到ion_device->heaps链表的节点
	struct ion_device *dev;				 //指向所属的ion_device
	enum ion_heap_type type;       //ion_heap的类型
	struct ion_heap_ops *ops;      //ion_heap对应的函数操作集合
	unsigned long flags;
	unsigned int id;							//ion heap的唯一id,在分配buffer时,决定哪一种类型的heap的优先级
	const char *name;
	struct shrinker shrinker;
	struct list_head free_list;
	size_t free_list_size;
	spinlock_t free_lock;					//用于保护free_list的原子锁
	wait_queue_head_t waitqueue;
	struct task_struct *task;
	int (*debug_show)(struct ion_heap *heap, struct seq_file *, void *);
};

常见的ion_heap介绍:

ION_HEAP_TYPE_SYSTEM: System类型,系统非物理连续类型堆,使用vmalloc分配,比如上层要求分配10M内存,实际会得到4M + 4M + 2M共一个内存块,组成散列数组(sg_table),这个散列数组可直接给dma使用,用户态和内核态cpu访问时,需要通过map_user,map_kernel映射,映射后的是连续虚拟空间。

ION_HEAP_TYPE_SYSTEM_CONTIG: system_contig类型,系统物理连续堆,分配由kmalloc完成,一次最多不能分配超过4M(linux系统限制)。

ION_HEAP_TYPE_CARVEOUT: Carveout类型,用户预留一块连续大物理内存(预留区不能在bank内,不能用memblock_reserve预留,可用memblock_remove预留),通过gen_pool_alloc进行分配,一次能分配的大小不受系统4M限制,由预留量和使用情况决定,这种堆能满足de/ve对连续大块(超过4M)物理内存的需求,但缺点是预留后,系统不能使用,造成浪费。

ION_HEAP_TYPE_CHUNK: 和carveout类似,先预留一块大物理内存,再用gen_pool_alloc进行分配。不同是,只能按指定单元大小(chunk_size)来分配,比如要求分配10M内存,指定单元大小为4M,则最终得到的是3块内存区,每块大小4M,两块之间可能不连续。

ION_HEAP_TYPE_DMA: cma类型,内存分配由内存管理进行,主要通过dma map的相关ops完成。

ION_HEAP_TYPE_CUSTOM: 作为与用户自定义的堆的分界线,不实际使用;

ion_heap_ops:对于各种ion_heap的操作集合,实现buffer分配,释放,映射,解除映射等操作的集合。

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);
		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);
		int (*phys)(struct ion_heap *heap, struct ion_buffer *buffer,
						ion_phys_addr_t *addr, size_t *len);
};

2.3 ION初始化

在注册ION驱动中,实现对ion的初始化和封装,万事都从设备probe开始进行,代码如下:

static int vexpress_ion_probe(struct platform_device *pdev)
{
	struct vexpress_ion_dev *ipdev;
	int i;

	ipdev = devm_kzalloc(&pdev->dev, sizeof(*ipdev), GFP_KERNEL);
	if (!ipdev)
		return -ENOMEM;

	platform_set_drvdata(pdev, ipdev);

	ipdev->idev = ion_device_create(NULL);
	if (IS_ERR(ipdev->idev))
		return PTR_ERR(ipdev->idev);

	g_idev = ipdev->idev;
	ipdev->data = ion_parse_dt(pdev, vexpress_heaps);
	if (IS_ERR(ipdev->data))
		return PTR_ERR(ipdev->data);

	ipdev->heaps = devm_kzalloc(&pdev->dev,
				sizeof(struct ion_heap) * ipdev->data->nr,
				GFP_KERNEL);
	if (!ipdev->heaps) {
		ion_destroy_platform_data(ipdev->data);
		return -ENOMEM;
	}

	for (i = 0; i < ipdev->data->nr; i++) {
		ipdev->heaps[i] = ion_heap_create(&ipdev->data->heaps[i]);
		if (!ipdev->heaps) {
			ion_destroy_platform_data(ipdev->data);
			return -ENOMEM;
		}
		ion_device_add_heap(ipdev->idev, ipdev->heaps[i]);
	}
	return 0;
}

static int vexpress_ion_remove(struct platform_device *pdev)
{
	struct vexpress_ion_dev *ipdev;
	int i;

	ipdev = platform_get_drvdata(pdev);

	for (i = 0; i < ipdev->data->nr; i++)
		ion_heap_destroy(ipdev->heaps[i]);

	ion_destroy_platform_data(ipdev->data);
	ion_device_destroy(ipdev->idev);

	return 0;
}

static const struct of_device_id vexpress_ion_match_table[] = {
	{.compatible = "vexpress,ion"},
	{},
};

static struct platform_driver vexpress_ion_driver = {
	.probe = vexpress_ion_probe,
	.remove = vexpress_ion_remove,
	.driver = {
		.name = "ion-vexpress",
		.of_match_table = vexpress_ion_match_table,
	},
};

static int __init vexpress_ion_init(void)
{
	return platform_driver_register(&vexpress_ion_driver);
}

subsys_initcall(vexpress_ion_init);

2.4 ION内存的分配

2.4.1 用户空间ION内存的分配

在用户空间,如果想使用ion,需要进行以下两步操作:

1.创建ion client,只需简单的通过open系统调用打开/dev/ion设备,即可创建一个ion client。代码如下所示:

//在ion_device_create()中,将ion_device的文件操作集初始化为ion_fops
static const struct file_operations ion_fops = {
	.owner          = THIS_MODULE,
	.open           = ion_open,
	.release        = ion_release,
	.unlocked_ioctl = ion_ioctl,
	.compat_ioctl   = compat_ion_ioctl,
};

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));
	client = ion_client_create(dev, debug_name);		//创建ion client
	if (IS_ERR(client))
		return PTR_ERR(client);
	file->private_data = client;

	return 0;
}

2.在创建client之后,用户程序可以通过ioctl()函数的命令进行内存分配,释放,映射和解除映射等操作

3.ion在用户态使用ion的demo如下所示:

int main(void)
{
	/*数据结构见drivers/staging/android/uapi/ion.h*/
	struct ion_allocation_data alloc_data;
	struct ion_handle_data handle_data;
	struct ion_fd_data fd_data;
	int fd, ret = 0, ret1 = 0;
	unsigned char *buffer;

	//open device
	fd = open("/dev/ion", O_RDONLY);
	if (fd < 0) {
		printf("open device error!\n");
		ret = -1;
		return ret;
	}
	//设置ion alloc的参数
	alloc_data.len = 1024 * 768 *4;
	alloc_data.align = 0;
	alloc_data.heap_id_mask = ION_HEAP_TYPE_DMA_MASK;	//使用cma堆
	alloc_data.flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
	ret = ioctl(fd, ION_IOC_ALLOC, &alloc_data);		//分配ion内存
	if (ret) {
		printf("ion alloc error!\n");
		goto out1;
	}

	//获取dmabuf的fd
	fd_data.handle = alloc_data.handle;
	ret = ioctl(fd, ION_IOC_MAP, &fd_data);
	if (ret) {
		printf("ion map error!\n");
		goto out2;
	}

	//mmap to user space
	buffer = mmap(NULL, alloc_data.len, PROT_READ|PROT_WRITE, MAP_SHARED, fd_data.fd, 0);
	if (MAP_FAILED == buffer) {
		printf("mmap to user space error!\n");
		goto out3;
	}

	//use in user space
	memset(buffer, 0, alloc_data.len);

	//after use, munmap user buffer
	ret = munmap(buffer, alloc_data.len);
	if (ret) {
		printf("munmap error!\n");
	}

out3:
	//close dmabuf fd
	close(fd_data.fd);

out2:
	//free buffer
	handle_data.handle = alloc_data.handle;
	ret = ioctl(fd, ION_IOC_FREE, &handle_data);
	if (ret) {
		printf("ion free buffer error!\n");
	}

out1:
	//close ion device
	close(fd);
	return ret;
}

2.4.2 内核空间ION内存的分配

内核空间使用ion跟用户空间差不多,只是它的使用对用户空间来说是透明的而已。

1.同样,通过调用ion_client_create()函数创建一个ion client,该函数在kernel空间被ION驱动中封装成以下函数:

struct ion_device *g_idev = NULL;

struct ion_client *vexpress_ion_client_create(char *name)
{
	/*
	* The assumption is that if there is a NULL device, the ion
	* driver has not yet probed.
	*/
	if (IS_ERR_OR_NULL(g_idev))
		return ERR_PTR(-EPROBE_DEFER);

	if (IS_ERR(g_idev))
		return (struct ion_client *)g_idev;

	return ion_client_create(g_idev, name);
}
EXPORT_SYMBOL(vexpress_ion_client_create);

2.通过ion_alloc()来进行内存分配

3.通过ion_map_kernel()来进行地址映射

4.如果有些硬件只能通过physical addresses来操作physically-contiguous buffers,那么,这些对应的drivers就需通过调用以下接口来获取虚拟地址所对应的物理地址才行。但ion框架中不推荐使用这种方式,而是建议通过ion_sg_table方式来使用。

int ion_phys(struct ion_client *client, struct ion_handle *handle,ion_phys_addr_t *addr, size_t *len)

5.如果多个设备使用到同一个ion buffer,可以使用以下几个个接口实现buffer的共享,减少buffer的拷贝过程,实现zero-copy.

struct dma_buf *ion_share_dma_buf(struct ion_client *client,struct ion_handle *handle);
int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle);
int ion_share_dma_buf_fd2(struct ion_client *client, struct ion_handle *handle);
struct ion_handle *ion_import_dma_buf(struct ion_client *client,struct dma_buf *dmabuf);
struct ion_handle *ion_import_dma_buf_fd(struct ion_client *client, int fd);

以上两个接口分别实现了dma_buf的共享和获取。

2.4.3 ION内存分配流程

在这里插入图片描述

3. ION共享内存使用

内存共享和大块内存的使用,在实际场景下面的需求是很大的,这里举三个简单的应用场景:

  1. 用户态和内核态共享内存

  2. 用户态不同进程共享内存

  3. 内核态使用ION分配内存

3.1 用户态和内核态共享内存

用户态和内核态共享内存分两种情况,一种是内核态申请buffer,共享给用户态,另一种是用户态申请buffer,共享给内核态,这两种方法,都是通过fd方式进行共享即可,不用进行copy_to_user()或者copy_from_user()这些copy操作,即zero-copy。

3.1.1 用户态申请内存,共享给内核态

  1. 用户态申请dmabuf
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include "dmabuf_test.h"
#include "ion.h"

#define PAGE_SIZE 4096

static int test_share_dmabuf_to_kernel(int fd)
{
	int ret = 0;
	struct dmabuf_test_rw_data data = {0};
	int ion_fd = 0;
	struct ion_allocation_data alloc_data;
	struct ion_handle_data handle_data;
	struct ion_fd_data fd_data;
	unsigned char *buffer;

	ion_fd = open("/dev/ion", O_RDONLY);
	if (ion_fd < 0) {
		printf("open ion device faile\n");
		return ion_fd;
	}

	alloc_data.len = PAGE_SIZE;
	alloc_data.align = 0;
	alloc_data.heap_id_mask = ION_HEAP_SYSTEM_MASK;
	alloc_data.flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;

	/* alloc dmabuf */
	ret = ioctl(ion_fd, ION_IOC_ALLOC, &alloc_data);
	if (ret < 0) {
		printf("ion alloc error\n");
		goto close_fd;
	}

	/* get dmabuf fd */
	fd_data.handle = alloc_data.handle;
	ret = ioctl(ion_fd, ION_IOC_MAP, &fd_data);
	if (ret < 0) {
		printf("ion map error\n");
		goto free_buf;
	}

	//mmap to user space
	buffer = mmap(NULL, alloc_data.len, PROT_READ|PROT_WRITE, MAP_SHARED,
			fd_data.fd, 0);
	if (MAP_FAILED == buffer) {
		printf("mmap to user space error!\n");
		goto close_dmabuf_fd;
	}

	buffer[0] = 'a';
	buffer[1] = 'b';
	buffer[2] = 'c';
	buffer[3] = 'd';
	buffer[4] = '\n';

	data.fd = fd_data.fd;

	/* share dmabuf fd to kernel */
	ret = ioctl(fd, DMABUF_IOC_TEST_GET_FD_FROM_USER, &data);
	if (ret < 0) {
		printf("share dmabuf fd to kernel space error\n");
	}

	ret = munmap(buffer, alloc_data.len);
	if (ret < 0) {
		printf("munmap error\n!");
	}

close_dmabuf_fd:
	close(fd_data.fd);

free_buf:
	handle_data.handle = alloc_data.handle;
	ret = ioctl(ion_fd, ION_IOC_FREE, &handle_data);
	if (ret)
		printf("ion free buffer error\n");
close_fd:
	close(ion_fd);
	return ret;
}

int main()
{
	int fd;
	int ret = 0;

	printf("start demo\n");
	fd = open("/dev/dmabuf-test", O_RDONLY);
	if (fd < 0) {
		printf("open device faile\n");
		return fd;
	}

	ret = test_share_dmabuf_to_kernel(fd);

	close(fd);

	return ret;
}
  1. 内核驱动获取共享的dmabuf
static long dmabuf_test_ioctl(struct file *filp, unsigned int cmd,
				unsigned long arg)
{
	struct miscdevice *miscdev = filp->private_data;
	struct dmabuf_test_device *dev = container_of(miscdev, struct dmabuf_test_device, misc);
	unsigned int dir;
	struct dmabuf_test_rw_data data;

	dir = dmabuf_test_ioctl_dir(cmd);

	if (_IOC_SIZE(cmd) > sizeof(data))
		return -EINVAL;

	if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
		return -EFAULT;

	if (!(dir & _IOC_WRITE))
		memset(&data, 0, sizeof(data));

	switch(cmd) {
	case DMABUF_IOC_TEST_GET_FD_FROM_KERNEL:
	{

		/* share fd must keep it in device ioctl, otherwise userspace
		 * mapping file faile
		 */
		data.fd = ion_share_dma_buf_fd(dev->client, dev->handle);
		if (data.fd < 0) {
			pr_err("get dmabuf fd error\n");
			return -EBADF;
		}
		break;
	}
	case DMABUF_IOC_TEST_GET_FD_FROM_USER:
	{
		struct dma_buf *dmabuf;
		unsigned char *buf;
		int ret;

		dmabuf = dma_buf_get(data.fd);
		if (IS_ERR_OR_NULL(dmabuf)) {
			pr_err("get dmabuf from dmabuf fd:%d error\n", data.fd);
			return -EINVAL;
		}
		ret = dma_buf_begin_cpu_access(dmabuf, DMA_BIDIRECTIONAL);
		if (ret < 0) {
			pr_err("begin cpu access error\n");
			dma_buf_put(dmabuf);
			return -EFAULT;
		}

		/* buf size is PAGE_SIZE */
		buf = (unsigned char *)dma_buf_kmap(dmabuf, 0);
		if (IS_ERR_OR_NULL(buf)) {
			pr_err("map dmabuf to kernel space error\n");
			return -EFAULT;
		}
		printk("buf data:%s\n", buf);
		ret = dma_buf_end_cpu_access(dmabuf, DMA_BIDIRECTIONAL);
		if (ret < 0) {
			pr_err("end cpu access error\n");
			dma_buf_kunmap(dmabuf, 0, (void *)buf);
			dma_buf_put(dmabuf);
			return -EFAULT;
		}
		dma_buf_kunmap(dmabuf, 0, (void *)buf);
		dma_buf_put(dmabuf);
		break;
	}
	default:
		return -ENOTTY;
	}

	if (dir & _IOC_READ) {
		if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd))) {
			return -EFAULT;
		}
	}

	return 0;
}

3.1.2 内核态申请,共享给用户态

  1. 内核驱动申请dmabuf,通过驱动设备ioctl()接口共享dmabuf fd到userspace
/*
 *
 * Copyright (C) 2013 Google, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#define pr_fmt(fmt) "dma-buf-test: " fmt

#include <linux/dma-buf.h>
#include <linux/dma-direction.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>

#include "ion.h"
#include "../uapi/dmabuf_test.h"

struct dmabuf_test_device {
	struct miscdevice misc;
	struct ion_client *client;
	struct ion_handle *handle;
};

static int dmabuf_alloc_dmabuf(struct dmabuf_test_device *dev)
{
	struct ion_handle *handle = NULL;
	unsigned char *buf = NULL;
	unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;

	handle = ion_alloc(dev->client, PAGE_SIZE, 0, (1 << ION_HEAP_TYPE_SYSTEM), flags);

	if (IS_ERR_OR_NULL(handle)) {
		pr_err("ion alloc handle error\n");
		return -EINVAL;
	}

	dev->handle = handle;
	buf = (unsigned char *)ion_map_kernel(dev->client, handle);
	buf[0] = 'e';
	buf[1] = 'f';
	buf[2] = 'g';
	buf[3] = 'h';
	buf[4] = '\n';
	ion_unmap_kernel(dev->client, handle);
	return 0;
}

static int dmabuf_test_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int dmabuf_test_release(struct inode *inode, struct file *file)
{
	return 0;
}

static unsigned long dmabuf_test_ioctl_dir(unsigned int cmd)
{
	switch(cmd) {
	case DMABUF_IOC_TEST_GET_FD_FROM_KERNEL:
	case DMABUF_IOC_TEST_GET_FD_FROM_USER:
		return _IOC_DIR(cmd);
	default:
		return _IOC_DIR(cmd);
	}
}

static long dmabuf_test_ioctl(struct file *filp, unsigned int cmd,
				unsigned long arg)
{
	struct miscdevice *miscdev = filp->private_data;
	struct dmabuf_test_device *dev = container_of(miscdev, struct dmabuf_test_device, misc);
	unsigned int dir;
	struct dmabuf_test_rw_data data;

	dir = dmabuf_test_ioctl_dir(cmd);

	if (_IOC_SIZE(cmd) > sizeof(data))
		return -EINVAL;

	if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
		return -EFAULT;

	if (!(dir & _IOC_WRITE))
		memset(&data, 0, sizeof(data));

	switch(cmd) {
	case DMABUF_IOC_TEST_GET_FD_FROM_KERNEL:
	{

		/* share fd must keep it in device ioctl, otherwise userspace
		 * mapping file faile
		 */
		data.fd = ion_share_dma_buf_fd(dev->client, dev->handle);
		if (data.fd < 0) {
			pr_err("get dmabuf fd error\n");
			return -EBADF;
		}
		break;
	}
	case DMABUF_IOC_TEST_GET_FD_FROM_USER:
	{
		struct dma_buf *dmabuf;
		unsigned char *buf;
		int ret;

		dmabuf = dma_buf_get(data.fd);
		if (IS_ERR_OR_NULL(dmabuf)) {
			pr_err("get dmabuf from dmabuf fd:%d error\n", data.fd);
			return -EINVAL;
		}
		ret = dma_buf_begin_cpu_access(dmabuf, DMA_BIDIRECTIONAL);
		if (ret < 0) {
			pr_err("begin cpu access error\n");
			dma_buf_put(dmabuf);
			return -EFAULT;
		}

		/* buf size is PAGE_SIZE */
		buf = (unsigned char *)dma_buf_kmap(dmabuf, 0);
		if (IS_ERR_OR_NULL(buf)) {
			pr_err("map dmabuf to kernel space error\n");
			return -EFAULT;
		}
		printk("buf data:%s\n", buf);
		ret = dma_buf_end_cpu_access(dmabuf, DMA_BIDIRECTIONAL);
		if (ret < 0) {
			pr_err("end cpu access error\n");
			dma_buf_kunmap(dmabuf, 0, (void *)buf);
			dma_buf_put(dmabuf);
			return -EFAULT;
		}
		dma_buf_kunmap(dmabuf, 0, (void *)buf);
		dma_buf_put(dmabuf);
		break;
	}
	default:
		return -ENOTTY;
	}

	if (dir & _IOC_READ) {
		if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd))) {
			return -EFAULT;
		}
	}

	return 0;
}

static const struct file_operations dmabuf_test_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = dmabuf_test_ioctl,
	.compat_ioctl = dmabuf_test_ioctl,
	.open = dmabuf_test_open,
	.release = dmabuf_test_release,
};

static int __init dmabuf_test_probe(struct platform_device *pdev)
{
	int ret;
	struct dmabuf_test_device *testdev;

	testdev = devm_kzalloc(&pdev->dev, sizeof(struct dmabuf_test_device),
			       GFP_KERNEL);
	if (!testdev)
		return -ENOMEM;

	testdev->misc.minor = MISC_DYNAMIC_MINOR;
	testdev->misc.name = "dmabuf-test";
	testdev->misc.fops = &dmabuf_test_fops;
	testdev->misc.parent = &pdev->dev;
	ret = misc_register(&testdev->misc);
	if (ret) {
		pr_err("failed to register misc device.\n");
		return ret;
	}

	/*create ion client */
	testdev->client = vexpress_ion_client_create("dmabuf-test-client");
	if (!testdev->client) {
		pr_err("failed to create ion client\n");
		return -EINVAL;
	}

	dmabuf_alloc_dmabuf(testdev);
	platform_set_drvdata(pdev, testdev);

	return 0;
}

static int dmabuf_test_remove(struct platform_device *pdev)
{
	struct dmabuf_test_device *testdev;

	testdev = platform_get_drvdata(pdev);
	if (!testdev)
		return -ENODATA;

	misc_deregister(&testdev->misc);
	return 0;
}

static struct platform_device *dmabuf_test_pdev;
static struct platform_driver dmabuf_test_platform_driver = {
	.remove = dmabuf_test_remove,
	.driver = {
		.name = "dmabuf-test",
	},
};

static int __init dmabuf_test_init(void)
{
	dmabuf_test_pdev = platform_device_register_simple("dmabuf-test",
							-1, NULL, 0);
	if (IS_ERR(dmabuf_test_pdev))
		return PTR_ERR(dmabuf_test_pdev);

	return platform_driver_probe(&dmabuf_test_platform_driver, dmabuf_test_probe);
}

static void __exit dmabuf_test_exit(void)
{
	platform_driver_unregister(&dmabuf_test_platform_driver);
	platform_device_unregister(dmabuf_test_pdev);
}

module_init(dmabuf_test_init);
module_exit(dmabuf_test_exit);
MODULE_LICENSE("GPL v2");
  1. 用户空间通过fd访问dmabuf
#include <sys/ioctl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include "dmabuf_test.h"
#include "ion.h"

#define PAGE_SIZE 4096

static int test_share_dmabuf_from_kernel(int fd)
{
	int ret = 0;
	unsigned char *buf = NULL;
	struct dmabuf_test_rw_data data = {0};

	ret = ioctl(fd, DMABUF_IOC_TEST_GET_FD_FROM_KERNEL, &data);
	if (ret < 0) {
		printf("get dmabuf fd faile\n");
		return ret;
	}

	buf = (unsigned char *)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, data.fd, 0);
	if (buf == MAP_FAILED) {
		close(data.fd);
		printf("map dmabuf fail\n");
		return -1;
	}
	printf("user space buffer data:%s\n", buf);
	ret = munmap(buf, PAGE_SIZE);
	if (ret < 0) {
		printf("munmap error\n!");
	}

	close(data.fd);
	return 0;
}

int main()
{
	int fd;
	int ret = 0;

	printf("start demo\n");
	fd = open("/dev/dmabuf-test", O_RDONLY);
	if (fd < 0) {
		printf("open device faile\n");
		return fd;
	}

	ret = test_share_dmabuf_from_kernel(fd);

	close(fd);

	return ret;
}

3.2 用户态不同进程共享内存

服务进程

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <strings.h>
#include <sys/mman.h>

#define PAGE_SIZE 4096

int main()
{
	int clientfd, listenfd;
	struct sockaddr_un servaddr, cliaddr;
	int ret;
	struct msghdr msg;
	struct iovec iov[1];
	char buf[100];
	union {
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *cmptr;
	int recvfd;
	socklen_t len;
	unsigned char *buffer;

	listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (listenfd < 0) {
		printf("socket failed\n");
		return listenfd;
	}

	unlink("test_socket");
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_UNIX;
	strcpy(servaddr.sun_path, "test_socket");
	//servaddr.sun_path = "test_socket";

	ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if (ret < 0) {
		printf("bind socket failed.\n");
		close(listenfd);
		return ret;
	}

	listen(listenfd, 5);

	while(1) {
		len = sizeof(cliaddr);
		clientfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);
		if (clientfd < 0) {
			printf("accept failed\n");
			continue;
		}
		msg.msg_control = control_un.control;
		msg.msg_controllen = sizeof(control_un.control);

		msg.msg_name = NULL;
		msg.msg_namelen = 0;
		iov[0].iov_base = buf;
		iov[0].iov_len = sizeof(buf)/sizeof(buf[0]);

		msg.msg_iov = iov;
		msg.msg_iovlen = 1;

		ret = recvmsg(clientfd, &msg, 0);
		if (ret < 0) {
			return ret;
		}

		//check recv data and len
		if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL && (cmptr->cmsg_len == CMSG_LEN(sizeof(int)))) {
			if(cmptr->cmsg_level != SOL_SOCKET) {
				printf("cmsg_level is not SOL_SOCKET\n");
				continue;
			}

			if (cmptr->cmsg_type != SCM_RIGHTS) {
				printf("cmsg_type is not SCM_RIGHTS\n");
				continue;
			}

			recvfd = *((int *)CMSG_DATA(cmptr));
			break;
		}
	}
	printf("recv fd:%d\n", recvfd);
	buffer = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, recvfd, 0);
	if (buffer == MAP_FAILED) {
		close(recvfd);
		printf("map dmmbuf fail\n");
		return MAP_FAILED;
	}

	printf("service buffer data:%s\n", buffer);

	ret = munmap(buffer, PAGE_SIZE);
	if (ret < 0) {
		printf("munmap error\n!");
	}

	close(recvfd);

	return ret;
}

服务进程主要工作:

  1. 监听本地socket,等待客户端进程共享dmabuf fd

  2. 拿到共享的dmabuf fd后,map到自己进程空间,访问该buffer

  3. 关闭dmabuf fd,退出进程

客户端进程

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <strings.h>
#include "dmabuf_test.h"
#include "ion.h"

#define PAGE_SIZE 4096

int test_share_dmabuf_in_process()
{
	int ret;
	int ion_fd = 0;
	struct ion_allocation_data alloc_data;
	struct ion_handle_data handle_data;
	struct ion_fd_data fd_data;
	unsigned char *buffer;
	/* use to process communication */
	int socket_fd;
	struct sockaddr_un addr;
	struct msghdr msg;
	union {
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *cmptr;
	struct iovec iov[1];
	char buf[100];			//for what?

	ion_fd = open("/dev/ion", O_RDONLY);
	if (ion_fd < 0) {
		printf("open ion device faile\n");
		return ion_fd;
	}

	alloc_data.len = PAGE_SIZE;
	alloc_data.align = 0;
	alloc_data.heap_id_mask = ION_HEAP_SYSTEM_MASK;
	alloc_data.flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;

	/* alloc dmabuf */
	ret = ioctl(ion_fd, ION_IOC_ALLOC, &alloc_data);
	if (ret < 0) {
		printf("ion alloc error\n");
		goto close_fd;
	}

	/* get dmabuf share fd */
	fd_data.handle = alloc_data.handle;
	ret = ioctl(ion_fd, ION_IOC_MAP, &fd_data);
	if (ret < 0) {
		printf("ion map error\n");
		goto free_buf;
	}

	//mmap to user space
	buffer = mmap(NULL, alloc_data.len, PROT_READ|PROT_WRITE, MAP_SHARED,
			fd_data.fd, 0);
	if (MAP_FAILED == buffer) {
		printf("mmap to user space error!\n");
		goto close_dmabuf_fd;
	}

	buffer[0] = '1';
	buffer[1] = '2';
	buffer[2] = '3';
	buffer[3] = '4';
	buffer[4] = '\n';

	printf("client buffer data:%s\n", buffer);
	ret = munmap(buffer, alloc_data.len);
	if (ret < 0) {
		printf("munmap error\n!");
		goto close_dmabuf_fd;
	}

	socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (socket_fd < 0) {
		printf("create socket failed\n");
		ret = socket_fd;
		goto close_dmabuf_fd;
	}

	bzero(&addr, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strcpy(addr.sun_path, "test_socket");
	//addr.sun_path = "test_socket";

	ret = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr));
	if(ret < 0) {
		printf("connect failed!\n");
		goto close_dmabuf_fd;
	}

	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);

	cmptr = CMSG_FIRSTHDR(&msg);
	cmptr->cmsg_len = CMSG_LEN(sizeof(int));
	cmptr->cmsg_level = SOL_SOCKET;
	cmptr->cmsg_type = SCM_RIGHTS;
	*((int *) CMSG_DATA(cmptr)) = fd_data.fd;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	iov[0].iov_base = buf;
	iov[0].iov_len = sizeof(buf)/sizeof(buf[0]);

	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	ret = sendmsg(socket_fd, &msg, 0);
	if (ret < 0) {
		printf("sendmsg failed\n");
		goto close_dmabuf_fd;
	}

	printf("ret :%d, fd:%d\n", ret, fd_data.fd);

close_dmabuf_fd:
	close(fd_data.fd);

free_buf:
	handle_data.handle = alloc_data.handle;
	ret = ioctl(ion_fd, ION_IOC_FREE, &handle_data);
	if (ret)
		printf("ion free buffer error\n");
close_fd:
	close(ion_fd);
	return ret;
}

int main()
{
	int fd;
	int ret = 0;

	printf("start demo\n");
	fd = open("/dev/dmabuf-test", O_RDONLY);
	if (fd < 0) {
		printf("open device faile\n");
		return fd;
	}

	ret = test_share_dmabuf_in_process();
	if (ret < 0) {
		printf("test share dmabuf in process faile\n");
	}
	close(fd);

	return 0;
}

客户端进程主要工作:

  1. 通过ION申请dmabuf,并且获取到dmabuf对应的fd

  2. 通过socket,把dmabuf fd共享到其他进程

  3. 释放自己进程申请的资源,解除buffer映射关系等

3.3 内核态使用ION分配内存

内核态使用ion分配内存,demo如下:

static int dmabuf_alloc_dmabuf(struct dmabuf_test_device *dev)
{
	struct ion_handle *handle = NULL;
	unsigned char *buf = NULL;
	unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
	struct ion_client *client;

	client = vexpress_ion_client_create("dmabuf-test-client");

	handle = ion_alloc(client, PAGE_SIZE, 0, (1 << ION_HEAP_TYPE_SYSTEM), flags);

	if (IS_ERR_OR_NULL(handle)) {
		pr_err("ion alloc handle error\n");
		return -EINVAL;
	}

	dev->handle = handle;
	buf = (unsigned char *)ion_map_kernel(client, handle);
	buf[0] = 'e';
	buf[1] = 'f';
	buf[2] = 'g';
	buf[3] = 'h';
	buf[4] = '\n';
	ion_unmap_kernel(client, handle);
	return 0;
}
  1. 创建ion client

  2. 申请ion handle

  3. 通过ion_map_kernel()接口map dmabuf到kernel space

  4. 使用完后,调用ion_unmap_kernel()接口做解除映射

  • 10
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值