Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现,本文通过源码分析方式详细介绍Android系统的匿名共享内存机制。在Android系统中,匿名共享内存也是进程间通信方式的一种。相比于malloc和anonymous/named mmap等传统的内存分配机制,Ashmem的优势是通过内核驱动提供了辅助内核的内存回收算法机制(pin/unpin)。内存回收算法机制就是当你使用Ashmem分配了一块内存,但是其中某些部分却不会被使用时,那么就可以将这块内存unpin掉。unpin后,内核可以将它对应的物理页面回收,以作他用。你也不用担心进程无法对unpin掉的内存进行再次访问,因为回收后的内存还可以再次被获得(通过缺页handler),因为unpin操作并不会改变已经 mmap的地址空间。
实现代码文件:
kernel\mm\ashmem.c
kernel\include\linux\ashmem.h
struct ashmem_area { char name[ASHMEM_FULL_NAME_LEN]; /* 用于/proc/pid/maps中的一个标识名称 */ struct list_head unpinned_list; /* 所有的匿名共享内存区列表 */ struct file *file; /* Ashmem所支持的文件 */ size_t size; /* 字节数 */ unsigned long prot_mask; /* vm_flags */ };
#define ASHMEM_NAME_PREFIX "dev/ashmem/"
每一块匿名共享内存的名字会显示/proc/<pid>/maps文件中,<pid>表示打开这个共享内存文件的进程ID;
域unpinned_list是一个列表头,它把这块共享内存中所有被解锁的内存块连接在一起。一块匿名共享内存可以划分为若干小块,unpinned_list就是用于管理这些处于解锁状态下的小块内存,解锁内存块的地址相互独立,在unpinned_list链表中按地址值从大到小的顺序排列。
域file表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去,匿名共享内存是基于Linux内核的临时文件系统tmpfs实现的,每一块匿名共享内存在临时文件系统tmpfs中都有一个对应的文件。
域size表示这块共享内存的大小;域prot_mask表示这块共享内存的访问保护位。
struct ashmem_range { struct list_head lru; /* LRU列表 */ struct list_head unpinned; /* unpinned列表 */ struct ashmem_area *asma; /* ashmem_area结构 */ size_t pgstart; /* 开始页面 */ size_t pgend; /* 结束页面 */ unsigned int purged; /* 是否需要清除(ASHMEM_NOT_PURGED 或者ASHMEM_WAS_PURGED) */ };
ashmem_range数据结构就是用来表示某一块被解锁(unpinnd)的小块匿名共享内存,这些解锁的小块内存都是从一块匿名共享内存中划分出来的。域asma表示这块被解锁的内存所属于的匿名共享内存,它通过域unpinned连接在asma->unpinned_list表示的列表中;域pgstart和paend表示这个内存块的开始和结束页面号,它们表示一个前后闭合的区间;域purged表示这个内存块占用的物理内存是否已经被回收,如果被回收,它的值为ASHMEM_WAS_PURGED,否则为ASHMEM_NOT_PURGED
#define ASHMEM_NOT_PURGED 0
#define ASHMEM_WAS_PURGED 1
这块被解锁的内存块除了保存在它所属的匿名共享内存asma的解锁列表unpinned_list之外,还通过域lru保存在一个全局的最近最少使用列表ashmem_lru_list列表中,处于解锁状态的内存块都是可以回收的,因此当系统内存不足时,内存管理系统就会回收ashmem_lru_list列表中的内存块
址空间ashmem_range的成员变量lru插入全局链表ashmem_lru_list表中。
module_init(ashmem_init); static int __init ashmem_init(void) { int ret; //通过kmem_cache_create函数来创建一个名为"ashmem_area_cache"、对象大小为sizeof(struct ashmem_area)的缓冲区 ashmem_area_cachep = kmem_cache_create("ashmem_area_cache", sizeof(struct ashmem_area), 0, 0, NULL); if (unlikely(!ashmem_area_cachep)) { printk(KERN_ERR "ashmem: failed to create slab cache\n"); return -ENOMEM; } //通过kmem_cache_create函数来创建一个名为"ashmem_range_cache"、对象大小为sizeof(struct ashmem_range)的缓冲区 ashmem_range_cachep = kmem_cache_create("ashmem_range_cache", sizeof(struct ashmem_range), 0, 0, NULL); if (unlikely(!ashmem_range_cachep)) { printk(KERN_ERR "ashmem: failed to create slab cache\n"); return -ENOMEM; } //注册杂项设备 ret = misc_register(&ashmem_misc); if (unlikely(ret)) { printk(KERN_ERR "ashmem: failed to register misc device!\n"); return ret; } /* 注册回收函数 */ register_shrinker(&ashmem_shrinker); printk(KERN_INFO "ashmem: initialized\n"); return 0; }
static struct kmem_cache *ashmem_area_cachep __read_mostly; static struct kmem_cache *ashmem_range_cachep __read_mostly;
static struct miscdevice ashmem_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "ashmem", .fops = &ashmem_fops, }; 注册的字符操作函数集为ashmem_fops: [cpp] view plaincopy static 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, .compat_ioctl = ashmem_ioctl, };
static struct shrinker ashmem_shrinker = { .shrink = ashmem_shrink, .seeks = DEFAULT_SEEKS * 4, };
static void __exit ashmem_exit(void) { int ret; /* 卸载回收函数 */ unregister_shrinker(&ashmem_shrinker); /* 卸载Ashmem设备 */ ret = misc_deregister(&ashmem_misc); if (unlikely(ret)) printk(KERN_ERR "ashmem: failed to unregister misc device!\n"); /* 卸载cache */ kmem_cache_destroy(ashmem_range_cachep); kmem_cache_destroy(ashmem_area_cachep); printk(KERN_INFO "ashmem: unloaded\n"); }
static int ashmem_open(struct inode *inode, struct file *file) { struct ashmem_area *asma; int ret; ret = generic_file_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; }
#define ASHMEM_NAME_PREFIX "dev/ashmem/" #define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1) #define ASHMEM_FULL_NAME_LEN (ASHMEM_NAME_LEN + ASHMEM_NAME_PREFIX_LEN)
当应用程序调用mmap函数将匿名共享内存设备文件映射到进程的地址空间时,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);//加锁 //如果该匿名共享内存大小为0,返回 if (unlikely(!asma->size)) { ret = -EINVAL; goto out; } /* 检查虚拟地址空间的保护位是否和匿名共享内存块的保护位匹配*/ if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask)) & calc_vm_prot_bits(PROT_MASK))) { ret = -EPERM; goto out; } vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask); //如果该匿名共享内存块在临时文件系统中还未创建指定文件 if (!asma->file) { char *name = ASHMEM_NAME_DEF; struct file *vmfile; //s设置临时文件的文件名称为匿名共享内存块的名称 if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') name = asma->name; //为匿名共享内存创建临时文件,文件大小为该匿名共享内存大小 vmfile = shmem_file_setup(name, asma->size, vma->vm_flags); if (unlikely(IS_ERR(vmfile))) { ret = PTR_ERR(vmfile); goto out; } //将创建的临时文件指针保存到该匿名共享内存块的file成员变量中,这样就将匿名共享内存和它的临时文件关联起来了 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; }
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { 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; if (!asma->file) { ret = 0; asma->size = (size_t) arg; } 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 = 0, }; ret = ashmem_shrink(&ashmem_shrinker, &sc); sc.nr_to_scan = ret; ashmem_shrink(&ashmem_shrinker, &sc); } break; } return ret; }
static int set_name(struct ashmem_area *asma, void __user *name) { int ret = 0; mutex_lock(&ashmem_mutex); /* cannot change an existing mapping's name */ if (unlikely(asma->file)) { ret = -EINVAL; goto out; } if (unlikely(copy_from_user(asma->name + ASHMEM_NAME_PREFIX_LEN,name, ASHMEM_NAME_LEN))) ret = -EFAULT; asma->name[ASHMEM_FULL_NAME_LEN-1] = '\0'; out: mutex_unlock(&ashmem_mutex); return ret; }
static int get_name(struct ashmem_area *asma, void __user *name) { int ret = 0; mutex_lock(&ashmem_mutex); if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') { size_t len; len = strlen(asma->name + ASHMEM_NAME_PREFIX_LEN) + 1; //拷贝"dev/ashmem"字符串的后缀到用户空间 if (unlikely(copy_to_user(name,asma->name + ASHMEM_NAME_PREFIX_LEN, len))) ret = -EFAULT; } else { //拷贝字符串"dev/ashmem"到用户空间 if (unlikely(copy_to_user(name, ASHMEM_NAME_DEF,sizeof(ASHMEM_NAME_DEF)))) ret = -EFAULT; } mutex_unlock(&ashmem_mutex); return ret; }
获取匿名共享内存块名称很简单,就是将匿名内存块的名称返回到用户空间而已
static int set_prot_mask(struct ashmem_area *asma, unsigned long prot) { int ret = 0; mutex_lock(&ashmem_mutex); /* the user can only remove, not add, protection bits */ if (unlikely((asma->prot_mask & prot) != prot)) { ret = -EINVAL; goto out; } /* does the application expect PROT_READ to imply PROT_EXEC? */ if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC)) prot |= PROT_EXEC; asma->prot_mask = prot; out: mutex_unlock(&ashmem_mutex); return ret; }