Android应用程序中使用匿名共享内存,
主要是通过应用程序框架层提供的MemoryFile接口来使用的,
MemoryFile 接口是通过JNI方法调用到系统运行时库层中的匿名共享内存C接口,
最终通过这些C接口来使用内核空间中的匿名共享内存驱动模块。
c接口进入内核,分析Android系统的匿名共享内存Ashmem驱动程序的源代码,深入了解它是如何辅助内存管理系统。
Android系统的匿名共享内存Ashmem机制并没有自立山头,从头搞一套自己的共享内存机制,而是建立在Linux内核实现的共享内存的基础上的。
与此同时,它又向Linux内存管理系统的内存回收算法注册接口,告诉Linux内存管理系统它的某些内存块不再使用了,可以被回收了,
不过,这些不再使用的内存需要由它的使用者来告诉Ashmem驱动程序。通过这种用户-Ashmem驱动程序-内存管理系统三者的紧密合作,实现有效的内存管理机制,适合移动设备小内存的特点。
Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现自己的功能。
这里介绍Android系统匿名共享内存的创建(open)、映射(mmap)、读写(read/write)以及锁定和解锁(pin/unpin)四个使用情景。
-------------------------------------------------- --------------------------------------------------- --------------------------------------------------- ------------------
Ashmem驱动程序模块的初始化函数,给用户空间暴露了什么接口,
Ashmem驱动程序实现在kernel/common/mm/ashmem.c文件中,它的模块初始化函数定义为 ashmem_init:
static struct file_operations ashmem_fops = {
.owner = THIS_MODULE,
.open = ashmem_open,
.release = ashmem_release,
.mmap = ashmem_mmap,
.unlocked_ioctl = ashmem_ioctl,
.compat_ioctl = ashmem_ioctl,
};
为什么没有read和write操作呢?
因为读写共享内存的方法是通过内存映射地址来进行的,即通过mmap系统调用把这个设备文件映射到进程地址空间中,然后就直接对内存进行读写了,不需要通过read 和write文件操作。
static struct miscdevice ashmem_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = \"ashmem\",
.fops = &ashmem_fops,
};
static int __init ashmem_init(void)
{
int ret;
......
ret =misc_register(&ashmem_misc);
if (unlikely(ret)) {
printk(KERN_ERR \"ashmem: failedto register misc device!\n\");
return ret;
}
......
return 0;
}
-------------------------------------------------- --------------------------
一. 匿名共享内存的创建操作
public class MemoryFile
{
......
public MemoryFile(String name, intlength) throws IOException {
mLength = length;
mFD = native_open(name,length); //通过JNI方法native_open来创建匿名内存共享文件---->
......
}
......
}
这个构造函数最终是通过JNI方法native_open来创建匿名内存共享文件。
这个JNI方法native_open实现在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:
---->
static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstringname, jint length)
{
const char* namestr = (name ?env->GetStringUTFChars(name, NULL) : NULL);
int result =ashmem_create_region(namestr, length); //通过运行时库提供的接口ashmem_create_region,创建匿名共享内存!!!---->
if (name)
env->ReleaseStringUTFChars(name, namestr);
if (result < 0) {
jniThrowException(env, \"java/io/IOException\",\"ashmem_create_region failed\");
return NULL;
}
return jniCreateFileDescriptor(env,result);
}
---->
int ashmem_create_region(const char *name, size_t size)
{
int fd, ret;
fd = open(ASHMEM_DEVICE,O_RDWR); //打开ashmem设备
if (fd < 0)
return fd;
if (name) {
char buf;
strlcpy(buf, name,sizeof(buf));
ret = ioctl(fd, ASHMEM_SET_NAME,buf); //设置匿名共享内存的名称。
if (ret < 0)
goto error;
}
ret = ioctl(fd, ASHMEM_SET_SIZE,size); //设置匿名共享内存的大小。
if (ret < 0)
goto error;
return fd;
error:
close(fd);
return ret;
}
在Ashmem驱动程序中,所有的ashmem_area实例都是从自定义的一个slab缓冲区创建的。
这个slab缓冲区是在驱动程序模块初始化函数创建的,我们来看一个这个初始化函数的相关实现:
static int __init ashmem_init(void)
{
int ret;
ashmem_area_cachep =kmem_cache_create(\"ashmem_area_cache\",
sizeof(struct ashmem_area),
0, 0, NULL);
//缓冲区创建了以后,就可以每次从它分配一个struct ashmem_area对象了。
if (unlikely(!ashmem_area_cachep)){
printk(KERN_ERR \"ashmem:failed to create slab cache\n\");
return -ENOMEM;
}
----------------------------------------------
该结构体表示一块共享内存
struct ashmem_area {
charname;
struct list_head unpinned_list;
struct file *file;
size_t size;
unsigned long prot_mask;
};
域name表示这块共享内存的名字,这个名字会显示/proc/<pid>/maps文件中,<pid>表示打开这个共享内存文件的进程ID;
域unpinned_list是一个列表头,它把这块共享内存中所有被解锁的内存块连接在一起;
域file表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去;
域size表示这块共享内存的大小;
域prot_mask表示这块共享内存的访问保护位。
----------------------------------------------
......
return 0;
}
open进入内核调用ashmem_open:
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
ret = nonseekable_open(inode,file);
if (unlikely(ret))
return ret;
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;
file->private_data = asma;
return 0;
}
首先是通过nonseekable_open函数说明:不可以执行seek文件操作。
接着就是通过 kmem_cache_zalloc函数从刚才我们创建的slab缓冲区ashmem_area_cachep来创建一个ashmem_area结构体了,并且保存在本地变量asma中。
再接下去就是初始化变量asma的其它域。
最后是把这个ashmem_area结构保存在打开文件结构体的private_data域中,这样,Ashmem驱动程序就可以在其它地方通过这个private_data域来取回这个ashmem_area结构了。
小总结: 就是为了创建一个asma结构体。
之后的两个ioctl实现比较简单,只是把用户空间传进来的匿名共享内存的名字和大小值保存在对应的asma->name域和 asma->size域中。
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned longarg)
{
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_SET_SIZE:
ret = -EINVAL;
if (!asma->file) {
ret = 0;
asma->size = (size_t)arg;
}
break;
......
}
}
二. 匿名共享内存设备文件的内存映射操作
在MemoryFile类的构造函数中,进行了匿名共享内存的创建操作后,下一步就是要把匿名共享内存设备文件映射到进程空间来了:
public class MemoryFile
{
......
public MemoryFile(String name, intlength) throws IOException {
......
mAddress = native_mmap(mFD, length,PROT_READ | PROT_WRITE); //-->
......
}
}
映射匿名共享内存设备文件到进程空间是通过JNI方法native_mmap来进行的。
这个JNI方法实现在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:
static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobjectfileDescriptor,
jint length, jint prot)
{
int fd =jniGetFDFromFileDescriptor(env, fileDescriptor);
//进入到Ashmem驱动程序的ashmem_mmap函数中---->
jint result = (jint)mmap(NULL,length, prot, MAP_SHARED, fd, 0);
if (!result)
jniThrowException(env,\"java/io/IOException\", \"mmap failed\");
return result;
}
Kernel---->
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);
。。。 。。。
if (!asma->file) {
char *name =ASHMEM_NAME_DEF;
struct file *vmfile;
if(asma->name != '\0')
name = asma->name;
// 在临时文件系统tmpfs中创建一个临时文件,低碳生活低碳网
// 这个临时文件与Ashmem驱动程序创建的匿名共享内存对应
vmfile = shmem_file_setup(name,asma->size, vma->vm_flags);
if (unlikely(IS_ERR(vmfile))){
ret = PTR_ERR(vmfile);
goto out;
}
asma->file = vmfile;
}
get_file(asma->file);
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;
}
vma->vm_flags |=VM_CAN_NONLINEAR;
out:
mutex_unlock(&ashmem_mutex);
return ret;
}
通过shmem_file_setup函数创建的临时文件vmfile最终就保存在vma->file中了。
文件内存映射操作完成后(http://www.stoo.cn),用户访问这个范围的地址空间就相当于是访问对应的文件的内容了。
同时,这个临时文件vmfile也会保存asma->file域中,这样,Ashmem驱动程序后面就可以通过在asma->file来操作这个匿名内存共享文件了。
函数ashmem_mmap执行完成后,经过层层返回到JNI方法native_mmap中去,就从mmap函数的返回值中得到了这块虚拟空间的起始地址了,
这个起始地址最终返回到应用程序框架层的MemoryFile类的构造函数中,并且保存在成员变量mAddress中,后面,共享内存的读写操作就是对这个地址空间进行操作了。
三. 匿名共享内存的读写操作
因为前面对匿名共享内存文件进行内存映射操作,这里对匿名内存文件内容的读写操作就比较简单了,就像访问内存变量一样就行了。
我们来看一下MemoryFile类的读写操作函数:
public class MemoryFile
{
......
private static native intnative_read(FileDescriptor fd, int address, byte[] buffer,
int srcOffset, int destOffset,int count, boolean isUnpinned) throws IOException;
private static native voidnative_write(FileDescriptor fd, int address, byte[] buffer,
int srcOffset, int destOffset,int count, boolean isUnpinned) throws IOException;
......
/**
* Reads bytes from the memory file.
*/
public int readBytes(byte[] buffer,int srcOffset, int destOffset, int count)
throws IOException {
。。。 。。。
return native_read(mFD, mAddress,buffer, srcOffset, destOffset, count, mAllowPurging);
}
/**
* Write bytes to the memory file.
*/
public void writeBytes(byte[] buffer,int srcOffset, int destOffset, int count)
throws IOException {
。。。 。。。
native_write(mFD, mAddress,buffer, srcOffset, destOffset, count, mAllowPurging);
}
......
}
MemoryFile的匿名共享内存读写操作都是通过JNI方法来实现的,
读操作和写操作的JNI方法分别是native_read和 native_write,它们都是定义在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:
static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,
jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
jint count, jbooleanunpinned)
{
int fd =jniGetFDFromFileDescriptor(env, fileDescriptor);
if (unpinned &&ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
ashmem_unpin_region(fd, 0,0); //***
jniThrowException(env,\"java/io/IOException\", \"ashmem region was purged\");
return -1;
}
env->SetByteArrayRegion(buffer,destOffset, count, (const jbyte *)address + srcOffset);
if (unpinned) {
ashmem_unpin_region(fd, 0,0);
}
return count;
}
static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,
jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
jint count, jbooleanunpinned)
{
int fd =jniGetFDFromFileDescriptor(env, fileDescriptor);
if (unpinned &&ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
ashmem_unpin_region(fd, 0,0); //***
jniThrowException(env,\"java/io/IOException\", \"ashmem region was purged\");
return -1;
}
env->GetByteArrayRegion(buffer,srcOffset, count, (jbyte *)address + destOffset);
if (unpinned) {
ashmem_unpin_region(fd, 0,0);
}
return count;
}
参数address就是我们在前面执行mmap来映射匿名共享内存文件到内存中时,得到的进程虚拟地址空间的起始地址了,因此,这里就直接可以访问,不必进入到Ashmem驱动程序中去。
《Ashmem驱动如何辅助内存管理系统》
匿名共享内存的锁定和解锁操作,它们的作用是告诉Ashmem驱动程序,它的哪些内存块是正在使用的,需要锁定,哪些内存是不需要使用了,可以它解锁,
这样,Ashmem驱动程序就可以辅助内存管理系统来有效地管理内存了。
四. 匿名共享内存的锁定和解锁操作
前面提到,Android系统的运行时库提到了执行匿名共享内存的锁定和解锁操作的两个函数ashmem_pin_region和 ashmem_unpin_region,它们实现在system/core/libcutils/ashmem-dev.c文件中:
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len};
return ioctl(fd, ASHMEM_PIN,&pin);
}
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len};
return ioctl(fd, ASHMEM_UNPIN,&pin);
}
通过ASHMEM_PIN和ASHMEM_UNPIN两个ioctl操作来实现匿名共享内存的锁定和解锁操作。
static struct shrinker ashmem_shrinker = {
.shrink = ashmem_shrink,
.seeks = DEFAULT_SEEKS * 4,
};
static int __init ashmem_init(void)
{
int ret;
......
register_shrinker(&ashmem_shrinker);
printk(KERN_INFO \"ashmem:initialized\n\");
return 0;
}
这里通过调用register_shrinker函数向内存管理系统注册一个内存回收算法函数。
在Linux内核中,当系统内存紧张时,内存管理系统就会进行内存回收算法,将一些最近没有用过的内存换出物理内存去,这样可以增加物理内存的供应。
因此,当内存管理系统进行内存回收时,就会调用到这里的 ashmem_shrink函数,让Ashmem驱动程序执行内存回收操作:
static int ashmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
struct ashmem_range *range, *next;
/* We might recurse into filesystemcode, so bail out if necessary */
if (nr_to_scan && !(gfp_mask& __GFP_FS))
return -1;
if (!nr_to_scan)
return lru_count;
mutex_lock(&ashmem_mutex);
list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) {
struct inode *inode =range->asma->file->f_dentry->d_inode;
loff_t start = range->pgstart* PAGE_SIZE;
loff_t end = (range->pgend +1) * PAGE_SIZE - 1;
vmtruncate_range(inode, start,end);
range->purged =ASHMEM_WAS_PURGED;
lru_del(range);
nr_to_scan -=range_size(range);
if (nr_to_scan <= 0)
break;
}
mutex_unlock(&ashmem_mutex);
return lru_count;
}
参数nr_to_scan表示要扫描的页数,如果是0,则表示要查询一下,当前Ashmem驱动程序有多少页面可以回收,这里就等于挂在lru列表的内块页面的总数了,即lru_count;
否则,就要开始扫描lru列表,从中回收内存了,直到回收的内存页数等于nr_to_scan,或者已经没有内存可回收为止。
回收内存页面是通过vm_truncate_range函数进行的,这个函数定义在kernel/common/mm /memory.c文件中,它是Linux内核内存管理系统实现的。
小总结:
简单来说,它就是给使用者提供锁机制来辅助管理内存,当我们申请了一大块匿名共享内存时,中间过程有一部分不需要使用时,我们就可以将这一部分内存块解锁,这样内存管理系统就可以把它回收回去了。 系统学习完全工具初中版、高中理科版 [转载]寒假高中复习规划系列——如何学好高一数学 大同市龙年春节文化生活扫描(24P) 中国超声仪器行业深度调研及投资前景预测报告2012-2016年 橄榄油护肤的用法有哪些 婴儿洗护用品 内蒙古今年将全面实现高中阶段免费教育 [转载]五大行业变数早知道 保安服装 棉价大跌棉农纷纷转产 棉纺行业新一轮洗牌难免 我给小容小韬选择洗护用品