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

  • 用户态和内核态共享内存
  • 用户态不同进程内存共享
  • 内核态中使用ION分配buffer
用户态和内核态共享内存

在Android的BSP代码中有一个ion的library封装了一些对ion驱动设备操作的接口system/core/libion/

1
2
3
4
5
6
7
8
9
10
11
12
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);

可以参考改目录下的ion.c实现代码,都是对ion驱动设备的open/close/ioctl等操作。

初始化ion获取文件描述符,分配内存,获取内存共享的文件描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void init_ion()
{
size_t size = HON_ZONE_SIZE;
int ret = -1;

ion_fd = ion_open();
if(ion_fd < 0) {
ALOGE("Failed to open ion device\n");
ret = ion_fd;
goto out;
}

ret = ion_alloc(ion_fd, size, 0, (1 << ION_SYSTEM_HEAP_ID),
ION_FLAG_CACHED,
&ion_hdl);
if(ret < 0) {
ALOGE("Failed to allocate ion buffer\n");
goto close_fd;
}

ret = ion_share(ion_fd, ion_hdl, &shared_fd);
if(ret < 0) {
ALOGE("Failed to shared the ion fd\n");
goto close_ion;
}
}

void release_ion()
{
ion_free(ion_fd, ion_hdl);
close(ion_fd);
}
  • ion_open()函数打开/dev/ion设备获取文件描述符,后面对文件的操作都是基于这个文件描述符
  • ion_alloc()指定分配的buffer大小和类型,需要注意的是buffer的size,在camera等一些应用场景下经常会被要求buffer要4K对齐,返回的是ion_handler
  • ion_share()函数把指定ion_handler内存共享出去,获取到内存共享文件描述符,把shared_fd发送到另外一个进程中,然后通过mmap函数进行内存映射。

通过mmap函数把ion_handle转化为虚拟地址

1
2
3
4
5
g_buf = (char *)mmap(NULL, HON_ZONE_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED, shared_fd, 0);
if(g_buf == MAP_FAILED) {
ALOGE("Failed to mmap buffer\n");
}

本例中需要把buffer传到内核空间去,然后在kernel空间对buffer进行读写,首先需要把shared_fd传到kernel空间,然后通过shared_fd获取到ion_handler最后得到内核虚拟机地址。

使用ioctl把shared_fd传到内核空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void bind_driver()
{
int fd = -1;
int ret;

fd = open("/dev/hon_zone", O_RDWR);
if(fd < 0) {
ALOGE("Failed to open hon_zone device\n");
return;
}

ret = ioctl(fd, HON_ZONE_ION_INIT, &shared_fd);
if(ret < 0) {
ALOGE("Failed to do ioctl\n");
close(fd);
return;
}

zone_fd = fd;
ALOGD("zone fd = %d", zone_fd);
}

本文中自己实现了一个字符驱动hon_zone用来配合用户空间的进程读写用户空间分配的ion buffer,用户态使用ioctl把shared_fd传下去,来看kernel是如何处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
case HON_ZONE_ION_INIT:
ZONE_MTX_LOCK();
if(copy_from_user(&ion_fd, (int *)argp, sizeof(int))) {
LOGE("%s: failed to get fd from user", __func__);
rc = -EINVAL;
break;
}
if(zone->client == NULL) {
zone->client = msm_ion_client_create("hon_zone");
if(!zone->client) {
LOGE("%s: failed to create ion client", __func__);
rc = -ENOMEM;
break;
}
zone->handle = ion_import_dma_buf(zone->client, ion_fd);
} else {
rc = -EBUSY;
break;
}

zone->buffer = ion_map_kernel(zone->client, zone->handle);
ion_handle_get_size(zone->client, zone->handle, &len);
zone->size = len;
zone->is_registered = true;
LOGD("%s: virtual address is %p", __func__, (void *)zone->buffer);
  • copy_from_user拿到用户态传下来的shared_fd
  • 创建ion_client这里使用的是高通的内核,封装的函数msm_ion_client_create获取ion_client
  • ion_import_dma_bufshared_fd转化为ion_handle
  • 通过ion_map_kernel传入ion_handle获取到虚拟地址,与用户态的mmap函数功能类似
用户态不同进程共享内存

写一个简单的例子介绍如何在不同进程中进行内存共享

创建socketpair,获取两个可以连通的socket文件描述符,这两个文件描述符分别给父子进程使用,一个写一个读,这里其实和使用管道是类似的作用,但是管道是单向的,socketpair是双向读写的。

1
2
3
int pipefd[2];
/*创建父子进程管道*/
int ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, pipefd);

fork子进程,在子进程中获取ION buffer的共享内存文件描述符

1
2
3
4
5
6
7
8
9
10
11
int pid = fork();
if(pid == 0)
{
close(pipefd[0]);
int fd_to_pass = getIONBuffer();
if(fd_to_pass == -1)
{
return -1;
}
send_fd(pipefd[1], (fd_to_pass > 0) ? fd_to_pass : 0);
}

获取shared_fd然后通过socket发送给父进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int getIONBuffer()
{
int ion_fd = -1;
struct ion_fd_data sFdData = {};
struct ion_handle_data sHandleData = {};
struct ion_allocation_data sAllocInfo;
int pagesize = 4096;
int err;
int size = 0;
int bo_width = BO_W;
int bo_height = BO_H;
ion_fd=open("/dev/ion", O_RDWR);
size = AL(bo_width,32) * bo_height * 4;

char buffer[50] = "qwertysdfgh";
if (ion_fd < 0) {
return -1;;
}

/* allocate ion buffer */
sAllocInfo.len = 50*sizeof(char);
sAllocInfo.align = 50*sizeof(char);
//sAllocInfo.heap_id_mask = ION_HEAP_TYPE_DMA_MASK;
sAllocInfo.heap_id_mask = (1 << 25);
sAllocInfo.flags = ION_FLAG_CACHED;
// sAllocInfo.flags |= (1 << 17); // write combined
err = ioctl(ion_fd, ION_IOC_ALLOC, &sAllocInfo);
if(err) {
return -1;
}
sFdData.handle = sAllocInfo.handle;
sHandleData.handle = sAllocInfo.handle;

err = ioctl(ion_fd, ION_IOC_SHARE, &sFdData);
if(err) {
ioctl(ion_fd, ION_IOC_FREE, &sHandleData);
return -1;
}

void *data;
data = mmap(NULL, sizeof(buffer), PROT_READ | PROT_WRITE, MAP_SHARED, sFdData.fd, 0);
if (data == MAP_FAILED) {
printf("!!! Child mmap failed: %m\n");
return -1;
}
memcpy(data,buffer,50*sizeof(char));
munmap(data,sizeof(buffer));
err = ioctl(ion_fd, ION_IOC_FREE, &sHandleData);
if(err) {
close(sFdData.fd);
return -1;
}
return sFdData.fd;
}
  • 打开/dev/ion获取client文件描述符
  • 调用ioctl来分配ION buffer获取ion handle
  • 利用mmap映射虚拟地址,最终操作的是虚拟地址数据
  • 返回shared_fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void send_fd(int fd, int fd_to_send)
{
struct iovec iov[1];
struct msghdr msg;
char buf[0];

iov[0].iov_base = buf;
iov[0].iov_len = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

struct cmsghdr cm;
cm.cmsg_len = CONTROL_LEN;
cm.cmsg_level = SOL_SOCKET;
cm.cmsg_type = SCM_RIGHTS;
*(int*)CMSG_DATA(&cm) = fd_to_send;
msg.msg_control = &cm; /*设置辅助数据*/
msg.msg_controllen = CONTROL_LEN;
sendmsg(fd, &msg, 0);
}

通过socket进程间通信把shared_fd发送给父进程

父进程中获取共享buffer使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int pagesize = 4096;
char buff_data[50];
int c_ion_fd = -1;
int s_error;
int bo_width = BO_W;
int bo_height = BO_H;
int size = AL(bo_width,32) * bo_height * 4;
struct ion_fd_data stFdData = {};
close(pipefd[1]);
fd_to_pass = recv_fd(pipefd[0]);
c_ion_fd = open("/dev/ion", O_RDWR);
stFdData.fd = fd_to_pass;
s_error = ioctl(c_ion_fd,ION_IOC_IMPORT,&stFdData);
if(s_error) {
return -1;
}
void *data;
data = mmap(NULL, sizeof(buff_data), PROT_READ | PROT_WRITE, MAP_SHARED, stFdData.fd, 0);

memset(buff_data,0,50*sizeof(char));
memcpy(buff_data,data,50*sizeof(char));
close(fd_to_pass);
  • 通过recv_fd函数获取到子进程发送过来的shared_fd
  • 调用ioctl传入shared_fd获取ion handle
  • 调用mmap映射成可以操作的虚拟地址
  • 对数据进行读写操作

可以看到在父子进程中都对/dev/ion设备打开,而且在不同的进程中制允许有一个ion_client实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int recv_fd(int fd)
{
struct iovec iov[1];
struct msghdr msg;
char buf[0];

iov[0].iov_base = buf;
iov[0].iov_len = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

struct cmsghdr cm;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;

recvmsg(fd, &msg, 0);

int fd_to_read = *(int*)CMSG_DATA(&cm);
return fd_to_read;
}
总结

总的来说,在Android中使用ION机制进行共享内存还是很方便的。其中比较有意思的是如何把shared_fd传到不同的进程中,本例使用的是socketpair机制,在Android 8或以上的版本中,Android官方推荐使用binder机制通过把shared_fd封装成native_handler发送给别的进程。

有兴趣的可以去尝试一下。