安卓ashmem学习内核解析
来源:互联网 发布:开源java点餐系统 编辑:程序博客网 时间:2024/06/11 02:16
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 */ };
ashmem_area用来描述一块匿名共享内存,域name表示这块共享内存的名字,每一块匿名共享内存的名字以ASHMEM_NAME_PREFIX为前缀,如果应用程序在创建匿名共享内存时没有指定名称,则这块匿名共享内存的名称默认为ASHMEM_NAME_PREFIX
#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 plaincopystatic 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, };
卸载设备驱动
当Ashmem退出时,需要执行的ashmem_exit函数。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; }
首先是通过nonseekable_open函数来设备这个文件不可以执行定位操作,即不可以执行seek文件操作。接着就是通过kmem_cache_zalloc函数从刚才我们创建的slab缓冲区ashmem_area_cachep来创建一个ashmem_area结构体了,并且保存在本地变量asma中。再接下去就是初始化变量asma的其它域,其中,域name初始为ASHMEM_NAME_PREFIX,这是一个宏,定义为:
#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; }
通过以上的ashmem_mmap函数就可以将创建的匿名共享内存映射到进程的虚拟地址空间中,对该匿名共享内存的操作就变成了直接操作进程的某块地址空间了。
IO命令控制
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; }
1.设置匿名共享内存名称
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; }
匿名共享内存名称设置过程很简单,就是将用户空间传过来的名称拼接到"dev/ashmem/"字符串后面。每一个匿名共享内存在临时文件系统tmpfs中都有相应的文件,文件名称为该匿名共享内存的名称,当匿名共享内存驱动程序为某一块匿名共享内存创建了临时文件,就不可以更改该文件名称了,因此也就无法更改该匿名共享内存的名称了,因此在修改匿名共享内存块的名称时,会首先判断其成员变量file是否已经指向了某个文件,如果是,就表示已经为该匿名共享内存块创建了对应的临时文件,修改名称操作就直接返回,无法更改匿名共享内存块的名称了。
2.获取匿名共享内存名称
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; }
获取匿名共享内存块名称很简单,就是将匿名内存块的名称返回到用户空间而已
3.设置匿名共享掩码
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; }
- 安卓ashmem学习内核解析
- 安卓ashmem学习内核解析
- 安卓ashmem学习native篇
- 安卓ashmem(匿名共享内存映射)学习native篇
- ashmem学习example篇
- ashmem学习example篇
- ashmem
- ashmem
- Android学习之Ashmem driver
- Android内核源码阅读---ashmem.c
- Android内核源码阅读---ashmem.c
- 《安卓应用开发学习》--XML解析作业
- 安卓基于XMLPULSS解析XML的学习
- 安卓学习之---SAX方式解析XML文件
- 安卓学习笔记(12)-Json格式数据解析
- 安卓学习---PULL解析器和DOM解析器解析XML文件
- 安卓xml解析
- 安卓Jsoup解析
- 1411181709-ny-+-字符串
- 三星SSD企业级,型号PM853T参数及产品特点?
- iphone wifi连接时 ajax 同步请求错误
- 压缩算法-游程算法
- Finding memory Leak (ps –sort pmem)
- 安卓ashmem学习内核解析
- 升级Xcode后的插件无效问题
- Linux学习笔记(2014/11/18前 )
- android往SD卡写文本文件
- 【LeetCode】Combination Sum II
- 当python邂逅vim
- NGROK 内网穿透利器
- mysql 安装
- 得到