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