Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554java

在上一文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划中, 咱们简要介绍了Android系统的匿名共享内存机制,其中,简要提到了它具备辅助内存管理系统来有效地管理内存的特色,可是没有进一步去了解它是如何实 现的。在本文中,咱们将经过分析Android系统的匿名共享内存Ashmem驱动程序的源代码,来深刻了解它是如何辅助内存管理系node

        Android系统的匿名共享内存Ashmem机制并无自立山头,从头搞一套本身的共享内存机制,而是创建在Linux内核实现的共享内存的基础上 的。与此同时,它又向Linux内存管理系统的内存回收算法注册接口,告诉Linux内存管理系统它的某些内存块再也不使用了,能够被回收了,不过,这些不 再使用的内存须要由它的使用者来告诉Ashmem驱动程序。经过这种用户-Ashmem驱动程序-内存管理系统三者的紧密合做,实现有效的内存管理机制, 适合移动设备小内存的特色。linux

        Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现本身的功能,所以,它的实现很是小巧,总共代 码不到700行。虽然代码不多,可是这里不打算机械式地一行一行地阅读和分析Ashmem驱动程序的源代码,而是经过使用情景来分析,这样能够帮助咱们清 晰地理解它的实现原理。咱们这里所说的使用情景,将从Android系统的应用程序框架层提供的匿名共享内存接口开始,通过系统运行时库层,最终到达驱动 程序层,经过这样一个完整的过程来理解Android系统的匿名共享内存Ashmem机制。这里,咱们将从上一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划介绍的Android应用程序框架层提供MemoryFile接口开始,分别介绍Android系统匿名共享内存的建立(open)、映射(mmap)、读写(read/write)以及锁定和解锁(pin/unpin)四个使用情景。android

        在进入到这个四个使用情景前,咱们先来看一下Ashmem驱动程序模块的初始化函数,看看它给用户空间暴露了什么接口,即它建立了什么样的设备文件,以 及提供了什么函数来操做这个设备文件。Ashmem驱动程序实如今kernel/common/mm/ashmem.c文件中,它的模块初始化函数定义为 ashmem_init:算法

  1. static struct file_operations ashmem_fops = {  
  2.     .owner = THIS_MODULE,  
  3.     .open = ashmem_open,  
  4.     .release = ashmem_release,  
  5.     .mmap = ashmem_mmap,  
  6.     .unlocked_ioctl = ashmem_ioctl,  
  7.     .compat_ioctl = ashmem_ioctl,  
  8. };  
  9.   
  10. static struct miscdevice ashmem_misc = {  
  11.     .minor = MISC_DYNAMIC_MINOR,  
  12.     .name = "ashmem",  
  13.     .fops = &ashmem_fops,  
  14. };  
  15.   
  16. static int __init ashmem_init(void)  
  17. {  
  18.     int ret;  
  19.   
  20.     ......  
  21.   
  22.     ret = misc_register(&ashmem_misc);  
  23.     if (unlikely(ret)) {  
  24.         printk(KERN_ERR "ashmem: failed to register misc device!\n");  
  25.         return ret;  
  26.     }  
  27.   
  28.     ......  
  29.   
  30.     return 0;  
  31. }  

       这里,咱们能够看到,Ahshmem驱动程序在加载时,会建立一个/dev/ashmem的设备文件,这是一个misc类型的设备。注册misc设备是经过misc_register函数进行的,关于这个函数的详细实现,能够参考前面Android日志系统驱动程序Logger源代码分析一 文,调用这个函数成功后,就会在/dev目录下生成一个ashmem设备文件了。同时,咱们还能够看到,这个设备文件提供了open、mmap、 release和ioctl四种操做。为何没有read和write操做呢?这是由于读写共享内存的方法是经过内存映射地址来进行的,即经过mmap系 统调用把这个设备文件映射到进程地址空间中,而后就直接对内存进行读写了,不须要经过read 和write文件操做,后面咱们将会具体分析是如何实现的。
       有了这个基础以后,下面咱们就分四个部分来分别介绍匿名共享内存的建立(open)、映射(mmap)、读写(read/write)以及锁定和解锁(pin/unpin)使用情景。安全

        一. 匿名共享内存的建立操做数据结构

        在Android应用程序框架层提供MemoryFile类的构造函数中,进行了匿名共享内存的建立操做,咱们先来看一下这个构造函数的实现,它位于 frameworks/base/core/java/android/os/MemoryFile.java文件中:app

[java] view plain copy 在CODE上查看代码片 派生到个人代码片
  1. public class MemoryFile  
  2. {  
  3.     ......  
  4.   
  5.     private static native FileDescriptor native_open(String name, int length) throws IOException;  
  6.       
  7.     ......  
  8.   
  9.     private FileDescriptor mFD;        // ashmem file descriptor  
  10.     ......  
  11.     private int mLength;    // total length of our ashmem region  
  12.       
  13.     ......  
  14.   
  15.     /** 
  16.     * Allocates a new ashmem region. The region is initially not purgable. 
  17.     * 
  18.     * @param name optional name for the file (can be null). 
  19.     * @param length of the memory file in bytes. 
  20.     * @throws IOException if the memory file could not be created. 
  21.     */  
  22.     public MemoryFile(String name, int length) throws IOException {  
  23.         mLength = length;  
  24.         mFD = native_open(name, length);  
  25.         ......  
  26.     }  
  27.   
  28.     ......  
  29. }  

        这里咱们看到,这个构造函数最终是经过JNI方法native_open来建立匿名内存共享文件。这个JNI方法native_open实如今frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:框架

  1. static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)  
  2. {  
  3.     const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);  
  4.   
  5.     int result = ashmem_create_region(namestr, length);  
  6.   
  7.     if (name)  
  8.         env->ReleaseStringUTFChars(name, namestr);  
  9.   
  10.     if (result < 0) {  
  11.         jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");  
  12.         return NULL;  
  13.     }  
  14.   
  15.     return jniCreateFileDescriptor(env, result);  
  16. }  

        这个函数又经过运行时库提供的接口ashmem_create_region来建立匿名共享内存,这个函数实如今system/core/libcutils/ashmem-dev.c文件中:ide

  1. /* 
  2.  * ashmem_create_region - creates a new ashmem region and returns the file 
  3.  * descriptor, or <0 on error 
  4.  * 
  5.  * `name' is an optional label to give the region (visible in /proc/pid/maps) 
  6.  * `size' is the size of the region, in page-aligned bytes 
  7.  */  
  8. int ashmem_create_region(const char *name, size_t size)  
  9. {  
  10.     int fd, ret;  
  11.   
  12.     fd = open(ASHMEM_DEVICE, O_RDWR);  
  13.     if (fd < 0)  
  14.         return fd;  
  15.   
  16.     if (name) {  
  17.         char buf[ASHMEM_NAME_LEN];  
  18.   
  19.         strlcpy(buf, name, sizeof(buf));  
  20.         ret = ioctl(fd, ASHMEM_SET_NAME, buf);  
  21.         if (ret < 0)  
  22.             goto error;  
  23.     }  
  24.   
  25.     ret = ioctl(fd, ASHMEM_SET_SIZE, size);  
  26.     if (ret < 0)  
  27.         goto error;  
  28.   
  29.     return fd;  
  30.   
  31. error:  
  32.     close(fd);  
  33.     return ret;  
  34. }  

        这里,一共经过执行三个文件操做系统调用来和Ashmem驱动程序进行交互,分虽是一个open和两个ioctl操做,前者是打开设备文件ASHMEM_DEVICE,后者分别是设置匿名共享内存的名称和大小。

        在介绍这三个文件操做以前,咱们先来了解一下Ashmem驱动程序的一个相关数据结构struct ashmem_area,这个数据结构就是用来表示一块共享内存的,它定义在kernel/common/mm/ashmem.c文件中:

  1. /* 
  2.  * ashmem_area - anonymous shared memory area 
  3.  * Lifecycle: From our parent file's open() until its release() 
  4.  * Locking: Protected by `ashmem_mutex' 
  5.  * Big Note: Mappings do NOT pin this structure; it dies on close() 
  6.  */  
  7. struct ashmem_area {  
  8.     char name[ASHMEM_FULL_NAME_LEN];/* optional name for /proc/pid/maps */  
  9.     struct list_head unpinned_list; /* list of all ashmem areas */  
  10.     struct file *file;      /* the shmem-based backing file */  
  11.     size_t size;            /* size of the mapping, in bytes */  
  12.     unsigned long prot_mask;    /* allowed prot bits, as vm_flags */  
  13. };  

        域name表示这块共享内存的名字,这个名字会显示/proc/<pid>/maps文件中,<pid>表示打开这个共享内存 文件的进程ID;域unpinned_list是一个列表头,它把这块共享内存中全部被解锁的内存块链接在一块儿,下面咱们讲内存块的锁定和解锁操做时会看 到它的用法;域file表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到 这个临时文件中去;域size表示这块共享内存的大小;域prot_mask表示这块共享内存的访问保护位。

        在Ashmem驱动程中,全部的ashmem_area实例都是从自定义的一个slab缓冲区建立的。这个slab缓冲区是在驱动程序模块初始化函数建立的,咱们来看一个这个初始化函数的相关实现:

  1. static int __init ashmem_init(void)  
  2. {  
  3.     int ret;  
  4.   
  5.     ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",  
  6.         sizeof(struct ashmem_area),  
  7.         0, 0, NULL);  
  8.     if (unlikely(!ashmem_area_cachep)) {  
  9.         printk(KERN_ERR "ashmem: failed to create slab cache\n");  
  10.         return -ENOMEM;  
  11.     }  
  12.   
  13.     ......  
  14.   
  15.     return 0;  
  16. }  

         全局变量定义在文件开头的地方:

  1. static struct kmem_cache *ashmem_area_cachep __read_mostly;  

        它的类型是struct kmem_cache,表示这是一个slab缓冲区,由内核中的内存管理系统进行管理。

        这里就是经过kmem_cache_create函数来建立一个名为"ashmem_area_cache"、对象大小为sizeof(struct ashmem_area)的缓冲区了。缓冲区建立了之后,就能够每次从它分配一个struct ashmem_area对象了。关于Linux内核的slab缓冲区的相关知识,能够参考前面Android学习启动篇一文中提到的一本参考书籍《Understanding the Linux Kernel》的第八章Memory Managerment。

        有了这些基础知识后,咱们回到前面的ashmem_create_region函数中。

        首先是执行打开文件的操做:

  1. fd = open(ASHMEM_DEVICE, O_RDWR);  

        ASHMEM_DEVICE是一个宏,定义为:

  1. #define ASHMEM_DEVICE   "/dev/ashmem"  

         这里就是匿名共享内存设备文件/dev/ashmem了。

        从上面的描述咱们能够知道,调用这个open函数最终会进入到Ashmem驱动程序中的ashmem_open函数中去:

  1. static int ashmem_open(struct inode *inode, struct file *file)  
  2. {  
  3.     struct ashmem_area *asma;  
  4.     int ret;  
  5.   
  6.     ret = nonseekable_open(inode, file);  
  7.     if (unlikely(ret))  
  8.         return ret;  
  9.   
  10.     asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);  
  11.     if (unlikely(!asma))  
  12.         return -ENOMEM;  
  13.   
  14.     INIT_LIST_HEAD(&asma->unpinned_list);  
  15.     memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);  
  16.     asma->prot_mask = PROT_MASK;  
  17.     file->private_data = asma;  
  18.   
  19.     return 0;  
  20. }  

        首先是经过nonseekable_open函数来设备这个文件不能够执行定位操做,即不能够执行seek文件操做。接着就是经过 kmem_cache_zalloc函数从刚才咱们建立的slab缓冲区ashmem_area_cachep来建立一个ashmem_area结构体 了,而且保存在本地变量asma中。再接下去就是初始化变量asma的其它域,其中,域name初始为ASHMEM_NAME_PREFIX,这是一个 宏,定义为:

  1. #define ASHMEM_NAME_PREFIX "dev/ashmem/"  
  2. #define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1)  

        函数的最后是把这个ashmem_area结构保存在打开文件结构体的private_data域中,这样,Ashmem驱动程序就能够在其它地方经过这个private_data域来取回这个ashmem_area结构了。

        到这里,设备文件/dev/ashmem的打开操做就完成了,它实际上就是在Ashmem驱动程序中建立了一个ashmem_area结构,表示一块新的共享内存。

        再回到ashmem_create_region函数中,又调用了两次ioctl文件操做分别来设备这块新建的匿名共享内存的名字和大小。在 kernel/comon/mm/include/ashmem.h文件中,ASHMEM_SET_NAME和ASHMEM_SET_SIZE的定义为:

  1. #define ASHMEM_NAME_LEN     256  
  2.   
  3. #define __ASHMEMIOC     0x77  
  4.   
  5. #define ASHMEM_SET_NAME     _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN])  
  6. #define ASHMEM_SET_SIZE     _IOW(__ASHMEMIOC, 3, size_t)  

       先来看ASHMEM_SET_NAME命令的ioctl调用,它最终进入到Ashmem驱动程序的ashmem_ioctl函数中:

  1. static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  
  2. {  
  3.     struct ashmem_area *asma = file->private_data;  
  4.     long ret = -ENOTTY;  
  5.   
  6.     switch (cmd) {  
  7.     case ASHMEM_SET_NAME:  
  8.         ret = set_name(asma, (void __user *) arg);  
  9.         break;  
  10.     ......  
  11.     }  
  12.   
  13.     return ret;  
  14. }  

       这里经过set_name函数来进行实际操做:

  1. static int set_name(struct ashmem_area *asma, void __user *name)  
  2. {  
  3.     int ret = 0;  
  4.   
  5.     mutex_lock(&ashmem_mutex);  
  6.   
  7.     /* cannot change an existing mapping's name */  
  8.     if (unlikely(asma->file)) {  
  9.         ret = -EINVAL;  
  10.         goto out;  
  11.     }  
  12.   
  13.     if (unlikely(copy_from_user(asma->name + ASHMEM_NAME_PREFIX_LEN,  
  14.                     name, ASHMEM_NAME_LEN)))  
  15.         ret = -EFAULT;  
  16.     asma->name[ASHMEM_FULL_NAME_LEN-1] = '\0';  
  17.   
  18. out:  
  19.     mutex_unlock(&ashmem_mutex);  
  20.   
  21.     return ret;  
  22. }  

        这个函数实现很简单,把用户空间传进来的匿名共享内存的名字设备到asma->name域中去。注意,匿名共享内存块的名字的内容分两部分,前一 部分是前缀,这是在open操做时,由驱动程序默认设置的,固定为ASHMEM_NAME_PREFIX,即"dev/ashmem/";后一部分由用户 指定,这一部分是可选的,即用户能够不调用ASHMEM_SET_NAME命令来设置匿名共享内存块的名字。

        再来看ASHMEM_SET_SIZE命令的ioctl调用,它最终也是进入到Ashmem驱动程序的ashmem_ioctl函数中:

  1. static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  
  2. {  
  3.     struct ashmem_area *asma = file->private_data;  
  4.     long ret = -ENOTTY;  
  5.   
  6.     switch (cmd) {  
  7.     ......  
  8.     case ASHMEM_SET_SIZE:  
  9.         ret = -EINVAL;  
  10.         if (!asma->file) {  
  11.             ret = 0;  
  12.             asma->size = (size_t) arg;  
  13.         }  
  14.         break;  
  15.     ......  
  16.     }  
  17.   
  18.     return ret;  
  19. }  

        这个实现很简单,只是把用户空间传进来的匿名共享内存的大小值保存在对应的asma->size域中。

        这样,ashmem_create_region函数就执先完成了,层层返回,最后回到应用程序框架层提供的接口Memory的构造函数中,整个匿名共 享内存的建立过程就完成了。前面咱们说过过,Ashmem驱动程序不提供read和write文件操做,进程若要访问这个共享内存,必需要把这个设备文件 映射到本身的进程空间中,而后进行直接内存访问,这就是咱们下面要介绍的匿名共享内存设备文件的内存映射操做了。

        二. 匿名共享内存设备文件的内存映射操做

        在MemoryFile类的构造函数中,进行了匿名共享内存的建立操做后,下一步就是要把匿名共享内存设备文件映射到进程空间来了:

[java] view plain copy 在CODE上查看代码片 派生到个人代码片
  1. public class MemoryFile  
  2. {  
  3.     ......  
  4.   
  5.     // returns memory address for ashmem region  
  6.     private static native int native_mmap(FileDescriptor fd, int length, int mode)  
  7.         throws IOException;  
  8.       
  9.     ......  
  10.   
  11.     private int mAddress;   // address of ashmem memory  
  12.       
  13.     ......  
  14.   
  15.     /** 
  16.     * Allocates a new ashmem region. The region is initially not purgable. 
  17.     * 
  18.     * @param name optional name for the file (can be null). 
  19.     * @param length of the memory file in bytes. 
  20.     * @throws IOException if the memory file could not be created. 
  21.     */  
  22.     public MemoryFile(String name, int length) throws IOException {  
  23.         ......  
  24.         mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);  
  25.         ......  
  26.     }  
  27. }  

         映射匿名共享内存设备文件到进程空间是经过JNI方法native_mmap来进行的。这个JNI方法实如今frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:

  1. static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,  
  2.         jint length, jint prot)  
  3. {  
  4.     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);  
  5.     jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);  
  6.     if (!result)  
  7.         jniThrowException(env, "java/io/IOException", "mmap failed");  
  8.     return result;  
  9. }  

        这里的文件描述符fd是在前面open匿名设备文件/dev/ashmem得到的,有个这个文件描述符后,就能够直接经过mmap来执行内存映射操做了。这个mmap系统调用最终进入到Ashmem驱动程序的ashmem_mmap函数中:

  1. static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)  
  2. {  
  3.     struct ashmem_area *asma = file->private_data;  
  4.     int ret = 0;  
  5.   
  6.     mutex_lock(&ashmem_mutex);  
  7.   
  8.     /* user needs to SET_SIZE before mapping */  
  9.     if (unlikely(!asma->size)) {  
  10.         ret = -EINVAL;  
  11.         goto out;  
  12.     }  
  13.   
  14.     /* requested protection bits must match our allowed protection mask */  
  15.     if (unlikely((vma->vm_flags & ~asma->prot_mask) & PROT_MASK)) {  
  16.         ret = -EPERM;  
  17.         goto out;  
  18.     }  
  19.   
  20.     if (!asma->file) {  
  21.         char *name = ASHMEM_NAME_DEF;  
  22.         struct file *vmfile;  
  23.   
  24.         if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')  
  25.             name = asma->name;  
  26.   
  27.         /* ... and allocate the backing shmem file */  
  28.         vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);  
  29.         if (unlikely(IS_ERR(vmfile))) {  
  30.             ret = PTR_ERR(vmfile);  
  31.             goto out;  
  32.         }  
  33.         asma->file = vmfile;  
  34.     }  
  35.     get_file(asma->file);  
  36.   
  37.     if (vma->vm_flags & VM_SHARED)  
  38.         shmem_set_file(vma, asma->file);  
  39.     else {  
  40.         if (vma->vm_file)  
  41.             fput(vma->vm_file);  
  42.         vma->vm_file = asma->file;  
  43.     }  
  44.     vma->vm_flags |= VM_CAN_NONLINEAR;  
  45.   
  46. out:  
  47.     mutex_unlock(&ashmem_mutex);  
  48.     return ret;  
  49. }  

        这个函数的实现也很简单,它调用了Linux内核提供的shmem_file_setup函数来在临时文件系统tmpfs中建立一个临时文件,这个临时 文件与Ashmem驱动程序建立的匿名共享内存对应。函数shmem_file_setup是Linux内核中用来建立共享内存文件的方法,而Linux 内核中的共享内存机制实际上是一种进程间通讯(IPC)机制,它的实现相对也是比较复杂,Android系统的匿名共享内存机制正是因为直接使用了 Linux内核共享内存机制,它才会很小巧,它站在巨人的肩膀上了。关于Linux内核中的共享内存的相关知识,能够参考前面Android学习启动篇一文中提到的一本参考书籍《Linux内核源代码情景分析》的第六章传统的Unix进程间通讯第七小节共享内存。

        经过shmem_file_setup函数建立的临时文件vmfile最终就保存在vma->file中了。这里的vma是由Linux内核的文 件系统层传进来的,它的类型为struct vm_area_struct,它表示的是当前进程空间中一块连续的虚拟地址空间,它的起始地址能够由用户来指定,也能够由内核本身来分配,这里咱们从 JNI方法native_mmap调用的mmap的第一个参数为NULL能够看出,这块连续的虚拟地址空间的起始地址是由内核来指定的。文件内存映射操做 完成后,用户访问这个范围的地址空间就至关因而访问对应的文件的内容了。有关Linux文件的内存映射操做,一样能够参考前面Android学习启动篇一文中提到的一本参考书籍《Linux内核源代码情景分析》的第二章内存管理第十三小节系统调用mmap。从这里咱们也能够看出,Android系统的匿名共享内存是在虚拟地址空间连续的,可是在物理地址空间就不必定是连续的了。

        同时,这个临时文件vmfile也会保存asma->file域中,这样,Ashmem驱动程序后面就能够经过在asma->file来操做这个匿名内存共享文件了。

        函数ashmem_mmap执行完成后,通过层层返回到JNI方法native_mmap中去,就从mmap函数的返回值中获得了这块虚拟空间的起始地 址了,这个起始地址最终返回到应用程序框架层的MemoryFile类的构造函数中,而且保存在成员变量mAddress中,后面,共享内存的读写操做就 是对这个地址空间进行操做了。

        三. 匿名共享内存的读写操做

        由于前面对匿名共享内存文件进行内存映射操做,这里对匿名内存文件内容的读写操做就比较简单了,就像访问内存变量同样就好了。

        咱们来看一下MemoryFile类的读写操做函数:

  1. public class MemoryFile  
  2. {  
  3.     ......  
  4.   
  5.     private static native int native_read(FileDescriptor fd, int address, byte[] buffer,  
  6.         int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;  
  7.     private static native void native_write(FileDescriptor fd, int address, byte[] buffer,  
  8.         int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;  
  9.       
  10.     ......  
  11.   
  12.     private FileDescriptor mFD;        // ashmem file descriptor  
  13.     private int mAddress;   // address of ashmem memory  
  14.     private int mLength;    // total length of our ashmem region  
  15.     private boolean mAllowPurging = false;  // true if our ashmem region is unpinned  
  16.   
  17.     ......  
  18.   
  19.     /** 
  20.     * Reads bytes from the memory file. 
  21.     * Will throw an IOException if the file has been purged. 
  22.     * 
  23.     * @param buffer byte array to read bytes into. 
  24.     * @param srcOffset offset into the memory file to read from. 
  25.     * @param destOffset offset into the byte array buffer to read into. 
  26.     * @param count number of bytes to read. 
  27.     * @return number of bytes read. 
  28.     * @throws IOException if the memory file has been purged or deactivated. 
  29.     */  
  30.     public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)   
  31.     throws IOException {  
  32.         if (isDeactivated()) {  
  33.             throw new IOException("Can't read from deactivated memory file.");  
  34.         }  
  35.         if (destOffset < 0 || destOffset > buffer.length || count < 0  
  36.             || count > buffer.length - destOffset  
  37.             || srcOffset < 0 || srcOffset > mLength  
  38.             || count > mLength - srcOffset) {  
  39.                 throw new IndexOutOfBoundsException();  
  40.         }  
  41.         return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);  
  42.     }  
  43.   
  44.     /** 
  45.     * Write bytes to the memory file. 
  46.     * Will throw an IOException if the file has been purged. 
  47.     * 
  48.     * @param buffer byte array to write bytes from. 
  49.     * @param srcOffset offset into the byte array buffer to write from. 
  50.     * @param destOffset offset  into the memory file to write to. 
  51.     * @param count number of bytes to write. 
  52.     * @throws IOException if the memory file has been purged or deactivated. 
  53.     */  
  54.     public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)  
  55.         throws IOException {  
  56.             if (isDeactivated()) {  
  57.                 throw new IOException("Can't write to deactivated memory file.");  
  58.             }  
  59.             if (srcOffset < 0 || srcOffset > buffer.length || count < 0  
  60.                 || count > buffer.length - srcOffset  
  61.                 || destOffset < 0 || destOffset > mLength  
  62.                 || count > mLength - destOffset) {  
  63.                     throw new IndexOutOfBoundsException();  
  64.             }  
  65.             native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);  
  66.     }  
  67.   
  68.     ......  
  69. }  

        这里,咱们能够看到,MemoryFile的匿名共享内存读写操做都是经过JNI方法来实现的,读操做和写操做的JNI方法分别是 native_read和native_write,它们都是定义在frameworks/base/core/jni /adroid_os_MemoryFile.cpp文件中:

  1. static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,  
  2.         jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,  
  3.         jint count, jboolean unpinned)  
  4. {  
  5.     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);  
  6.     if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {  
  7.         ashmem_unpin_region(fd, 0, 0);  
  8.         jniThrowException(env, "java/io/IOException", "ashmem region was purged");  
  9.         return -1;  
  10.     }  
  11.   
  12.     env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);  
  13.   
  14.     if (unpinned) {  
  15.         ashmem_unpin_region(fd, 0, 0);  
  16.     }  
  17.     return count;  
  18. }  
  19.   
  20. static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,  
  21.         jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,  
  22.         jint count, jboolean unpinned)  
  23. {  
  24.     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);  
  25.     if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {  
  26.         ashmem_unpin_region(fd, 0, 0);  
  27.         jniThrowException(env, "java/io/IOException", "ashmem region was purged");  
  28.         return -1;  
  29.     }  
  30.   
  31.     env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);  
  32.   
  33.     if (unpinned) {  
  34.         ashmem_unpin_region(fd, 0, 0);  
  35.     }  
  36.     return count;  
  37. }  

        这里的address参数就是咱们在前面执行mmap来映射匿名共享内存文件到内存中时,获得的进程虚拟地址空间的起始地址了,所以,这里就直接能够访 问,没必要进入到Ashmem驱动程序中去,这也是为何Ashmem驱动程序没有提供read和write文件操做的缘由。

        这里咱们看到的ashmem_pin_region和ashmem_unpin_region两个函数是系统运行时库提供的接口,用来执行咱们前面说的 匿名共享内存的锁定和解锁操做,它们的做用是告诉Ashmem驱动程序,它的哪些内存块是正在使用的,须要锁定,哪些内存是不须要使用了,能够它解锁,这 样,Ashmem驱动程序就能够辅助内存管理系统来有效地管理内存了。下面咱们就看看Ashmem驱动程序是若是辅助内存管理系统来有效地管理内存的。

        四. 匿名共享内存的锁定和解锁操做
        前面提到,Android系统的运行时库提到了执行匿名共享内存的锁定和解锁操做的两个函数ashmem_pin_region和 ashmem_unpin_region,它们实如今system/core/libcutils/ashmem-dev.c文件中:

  1. int ashmem_pin_region(int fd, size_t offset, size_t len)  
  2. {  
  3.     struct ashmem_pin pin = { offset, len };  
  4.     return ioctl(fd, ASHMEM_PIN, &pin);  
  5. }  
  6.   
  7. int ashmem_unpin_region(int fd, size_t offset, size_t len)  
  8. {  
  9.     struct ashmem_pin pin = { offset, len };  
  10.     return ioctl(fd, ASHMEM_UNPIN, &pin);  
  11. }  

       它们的实现很简单,经过ASHMEM_PIN和ASHMEM_UNPIN两个ioctl操做来实现匿名共享内存的锁定和解锁操做。

       咱们先看来一下ASHMEM_PIN和ASHMEM_UNPIN这两个命令号的定义,它们的定义能够在kernel/common/include/linux/ashmem.h文件中找到:

  1. #define __ASHMEMIOC     0x77  
  2.   
  3. #define ASHMEM_PIN      _IOW(__ASHMEMIOC, 7, struct ashmem_pin)  
  4. #define ASHMEM_UNPIN        _IOW(__ASHMEMIOC, 8, struct ashmem_pin)  

       它们的参数类型为struct ashmem_pin,它也是定义在kernel/common/include/linux/ashmem.h文件中:

  1. struct ashmem_pin {  
  2.     __u32 offset;   /* offset into region, in bytes, page-aligned */  
  3.     __u32 len;  /* length forward from offset, in bytes, page-aligned */  
  4. };  

       这个结构体只有两个域,分别表示要锁定或者要解锁的内块块的起始大小以及大小。

       在分析这两个操做以前,咱们先来看一下Ashmem驱动程序中的一个数据结构struct ashmem_range,这个数据结构就是用来表示某一块被解锁(unpinnd)的内存:

  1. /* 
  2.  * ashmem_range - represents an interval of unpinned (evictable) pages 
  3.  * Lifecycle: From unpin to pin 
  4.  * Locking: Protected by `ashmem_mutex' 
  5.  */  
  6. struct ashmem_range {  
  7.     struct list_head lru;       /* entry in LRU list */  
  8.     struct list_head unpinned;  /* entry in its area's unpinned list */  
  9.     struct ashmem_area *asma;   /* associated area */  
  10.     size_t pgstart;         /* starting page, inclusive */  
  11.     size_t pgend;           /* ending page, inclusive */  
  12.     unsigned int purged;        /* ASHMEM_NOT or ASHMEM_WAS_PURGED */  
  13. };  

        域asma表示这块被解锁的内存所属于的匿名共享内存,它经过域unpinned链接在asma->unpinned_list表示的列表中;域 pgstart和paend表示这个内存块的开始和结束页面号,它们表示一个先后闭合的区间;域purged表示这个内存块占用的物理内存是否已经被回 收;这块被解锁的内存块除了保存在它所属的匿名共享内存asma的解锁列表unpinned_list以外,还经过域lru保存在一个全局的最近最少使用 列表ashmem_lru_list列表中,它的定义以下:

  1. /* LRU list of unpinned pages, protected by ashmem_mutex */  
  2. static LIST_HEAD(ashmem_lru_list);  

        了解了这个数据结构以后,咱们就能够来看ashmem_ioctl函数中关于ASHMEM_PIN和ASHMEM_UNPIN的操做了:

  1. static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  
  2. {  
  3.     struct ashmem_area *asma = file->private_data;  
  4.     long ret = -ENOTTY;  
  5.   
  6.     switch (cmd) {  
  7.     ......  
  8.     case ASHMEM_PIN:  
  9.     case ASHMEM_UNPIN:  
  10.         ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);  
  11.         break;  
  12.     ......  
  13.     }  
  14.   
  15.     return ret;  
  16. }  

        它们都是经过ashmem_pin_unpin来进一步处理:

  1. static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd,  
  2.                 void __user *p)  
  3. {  
  4.     struct ashmem_pin pin;  
  5.     size_t pgstart, pgend;  
  6.     int ret = -EINVAL;  
  7.   
  8.     if (unlikely(!asma->file))  
  9.         return -EINVAL;  
  10.   
  11.     if (unlikely(copy_from_user(&pin, p, sizeof(pin))))  
  12.         return -EFAULT;  
  13.   
  14.     /* per custom, you can pass zero for len to mean "everything onward" */  
  15.     if (!pin.len)  
  16.         pin.len = PAGE_ALIGN(asma->size) - pin.offset;  
  17.   
  18.     if (unlikely((pin.offset | pin.len) & ~PAGE_MASK))  
  19.         return -EINVAL;  
  20.   
  21.     if (unlikely(((__u32) -1) - pin.offset < pin.len))  
  22.         return -EINVAL;  
  23.   
  24.     if (unlikely(PAGE_ALIGN(asma->size) < pin.offset + pin.len))  
  25.         return -EINVAL;  
  26.   
  27.     pgstart = pin.offset / PAGE_SIZE;  
  28.     pgend = pgstart + (pin.len / PAGE_SIZE) - 1;  
  29.   
  30.     mutex_lock(&ashmem_mutex);  
  31.   
  32.     switch (cmd) {  
  33.     case ASHMEM_PIN:  
  34.         ret = ashmem_pin(asma, pgstart, pgend);  
  35.         break;  
  36.     case ASHMEM_UNPIN:  
  37.         ret = ashmem_unpin(asma, pgstart, pgend);  
  38.         break;  
  39.     ......  
  40.     }  
  41.   
  42.     mutex_unlock(&ashmem_mutex);  
  43.   
  44.     return ret;  
  45. }  

        首先是得到用户空间传进来的参数,并保存在本地变量pin中,这是一个struct ashmem_pin类型的变量,这个结构体咱们在前面已经见过了,它包括了要pin/unpin的内存块的起始地址和大小,这里的起始地址和大小都是以 字节为单位的,所以,经过转换把它们换成以页面为单位的,而且保存在本地变量pgstart和pgend中。这里除了要对参数做一个安全性检查外,还要一 个处理逻辑是,若是从用户空间传进来的内块块的大小值为0 ,则认为是要pin/unpin整个匿名共享内存。

        函数最后根据当前要执行的是ASHMEM_PIN操做仍是ASHMEM_UNPIN操做来分别执行ashmem_pin和ashmem_unpin来进 一步处理。建立匿名共享内存时,默认全部的内存都是pinned状态的,只有用户告诉Ashmem驱动程序要unpin某一块内存时,Ashmem驱动程 序才会把这块内存unpin,以后,用户能够再告诉Ashmem驱动程序要从新pin某一块以前被unpin过的内块,从而把这块内存从unpinned 状态改成pinned状态,也就是说,执行ASHMEM_PIN操做时,目标对象必须是一块当前处于unpinned状态的内存块。

       咱们先来看一下ASHMEM_UNPIN操做,进入到ashmem_unpin函数:

  1. /* 
  2.  * ashmem_unpin - unpin the given range of pages. Returns zero on success. 
  3.  * 
  4.  * Caller must hold ashmem_mutex. 
  5.  */  
  6. static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend)  
  7. {  
  8.     struct ashmem_range *range, *next;  
  9.     unsigned int purged = ASHMEM_NOT_PURGED;  
  10.   
  11. restart:  
  12.     list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {  
  13.         /* short circuit: this is our insertion point */  
  14.         if (range_before_page(range, pgstart))  
  15.             break;  
  16.   
  17.         /* 
  18.          * The user can ask us to unpin pages that are already entirely 
  19.          * or partially pinned. We handle those two cases here. 
  20.          */  
  21.         if (page_range_subsumed_by_range(range, pgstart, pgend))  
  22.             return 0;  
  23.         if (page_range_in_range(range, pgstart, pgend)) {  
  24.             pgstart = min_t(size_t, range->pgstart, pgstart),  
  25.             pgend = max_t(size_t, range->pgend, pgend);  
  26.             purged |= range->purged;  
  27.             range_del(range);  
  28.             goto restart;  
  29.         }  
  30.     }  
  31.   
  32.     return range_alloc(asma, range, purged, pgstart, pgend);  
  33. }  

        这个函数的主体就是在遍历asma->unpinned_list列表,从中查找当前处于unpinned状态的内存块是否与将要unpin的内 存块[pgstart, pgend]是否相交,若是相交,则要执行合并操做,即调整pgstart和pgend的大小,而后经过调用range_del函数删掉原来的已经被 unpinned过的内存块,最后再经过range_alloc函数来从新unpinned这块调整事后的内存块[pgstart, pgend],这里新的内存块[pgstart, pgend]已经包含了刚才全部被删掉的unpinned状态的内存。注意,这里若是找到一块相并的内存块,而且调整了pgstart和pgend的大小 以后,要从新再扫描一遍asma->unpinned_list列表,由于新的内存块[pgstart, pgend]可能还会与先后的处于unpinned状态的内存块发生相交。

        咱们来看一下range_before_page的操做,这是一个宏定义:

  1. #define range_before_page(range, page) \  
  2.   ((range)->pgend < (page))  

        表示range描述的内存块是否在page页面以前,若是是,则整个描述就结束了。从这里咱们能够看出asma->unpinned_list列表是按照页面号从大到小进行排列的,而且每一块被unpin的内存都是不相交的。

        再来看一下page_range_subsumed_by_range的操做,这也是一个宏定义:

  1. #define page_range_subsumed_by_range(range, start, end) \  
  2.   (((range)->pgstart <= (start)) && ((range)->pgend >= (end)))  

       表示range描述的内存块是否是包含了[start, end]这个内存块,若是包含了,则说明当前要unpin的内存块已经处于unpinned状态,什么也不用操做,直接返回便可。

       再看page_range_in_range的操做,它也是一个宏定义:

  1. #define page_range_in_range(range, start, end) \  
  2.   (page_in_range(range, start) || page_in_range(range, end) || \  
  3.    page_range_subsumes_range(range, start, end))  

      它用到的其它两个宏分别定义为:

  1. #define page_range_subsumed_by_range(range, start, end) \  
  2.   (((range)->pgstart <= (start)) && ((range)->pgend >= (end)))  
  3.   
  4. #define page_in_range(range, page) \  
  5.  (((range)->pgstart <= (page)) && ((range)->pgend >= (page)))  

      它们都是用来判断两个内存区间是否相交的。

      两个内存块相交分为四种状况:

      |-------range-----|        |-------range------|       |--------range---------|                 |----range---|

       |-start----end-|       |-start-----end-|                        |-start-------end-|        |-start-----------end-|
                 (1)                                (2)                                  (3)                                            (4)
      第一种状况,前面已经讨论过了,对于第二到第四种状况,都是须要执行合并操做的。

      再来看从asma->unpinned_list中删掉内存块的range_del函数:

  1. static void range_del(struct ashmem_range *range)  
  2. {  
  3.     list_del(&range->unpinned);  
  4.     if (range_on_lru(range))  
  5.         lru_del(range);  
  6.     kmem_cache_free(ashmem_range_cachep, range);  
  7. }  

      这个函数首先把range从相应的unpinned_list列表中删除,而后判断它是否在lru列表中:

  1. #define range_on_lru(range) \  
  2.   ((range)->purged == ASHMEM_NOT_PURGED)  

      若是它的状态purged等于ASHMEM_NOT_PURGED,即对应的物理页面还没有被回收,它就位于lru列表中,经过调用lru_del函数进行删除:

  1. static inline void lru_del(struct ashmem_range *range)  
  2. {  
  3.     list_del(&range->lru);  
  4.     lru_count -= range_size(range);  
  5. }  

       最后调用kmem_cache_free将它从slab缓冲区ashmem_range_cachep中释放。

       这里的slab缓冲区ashmem_range_cachep定义以下:

  1. static struct kmem_cache *ashmem_range_cachep __read_mostly;  

       它和前面介绍的slab缓冲区ashmem_area_cachep同样,是在Ashmem驱动程序模块初始化函数ashmem_init进行初始化的:

  1. static int __init ashmem_init(void)  
  2. {  
  3.     int ret;  
  4.   
  5.     ......  
  6.   
  7.     ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",  
  8.         sizeof(struct ashmem_range),  
  9.         0, 0, NULL);  
  10.     if (unlikely(!ashmem_range_cachep)) {  
  11.         printk(KERN_ERR "ashmem: failed to create slab cache\n");  
  12.         return -ENOMEM;  
  13.     }  
  14.   
  15.     ......  
  16.   
  17.     printk(KERN_INFO "ashmem: initialized\n");  
  18.   
  19.     return 0;  
  20. }  

       回到ashmem_unpin函数中,咱们再来看看range_alloc函数的实现:

  1. /* 
  2.  * range_alloc - allocate and initialize a new ashmem_range structure 
  3.  * 
  4.  * 'asma' - associated ashmem_area 
  5.  * 'prev_range' - the previous ashmem_range in the sorted asma->unpinned list 
  6.  * 'purged' - initial purge value (ASMEM_NOT_PURGED or ASHMEM_WAS_PURGED) 
  7.  * 'start' - starting page, inclusive 
  8.  * 'end' - ending page, inclusive 
  9.  * 
  10.  * Caller must hold ashmem_mutex. 
  11.  */  
  12. static int range_alloc(struct ashmem_area *asma,  
  13.                struct ashmem_range *prev_range, unsigned int purged,  
  14.                size_t start, size_t end)  
  15. {  
  16.     struct ashmem_range *range;  
  17.   
  18.     range = kmem_cache_zalloc(ashmem_range_cachep, GFP_KERNEL);  
  19.     if (unlikely(!range))  
  20.         return -ENOMEM;  
  21.   
  22.     range->asma = asma;  
  23.     range->pgstart = start;  
  24.     range->pgend = end;  
  25.     range->purged = purged;  
  26.   
  27.     list_add_tail(&range->unpinned, &prev_range->unpinned);  
  28.   
  29.     if (range_on_lru(range))  
  30.         lru_add(range);  
  31.   
  32.     return 0;  
  33. }  

       这个函数的做用是从slab 缓冲区中ashmem_range_cachep分配一个ashmem_range,而后对它做相应的初始化,放在相应的 ashmem_area->unpinned_list列表中,而且还要判断这个range的purged是不是 ASHMEM_NOT_PURGED状态,若是是,还要把它放在lru列表中:

  1. static inline void lru_add(struct ashmem_range *range)  
  2. {  
  3.     list_add_tail(&range->lru, &ashmem_lru_list);  
  4.     lru_count += range_size(range);  
  5. }  

       这样,ashmem_unpin的源代码咱们就分析完了。

       接着,咱们再来看一下ASHMEM_PIN操做,进入到ashmem_pin函数:

  1. /* 
  2.  * ashmem_pin - pin the given ashmem region, returning whether it was 
  3.  * previously purged (ASHMEM_WAS_PURGED) or not (ASHMEM_NOT_PURGED). 
  4.  * 
  5.  * Caller must hold ashmem_mutex. 
  6.  */  
  7. static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend)  
  8. {  
  9.     struct ashmem_range *range, *next;  
  10.     int ret = ASHMEM_NOT_PURGED;  
  11.   
  12.     list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {  
  13.         /* moved past last applicable page; we can short circuit */  
  14.         if (range_before_page(range, pgstart))  
  15.             break;  
  16.   
  17.         /* 
  18.          * The user can ask us to pin pages that span multiple ranges, 
  19.          * or to pin pages that aren't even unpinned, so this is messy. 
  20.          * 
  21.          * Four cases: 
  22.          * 1. The requested range subsumes an existing range, so we 
  23.          *    just remove the entire matching range. 
  24.          * 2. The requested range overlaps the start of an existing 
  25.          *    range, so we just update that range. 
  26.          * 3. The requested range overlaps the end of an existing 
  27.          *    range, so we just update that range. 
  28.          * 4. The requested range punches a hole in an existing range, 
  29.          *    so we have to update one side of the range and then 
  30.          *    create a new range for the other side. 
  31.          */  
  32.         if (page_range_in_range(range, pgstart, pgend)) {  
  33.             ret |= range->purged;  
  34.   
  35.             /* Case #1: Easy. Just nuke the whole thing. */  
  36.             if (page_range_subsumes_range(range, pgstart, pgend)) {  
  37.                 range_del(range);  
  38.                 continue;  
  39.             }  
  40.   
  41.             /* Case #2: We overlap from the start, so adjust it */  
  42.             if (range->pgstart >= pgstart) {  
  43.                 range_shrink(range, pgend + 1, range->pgend);  
  44.                 continue;  
  45.             }  
  46.   
  47.             /* Case #3: We overlap from the rear, so adjust it */  
  48.             if (range->pgend <= pgend) {  
  49.                 range_shrink(range, range->pgstart, pgstart-1);  
  50.                 continue;  
  51.             }  
  52.   
  53.             /* 
  54.              * Case #4: We eat a chunk out of the middle. A bit 
  55.              * more complicated, we allocate a new range for the 
  56.              * second half and adjust the first chunk's endpoint. 
  57.              */  
  58.             range_alloc(asma, range, range->purged,  
  59.                     pgend + 1, range->pgend);  
  60.             range_shrink(range, range->pgstart, pgstart - 1);  
  61.             break;  
  62.         }  
  63.     }  
  64.   
  65.     return ret;  
  66. }  

        前面咱们说过,被pin的内存块,必须是在unpinned_list列表中的,若是不在,就什么都不用作。要判断要pin的内存块是否在 unpinned_list列表中,又要经过遍历相应的asma->unpinned_list列表来找出与之相交的内存块了。这个函数的处理方法 大致与前面的ashmem_unpin函数是一致的,也是要考虑四种不一样的相交状况,这里就不详述了,读者能够本身分析一下。

        这里咱们只看一下range_shrink函数的实现:

  1. /* 
  2.  * range_shrink - shrinks a range 
  3.  * 
  4.  * Caller must hold ashmem_mutex. 
  5.  */  
  6. static inline void range_shrink(struct ashmem_range *range,  
  7.                 size_t start, size_t end)  
  8. {  
  9.     size_t pre = range_size(range);  
  10.   
  11.     range->pgstart = start;  
  12.     range->pgend = end;  
  13.   
  14.     if (range_on_lru(range))  
  15.         lru_count -= pre - range_size(range);  
  16. }  

        这个函数的实现很简单,只是调整一下range描述的内存块的起始页面号,若是它是位于lru列表中,还要调整一下在lru列表中的总页面数大小。

        这样,匿名共享内存的ASHMEM_PIN和ASHMEM_UNPIN操做就介绍完了,可是,咱们还看不出来Ashmem驱动程序是怎么样辅助内存管理 系统来有效管理内存的。有了前面这些unpinned的内存块列表以后,下面咱们就看一下Ashmem驱动程序是怎么样辅助内存管理系统来有效管理内存 的。

        首先看一下Ashmem驱动程序模块初始化函数ashmem_init:

  1. static struct shrinker ashmem_shrinker = {  
  2.     .shrink = ashmem_shrink,  
  3.     .seeks = DEFAULT_SEEKS * 4,  
  4. };  
  5.   
  6. static int __init ashmem_init(void)  
  7. {  
  8.     int ret;  
  9.   
  10.     ......  
  11.   
  12.     register_shrinker(&ashmem_shrinker);  
  13.   
  14.     printk(KERN_INFO "ashmem: initialized\n");  
  15.   
  16.     return 0;  
  17. }  

        这里经过调用register_shrinker函数向内存管理系统注册一个内存回收算法函数。在Linux内核中,当系统内存紧张时,内存管理系统就 会进行内存回收算法,将一些最近没有用过的内存换出物理内存去,这样能够增长物理内存的供应。所以,当内存管理系统进行内存回收时,就会调用到这里的 ashmem_shrink函数,让Ashmem驱动程序执行内存回收操做:

  1. /* 
  2.  * ashmem_shrink - our cache shrinker, called from mm/vmscan.c :: shrink_slab 
  3.  * 
  4.  * 'nr_to_scan' is the number of objects (pages) to prune, or 0 to query how 
  5.  * many objects (pages) we have in total. 
  6.  * 
  7.  * 'gfp_mask' is the mask of the allocation that got us into this mess. 
  8.  * 
  9.  * Return value is the number of objects (pages) remaining, or -1 if we cannot 
  10.  * proceed without risk of deadlock (due to gfp_mask). 
  11.  * 
  12.  * We approximate LRU via least-recently-unpinned, jettisoning unpinned partial 
  13.  * chunks of ashmem regions LRU-wise one-at-a-time until we hit 'nr_to_scan' 
  14.  * pages freed. 
  15.  */  
  16. static int ashmem_shrink(int nr_to_scan, gfp_t gfp_mask)  
  17. {  
  18.     struct ashmem_range *range, *next;  
  19.   
  20.     /* We might recurse into filesystem code, so bail out if necessary */  
  21.     if (nr_to_scan && !(gfp_mask & __GFP_FS))  
  22.         return -1;  
  23.     if (!nr_to_scan)  
  24.         return lru_count;  
  25.   
  26.     mutex_lock(&ashmem_mutex);  
  27.     list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) {  
  28.         struct inode *inode = range->asma->file->f_dentry->d_inode;  
  29.         loff_t start = range->pgstart * PAGE_SIZE;  
  30.         loff_t end = (range->pgend + 1) * PAGE_SIZE - 1;  
  31.   
  32.         vmtruncate_range(inode, start, end);  
  33.         range->purged = ASHMEM_WAS_PURGED;  
  34.         lru_del(range);  
  35.   
  36.         nr_to_scan -= range_size(range);  
  37.         if (nr_to_scan <= 0)  
  38.             break;  
  39.     }  
  40.     mutex_unlock(&ashmem_mutex);  
  41.   
  42.     return lru_count;  
  43. }  

        这里的参数nr_to_scan表示要扫描的页数,若是是0,则表示要查询一下,当前Ashmem驱动程序有多少页面能够回收,这里就等于挂在lru列 表的内块页面的总数了,即lru_count;不然,就要开始扫描lru列表,从中回收内存了,直到回收的内存页数等于nr_to_scan,或者已经没 有内存可回收为止。回收内存页面是经过vm_truncate_range函数进行的,这个函数定义在kernel/common/mm /memory.c文件中,它是Linux内核内存管理系统实现的,有兴趣的读者能够研究一下。

        这样,Android系统匿名共享内存Ashmem驱动程序源代码就分析完了,在下一篇文章中,咱们将继续分析Android系统的匿名共享内存机制,研究它是如何经过Binder进程间通讯机制实如今不一样进程程进行内存共享的,敬请关注。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

相关文章
相关标签/搜索