PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
环境说明
无
前言
无
背景
没有买卖,就没有伤害。 ------ (佚名)
作为一个打工仔,要积极完成领导分配的任务。so,我分配到一个关于android进程间高效传输大量数据的的任务。不用我说,只要提及“大量”“高效”“进程间”这几个词,首先就得想到共享内存。虽然共享内存有这样那样的缺点,但是有一个优点,那就是快。
可是这里有个问题,为什么我需要去读这些驱动或者乱七八糟的东西?因为Android提供了一个java类叫做:MemoryFile就可以实现共享内存,MemoryFile是android基于其共享内存机制实现的java层到java层之间的数据传输。但是,在我们的工作内容中,需要在android系统中用前向框架跑深度学习算法,这里有个问题,我们跑算法的时候是在C or C层跑的,但是我们有一些图像数据需要在apk里面采集或者使用。在Android中,怎么让图像数据在c or c层和java层快速传输,c or c++ 层之间快速传输,这就需要我们了解android 共享内存机制,做出合适的抉择。
Android Anonymous Shared Memory 简介
我们都知道Android是基于Linux内核搭建的一个分层系统,其中有kernel层,RunTimeLib和其他lib层,Framework层,Application层。虽然Linux os带了很多IPC的机制,其中Sharedmemory也有两种,但是在android系统里面,是没有这些内容的,android的linuxkernel你可以理解为是一种深度定制版,很多东西都与普通的linux kenel不一致。
由于Androidkernel不带普通的linuxkernel的常用共享内存方式,所以android 系统提供了另外一种替代方式,当然不是全部重复造轮子,只是通过驱动程序的方式,增加了自己想要的新特性,并封装了一个新的共享内存接口出来。
更详细和基本的介绍:请大家去参考百度的很多对Android共享内存的介绍。现在网上有很多关于android共享内存的介绍,有部分是精品,让我受益匪浅,是可以参考的。这里向这些无私奉献精品的前辈致敬。
Android Anonymous Shared Memory 驱动源码分析
在我记忆中,有一个不知道谁说的,学习的好方法,那就是:从源码来,到源码去。我一直抱着这种心态来学习新的事物,毕竟许多理论是需要我们去了解,虽然不需要去重复造轮子。
Android kernel的驱动编写和linuxkernel的驱动编写类似,都是有一个入口函数。我们首先从这两个函数开始分析,然后分析几个比较重要的接口就行。
源码版本:android-p release kernel 4.9
目录:\drivers\staging\android\ashmem.c
驱动入口
/**
* struct ashmem_area - The anonymous shared memory area
* @name: The optional name in /proc/pid/maps
* @unpinned_list: The list of all ashmem areas
* @file: The shmem-based backing file
* @size: The size of the mapping, in bytes
* @prot_mask: The allowed protection bits, as vm_flags
*
* The lifecycle of this structure is from our parent file's open() until
* its release(). It is also protected by 'ashmem_mutex'
*
* Warning: Mappings do NOT pin this structure; It dies on close()
*/
struct ashmem_area {
char name[ASHMEM_FULL_NAME_LEN];
struct list_head unpinned_list;
struct file *file;
size_t size;
unsigned long prot_mask;
};
/**
* struct ashmem_range - A range of unpinned/evictable pages
* @lru: The entry in the LRU list
* @unpinned: The entry in its area's unpinned list
* @asma: The associated anonymous shared memory area.
* @pgstart: The starting page (inclusive)
* @pgend: The ending page (inclusive)
* @purged: The purge status (ASHMEM_NOT or ASHMEM_WAS_PURGED)
*
* The lifecycle of this structure is from unpin to pin.
* It is protected by 'ashmem_mutex'
*/
struct ashmem_range {
struct list_head lru;
struct list_head unpinned;
struct ashmem_area *asma;
size_t pgstart;
size_t pgend;
unsigned int purged;
};
static const struct file_operations ashmem_fops = {
.owner = THIS_MODULE,
.open = ashmem_open,
.release = ashmem_release,
.read = ashmem_read,
.llseek = ashmem_llseek,
.mmap = ashmem_mmap,
.unlocked_ioctl = ashmem_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_ashmem_ioctl,
#endif
};
static struct miscdevice ashmem_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ashmem",
.fops = &ashmem_fops,
};
static struct kmem_cache *ashmem_area_cachep __read_mostly;
static struct kmem_cache *ashmem_range_cachep __read_mostly;
static int __init ashmem_init(void)
{
int ret = -ENOMEM;
//slab 缓存 中 创建struct ashmem_area内存区域结构,这个创建后,下一次分配这个结构体的时候可以更快。
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);
////unlikely()--执行else后面的语句概率较高,增加cache命中率,likely()与此功能相反
//
if (unlikely(!ashmem_area_cachep)) {
pr_err("failed to create slab cache\n");
goto out;
}
//同上
ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
sizeof(struct ashmem_range),
0, 0, NULL);
if (unlikely(!ashmem_range_cachep)) {
pr_err("failed to create slab cache\n");
goto out_free1;
}
//杂项设备注册
ret = misc_register(&ashmem_misc);
if (unlikely(ret)) {
pr_err("failed to register misc device!\n");
goto out_free2;
}
//这个好像和内存回收有关。我这里不关心。
register_shrinker(&ashmem_shrinker);
pr_info("initialized\n");
return 0;
out_free2:
kmem_cache_destroy(ashmem_range_cachep);
out_free1:
kmem_cache_destroy(ashmem_area_cachep);
out:
return ret;
}
device_initcall(ashmem_init);
当这个设备创建以后,就会在/dev/下生成一个ashmem字符设备。
在struct file_operations ashmem_fops中,我们注册了很多接口,如果大家对linux 驱动编程没有一点了解的话,你可以直接理解为我们在用户态调用open就会调用这里的ashmem_open,其他的类似。
下面我们重点介绍几个我们常用的接口:ashmem_open,ashmem_ioctl,ashmem_mmap。
ashmem_open
我们调用open的时候,打开一个内存共享。
/**
* ashmem_open() - Opens an Anonymous Shared Memory structure
* @inode: The backing file's index node(?)
* @file: The backing file
*
* Please note that the ashmem_area is not returned by this function - It is
* instead written to "file->private_data".
*
* Return: 0 if successful, or another code if unsuccessful.
*/
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
//检查vfs打开的文件,在32为系统下打开大文件导致overflow的问题
ret = generic_file_open(inode, file);
////unlikely()--ret为0的概率较大,增加cache命中率,方便编译器优化分支语句。
//(把else后的语句紧贴if之后,把return 放到jmp之后。)likely()与此功能相反
//
if (unlikely(ret))
return ret;
/*
GFP_KERNEL —— 正常分配内存,可以被中断,还有其他内存分配标志。GFP_KERNEL,GFP_DMA
*/
//快速分配一个struct ashmem_area结构体,相当于这段新开辟的共享内存的句柄
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
if (unlikely(!asma))
return -ENOMEM;
//这个和内存回收有关,不管
INIT_LIST_HEAD(&asma->unpinned_list);
//给共享内存名字赋初值
memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
//设置保护位
asma->prot_mask = PROT_MASK;
//利用struct file结构体中的private_data 来保存我们刚刚分配的句柄的指针。private_data 可以用来携带个人定制的数据,这里用来携带我们定义的共享内存句柄。注意这个地方很重要,为啥重要后面独立解释。
file->private_data = asma;
return 0;
}
ashmem_ioctl
这里我们关注一下,给struct ashmem_area 的name和size赋值即可,这里注意,还没有分配实际的内存,仅仅是句柄的相关信息填充完毕了。
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
//这里就是获取之前ashmem_open的过程中,我们保存的共享内存句柄
struct ashmem_area *asma = file->private_data;
long ret = -ENOTTY;
switch (cmd) {
case ASHMEM_SET_NAME:
ret = set_name(asma, (void __user *)arg);
break;
case ASHMEM_GET_NAME:
ret = get_name(asma, (void __user *)arg);
break;
case ASHMEM_SET_SIZE:
ret = -EINVAL;
mutex_lock(&ashmem_mutex);
if (!asma->file) {
ret = 0;
asma->size = (size_t)arg;
}
mutex_unlock(&ashmem_mutex);
break;
case ASHMEM_GET_SIZE:
ret = asma->size;
break;
case ASHMEM_SET_PROT_MASK:
ret = set_prot_mask(asma, arg);
break;
case ASHMEM_GET_PROT_MASK:
ret = asma->prot_mask;
break;
case ASHMEM_PIN:
case ASHMEM_UNPIN:
case ASHMEM_GET_PIN_STATUS:
ret = ashmem_pin_unpin(asma, cmd, (void __user *)arg);
break;
case ASHMEM_PURGE_ALL_CACHES:
ret = -EPERM;
if (capable(CAP_SYS_ADMIN)) {
struct shrink_control sc = {
.gfp_mask = GFP_KERNEL,
.nr_to_scan = LONG_MAX,
};
ret = ashmem_shrink_count(&ashmem_shrinker, &sc);
ashmem_shrink_scan(&ashmem_shrinker, &sc);
}
break;
}
return ret;
}
ashmem_mmap
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
//同上
struct ashmem_area *asma = file->private_data;
int ret = 0;
mutex_lock(&ashmem_mutex);
/* user needs to SET_SIZE before mapping */
if (unlikely(!asma->size)) {
ret = -EINVAL;
goto out;
}
/* requested mapping size larger than object size */
if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {
ret = -EINVAL;
goto out;
}
/* requested protection bits must match our allowed protection mask */
if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
calc_vm_prot_bits(PROT_MASK, 0))) {
ret = -EPERM;
goto out;
}
vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
//第一次mmap时,正式申请内存
if (!asma->file) {
char *name = ASHMEM_NAME_DEF;
struct file *vmfile;
if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
name = asma->name;
/* ... and allocate the backing shmem file */
/**
* shmem_kernel_file_setup - get an unlinked file living in tmpfs which must be
* kernel internal. There will be NO LSM permission checks against the
* underlying inode. So users of this interface must do LSM checks at a
* higher layer. The users are the big_key and shm implementations. LSM
* checks are provided at the key or shm level rather than the inode.
* @name: name for dentry (to be seen in /proc/<pid>/maps
* @size: size to be set for the file
* @flags: VM_NORESERVE suppresses pre-accounting of the entire object size
*/
//在tmpfs中创建一个文件,并创建一个inode指向这个文件,并把inode和struct file的返回值关联起来。这个文件就是我们实际的共享的内存文件。这里我们看到其实android 匿名共享内存也是基于linux 普通的共享内存底层来实现的,不重复造轮子。
vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
if (IS_ERR(vmfile)) {
ret = PTR_ERR(vmfile);
goto out;
}
vmfile->f_mode |= FMODE_LSEEK;
//用file域保存我们在tmpfs中创建的共享内存文件的file结构指针。也就是说现在开始,asma->file指向了我们的共享内存文件。
asma->file = vmfile;
}
get_file(asma->file);
//把asma->file和我们mmap 的内存区域中的vma->vm_file关联起来。这样访问mmap的这段内存区域就等于访问我们创建的这个共享内存文件。
if (vma->vm_flags & VM_SHARED)
shmem_set_file(vma, asma->file);
else {
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = asma->file;
}
out:
mutex_unlock(&ashmem_mutex);
return ret;
}
Android Anonymous Shared Memory 驱动使用
Android 官方的一个lib用例库中,封装了一部分共享内存的用法,这部分内容就是给MemoryFile的Native层的库使用的。下面我们一起来分析分析。
Android O r4
system/core/libcutils/ashmem-dev.c
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Implementation of the user-space ashmem API for devices, which have our
* ashmem-enabled kernel. See ashmem-sim.c for the "fake" tmp-based version,
* used by the simulator.
*/
#define LOG_TAG "ashmem"
#include <errno.h>
#include <fcntl.h>
#include <linux/ashmem.h>
#include <pthread.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cutils/ashmem.h>
#include <log/log.h>
#define ASHMEM_DEVICE "/dev/ashmem"
/* ashmem identity */
static dev_t __ashmem_rdev;
/*
* If we trigger a signal handler in the middle of locked activity and the
* signal handler calls ashmem, we could get into a deadlock state.
*/
static pthread_mutex_t __ashmem_lock = PTHREAD_MUTEX_INITIALIZER;
/* logistics of getting file descriptor for ashmem */
static int __ashmem_open_locked()
{
int ret;
struct stat st;
//这里打开了"/dev/ashmem"驱动设备,创建了一个共享内存文件,返回了这个共享文件的文件描述符
int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR));
if (fd < 0) {
return fd;
}
ret = TEMP_FAILURE_RETRY(fstat(fd, &st));
if (ret < 0) {
int save_errno = errno;
close(fd);
errno = save_errno;
return ret;
}
if (!S_ISCHR(st.st_mode) || !st.st_rdev) {
close(fd);
errno = ENOTTY;
return -1;
}
__ashmem_rdev = st.st_rdev;
return fd;
}
static int __ashmem_open()
{
int fd;
pthread_mutex_lock(&__ashmem_lock);
fd = __ashmem_open_locked();
pthread_mutex_unlock(&__ashmem_lock);
return fd;
}
/* Make sure file descriptor references ashmem, negative number means false */
static int __ashmem_is_ashmem(int fd, int fatal)
{
dev_t rdev;
struct stat st;
if (TEMP_FAILURE_RETRY(fstat(fd, &st)) < 0) {
return -1;
}
rdev = 0; /* Too much complexity to sniff __ashmem_rdev */
if (S_ISCHR(st.st_mode) && st.st_rdev) {
pthread_mutex_lock(&__ashmem_lock);
rdev = __ashmem_rdev;
if (rdev) {
pthread_mutex_unlock(&__ashmem_lock);
} else {
int fd = __ashmem_open_locked();
if (fd < 0) {
pthread_mutex_unlock(&__ashmem_lock);
return -1;
}
rdev = __ashmem_rdev;
pthread_mutex_unlock(&__ashmem_lock);
close(fd);
}
if (st.st_rdev == rdev) {
return 0;
}
}
if (fatal) {
if (rdev) {
LOG_ALWAYS_FATAL("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o %d:%d",
fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP,
major(rdev), minor(rdev));
} else {
LOG_ALWAYS_FATAL("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o",
fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP);
}
/* NOTREACHED */
}
errno = ENOTTY;
return -1;
}
int ashmem_valid(int fd)
{
return __ashmem_is_ashmem(fd, 0) >= 0;
}
/*
* ashmem_create_region - creates a new ashmem region and returns the file
* descriptor, or <0 on error
*
* `name' is an optional label to give the region (visible in /proc/pid/maps)
* `size' is the size of the region, in page-aligned bytes
*/
//实际我们要创建的共享内存方法就是这个,其实就是打开设备,然后设置name和size,最终通过mmap把这个fd映射到我们的进程空间,然后我们的程序就可以访问了。
int ashmem_create_region(const char *name, size_t size)
{
int ret, save_errno;
int fd = __ashmem_open();
if (fd < 0) {
return fd;
}
if (name) {
char buf[ASHMEM_NAME_LEN] = {0};
strlcpy(buf, name, sizeof(buf));
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));
if (ret < 0) {
goto error;
}
}
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));
if (ret < 0) {
goto error;
}
return fd;
error:
save_errno = errno;
close(fd);
errno = save_errno;
return ret;
}
int ashmem_set_prot_region(int fd, int prot)
{
int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
}
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot));
}
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
}
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin));
}
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
}
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin));
}
int ashmem_get_size_region(int fd)
{
int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
}
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
}
同时,在MemoryFile的jni接口中,我们可以发现直接调用ashmem_create_region,创建了一块共享内存区域。
static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) {
// Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null
const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;
int fd = ashmem_create_region(name, size);
// Capture the error, if there is one, before calling ReleaseStringUTFChars
int err = fd < 0 ? errno : 0;
if (name) {
env->ReleaseStringUTFChars(jname, name);
}
if (fd < 0) {
throwErrnoException(env, "SharedMemory_create", err);
return nullptr;
}
return jniCreateFileDescriptor(env, fd);
}
后记
总结
这里我们看到,共享内存的创建原理。但是这里有个问题没有解释清楚,那就是内存是怎么共享的?
在本文中,我们可以知道我们的创建共享内存的进程可以得到一个共享内存文件的文件描述符,其他的进程怎么知道这块内存在那里呢?这里我明确说明,android还要靠共享共享内存文件文件描述符来实现共享内存,但是一个文件描述符只对当前进程有效,其他进程的同一个值的文件描述符可能指向不同的文件,所以得有一种可靠的方式来实现文件描述符的传递即可。
预知后事如何,请听下回分解!!
2019/3/18更新
本来是一系列的文章,但是后续篇整理后,发现不能成为一个系列了。所以这里留个传送门:《linux kernel 中进程间描述符的传递方法及原理》:https://blog.csdn.net/u011728480/article/details/88553602
参考文献
-
无

PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。