ION

Android 系统中,SurfaceFlinger分配buf 或 CameraService拿到出的图像buf都是通过ION的机制;
Android中能通过Binder实现 IonFd跨进程共享,IonFd方便应用程序 user space 以及各驱动模块间零拷贝访问buf。

android共享内存 同步机制 android ion 共享内存_android共享内存 同步机制

system/core/libion/ion.c libion
kernel/msm-4.19/drivers/staging/android/ion

libion API:
int ion_open();
int ion_close(int fd);
int ion_alloc(int fd, size_t len, size_t align, unsigned int heap_mask,
unsigned int flags, ion_user_handle_t *handle);
int ion_alloc_fd(int fd, size_t len, size_t align, unsigned int heap_mask,
unsigned int flags, int *handle_fd);
int ion_sync_fd(int fd, int handle_fd);
int ion_free(int fd, ion_user_handle_t handle);
int ion_map(int fd, ion_user_handle_t handle, size_t length, int prot,
int flags, off_t offset, unsigned char **ptr, int *map_fd);
int ion_share(int fd, ion_user_handle_t handle, int *share_fd);
int ion_import(int fd, int share_fd, ion_user_handle_t *handle);

Heap

Heap: 用来表示内存分配的相关信息,包括id, name, heap_type等。用struct ion_heap表示。

enum ion_heap_type {
 ION_HEAP_TYPE_SYSTEM,        //memory allocated via vmalloc   ion_system_heap.c
 ION_HEAP_TYPE_SYSTEM_CONTIG, //memory allocated via kmalloc   ion_system_heap.c
 ION_HEAP_TYPE_CARVEOUT,      //memory allocated from a prereserved carveout heap, 
                              //allocations are physicallycontiguous 
 ION_HEAP_TYPE_CHUNK,
 ION_HEAP_TYPE_DMA,           //memory allocated via DMA API ion_cma_heap.c
 ION_HEAP_TYPE_CUSTOM,        //device specific heaps
 ION_NUM_HEAPS = 16,
};

不同type的heap需要不同的method例如ion_system_heap.c/ion_cma_heap.c去分配;
相同type的heap则需要使用heapid来进行区分

enum ion_heap_ids {
        INVALID_HEAP_ID = -1,
        ION_CP_MM_HEAP_ID = 8,
        ION_SECURE_HEAP_ID = 9,
        ION_SECURE_DISPLAY_HEAP_ID = 10,
        ION_SPSS_HEAP_ID = 13, /* Secure Processor ION heap */
        ION_ADSP_HEAP_ID = 22,
        ION_SYSTEM_HEAP_ID = 25,
        ION_QSECOM_HEAP_ID = 27,
        ION_HEAP_ID_RESERVED = 31 /** Bit reserved for ION_FLAG_SECURE flag */
};
/**
 * Newly added heap ids have to be #define(d) since all API changes must
 * include a new #define.
 */
#define ION_SECURE_CARVEOUT_HEAP_ID     14
#define ION_QSECOM_TA_HEAP_ID           19
#define ION_AUDIO_HEAP_ID               28
#define ION_CAMERA_HEAP_ID              20
#define ION_USER_CONTIG_HEAP_ID         26

Client

      用户空间首先就要通过调用ion_open就可获得一个以handle形式返回的file descriptor,这个fd可以用来代表一个ion client。注意,虽然传给open一个O_RDONLY参数,但是你仍然可对这块memory进行写操作。

int ion_open() {
    int fd = open("/dev/ion", O_RDONLY | O_CLOEXEC);
    if (fd < 0) ALOGE("open /dev/ion failed: %s", strerror(errno));
    return fd;
}

Handle

      在一个用户进程中最多有一个client。当有了一个client之后,就可以通过调用ion_alloc_fd→ion_alloc→ion_ioctl分配ion内存。其中需要注意的就是ion_allocation_data 结构体,分别指明内存的大小、对齐方式以及flags。flags是一个bit mask,用来说明可以从哪些heaps中分配想要的内存,分配的buffer会通过ion_allocatoin_data的handle来返回。

ion_alloc(fd, len, align, heap_mask, flags, &handle);

int ion_alloc(int fd, size_t len, size_t align, unsigned int heap_mask, unsigned int flags,
              ion_user_handle_t* handle) {
    int ret = 0;

    if ((handle == NULL) || (!ion_is_legacy(fd))) return -EINVAL;

    struct ion_allocation_data data = {
        .len = len, .align = align, .heap_id_mask = heap_mask, .flags = flags,
    };

    ret = ion_ioctl(fd, ION_IOC_ALLOC, &data);
    if (ret < 0) return ret;

    *handle = data.handle;

    return ret;
}

      分配完内存后返回的handle,CPU不可以直接访问这个buf,因此需要调用ion_share→ion_ioctl通过给定一个handle最终获得这个buffer的唯一id(用于share这个buf的mBufFd);ION_IOC_SHARE 及ION_IOC_IMPORT是基于DMABUF实现的,所以当共享进程获取文件描述符后,可以直接调用mmap来操作共享内存。

ret = ion_share(fd, handle, handle_fd);

int ion_share(int fd, ion_user_handle_t handle, int* share_fd) {
    int ret;
    struct ion_fd_data data = {
        .handle = handle,
    };

    if (!ion_is_legacy(fd)) return -EINVAL;
    if (share_fd == NULL) return -EINVAL;

    ret = ion_ioctl(fd, ION_IOC_SHARE, &data);
    if (ret < 0) return ret;
    if (data.fd < 0) {
        ALOGE("share ioctl returned negative fd");
        return -EINVAL;
    }
    *share_fd = data.fd;
    return ret;
}

dma-buf

      从ion_ioctl(fd, ION_IOC_ALLOC, &data)调用平台ION的操作可以得知是如何绑定dma_buf,dma-buf 本质上是 buffer 与 file 的结合,因此它仍然是一块 buffer。dma-buf 不仅能用于 DMA 硬件访问,也同样适用于 CPU 软件访问,

long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	case ION_IOC_ALLOC:
	{
		int fd;
	
	    fd = ion_alloc_fd(data.allocation.len,
	    				  data.allocation.heap_id_mask,
	                      data.allocation.flags);
	    if (fd < 0)
	    	return fd;
	
	    data.allocation.fd = fd;
	
	    break;
	 }
kernel ioctl中的ion_alloc_fd 区分与libion中的ion_alloc_fd
int ion_alloc_fd(size_t len, unsigned int heap_id_mask, unsigned int flags)
{
        int fd;
        struct dma_buf *dmabuf;

        dmabuf = ion_alloc_dmabuf(len, heap_id_mask, flags);
        if (IS_ERR(dmabuf))
                return PTR_ERR(dmabuf);

        fd = dma_buf_fd(dmabuf, O_CLOEXEC);
        if (fd < 0)
                dma_buf_put(dmabuf);

        return fd;
}

      只要是文件,内部都会有一个引用计数(f_count),在 linux 内核中操作 file 引用计数的常用函数为 fget() 和 fput(),而 dma-buf 又在此基础上进行了封装

函数

区别

get_dma_buf()

引用计数加1

dma_buf_get()

引用计数加1,并将 fd 转换成 dma_buf 指针

dma_buf_put()

引用计数减1

dma_buf_fd()

引用计数不变,仅创建 fd

      用户空间的程序可以使用 dma-buf 的 fd 做 mmap() 操作接口成功的访问到 dma-buf 的物理内存,通过 dma_buf_kmap() / dma_buf_vmap() 操作,就可以把实际的物理内存,映射到 kernel 空间,并转化成 CPU 可以连续访问的虚拟地址,方便后续软件直接读写这块物理内存。因此,无论这块 buffer 在物理上是否连续,在经过 kmap / vmap 映射后的虚拟地址一定是连续的。

Test case

      以下是项目中使用到的一段代码,虽然摘取修改,但使用的API还是很清晰的

User Sapce

int test::open(){
	int rc = -1;
	mFd = ::open("/dev/test", O_RDWR);//可以misc_register等去注册设备
	if(mFd < 0) {
        rc = -EINVAL;
        goto out;
    }
	mIonFd = ::ion_open(); //mFd mIonFd构造时默认值为-1
	if(mIonFd < 0) {
        rc = -EINVAL;
        goto err_ion_open;
    }
    rc = allocateMetadata();
    if(rc < 0) {
        goto err_alloc;
    }
	return 0;
err_alloc:
    if(mIonFd > 0) {
        ::close(mIonFd);
        mIonFd = -1;
    }
err_ion_open:
    if(mFd > 0) {
        ::close(mFd);
        mFd = -1;
out:
    return rc;
}
int test::allocateMetadata()
{
    uint32_t size = sizeof(struct ion_metadata);//ion_metada共享内容
    uint32_t len = (size + 4095U) & (~4095U); //alignMin
    uint32_t align = 4096; //alignment 字节对齐
    ::ion_alloc_fd(mIonFd, len, align,
            (1 << ION_SYSTEM_HEAP_ID), ION_FLAG_CACHED, &mBufFd);
    if(mBufFd < 0) {
        return -1;
    }

    void *buf = ::mmap(NULL, len, PROT_READ | PROT_WRITE,
            MAP_SHARED, mBufFd, 0);
    if(buf == MAP_FAILED) {
        ::close(mBufFd);
        mBufFd = -1;
        return -1;
    }
    mion_metadata.data = (struct ion_metadata*)buf;
    mion_metadata.size = len;

    /* share the buffer to driver */
    int rc = ::ioctl(mFd, REGISTER_BUF, &mBufFd);
    if(rc < 0) {
        ::munmap(mion_metadata.data, mion_metadata.size);
        mIon_data .size = 0;
        mIon_data .data = nullptr;
        return -1;
    }

    return 0;
}

Kernel Space

struct ion_data {
	size_t size;
	struct ion_metadata *data;     
};// 用于mIon

static long ioctl(struct file *filp, unsigned int cmd,
		unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	long rc = 0;
	int dma_fd = -1;
	struct dma_buf *dmabuf;
	
	switch(cmd) {
		case REGISTER_BUF:
			if(copy_from_user(&dma_fd, (int *)argp,sizeof(int))) {
				rc = -EINVAL;
				break;
			}
			if(dma_fd < 0) {
				rc = -EINVAL;
				break;
			}
			dmabuf = dma_buf_get(dma_fd);
			if(dmabuf == NULL) {
				rc = -EINVAL;
				break;
			}
			get_dma_buf(dmabuf);
			mIon.size = dmabuf->size;
			mIon.data = (struct ion_metadata*)dma_buf_vmap(dmabuf);
			if(mIon.data == NULL) {
				rc = -EINVAL;
				break;
			}
			break;
		default:
			break;
	}
	return rc;
}