879 lines · 22.4 KB · C
RawDownload
/* mm/ashmem.c * * Anonymous Shared Memory Subsystem, ashmem * * Copyright (C) 2008 Google, Inc. * * Robert Love <rlove@google.com> * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. */ #define pr_fmt(fmt) "ashmem: " fmt #include <linux/module.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/falloc.h>#include <linux/miscdevice.h>#include <linux/security.h>#include <linux/mm.h>#include <linux/mman.h>#include <linux/uaccess.h>#include <linux/personality.h>#include <linux/bitops.h>#include <linux/mutex.h>#include <linux/shmem_fs.h>#include "ashmem.h" #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) /** * 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_masks:		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;}; /* LRU list of unpinned pages, protected by ashmem_mutex */static LIST_HEAD(ashmem_lru_list); /** * long lru_count - The count of pages on our LRU list. * * This is protected by ashmem_mutex. */static unsigned long lru_count; /** * ashmem_mutex - protects the list of and each individual ashmem_area * * Lock Ordering: ashmex_mutex -> i_mutex -> i_alloc_sem */static DEFINE_MUTEX(ashmem_mutex); static struct kmem_cache *ashmem_area_cachep __read_mostly;static struct kmem_cache *ashmem_range_cachep __read_mostly; #define range_size(range) \	((range)->pgend - (range)->pgstart + 1) #define range_on_lru(range) \	((range)->purged == ASHMEM_NOT_PURGED) #define page_range_subsumes_range(range, start, end) \	(((range)->pgstart >= (start)) && ((range)->pgend <= (end))) #define page_range_subsumed_by_range(range, start, end) \	(((range)->pgstart <= (start)) && ((range)->pgend >= (end))) #define page_in_range(range, page) \	(((range)->pgstart <= (page)) && ((range)->pgend >= (page))) #define page_range_in_range(range, start, end) \	(page_in_range(range, start) || page_in_range(range, end) || \		page_range_subsumes_range(range, start, end)) #define range_before_page(range, page) \	((range)->pgend < (page)) #define PROT_MASK		(PROT_EXEC | PROT_READ | PROT_WRITE) /** * lru_add() - Adds a range of memory to the LRU list * @range:     The memory range being added. * * The range is first added to the end (tail) of the LRU list. * After this, the size of the range is added to @lru_count */static inline void lru_add(struct ashmem_range *range){	list_add_tail(&range->lru, &ashmem_lru_list);	lru_count += range_size(range);} /** * lru_del() - Removes a range of memory from the LRU list * @range:     The memory range being removed * * The range is first deleted from the LRU list. * After this, the size of the range is removed from @lru_count */static inline void lru_del(struct ashmem_range *range){	list_del(&range->lru);	lru_count -= range_size(range);} /** * range_alloc() - Allocates and initializes a new ashmem_range structure * @asma:	   The associated ashmem_area * @prev_range:	   The previous ashmem_range in the sorted asma->unpinned list * @purged:	   Initial purge status (ASMEM_NOT_PURGED or ASHMEM_WAS_PURGED) * @start:	   The starting page (inclusive) * @end:	   The ending page (inclusive) * * This function is protected by ashmem_mutex. * * Return: 0 if successful, or -ENOMEM if there is an error */static int range_alloc(struct ashmem_area *asma,		       struct ashmem_range *prev_range, unsigned int purged,		       size_t start, size_t end){	struct ashmem_range *range; 	range = kmem_cache_zalloc(ashmem_range_cachep, GFP_KERNEL);	if (unlikely(!range))		return -ENOMEM; 	range->asma = asma;	range->pgstart = start;	range->pgend = end;	range->purged = purged; 	list_add_tail(&range->unpinned, &prev_range->unpinned); 	if (range_on_lru(range))		lru_add(range); 	return 0;} /** * range_del() - Deletes and dealloctes an ashmem_range structure * @range:	 The associated ashmem_range that has previously been allocated */static void range_del(struct ashmem_range *range){	list_del(&range->unpinned);	if (range_on_lru(range))		lru_del(range);	kmem_cache_free(ashmem_range_cachep, range);} /** * range_shrink() - Shrinks an ashmem_range * @range:	    The associated ashmem_range being shrunk * @start:	    The starting byte of the new range * @end:	    The ending byte of the new range * * This does not modify the data inside the existing range in any way - It * simply shrinks the boundaries of the range. * * Theoretically, with a little tweaking, this could eventually be changed * to range_resize, and expand the lru_count if the new range is larger. */static inline void range_shrink(struct ashmem_range *range,				size_t start, size_t end){	size_t pre = range_size(range); 	range->pgstart = start;	range->pgend = end; 	if (range_on_lru(range))		lru_count -= pre - range_size(range);} /** * 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; 	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;} /** * ashmem_release() - Releases an Anonymous Shared Memory structure * @ignored:	      The backing file's Index Node(?) - It is ignored here. * @file:	      The backing file * * Return: 0 if successful. If it is anything else, go have a coffee and * try again. */static int ashmem_release(struct inode *ignored, struct file *file){	struct ashmem_area *asma = file->private_data;	struct ashmem_range *range, *next; 	mutex_lock(&ashmem_mutex);	list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned)		range_del(range);	mutex_unlock(&ashmem_mutex); 	if (asma->file)		fput(asma->file);	kmem_cache_free(ashmem_area_cachep, asma); 	return 0;} /** * ashmem_read() - Reads a set of bytes from an Ashmem-enabled file * @file:	   The associated backing file. * @buf:	   The buffer of data being written to * @len:	   The number of bytes being read * @pos:	   The position of the first byte to read. * * Return: 0 if successful, or another return code if not. */static ssize_t ashmem_read(struct file *file, char __user *buf,			   size_t len, loff_t *pos){	struct ashmem_area *asma = file->private_data;	int ret = 0; 	mutex_lock(&ashmem_mutex); 	/* If size is not set, or set to 0, always return EOF. */	if (asma->size == 0)		goto out; 	if (!asma->file) {		ret = -EBADF;		goto out;	} 	ret = asma->file->f_op->read(asma->file, buf, len, pos);	if (ret < 0)		goto out; 	/** Update backing file pos, since f_ops->read() doesn't */	asma->file->f_pos = *pos; out:	mutex_unlock(&ashmem_mutex);	return ret;} static loff_t ashmem_llseek(struct file *file, loff_t offset, int origin){	struct ashmem_area *asma = file->private_data;	int ret; 	mutex_lock(&ashmem_mutex); 	if (asma->size == 0) {		ret = -EINVAL;		goto out;	} 	if (!asma->file) {		ret = -EBADF;		goto out;	} 	ret = asma->file->f_op->llseek(asma->file, offset, origin);	if (ret < 0)		goto out; 	/** Copy f_pos from backing file, since f_ops->llseek() sets it */	file->f_pos = asma->file->f_pos; out:	mutex_unlock(&ashmem_mutex);	return ret;} static inline vm_flags_t calc_vm_may_flags(unsigned long prot){	return _calc_vm_trans(prot, PROT_READ,  VM_MAYREAD) |	       _calc_vm_trans(prot, PROT_WRITE, VM_MAYWRITE) |	       _calc_vm_trans(prot, PROT_EXEC,  VM_MAYEXEC);} 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 protection bits must match our allowed protection mask */	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; 		if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')			name = asma->name; 		/* ... and allocate the backing shmem file */		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); 	/*	 * XXX - Reworked to use shmem_zero_setup() instead of	 * shmem_set_file while we're in staging. -jstultz	 */	if (vma->vm_flags & VM_SHARED) {		ret = shmem_zero_setup(vma);		if (ret) {			fput(asma->file);			goto out;		}	} 	if (vma->vm_file)		fput(vma->vm_file);	vma->vm_file = asma->file; out:	mutex_unlock(&ashmem_mutex);	return ret;} /* * ashmem_shrink - our cache shrinker, called from mm/vmscan.c :: shrink_slab * * 'nr_to_scan' is the number of objects to scan for freeing. * * 'gfp_mask' is the mask of the allocation that got us into this mess. * * Return value is the number of objects freed or -1 if we cannot * proceed without risk of deadlock (due to gfp_mask). * * We approximate LRU via least-recently-unpinned, jettisoning unpinned partial * chunks of ashmem regions LRU-wise one-at-a-time until we hit 'nr_to_scan' * pages freed. */static unsigned longashmem_shrink_scan(struct shrinker *shrink, struct shrink_control *sc){	struct ashmem_range *range, *next;	unsigned long freed = 0; 	/* We might recurse into filesystem code, so bail out if necessary */	if (!(sc->gfp_mask & __GFP_FS))		return SHRINK_STOP; 	mutex_lock(&ashmem_mutex);	list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) {		loff_t start = range->pgstart * PAGE_SIZE;		loff_t end = (range->pgend + 1) * PAGE_SIZE; 		do_fallocate(range->asma->file,				FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,				start, end - start);		range->purged = ASHMEM_WAS_PURGED;		lru_del(range); 		freed += range_size(range);		if (--sc->nr_to_scan <= 0)			break;	}	mutex_unlock(&ashmem_mutex);	return freed;} static unsigned longashmem_shrink_count(struct shrinker *shrink, struct shrink_control *sc){	/*	 * note that lru_count is count of pages on the lru, not a count of	 * objects on the list. This means the scan function needs to return the	 * number of pages freed, not the number of objects scanned.	 */	return lru_count;} static struct shrinker ashmem_shrinker = {	.count_objects = ashmem_shrink_count,	.scan_objects = ashmem_shrink_scan,	/*	 * XXX (dchinner): I wish people would comment on why they need on	 * significant changes to the default value here	 */	.seeks = DEFAULT_SEEKS * 4,}; 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;} static int set_name(struct ashmem_area *asma, void __user *name){

Lines 1–500 of 879.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t