cregit-Linux how code gets into the kernel

Release 4.13 mm/shmem.c

Directory: mm
/*
 * Resizable virtual memory filesystem for Linux.
 *
 * Copyright (C) 2000 Linus Torvalds.
 *               2000 Transmeta Corp.
 *               2000-2001 Christoph Rohland
 *               2000-2001 SAP AG
 *               2002 Red Hat Inc.
 * Copyright (C) 2002-2011 Hugh Dickins.
 * Copyright (C) 2011 Google Inc.
 * Copyright (C) 2002-2005 VERITAS Software Corporation.
 * Copyright (C) 2004 Andi Kleen, SuSE Labs
 *
 * Extended attribute support for tmpfs:
 * Copyright (c) 2004, Luke Kenneth Casson Leighton <lkcl@lkcl.net>
 * Copyright (c) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com>
 *
 * tiny-shmem:
 * Copyright (c) 2004, 2008 Matt Mackall <mpm@selenic.com>
 *
 * This file is released under the GPL.
 */

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/vfs.h>
#include <linux/mount.h>
#include <linux/ramfs.h>
#include <linux/pagemap.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/sched/signal.h>
#include <linux/export.h>
#include <linux/swap.h>
#include <linux/uio.h>
#include <linux/khugepaged.h>

#include <asm/tlbflush.h> /* for arch/microblaze update_mmu_cache() */


static struct vfsmount *shm_mnt;

#ifdef CONFIG_SHMEM
/*
 * This virtual memory filesystem is heavily based on the ramfs. It
 * extends ramfs by the ability to use swap and honor resource limits
 * which makes it a completely usable filesystem.
 */

#include <linux/xattr.h>
#include <linux/exportfs.h>
#include <linux/posix_acl.h>
#include <linux/posix_acl_xattr.h>
#include <linux/mman.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/backing-dev.h>
#include <linux/shmem_fs.h>
#include <linux/writeback.h>
#include <linux/blkdev.h>
#include <linux/pagevec.h>
#include <linux/percpu_counter.h>
#include <linux/falloc.h>
#include <linux/splice.h>
#include <linux/security.h>
#include <linux/swapops.h>
#include <linux/mempolicy.h>
#include <linux/namei.h>
#include <linux/ctype.h>
#include <linux/migrate.h>
#include <linux/highmem.h>
#include <linux/seq_file.h>
#include <linux/magic.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <uapi/linux/memfd.h>
#include <linux/userfaultfd_k.h>
#include <linux/rmap.h>
#include <linux/uuid.h>

#include <linux/uaccess.h>
#include <asm/pgtable.h>

#include "internal.h"


#define BLOCKS_PER_PAGE  (PAGE_SIZE/512)

#define VM_ACCT(size)    (PAGE_ALIGN(size) >> PAGE_SHIFT)

/* Pretend that each entry is of this size in directory's i_size */

#define BOGO_DIRENT_SIZE 20

/* Symlink up to this size is kmalloc'ed instead of using a swappable page */

#define SHORT_SYMLINK_LEN 128

/*
 * shmem_fallocate communicates with shmem_fault or shmem_writepage via
 * inode->i_private (with i_mutex making sure that it has only one user at
 * a time): we would prefer not to enlarge the shmem inode just for that.
 */

struct shmem_falloc {
	
wait_queue_head_t *waitq; /* faults into hole wait for punch to end */
	
pgoff_t start;		/* start of range currently being fallocated */
	
pgoff_t next;		/* the next page offset to be fallocated */
	
pgoff_t nr_falloced;	/* how many new pages have been fallocated */
	
pgoff_t nr_unswapped;	/* how often writepage refused to swap out */
};

#ifdef CONFIG_TMPFS

static unsigned long shmem_default_max_blocks(void) { return totalram_pages / 2; }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton14100.00%1100.00%
Total14100.00%1100.00%


static unsigned long shmem_default_max_inodes(void) { return min(totalram_pages - totalhigh_pages, totalram_pages / 2); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton21100.00%1100.00%
Total21100.00%1100.00%

#endif static bool shmem_should_replace_page(struct page *page, gfp_t gfp); static int shmem_replace_page(struct page **pagep, gfp_t gfp, struct shmem_inode_info *info, pgoff_t index); static int shmem_getpage_gfp(struct inode *inode, pgoff_t index, struct page **pagep, enum sgp_type sgp, gfp_t gfp, struct vm_area_struct *vma, struct vm_fault *vmf, int *fault_type);
int shmem_getpage(struct inode *inode, pgoff_t index, struct page **pagep, enum sgp_type sgp) { return shmem_getpage_gfp(inode, index, pagep, sgp, mapping_gfp_mask(inode->i_mapping), NULL, NULL, NULL); }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins4389.58%133.33%
Andres Lagar-Cavilla36.25%133.33%
Mike Rapoport24.17%133.33%
Total48100.00%3100.00%


static inline struct shmem_sb_info *SHMEM_SB(struct super_block *sb) { return sb->s_fs_info; }

Contributors

PersonTokensPropCommitsCommitProp
Brian Gerst1578.95%266.67%
Linus Torvalds421.05%133.33%
Total19100.00%3100.00%

/* * shmem_file_setup pre-accounts the whole fixed size of a VM object, * for shared memory and for shared anonymous (/dev/zero) mappings * (unless MAP_NORESERVE and sysctl_overcommit_memory <= 1), * consistent with the pre-accounting of private mappings ... */
static inline int shmem_acct_size(unsigned long flags, loff_t size) { return (flags & VM_NORESERVE) ? 0 : security_vm_enough_memory_mm(current->mm, VM_ACCT(size)); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton2777.14%133.33%
Al Viro514.29%133.33%
Hugh Dickins38.57%133.33%
Total35100.00%3100.00%


static inline void shmem_unacct_size(unsigned long flags, loff_t size) { if (!(flags & VM_NORESERVE)) vm_unacct_memory(VM_ACCT(size)); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton2787.10%150.00%
Hugh Dickins412.90%150.00%
Total31100.00%2100.00%


static inline int shmem_reacct_size(unsigned long flags, loff_t oldsize, loff_t newsize) { if (!(flags & VM_NORESERVE)) { if (VM_ACCT(newsize) > VM_ACCT(oldsize)) return security_vm_enough_memory_mm(current->mm, VM_ACCT(newsize) - VM_ACCT(oldsize)); else if (VM_ACCT(newsize) < VM_ACCT(oldsize)) vm_unacct_memory(VM_ACCT(oldsize) - VM_ACCT(newsize)); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Konstantin Khlebnikov87100.00%1100.00%
Total87100.00%1100.00%

/* * ... whereas tmpfs objects are accounted incrementally as * pages are allocated, in order to allow large sparse files. * shmem_getpage reports shmem_acct_block failure as -ENOSPC not -ENOMEM, * so that a failure on a sparse tmpfs mapping will give SIGBUS not OOM. */
static inline int shmem_acct_block(unsigned long flags, long pages) { if (!(flags & VM_NORESERVE)) return 0; return security_vm_enough_memory_mm(current->mm, pages * VM_ACCT(PAGE_SIZE)); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton2151.22%120.00%
Kirill A. Shutemov1434.15%240.00%
Al Viro512.20%120.00%
Hugh Dickins12.44%120.00%
Total41100.00%5100.00%


static inline void shmem_unacct_blocks(unsigned long flags, long pages) { if (flags & VM_NORESERVE) vm_unacct_memory(pages * VM_ACCT(PAGE_SIZE)); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton2893.33%133.33%
Hugh Dickins13.33%133.33%
Kirill A. Shutemov13.33%133.33%
Total30100.00%3100.00%

static const struct super_operations shmem_ops; static const struct address_space_operations shmem_aops; static const struct file_operations shmem_file_operations; static const struct inode_operations shmem_inode_operations; static const struct inode_operations shmem_dir_inode_operations; static const struct inode_operations shmem_special_inode_operations; static const struct vm_operations_struct shmem_vm_ops; static struct file_system_type shmem_fs_type;
bool vma_is_shmem(struct vm_area_struct *vma) { return vma->vm_ops == &shmem_vm_ops; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport18100.00%1100.00%
Total18100.00%1100.00%

static LIST_HEAD(shmem_swaplist); static DEFINE_MUTEX(shmem_swaplist_mutex);
static int shmem_reserve_inode(struct super_block *sb) { struct shmem_sb_info *sbinfo = SHMEM_SB(sb); if (sbinfo->max_inodes) { spin_lock(&sbinfo->stat_lock); if (!sbinfo->free_inodes) { spin_unlock(&sbinfo->stat_lock); return -ENOSPC; } sbinfo->free_inodes--; spin_unlock(&sbinfo->stat_lock); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Pavel Emelyanov74100.00%1100.00%
Total74100.00%1100.00%


static void shmem_free_inode(struct super_block *sb) { struct shmem_sb_info *sbinfo = SHMEM_SB(sb); if (sbinfo->max_inodes) { spin_lock(&sbinfo->stat_lock); sbinfo->free_inodes++; spin_unlock(&sbinfo->stat_lock); } }

Contributors

PersonTokensPropCommitsCommitProp
Pavel Emelyanov50100.00%1100.00%
Total50100.00%1100.00%

/** * shmem_recalc_inode - recalculate the block usage of an inode * @inode: inode to recalc * * We have to calculate the free blocks since the mm can drop * undirtied hole pages behind our back. * * But normally info->alloced == inode->i_mapping->nrpages + info->swapped * So mm freed is info->alloced - (inode->i_mapping->nrpages + info->swapped) * * It has to be called with the spinlock held. */
static void shmem_recalc_inode(struct inode *inode) { struct shmem_inode_info *info = SHMEM_I(inode); long freed; freed = info->alloced - info->swapped - inode->i_mapping->nrpages; if (freed > 0) { struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); if (sbinfo->max_blocks) percpu_counter_add(&sbinfo->used_blocks, -freed); info->alloced -= freed; inode->i_blocks -= freed * BLOCKS_PER_PAGE; shmem_unacct_blocks(info->flags, freed); } }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins6767.00%375.00%
Linus Torvalds3333.00%125.00%
Total100100.00%4100.00%


bool shmem_charge(struct inode *inode, long pages) { struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); unsigned long flags; if (shmem_acct_block(info->flags, pages)) return false; spin_lock_irqsave(&info->lock, flags); info->alloced += pages; inode->i_blocks += pages * BLOCKS_PER_PAGE; shmem_recalc_inode(inode); spin_unlock_irqrestore(&info->lock, flags); inode->i_mapping->nrpages += pages; if (!sbinfo->max_blocks) return true; if (percpu_counter_compare(&sbinfo->used_blocks, sbinfo->max_blocks - pages) > 0) { inode->i_mapping->nrpages -= pages; spin_lock_irqsave(&info->lock, flags); info->alloced -= pages; shmem_recalc_inode(inode); spin_unlock_irqrestore(&info->lock, flags); shmem_unacct_blocks(info->flags, pages); return false; } percpu_counter_add(&sbinfo->used_blocks, pages); return true; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov14373.71%240.00%
Hugh Dickins4523.20%240.00%
Johannes Weiner63.09%120.00%
Total194100.00%5100.00%


void shmem_uncharge(struct inode *inode, long pages) { struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); unsigned long flags; spin_lock_irqsave(&info->lock, flags); info->alloced -= pages; inode->i_blocks -= pages * BLOCKS_PER_PAGE; shmem_recalc_inode(inode); spin_unlock_irqrestore(&info->lock, flags); if (sbinfo->max_blocks) percpu_counter_sub(&sbinfo->used_blocks, pages); shmem_unacct_blocks(info->flags, pages); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov8380.58%250.00%
Hugh Dickins2019.42%250.00%
Total103100.00%4100.00%

/* * Replace item expected in radix tree by a new item, while holding tree lock. */
static int shmem_radix_tree_replace(struct address_space *mapping, pgoff_t index, void *expected, void *replacement) { struct radix_tree_node *node; void **pslot; void *item; VM_BUG_ON(!expected); VM_BUG_ON(!replacement); item = __radix_tree_lookup(&mapping->page_tree, index, &node, &pslot); if (!item) return -ENOENT; if (item != expected) return -ENOENT; __radix_tree_replace(&mapping->page_tree, node, pslot, replacement, NULL, NULL); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov8075.47%133.33%
Johannes Weiner2624.53%266.67%
Total106100.00%3100.00%

/* * Sometimes, before we decide whether to proceed or to fail, we must check * that an entry was not already brought back from swap by a racing thread. * * Checking page is not enough: by the time a SwapCache page is locked, it * might be reused, and again be SwapCache, using the same swap as before. */
static bool shmem_confirm_swap(struct address_space *mapping, pgoff_t index, swp_entry_t swap) { void *item; rcu_read_lock(); item = radix_tree_lookup(&mapping->page_tree, index); rcu_read_unlock(); return item == swp_to_radix_entry(swap); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov3574.47%150.00%
Hugh Dickins1225.53%150.00%
Total47100.00%2100.00%

/* * Definitions for "huge tmpfs": tmpfs mounted with the huge= option * * SHMEM_HUGE_NEVER: * disables huge pages for the mount; * SHMEM_HUGE_ALWAYS: * enables huge pages for the mount; * SHMEM_HUGE_WITHIN_SIZE: * only allocate huge pages if the page will be fully within i_size, * also respect fadvise()/madvise() hints; * SHMEM_HUGE_ADVISE: * only allocate huge pages if requested with fadvise()/madvise(); */ #define SHMEM_HUGE_NEVER 0 #define SHMEM_HUGE_ALWAYS 1 #define SHMEM_HUGE_WITHIN_SIZE 2 #define SHMEM_HUGE_ADVISE 3 /* * Special values. * Only can be set via /sys/kernel/mm/transparent_hugepage/shmem_enabled: * * SHMEM_HUGE_DENY: * disables huge on shm_mnt and all mounts, for emergency use; * SHMEM_HUGE_FORCE: * enables huge on shm_mnt and all mounts, w/o needing option, for testing; * */ #define SHMEM_HUGE_DENY (-1) #define SHMEM_HUGE_FORCE (-2) #ifdef CONFIG_TRANSPARENT_HUGE_PAGECACHE /* ifdef here to avoid bloating shmem.o when not necessary */ int shmem_huge __read_mostly; #if defined(CONFIG_SYSFS) || defined(CONFIG_TMPFS)
static int shmem_parse_huge(const char *str) { if (!strcmp(str, "never")) return SHMEM_HUGE_NEVER; if (!strcmp(str, "always")) return SHMEM_HUGE_ALWAYS; if (!strcmp(str, "within_size")) return SHMEM_HUGE_WITHIN_SIZE; if (!strcmp(str, "advise")) return SHMEM_HUGE_ADVISE; if (!strcmp(str, "deny")) return SHMEM_HUGE_DENY; if (!strcmp(str, "force")) return SHMEM_HUGE_FORCE; return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov93100.00%1100.00%
Total93100.00%1100.00%


static const char *shmem_format_huge(int huge) { switch (huge) { case SHMEM_HUGE_NEVER: return "never"; case SHMEM_HUGE_ALWAYS: return "always"; case SHMEM_HUGE_WITHIN_SIZE: return "within_size"; case SHMEM_HUGE_ADVISE: return "advise"; case SHMEM_HUGE_DENY: return "deny"; case SHMEM_HUGE_FORCE: return "force"; default: VM_BUG_ON(1); return "bad_val"; } }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov62100.00%1100.00%
Total62100.00%1100.00%

#endif
static unsigned long shmem_unused_huge_shrink(struct shmem_sb_info *sbinfo, struct shrink_control *sc, unsigned long nr_to_split) { LIST_HEAD(list), *pos, *next; LIST_HEAD(to_remove); struct inode *inode; struct shmem_inode_info *info; struct page *page; unsigned long batch = sc ? sc->nr_to_scan : 128; int removed = 0, split = 0; if (list_empty(&sbinfo->shrinklist)) return SHRINK_STOP; spin_lock(&sbinfo->shrinklist_lock); list_for_each_safe(pos, next, &sbinfo->shrinklist) { info = list_entry(pos, struct shmem_inode_info, shrinklist); /* pin the inode */ inode = igrab(&info->vfs_inode); /* inode is about to be evicted */ if (!inode) { list_del_init(&info->shrinklist); removed++; goto next; } /* Check if there's anything to gain */ if (round_up(inode->i_size, PAGE_SIZE) == round_up(inode->i_size, HPAGE_PMD_SIZE)) { list_move(&info->shrinklist, &to_remove); removed++; goto next; } list_move(&info->shrinklist, &list); next: if (!--batch) break; } spin_unlock(&sbinfo->shrinklist_lock); list_for_each_safe(pos, next, &to_remove) { info = list_entry(pos, struct shmem_inode_info, shrinklist); inode = &info->vfs_inode; list_del_init(&info->shrinklist); iput(inode); } list_for_each_safe(pos, next, &list) { int ret; info = list_entry(pos, struct shmem_inode_info, shrinklist); inode = &info->vfs_inode; if (nr_to_split && split >= nr_to_split) { iput(inode); continue; } page = find_lock_page(inode->i_mapping, (inode->i_size & HPAGE_PMD_MASK) >> PAGE_SHIFT); if (!page) goto drop; if (!PageTransHuge(page)) { unlock_page(page); put_page(page); goto drop; } ret = split_huge_page(page); unlock_page(page); put_page(page); if (ret) { /* split failed: leave it on the list */ iput(inode); continue; } split++; drop: list_del_init(&info->shrinklist); removed++; iput(inode); } spin_lock(&sbinfo->shrinklist_lock); list_splice_tail(&list, &sbinfo->shrinklist); sbinfo->shrinklist_len -= removed; spin_unlock(&sbinfo->shrinklist_lock); return split; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov444100.00%2100.00%
Total444100.00%2100.00%


static long shmem_unused_huge_scan(struct super_block *sb, struct shrink_control *sc) { struct shmem_sb_info *sbinfo = SHMEM_SB(sb); if (!READ_ONCE(sbinfo->shrinklist_len)) return SHRINK_STOP; return shmem_unused_huge_shrink(sbinfo, sc, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov49100.00%1100.00%
Total49100.00%1100.00%


static long shmem_unused_huge_count(struct super_block *sb, struct shrink_control *sc) { struct shmem_sb_info *sbinfo = SHMEM_SB(sb); return READ_ONCE(sbinfo->shrinklist_len); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov34100.00%1100.00%
Total34100.00%1100.00%

#else /* !CONFIG_TRANSPARENT_HUGE_PAGECACHE */ #define shmem_huge SHMEM_HUGE_DENY
static unsigned long shmem_unused_huge_shrink(struct shmem_sb_info *sbinfo, struct shrink_control *sc, unsigned long nr_to_split) { return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov24100.00%1100.00%
Total24100.00%1100.00%

#endif /* CONFIG_TRANSPARENT_HUGE_PAGECACHE */ /* * Like add_to_page_cache_locked, but error if expected item has gone. */
static int shmem_add_to_page_cache(struct page *page, struct address_space *mapping, pgoff_t index, void *expected) { int error, nr = hpage_nr_pages(page); VM_BUG_ON_PAGE(PageTail(page), page); VM_BUG_ON_PAGE(index != round_down(index, nr), page); VM_BUG_ON_PAGE(!PageLocked(page), page); VM_BUG_ON_PAGE(!PageSwapBacked(page), page); VM_BUG_ON(expected && PageTransHuge(page)); page_ref_add(page, nr); page->mapping = mapping; page->index = index; spin_lock_irq(&mapping->tree_lock); if (PageTransHuge(page)) { void __rcu **results; pgoff_t idx; int i; error = 0; if (radix_tree_gang_lookup_slot(&mapping->page_tree, &results, &idx, index, 1) && idx < index + HPAGE_PMD_NR) { error = -EEXIST; } if (!error) { for (i = 0; i < HPAGE_PMD_NR; i++) { error = radix_tree_insert(&mapping->page_tree, index + i, page + i); VM_BUG_ON(error); } count_vm_event(THP_FILE_ALLOC); } } else if (!expected) { error = radix_tree_insert(&mapping->page_tree, index, page); } else { error = shmem_radix_tree_replace(mapping, index, expected, page); } if (!error) { mapping->nrpages += nr; if (PageTransHuge(page)) __inc_node_page_state(page, NR_SHMEM_THPS); __mod_node_page_state(page_pgdat(page), NR_FILE_PAGES, nr); __mod_node_page_state(page_pgdat(page), NR_SHMEM, nr); spin_unlock_irq(&mapping->tree_lock); } else { page->mapping = NULL; spin_unlock_irq(&mapping->tree_lock); page_ref_sub(page, nr); } return error; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov20057.47%125.00%
Hugh Dickins13739.37%125.00%
Sasha Levin61.72%125.00%
Mel Gorman51.44%125.00%
Total348100.00%4100.00%

/* * Like delete_from_page_cache, but substitutes swap for page. */
static void shmem_delete_from_page_cache(struct page *page, void *radswap) { struct address_space *mapping = page->mapping; int error; VM_BUG_ON_PAGE(PageCompound(page), page); spin_lock_irq(&mapping->tree_lock); error = shmem_radix_tree_replace(mapping, page->index, page, radswap); page->mapping = NULL; mapping->nrpages--; __dec_node_page_state(page, NR_FILE_PAGES); __dec_node_page_state(page, NR_SHMEM); spin_unlock_irq(&mapping->tree_lock); put_page(page); BUG_ON(error); }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins9087.38%125.00%
Kirill A. Shutemov1110.68%250.00%
Mel Gorman21.94%125.00%
Total103100.00%4100.00%

/* * Remove swap entry from radix tree, free the swap and its page cache. */
static int shmem_free_swap(struct address_space *mapping, pgoff_t index, void *radswap) { void *old; spin_lock_irq(&mapping->tree_lock); old = radix_tree_delete_item(&mapping->page_tree, index, radswap); spin_unlock_irq(&mapping->tree_lock); if (old != radswap) return -ENOENT; free_swap_and_cache(radix_to_swp_entry(radswap)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins5778.08%150.00%
Johannes Weiner1621.92%150.00%
Total73100.00%2100.00%

/* * Determine (in bytes) how many of the shmem object's pages mapped by the * given offsets are swapped out. * * This is safe to call without i_mutex or mapping->tree_lock thanks to RCU, * as long as the inode doesn't go away and racy results are not a problem. */
unsigned long shmem_partial_swap_usage(struct address_space *mapping, pgoff_t start, pgoff_t end) { struct radix_tree_iter iter; void **slot; struct page *page; unsigned long swapped = 0; rcu_read_lock(); radix_tree_for_each_slot(slot, &mapping->page_tree, &iter, start) { if (iter.index >= end) break; page = radix_tree_deref_slot(slot); if (radix_tree_deref_retry(page)) { slot = radix_tree_iter_retry(&iter); continue; } if (radix_tree_exceptional_entry(page)) swapped++; if (need_resched()) { slot = radix_tree_iter_resume(slot, &iter); cond_resched_rcu(); } } rcu_read_unlock(); return swapped << PAGE_SHIFT; }

Contributors

PersonTokensPropCommitsCommitProp
Vlastimil Babka10383.06%240.00%
Matthew Wilcox2116.94%360.00%
Total124100.00%5100.00%

/* * Determine (in bytes) how many of the shmem object's pages mapped by the * given vma is swapped out. * * This is safe to call without i_mutex or mapping->tree_lock thanks to RCU, * as long as the inode doesn't go away and racy results are not a problem. */
unsigned long shmem_swap_usage(struct vm_area_struct *vma) { struct inode *inode = file_inode(vma->vm_file); struct shmem_inode_info *info = SHMEM_I(inode); struct address_space *mapping = inode->i_mapping; unsigned long swapped; /* Be careful as we don't hold info->lock */ swapped = READ_ONCE(info->swapped); /* * The easier cases are when the shmem object has nothing in swap, or * the vma maps it whole. Then we can simply use the stats that we * already track. */ if (!swapped) return 0; if (!vma->vm_pgoff && vma->vm_end - vma->vm_start >= inode->i_size) return swapped << PAGE_SHIFT; /* Here comes the more involved part */ return shmem_partial_swap_usage(mapping, linear_page_index(vma, vma->vm_start), linear_page_index(vma, vma->vm_end)); }

Contributors

PersonTokensPropCommitsCommitProp
Vlastimil Babka114100.00%1100.00%
Total114100.00%1100.00%

/* * SysV IPC SHM_UNLOCK restore Unevictable pages to their evictable lists. */
void shmem_unlock_mapping(struct address_space *mapping) { struct pagevec pvec; pgoff_t indices[PAGEVEC_SIZE]; pgoff_t index = 0; pagevec_init(&pvec, 0); /* * Minor point, but we might as well stop if someone else SHM_LOCKs it. */ while (!mapping_unevictable(mapping)) { /* * Avoid pagevec_lookup(): find_get_pages() returns 0 as if it * has finished, if it hits a row of PAGEVEC_SIZE swap entries. */ pvec.nr = find_get_entries(mapping, index, PAGEVEC_SIZE, pvec.pages, indices); if (!pvec.nr) break; index = indices[pvec.nr - 1] + 1; pagevec_remove_exceptionals(&pvec); check_move_unevictable_pages(pvec.pages, pvec.nr); pagevec_release(&pvec); cond_resched(); } }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins10998.20%266.67%
Johannes Weiner21.80%133.33%
Total111100.00%3100.00%

/* * Remove range of pages and swap entries from radix tree, and free them. * If !unfalloc, truncate or punch hole; if unfalloc, undo failed fallocate. */
static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend, bool unfalloc) { struct address_space *mapping = inode->i_mapping; struct shmem_inode_info *info = SHMEM_I(inode); pgoff_t start = (lstart + PAGE_SIZE - 1) >> PAGE_SHIFT; pgoff_t end = (lend + 1) >> PAGE_SHIFT; unsigned int partial_start = lstart & (PAGE_SIZE - 1); unsigned int partial_end = (lend + 1) & (PAGE_SIZE - 1); struct pagevec pvec; pgoff_t indices[PAGEVEC_SIZE]; long nr_swaps_freed = 0; pgoff_t index; int i; if (lend == -1) end = -1; /* unsigned, so actually very big */ pagevec_init(&pvec, 0); index = start; while (index < end) { pvec.nr = find_get_entries(mapping, index, min(end - index, (pgoff_t)PAGEVEC_SIZE), pvec.pages, indices); if (!pvec.nr) break; for (i = 0; i < pagevec_count(&pvec); i++) { struct page *page = pvec.pages[i]; index = indices[i]; if (index >= end) break; if (radix_tree_exceptional_entry(page)) { if (unfalloc) continue; nr_swaps_freed += !shmem_free_swap(mapping, index, page); continue; } VM_BUG_ON_PAGE(page_to_pgoff(page) != index, page); if (!trylock_page(page)) continue; if (PageTransTail(page)) { /* Middle of THP: zero out the page */ clear_highpage(page); unlock_page(page); continue; } else if (PageTransHuge(page)) { if (index == round_down(end, HPAGE_PMD_NR)) { /* * Range ends in the middle of THP: * zero out the page */ clear_highpage(page); unlock_page(page); continue; } index += HPAGE_PMD_NR - 1; i += HPAGE_PMD_NR - 1; } if (!unfalloc || !PageUptodate(page)) { VM_BUG_ON_PAGE(PageTail(page), page); if (page_mapping(page) == mapping) { VM_BUG_ON_PAGE(PageWriteback(page), page); truncate_inode_page(mapping, page); } } unlock_page(page); } pagevec_remove_exceptionals(&pvec); pagevec_release(&pvec); cond_resched(); index++; } if (partial_start) { struct page *page = NULL; shmem_getpage(inode, start - 1, &page, SGP_READ); if (page) { unsigned int top = PAGE_SIZE; if (start > end) { top = partial_end; partial_end = 0; } zero_user_segment(page, partial_start, top); set_page_dirty(page); unlock_page(page); put_page(page); } } if (partial_end) { struct page *page = NULL; shmem_getpage(inode, end, &page, SGP_READ); if (page) { zero_user_segment(page, 0, partial_end); set_page_dirty(page); unlock_page(page); put_page(page); } } if (start >= end) return; index = start; while (index < end) { cond_resched(); pvec.nr = find_get_entries(mapping, index, min(end - index, (pgoff_t)PAGEVEC_SIZE), pvec.pages, indices); if (!pvec.nr) { /* If all gone or hole-punch or unfalloc, we're done */ if (index == start || end != -1) break; /* But if truncating, restart to make sure all gone */ index = start; continue; } for (i = 0; i < pagevec_count(&pvec); i++) { struct page *page = pvec.pages[i]; index = indices[i]; if (index >= end) break; if (radix_tree_exceptional_entry(page)) { if (unfalloc) continue; if (shmem_free_swap(mapping, index, page)) { /* Swap was replaced by page: retry */ index--; break; } nr_swaps_freed++; continue; } lock_page(page); if (PageTransTail(page)) { /* Middle of THP: zero out the page */ clear_highpage(page); unlock_page(page); /* * Partial thp truncate due 'start' in middle * of THP: don't need to look on these pages * again on !pvec.nr restart. */ if (index != round_down(end, HPAGE_PMD_NR)) start++; continue; } else if (PageTransHuge(page)) { if (index == round_down(end, HPAGE_PMD_NR)) { /* * Range ends in the middle of THP: * zero out the page */ clear_highpage(page); unlock_page(page); continue; } index += HPAGE_PMD_NR - 1; i += HPAGE_PMD_NR - 1; } if (!unfalloc || !PageUptodate(page)) { VM_BUG_ON_PAGE(PageTail(page), page); if (page_mapping(page) == mapping) { VM_BUG_ON_PAGE(PageWriteback(page), page); truncate_inode_page(mapping, page); } else { /* Page was replaced by swap: retry */ unlock_page(page); index--; break; } } unlock_page(page); } pagevec_remove_exceptionals(&pvec); pagevec_release(&pvec); index++; } spin_lock_irq(&info->lock); info->swapped -= nr_swaps_freed; shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins68174.84%1260.00%
Kirill A. Shutemov20522.53%315.00%
Linus Torvalds (pre-git)101.10%15.00%
Sasha Levin60.66%15.00%
Johannes Weiner40.44%15.00%
Linus Torvalds30.33%15.00%
Andrew Morton10.11%15.00%
Total910100.00%20100.00%


void shmem_truncate_range(struct inode *inode, loff_t lstart, loff_t lend) { shmem_undo_range(inode, lstart, lend, false); inode->i_ctime = inode->i_mtime = current_time(inode); }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins3690.00%375.00%
Deepa Dinamani410.00%125.00%
Total40100.00%4100.00%

EXPORT_SYMBOL_GPL(shmem_truncate_range);
static int shmem_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = path->dentry->d_inode; struct shmem_inode_info *info = SHMEM_I(inode); if (info->alloced - info->swapped != inode->i_mapping->nrpages) { spin_lock_irq(&info->lock); shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); } generic_fillattr(inode, stat); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Yu Zhao6265.96%125.00%
Hugh Dickins1819.15%125.00%
David Howells1212.77%125.00%
Kirill A. Shutemov22.13%125.00%
Total94100.00%4100.00%


static int shmem_setattr(struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); int error; error = setattr_prepare(dentry, attr); if (error) return error; if (S_ISREG(inode->i_mode) && (attr->ia_valid & ATTR_SIZE)) { loff_t oldsize = inode->i_size; loff_t newsize = attr->ia_size; /* protected by i_mutex */ if ((newsize < oldsize && (info->seals & F_SEAL_SHRINK)) || (newsize > oldsize && (info->seals & F_SEAL_GROW))) return -EPERM; if (newsize != oldsize) { error = shmem_reacct_size(SHMEM_I(inode)->flags, oldsize, newsize); if (error) return error; i_size_write(inode, newsize); inode->i_ctime = inode->i_mtime = current_time(inode); } if (newsize <= oldsize) { loff_t holebegin = round_up(newsize, PAGE_SIZE); if (oldsize > holebegin) unmap_mapping_range(inode->i_mapping, holebegin, 0, 1); if (info->alloced) shmem_truncate_range(inode, newsize, (loff_t)-1); /* unmap again to remove racily COWed private pages */ if (oldsize > holebegin) unmap_mapping_range(inode->i_mapping, holebegin, 0, 1); /* * Part of the huge page can be beyond i_size: subject * to shrink under memory pressure. */ if (IS_ENABLED(CONFIG_TRANSPARENT_HUGE_PAGECACHE)) { spin_lock(&sbinfo->shrinklist_lock); /* * _careful to defend against unlocked access to * ->shrink_list in shmem_unused_huge_shrink() */ if (list_empty_careful(&info->shrinklist)) { list_add_tail(&info->shrinklist, &sbinfo->shrinklist); sbinfo->shrinklist_len++; } spin_unlock(&sbinfo->shrinklist_lock); } } } setattr_copy(inode, attr); if (attr->ia_valid & ATTR_MODE) error = posix_acl_chmod(inode, inode->i_mode); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins8925.57%316.67%
Kirill A. Shutemov6719.25%15.56%
David Herrmann4512.93%15.56%
Andrew Morton4212.07%15.56%
Nicholas Piggin277.76%211.11%
Andreas Gruenbacher236.61%15.56%
Konstantin Khlebnikov236.61%15.56%
Christoph Hellwig205.75%316.67%
Deepa Dinamani41.15%15.56%
David Howells30.86%15.56%
Jan Kara20.57%15.56%
Américo Wang20.57%15.56%
Josef Bacik10.29%15.56%
Total348100.00%18100.00%


static void shmem_evict_inode(struct inode *inode) { struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); if (inode->i_mapping->a_ops == &shmem_aops) { shmem_unacct_size(info->flags, inode->i_size); inode->i_size = 0; shmem_truncate_range(inode, 0, (loff_t)-1); if (!list_empty(&info->shrinklist)) { spin_lock(&sbinfo->shrinklist_lock); if (!list_empty(&info->shrinklist)) { list_del_init(&info->shrinklist); sbinfo->shrinklist_len--; } spin_unlock(&sbinfo->shrinklist_lock); } if (!list_empty(&info->swaplist)) { mutex_lock(&shmem_swaplist_mutex); list_del_init(&info->swaplist); mutex_unlock(&shmem_swaplist_mutex); } } simple_xattrs_free(&info->xattrs); WARN_ON(inode->i_blocks); shmem_free_inode(inode->i_sb); clear_inode(inode); }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins9851.31%753.85%
Kirill A. Shutemov6735.08%17.69%
Linus Torvalds (pre-git)136.81%17.69%
Linus Torvalds73.66%17.69%
Aristeu Sergio Rozanski Filho42.09%17.69%
Jan Kara10.52%17.69%
Al Viro10.52%17.69%
Total191100.00%13100.00%


static unsigned long find_swap_entry(struct radix_tree_root *root, void *item) { struct radix_tree_iter iter; void **slot; unsigned long found = -1; unsigned int checked = 0; rcu_read_lock(); radix_tree_for_each_slot(slot, root, &iter, 0) { if (*slot == item) { found = iter.index; break; } checked++; if ((checked % 4096) != 0) continue; slot = radix_tree_iter_resume(slot, &iter); cond_resched_rcu(); } rcu_read_unlock(); return found; }

Contributors

PersonTokensPropCommitsCommitProp
Matthew Wilcox102100.00%1100.00%
Total102100.00%1100.00%

/* * If swap found in inode, free it and move page from swapcache to filecache. */
static int shmem_unuse_inode(struct shmem_inode_info *info, swp_entry_t swap, struct page **pagep) { struct address_space *mapping = info->vfs_inode.i_mapping; void *radswap; pgoff_t index; gfp_t gfp; int error = 0; radswap = swp_to_radix_entry(swap); index = find_swap_entry(&mapping->page_tree, radswap); if (index == -1) return -EAGAIN; /* tell shmem_unuse we found nothing */ /* * Move _head_ to start search for next from here. * But be careful: shmem_evict_inode checks list_empty without taking * mutex, and there's an instant in list_move_tail when info->swaplist * would appear empty, if it were the only one on shmem_swaplist. */ if (shmem_swaplist.next != &info->swaplist) list_move_tail(&shmem_swaplist, &info->swaplist); gfp = mapping_gfp_mask(mapping); if (shmem_should_replace_page(*pagep, gfp)) { mutex_unlock(&shmem_swaplist_mutex); error = shmem_replace_page(pagep, gfp, info, index); mutex_lock(&shmem_swaplist_mutex); /* * We needed to drop mutex to make that restrictive page * allocation, but the inode might have been freed while we * dropped it: although a racing shmem_evict_inode() cannot * complete without emptying the radix_tree, our page lock * on this swapcache page is not enough to prevent that - * free_swap_and_cache() of our swap entry will only * trylock_page(), removing swap from radix_tree whatever. * * We must not proceed to shmem_add_to_page_cache() if the * inode has been freed, but of course we cannot rely on * inode or mapping or info to check that. However, we can * safely check if our swap entry is still in use (and here * it can't have got reused for another page): if it's still * in use, then the inode cannot have been freed yet, and we * can safely proceed (if it's no longer in use, that tells * nothing about the inode, but we don't need to unuse swap). */ if (!page_swapcount(*pagep)) error = -ENOENT; } /* * We rely on shmem_swaplist_mutex, not only to protect the swaplist, * but also to hold up shmem_evict_inode(): so inode cannot be freed * beneath us (pagelock doesn't help until the page is in pagecache). */ if (!error) error = shmem_add_to_page_cache(*pagep, mapping, index, radswap); if (error != -ENOMEM) { /* * Truncation and eviction use free_swap_and_cache(), which * only does trylock page: if we raced, best clean up here. */ delete_from_swap_cache(*pagep); set_page_dirty(*pagep); if (!error) { spin_lock_irq(&info->lock); info->swapped--; spin_unlock_irq(&info->lock); swap_free(swap); } } return error; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins22293.67%1768.00%
Linus Torvalds52.11%28.00%
Andrew Morton31.27%28.00%
Johannes Weiner31.27%14.00%
Kirill A. Shutemov20.84%14.00%
Matthew Wilcox10.42%14.00%
Linus Torvalds (pre-git)10.42%14.00%
Total237100.00%25100.00%

/* * Search through swapped inodes to find and replace swap by page. */
int shmem_unuse(swp_entry_t swap, struct page *page) { struct list_head *this, *next; struct shmem_inode_info *info; struct mem_cgroup *memcg; int error = 0; /* * There's a faint possibility that swap page was replaced before * caller locked it: caller will come back later with the right page. */ if (unlikely(!PageSwapCache(page) || page_private(page) != swap.val)) goto out; /* * Charge page using GFP_KERNEL while we can wait, before taking * the shmem_swaplist_mutex which might hold up shmem_writepage(). * Charged back to the user (not to caller) when swap account is used. */ error = mem_cgroup_try_charge(page, current->mm, GFP_KERNEL, &memcg, false); if (error) goto out; /* No radix_tree_preload: swap entry keeps a place for page in tree */ error = -EAGAIN; mutex_lock(&shmem_swaplist_mutex); list_for_each_safe(this, next, &shmem_swaplist) { info = list_entry(this, struct shmem_inode_info, swaplist); if (info->swapped) error = shmem_unuse_inode(info, swap, &page); else list_del_init(&info->swaplist); cond_resched(); if (error != -EAGAIN) break; /* found nothing in this: move on to search the next */ } mutex_unlock(&shmem_swaplist_mutex); if (error) { if (error != -ENOMEM) error = 0; mem_cgroup_cancel_charge(page, memcg, false); } else mem_cgroup_commit_charge(page, memcg, true, false); out: unlock_page(page); put_page(page); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins12155.50%1365.00%
Johannes Weiner4721.56%15.00%
Linus Torvalds3616.51%315.00%
Kirill A. Shutemov73.21%210.00%
Linus Torvalds (pre-git)73.21%15.00%
Total218100.00%20100.00%

/* * Move the page from the page cache to the swap cache. */
static int shmem_writepage(struct page *page, struct writeback_control *wbc) { struct shmem_inode_info *info; struct address_space *mapping; struct inode *inode; swp_entry_t swap; pgoff_t index; VM_BUG_ON_PAGE(PageCompound(page), page); BUG_ON(!PageLocked(page)); mapping = page->mapping; index = page->index; inode = mapping->host; info = SHMEM_I(inode); if (info->flags & VM_LOCKED) goto redirty; if (!total_swap_pages) goto redirty; /* * Our capabilities prevent regular writeback or sync from ever calling * shmem_writepage; but a stacking filesystem might use ->writepage of * its underlying filesystem, in which case tmpfs should write out to * swap only in response to memory pressure, and not for the writeback * threads or sync. */ if (!wbc->for_reclaim) { WARN_ON_ONCE(1); /* Still happens? Tell us about it! */ goto redirty; } /* * This is somewhat ridiculous, but without plumbing a SWAP_MAP_FALLOC * value into swapfile.c, the only way we can correctly account for a * fallocated page arriving here is now to initialize it and write it. * * That's okay for a page already fallocated earlier, but if we have * not yet completed the fallocation, then (a) we want to keep track * of this page in case we have to undo it, and (b) it may not be a * good idea to continue anyway, once we're pushing into swap. So * reactivate the page, and let shmem_fallocate() quit when too many. */ if (!PageUptodate(page)) { if (inode->i_private) { struct shmem_falloc *shmem_falloc; spin_lock(&inode->i_lock); shmem_falloc = inode->i_private; if (shmem_falloc && !shmem_falloc->waitq && index >= shmem_falloc->start && index < shmem_falloc->next) shmem_falloc->nr_unswapped++; else shmem_falloc = NULL; spin_unlock(&inode->i_lock); if (shmem_falloc) goto redirty; } clear_highpage(page); flush_dcache_page(page); SetPageUptodate(page); } swap = get_swap_page(page); if (!swap.val) goto redirty; if (mem_cgroup_try_charge_swap(page, swap)) goto free_swap; /* * Add inode to shmem_unuse()'s list of swapped-out inodes, * if it's not already there. Do it now before the page is * moved to swap cache, when its pagelock no longer protects * the inode from eviction. But don't unlock the mutex until * we've incremented swapped, because shmem_unuse_inode() will * prune a !swapped inode from the swaplist under this mutex. */ mutex_lock(&shmem_swaplist_mutex); if (list_empty(&info->swaplist)) list_add_tail(&info->swaplist, &shmem_swaplist); if (add_to_swap_cache(page, swap, GFP_ATOMIC) == 0) { spin_lock_irq(&info->lock); shmem_recalc_inode(inode); info->swapped++; spin_unlock_irq(&info->lock); swap_shmem_alloc(swap); shmem_delete_from_page_cache(page, swp_to_radix_entry(swap)); mutex_unlock(&shmem_swaplist_mutex); BUG_ON(page_mapped(page)); swap_writepage(page, wbc); return 0; } mutex_unlock(&shmem_swaplist_mutex); free_swap: put_swap_page(page, swap); redirty: set_page_dirty(page); if (wbc->for_reclaim) return AOP_WRITEPAGE_ACTIVATE; /* Return with page locked */ unlock_page(page); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins26065.82%2058.82%
Linus Torvalds5814.68%38.82%
Andrew Morton256.33%38.82%
Linus Torvalds (pre-git)184.56%12.94%
Vladimir Davydov143.54%12.94%
Kirill A. Shutemov123.04%25.88%
MinChan Kim30.76%12.94%
Huang Ying30.76%12.94%
Zach Brown10.25%12.94%
Christoph Hellwig10.25%12.94%
Total395100.00%34100.00%

#if defined(CONFIG_NUMA) && defined(CONFIG_TMPFS)
static void shmem_show_mpol(struct seq_file *seq, struct mempolicy *mpol) { char buffer[64]; if (!mpol || mpol->mode == MPOL_DEFAULT) return; /* show nothing */ mpol_to_str(buffer, sizeof(buffer), mpol); seq_printf(seq, ",mpol=%s", buffer); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton3257.14%133.33%
Lee Schermerhorn2442.86%266.67%
Total56100.00%3100.00%


static struct mempolicy *shmem_get_sbmpol(struct shmem_sb_info *sbinfo) { struct mempolicy *mpol = NULL; if (sbinfo->mpol) { spin_lock(&sbinfo->stat_lock); /* prevent replace/use races */ mpol = sbinfo->mpol; mpol_get(mpol); spin_unlock(&sbinfo->stat_lock); } return mpol; }

Contributors

PersonTokensPropCommitsCommitProp
Lee Schermerhorn59100.00%1100.00%
Total59100.00%1100.00%

#else /* !CONFIG_NUMA || !CONFIG_TMPFS */
static inline void shmem_show_mpol(struct seq_file *seq, struct mempolicy *mpol) { }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins16100.00%1100.00%
Total16100.00%1100.00%


static inline struct mempolicy *shmem_get_sbmpol(struct shmem_sb_info *sbinfo) { return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins17100.00%1100.00%
Total17100.00%1100.00%

#endif /* CONFIG_NUMA && CONFIG_TMPFS */ #ifndef CONFIG_NUMA #define vm_policy vm_private_data #endif
static void shmem_pseudo_vma_init(struct vm_area_struct *vma, struct shmem_inode_info *info, pgoff_t index) { /* Create a pseudo vma that just contains the policy */ vma->vm_start = 0; /* Bias interleave by inode number to distribute better across nodes */ vma->vm_pgoff = index + info->vfs_inode.i_ino; vma->vm_ops = NULL; vma->vm_policy = mpol_shared_policy_lookup(&info->policy, index); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov59100.00%1100.00%
Total59100.00%1100.00%


static void shmem_pseudo_vma_destroy(struct vm_area_struct *vma) { /* Drop reference taken by mpol_shared_policy_lookup() */ mpol_cond_put(vma->vm_policy); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov19100.00%1100.00%
Total19100.00%1100.00%


static struct page *shmem_swapin(swp_entry_t swap, gfp_t gfp, struct shmem_inode_info *info, pgoff_t index) { struct vm_area_struct pvma; struct page *page; shmem_pseudo_vma_init(&pvma, info, index); page = swapin_readahead(swap, gfp, &pvma, 0); shmem_pseudo_vma_destroy(&pvma); return page; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov2945.31%116.67%
Hugh Dickins1523.44%350.00%
Andrew Morton1523.44%116.67%
Mel Gorman57.81%116.67%
Total64100.00%6100.00%


static struct page *shmem_alloc_hugepage(gfp_t gfp, struct shmem_inode_info *info, pgoff_t index) { struct vm_area_struct pvma; struct inode *inode = &info->vfs_inode; struct address_space *mapping = inode->i_mapping; pgoff_t idx, hindex; void __rcu **results; struct page *page; if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGE_PAGECACHE)) return NULL; hindex = round_down(index, HPAGE_PMD_NR); rcu_read_lock(); if (radix_tree_gang_lookup_slot(&mapping->page_tree, &results, &idx, hindex, 1) && idx < hindex + HPAGE_PMD_NR) { rcu_read_unlock(); return NULL; } rcu_read_unlock(); shmem_pseudo_vma_init(&pvma, info, hindex); page = alloc_pages_vma(gfp | __GFP_COMP | __GFP_NORETRY | __GFP_NOWARN, HPAGE_PMD_ORDER, &pvma, 0, numa_node_id(), true); shmem_pseudo_vma_destroy(&pvma); if (page) prep_transhuge_page(page); return page; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov15188.30%225.00%
Geert Uytterhoeven95.26%112.50%
Andrew Morton42.34%112.50%
Nathan Zimmer31.75%112.50%
Mel Gorman21.17%112.50%
Hugh Dickins21.17%225.00%
Total171100.00%8100.00%


static struct page *shmem_alloc_page(gfp_t gfp, struct shmem_inode_info *info, pgoff_t index) { struct vm_area_struct pvma; struct page *page; shmem_pseudo_vma_init(&pvma, info, index); page = alloc_page_vma(gfp, &pvma, 0); shmem_pseudo_vma_destroy(&pvma); return page; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov3559.32%125.00%
Mel Gorman1322.03%125.00%
Andrew Morton915.25%125.00%
Hugh Dickins23.39%125.00%
Total59100.00%4100.00%


static struct page *shmem_alloc_and_acct_page(gfp_t gfp, struct shmem_inode_info *info, struct shmem_sb_info *sbinfo, pgoff_t index, bool huge) { struct page *page; int nr; int err = -ENOSPC; if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGE_PAGECACHE)) huge = false; nr = huge ? HPAGE_PMD_NR : 1; if (shmem_acct_block(info->flags, nr)) goto failed; if (sbinfo->max_blocks) { if (percpu_counter_compare(&sbinfo->used_blocks, sbinfo->max_blocks - nr) > 0) goto unacct; percpu_counter_add(&sbinfo->used_blocks, nr); } if (huge) page = shmem_alloc_hugepage(gfp, info, index); else page = shmem_alloc_page(gfp, info, index); if (page) { __SetPageLocked(page); __SetPageSwapBacked(page); return page; } err = -ENOMEM; if (sbinfo->max_blocks) percpu_counter_add(&sbinfo->used_blocks, -nr); unacct: shmem_unacct_blocks(info->flags, nr); failed: return ERR_PTR(err); }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov13667.66%220.00%
Andrew Morton3215.92%110.00%
Hugh Dickins178.46%330.00%
Lee Schermerhorn73.48%110.00%
Mel Gorman62.99%110.00%
Nathan Zimmer21.00%110.00%
Al Viro10.50%110.00%
Total201100.00%10100.00%

/* * When a page is moved from swapcache to shmem filecache (either by the * usual swapin of shmem_getpage_gfp(), or by the less common swapoff of * shmem_unuse_inode()), it may have been read in earlier from swap, in * ignorance of the mapping it belongs to. If that mapping has special * constraints (like the gma500 GEM driver, which requires RAM below 4GB), * we may need to copy to a suitable page before moving to filecache. * * In a future release, this may well be extended to respect cpuset and * NUMA mempolicy, and applied also to anonymous pages in do_swap_page(); * but for now it is a simple matter of zone. */
static bool shmem_should_replace_page(struct page *page, gfp_t gfp) { return page_zonenum(page) > gfp_zone(gfp); }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins25100.00%1100.00%
Total25100.00%1100.00%


static int shmem_replace_page(struct page **pagep, gfp_t gfp, struct shmem_inode_info *info, pgoff_t index) { struct page *oldpage, *newpage; struct address_space *swap_mapping; pgoff_t swap_index; int error; oldpage = *pagep; swap_index = page_private(oldpage); swap_mapping = page_mapping(oldpage); /* * We have arrived here because our zones are constrained, so don't * limit chance of success by further cpuset and node constraints. */ gfp &= ~GFP_CONSTRAINT_MASK; newpage = shmem_alloc_page(gfp, info, index); if (!newpage) return -ENOMEM; get_page(newpage); copy_highpage(newpage, oldpage); flush_dcache_page(newpage); __SetPageLocked(newpage); __SetPageSwapBacked(newpage); SetPageUptodate(newpage); set_page_private(newpage, swap_index); SetPageSwapCache(newpage); /* * Our caller will very soon move newpage out of swapcache, but it's * a nice clean interface for us to replace oldpage by newpage there. */ spin_lock_irq(&swap_mapping->tree_lock); error = shmem_radix_tree_replace(swap_mapping, swap_index, oldpage, newpage); if (!error) { __inc_node_page_state(newpage, NR_FILE_PAGES); __dec_node_page_state(oldpage, NR_FILE_PAGES); } spin_unlock_irq(&swap_mapping->tree_lock); if (unlikely(error)) { /* * Is this possible? I think not, now that our callers check * both PageSwapCache and page_private after getting page lock; * but be defensive. Reverse old to newpage for clear and free. */ oldpage = newpage; } else { mem_cgroup_migrate(oldpage, newpage); lru_cache_add_anon(newpage); *pagep = newpage; } ClearPageSwapCache(oldpage); set_page_private(oldpage, 0); unlock_page(oldpage); put_page(oldpage); put_page(oldpage); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins24097.56%457.14%
Kirill A. Shutemov31.22%114.29%
Mel Gorman20.81%114.29%
Johannes Weiner10.41%114.29%
Total246100.00%7100.00%

/* * shmem_getpage_gfp - find page in cache, or get from swap, or allocate * * If we allocate a new one we do not mark it dirty. That's up to the * vm. If we swap it in we mark it dirty since we also free the swap * entry since a page cannot live in both the swap and page cache. * * fault_mm and fault_type are only supplied by shmem_fault: * otherwise they are NULL. */
static int shmem_getpage_gfp(struct inode *inode, pgoff_t index, struct page **pagep, enum sgp_type sgp, gfp_t gfp, struct vm_area_struct *vma, struct vm_fault *vmf, int *fault_type) { struct address_space *mapping = inode->i_mapping; struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_sb_info *sbinfo; struct mm_struct *charge_mm; struct mem_cgroup *memcg; struct page *page; swp_entry_t swap; enum sgp_type sgp_huge = sgp; pgoff_t hindex = index; int error; int once = 0; int alloced = 0; if (index > (MAX_LFS_FILESIZE >> PAGE_SHIFT)) return -EFBIG; if (sgp == SGP_NOHUGE || sgp == SGP_HUGE) sgp = SGP_CACHE; repeat: swap.val = 0; page = find_lock_entry(mapping, index); if (radix_tree_exceptional_entry(page)) { swap = radix_to_swp_entry(page); page = NULL; } if (sgp <= SGP_CACHE && ((loff_t)index << PAGE_SHIFT) >= i_size_read(inode)) { error = -EINVAL; goto unlock; } if (page && sgp == SGP_WRITE) mark_page_accessed(page); /* fallocated page? */ if (page && !PageUptodate(page)) { if (sgp != SGP_READ) goto clear; unlock_page(page); put_page(page); page = NULL; } if (page || (sgp == SGP_READ && !swap.val)) { *pagep = page; return 0; } /* * Fast cache lookup did not find it: * bring it back from swap or allocate. */ sbinfo = SHMEM_SB(inode->i_sb); charge_mm = vma ? vma->vm_mm : current->mm; if (swap.val) { /* Look it up and read it in.. */ page = lookup_swap_cache(swap); if (!page) { /* Or update major stats only when swapin succeeds?? */ if (fault_type) { *fault_type |= VM_FAULT_MAJOR; count_vm_event(PGMAJFAULT); count_memcg_event_mm(charge_mm, PGMAJFAULT); } /* Here we actually start the io */ page = shmem_swapin(swap, gfp, info, index); if (!page) { error = -ENOMEM; goto failed; } } /* We have to do this with page locked to prevent races */ lock_page(page); if (!PageSwapCache(page) || page_private(page) != swap.val || !shmem_confirm_swap(mapping, index, swap)) { error = -EEXIST; /* try again */ goto unlock; } if (!PageUptodate(page)) { error = -EIO; goto failed; } wait_on_page_writeback(page); if (shmem_should_replace_page(page, gfp)) { error = shmem_replace_page(&page, gfp, info, index); if (error) goto failed; } error = mem_cgroup_try_charge(page, charge_mm, gfp, &memcg, false); if (!error) { error = shmem_add_to_page_cache(page, mapping, index, swp_to_radix_entry(swap)); /* * We already confirmed swap under page lock, and make * no memory allocation here, so usually no possibility * of error; but free_swap_and_cache() only trylocks a * page, so it is just possible that the entry has been * truncated or holepunched since swap was confirmed. * shmem_undo_range() will have done some of the * unaccounting, now delete_from_swap_cache() will do * the rest. * Reset swap.val? No, leave it so "failed" goes back to * "repeat": reading a hole and writing should succeed. */ if (error) { mem_cgroup_cancel_charge(page, memcg, false); delete_from_swap_cache(page); } } if (error) goto failed; mem_cgroup_commit_charge(page, memcg, true, false); spin_lock_irq(&info->lock); info->swapped--; shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); if (sgp == SGP_WRITE) mark_page_accessed(page); delete_from_swap_cache(page); set_page_dirty(page); swap_free(swap); } else { if (vma && userfaultfd_missing(vma)) { *fault_type = handle_userfault(vmf, VM_UFFD_MISSING); return 0; } /* shmem_symlink() */ if (mapping->a_ops != &shmem_aops) goto alloc_nohuge; if (shmem_huge == SHMEM_HUGE_DENY || sgp_huge == SGP_NOHUGE) goto alloc_nohuge; if (shmem_huge == SHMEM_HUGE_FORCE) goto alloc_huge; switch (sbinfo->huge) { loff_t i_size; pgoff_t off; case SHMEM_HUGE_NEVER: goto alloc_nohuge; case SHMEM_HUGE_WITHIN_SIZE: off = round_up(index, HPAGE_PMD_NR); i_size = round_up(i_size_read(inode), PAGE_SIZE); if (i_size >= HPAGE_PMD_SIZE && i_size >> PAGE_SHIFT >= off) goto alloc_huge; /* fallthrough */ case SHMEM_HUGE_ADVISE: if (sgp_huge == SGP_HUGE) goto alloc_huge; /* TODO: implement fadvise() hints */ goto alloc_nohuge; } alloc_huge: page = shmem_alloc_and_acct_page(gfp, info, sbinfo, index, true); if (IS_ERR(page)) { alloc_nohuge: page = shmem_alloc_and_acct_page(gfp, info, sbinfo, index, false); } if (IS_ERR(page)) { int retry = 5; error = PTR_ERR(page); page = NULL; if (error != -ENOSPC) goto failed; /* * Try to reclaim some spece by splitting a huge page * beyond i_size on the filesystem. */ while (retry--) { int ret; ret = shmem_unused_huge_shrink(sbinfo, NULL, 1); if (ret == SHRINK_STOP) break; if (ret) goto alloc_nohuge; } goto failed; } if (PageTransHuge(page)) hindex = round_down(index, HPAGE_PMD_NR); else hindex = index; if (sgp == SGP_WRITE) __SetPageReferenced(page); error = mem_cgroup_try_charge(page, charge_mm, gfp, &memcg, PageTransHuge(page)); if (error) goto unacct; error = radix_tree_maybe_preload_order(gfp & GFP_RECLAIM_MASK, compound_order(page)); if (!error) { error = shmem_add_to_page_cache(page, mapping, hindex, NULL); radix_tree_preload_end(); } if (error) { mem_cgroup_cancel_charge(page, memcg, PageTransHuge(page)); goto unacct; } mem_cgroup_commit_charge(page, memcg, false, PageTransHuge(page)); lru_cache_add_anon(page); spin_lock_irq(&info->lock); info->alloced += 1 << compound_order(page); inode->i_blocks += BLOCKS_PER_PAGE << compound_order(page); shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); alloced = true; if (PageTransHuge(page) && DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE) < hindex + HPAGE_PMD_NR - 1) { /* * Part of the huge page is beyond i_size: subject * to shrink under memory pressure. */ spin_lock(&sbinfo->shrinklist_lock); /* * _careful to defend against unlocked access to * ->shrink_list in shmem_unused_huge_shrink() */ if (list_empty_careful(&info->shrinklist)) { list_add_tail(&info->shrinklist, &sbinfo->shrinklist); sbinfo->shrinklist_len++; } spin_unlock(&sbinfo->shrinklist_lock); } /* * Let SGP_FALLOC use the SGP_WRITE optimization on a new page. */ if (sgp == SGP_FALLOC) sgp = SGP_WRITE; clear: /* * Let SGP_WRITE caller clear ends if write does not fill page; * but SGP_FALLOC on a page fallocated earlier must initialize * it now, lest undo on failure cancel our earlier guarantee. */ if (sgp != SGP_WRITE && !PageUptodate(page)) { struct page *head = compound_head(page); int i; for (i = 0; i < (1 << compound_order(head)); i++) { clear_highpage(head + i); flush_dcache_page(head + i); } SetPageUptodate(head); } } /* Perhaps the file has been truncated since we checked */ if (sgp <= SGP_CACHE && ((loff_t)index << PAGE_SHIFT) >= i_size_read(inode)) { if (alloced) { ClearPageDirty(page); delete_from_page_cache(page); spin_lock_irq(&info->lock); shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); } error = -EINVAL; goto unlock; } *pagep = page + index - hindex; return 0; /* * Error recovery. */ unacct: if (sbinfo->max_blocks) percpu_counter_sub(&sbinfo->used_blocks, 1 << compound_order(page)); shmem_unacct_blocks(info->flags, 1 << compound_order(page)); if (PageTransHuge(page)) { unlock_page(page); put_page(page); goto alloc_nohuge; } failed: if (swap.val && !shmem_confirm_swap(mapping, index, swap)) error = -EEXIST; unlock: if (page) { unlock_page(page); put_page(page); } if (error == -ENOSPC && !once++) { spin_lock_irq(&info->lock); shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); goto repeat; } if (error == -EEXIST) /* from above or from radix_tree_insert */ goto repeat; return error; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins67447.23%3650.00%
Kirill A. Shutemov44130.90%68.33%
Linus Torvalds704.91%34.17%
Andrew Morton654.56%1318.06%
Johannes Weiner422.94%22.78%
Mike Rapoport362.52%11.39%
Andres Lagar-Cavilla322.24%11.39%
Linus Torvalds (pre-git)312.17%11.39%
David Shaohua Li161.12%11.39%
Tim Chen70.49%11.39%
Arnd Bergmann50.35%11.39%
Nicholas Piggin20.14%22.78%
Américo Wang20.14%11.39%
Kamezawa Hiroyuki20.14%11.39%
Vladimir Davydov10.07%11.39%
Roman Gushchin10.07%11.39%
Total1427100.00%72100.00%

/* * This is like autoremove_wake_function, but it removes the wait queue * entry unconditionally - even if something else had already woken the * target. */
static int synchronous_wake_function(wait_queue_entry_t *wait, unsigned mode, int sync, void *key) { int ret = default_wake_function(wait, mode, sync, key); list_del_init(&wait->entry); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds4395.56%133.33%
Ingo Molnar24.44%266.67%
Total45100.00%3100.00%


static int shmem_fault(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct inode *inode = file_inode(vma->vm_file); gfp_t gfp = mapping_gfp_mask(inode->i_mapping); enum sgp_type sgp; int error; int ret = VM_FAULT_LOCKED; /* * Trinity finds that probing a hole which tmpfs is punching can * prevent the hole-punch from ever completing: which in turn * locks writers out with its hold on i_mutex. So refrain from * faulting pages into the hole while it's being punched. Although * shmem_undo_range() does remove the additions, it may be unable to * keep up, as each new page needs its own unmap_mapping_range() call, * and the i_mmap tree grows ever slower to scan if new vmas are added. * * It does not matter if we sometimes reach this check just before the * hole-punch begins, so that one fault then races with the punch: * we just need to make racing faults a rare case. * * The implementation below would be much simpler if we just used a * standard mutex or completion: but we cannot take i_mutex in fault, * and bloating every shmem inode for this unlikely case would be sad. */ if (unlikely(inode->i_private)) { struct shmem_falloc *shmem_falloc; spin_lock(&inode->i_lock); shmem_falloc = inode->i_private; if (shmem_falloc && shmem_falloc->waitq && vmf->pgoff >= shmem_falloc->start && vmf->pgoff < shmem_falloc->next) { wait_queue_head_t *shmem_falloc_waitq; DEFINE_WAIT_FUNC(shmem_fault_wait, synchronous_wake_function); ret = VM_FAULT_NOPAGE; if ((vmf->flags & FAULT_FLAG_ALLOW_RETRY) && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT)) { /* It's polite to up mmap_sem if we can */ up_read(&vma->vm_mm->mmap_sem); ret = VM_FAULT_RETRY; } shmem_falloc_waitq = shmem_falloc->waitq; prepare_to_wait(shmem_falloc_waitq, &shmem_fault_wait, TASK_UNINTERRUPTIBLE); spin_unlock(&inode->i_lock); schedule(); /* * shmem_falloc_waitq points into the shmem_fallocate() * stack of the hole-punching task: shmem_falloc_waitq * is usually invalid by the time we reach here, but * finish_wait() does not dereference it in that case; * though i_lock needed lest racing with wake_up_all(). */ spin_lock(&inode->i_lock); finish_wait(shmem_falloc_waitq, &shmem_fault_wait); spin_unlock(&inode->i_lock); return ret; } spin_unlock(&inode->i_lock); } sgp = SGP_CACHE; if ((vma->vm_flags & VM_NOHUGEPAGE) || test_bit(MMF_DISABLE_THP, &vma->vm_mm->flags)) sgp = SGP_NOHUGE; else if (vma->vm_flags & VM_HUGEPAGE) sgp = SGP_HUGE; error = shmem_getpage_gfp(inode, vmf->pgoff, &vmf->page, sgp, gfp, vma, vmf, &ret); if (error) return ((error == -ENOMEM) ? VM_FAULT_OOM : VM_FAULT_SIGBUS); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins18759.18%421.05%
Kirill A. Shutemov309.49%15.26%
Nicholas Piggin196.01%210.53%
Michal Hocko185.70%15.26%
Andres Lagar-Cavilla154.75%15.26%
Linus Torvalds134.11%210.53%
Linus Torvalds (pre-git)113.48%15.26%
Dave Jiang92.85%15.26%
Andrew Morton82.53%315.79%
Al Viro30.95%15.26%
Mike Rapoport20.63%15.26%
Adrian Bunk10.32%15.26%
Total316100.00%19100.00%


unsigned long shmem_get_unmapped_area(struct file *file, unsigned long uaddr, unsigned long len, unsigned long pgoff, unsigned long flags) { unsigned long (*get_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); unsigned long addr; unsigned long offset; unsigned long inflated_len; unsigned long inflated_addr; unsigned long inflated_offset; if (len > TASK_SIZE) return -ENOMEM; get_area = current->mm->get_unmapped_area; addr = get_area(file, uaddr, len, pgoff, flags); if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGE_PAGECACHE)) return addr; if (IS_ERR_VALUE(addr)) return addr; if (addr & ~PAGE_MASK) return addr; if (addr > TASK_SIZE - len) return addr; if (shmem_huge == SHMEM_HUGE_DENY) return addr; if (len < HPAGE_PMD_SIZE) return addr; if (flags & MAP_FIXED) return addr; /* * Our priority is to support MAP_SHARED mapped hugely; * and support MAP_PRIVATE mapped hugely too, until it is COWed. * But if caller specified an address hint, respect that as before. */ if (uaddr) return addr; if (shmem_huge != SHMEM_HUGE_FORCE) { struct super_block *sb; if (file) { VM_BUG_ON(file->f_op != &shmem_file_operations); sb = file_inode(file)->i_sb; } else { /* * Called directly from mm/mmap.c, or drivers/char/mem.c * for "/dev/zero", to create a shared anonymous object. */ if (IS_ERR(shm_mnt)) return addr; sb = shm_mnt->mnt_sb; } if (SHMEM_SB(sb)->huge == SHMEM_HUGE_NEVER) return addr; } offset = (pgoff << PAGE_SHIFT) & (HPAGE_PMD_SIZE-1); if (offset && offset + len < 2 * HPAGE_PMD_SIZE) return addr; if ((addr & (HPAGE_PMD_SIZE-1)) == offset) return addr; inflated_len = len + HPAGE_PMD_SIZE - PAGE_SIZE; if (inflated_len > TASK_SIZE) return addr; if (inflated_len < len) return addr; inflated_addr = get_area(NULL, 0, inflated_len, 0, flags); if (IS_ERR_VALUE(inflated_addr)) return addr; if (inflated_addr & ~PAGE_MASK) return addr; inflated_offset = inflated_addr & (HPAGE_PMD_SIZE-1); inflated_addr += offset - inflated_offset; if (inflated_offset > offset) inflated_addr += HPAGE_PMD_SIZE; if (inflated_addr > TASK_SIZE - len) return addr; return inflated_addr; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins39899.50%133.33%
Kirill A. Shutemov10.25%133.33%
Toshi Kani10.25%133.33%
Total400100.00%3100.00%

#ifdef CONFIG_NUMA
static int shmem_set_policy(struct vm_area_struct *vma, struct mempolicy *mpol) { struct inode *inode = file_inode(vma->vm_file); return mpol_set_shared_policy(&SHMEM_I(inode)->policy, vma, mpol); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton3681.82%125.00%
Hugh Dickins49.09%125.00%
Al Viro36.82%125.00%
Adrian Bunk12.27%125.00%
Total44100.00%4100.00%


static struct mempolicy *shmem_get_policy(struct vm_area_struct *vma, unsigned long addr) { struct inode *inode = file_inode(vma->vm_file); pgoff_t index; index = ((addr - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff; return mpol_shared_policy_lookup(&SHMEM_I(inode)->policy, index); }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton5484.38%125.00%
Hugh Dickins69.38%125.00%
Al Viro34.69%125.00%
Adrian Bunk11.56%125.00%
Total64100.00%4100.00%

#endif
int shmem_lock(struct file *file, int lock, struct user_struct *user) { struct inode *inode = file_inode(file); struct shmem_inode_info *info = SHMEM_I(inode); int retval = -ENOMEM; spin_lock_irq(&info->lock); if (lock && !(info->flags & VM_LOCKED)) { if (!user_shm_lock(inode->i_size, user)) goto out_nomem; info->flags |= VM_LOCKED; mapping_set_unevictable(file->f_mapping); } if (!lock && (info->flags & VM_LOCKED) && user) { user_shm_unlock(inode->i_size, user); info->flags &= ~VM_LOCKED; mapping_clear_unevictable(file->f_mapping); } retval = 0; out_nomem: spin_unlock_irq(&info->lock); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Rik Van Riel7348.03%112.50%
Linus Torvalds3623.68%225.00%
Hugh Dickins1610.53%112.50%
Lee Schermerhorn149.21%112.50%
Linus Torvalds (pre-git)85.26%112.50%
Al Viro31.97%112.50%
Kirill A. Shutemov21.32%112.50%
Total152100.00%8100.00%


static int shmem_mmap(struct file *file, struct vm_area_struct *vma) { file_accessed(file); vma->vm_ops = &shmem_vm_ops; if (IS_ENABLED(CONFIG_TRANSPARENT_HUGE_PAGECACHE) && ((vma->vm_start + ~HPAGE_PMD_MASK) & HPAGE_PMD_MASK) < (vma->vm_end & HPAGE_PMD_MASK)) { khugepaged_enter(vma, vma->vm_flags); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov3955.71%233.33%
Linus Torvalds2332.86%116.67%
Al Viro45.71%116.67%
Linus Torvalds (pre-git)34.29%116.67%
Adrian Bunk11.43%116.67%
Total70100.00%6100.00%


static struct inode *shmem_get_inode(struct super_block *sb, const struct inode *dir, umode_t mode, dev_t dev, unsigned long flags) { struct inode *inode; struct shmem_inode_info *info; struct shmem_sb_info *sbinfo = SHMEM_SB(sb); if (shmem_reserve_inode(sb)) return NULL; inode = new_inode(sb); if (inode) { inode->i_ino = get_next_ino(); inode_init_owner(inode, dir, mode); inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_generation = get_seconds(); info = SHMEM_I(inode); memset(info, 0, (char *)inode - (char *)info); spin_lock_init(&info->lock); info->seals = F_SEAL_SEAL; info->flags = flags & VM_NORESERVE; INIT_LIST_HEAD(&info->shrinklist); INIT_LIST_HEAD(&info->swaplist); simple_xattrs_init(&info->xattrs); cache_no_acl(inode); switch (mode & S_IFMT) { default: inode->i_op = &shmem_special_inode_operations; init_special_inode(inode, mode, dev); break; case S_IFREG: inode->i_mapping->a_ops = &shmem_aops; inode->i_op = &shmem_inode_operations; inode->i_fop = &shmem_file_operations; mpol_shared_policy_init(&info->policy, shmem_get_sbmpol(sbinfo)); break; case S_IFDIR: inc_nlink(inode); /* Some things misbehave if size == 0 on a directory */ inode->i_size = 2 * BOGO_DIRENT_SIZE; inode->i_op = &shmem_dir_inode_operations; inode->i_fop = &simple_dir_operations; break; case S_IFLNK: /* * Must not load anything in the rbtree, * mpol_free_shared_policy will not be called. */ mpol_shared_policy_init(&info->policy, NULL); break; } } else shmem_free_inode(sb); return inode; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds15548.90%310.34%
Hugh Dickins5818.30%724.14%
Andrea Arcangeli175.36%13.45%
Dmitriy Monakhov123.79%13.45%
Kirill A. Shutemov82.52%13.45%
Al Viro72.21%310.34%
Christoph Hellwig72.21%13.45%
Andreas Gruenbacher72.21%13.45%
David M. Grimes72.21%13.45%
Eric Paris61.89%13.45%
David Herrmann61.89%13.45%
Pavel Emelyanov51.58%13.45%
Deepa Dinamani41.26%13.45%
Linus Torvalds (pre-git)41.26%13.45%
Robin Holt41.26%13.45%
Lee Schermerhorn30.95%13.45%
Dave Hansen30.95%13.45%
Aristeu Sergio Rozanski Filho20.63%13.45%
Andries E. Brouwer20.63%13.45%
Total317100.00%29100.00%


bool shmem_mapping(struct address_space *mapping) { return mapping->a_ops == &shmem_aops; }

Contributors

PersonTokensPropCommitsCommitProp
Johannes Weiner1688.89%150.00%
Hugh Dickins211.11%150.00%
Total18100.00%2100.00%


int shmem_mcopy_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd, struct vm_area_struct *dst_vma, unsigned long dst_addr, unsigned long src_addr, struct page **pagep) { struct inode *inode = file_inode(dst_vma->vm_file); struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); struct address_space *mapping = inode->i_mapping; gfp_t gfp = mapping_gfp_mask(mapping); pgoff_t pgoff = linear_page_index(dst_vma, dst_addr); struct mem_cgroup *memcg; spinlock_t *ptl; void *page_kaddr; struct page *page; pte_t _dst_pte, *dst_pte; int ret; ret = -ENOMEM; if (shmem_acct_block(info->flags, 1)) goto out; if (sbinfo->max_blocks) { if (percpu_counter_compare(&sbinfo->used_blocks, sbinfo->max_blocks) >= 0) goto out_unacct_blocks; percpu_counter_inc(&sbinfo->used_blocks); } if (!*pagep) { page = shmem_alloc_page(gfp, info, pgoff); if (!page) goto out_dec_used_blocks; page_kaddr = kmap_atomic(page); ret = copy_from_user(page_kaddr, (const void __user *)src_addr, PAGE_SIZE); kunmap_atomic(page_kaddr); /* fallback to copy_from_user outside mmap_sem */ if (unlikely(ret)) { *pagep = page; if (sbinfo->max_blocks) percpu_counter_add(&sbinfo->used_blocks, -1); shmem_unacct_blocks(info->flags, 1); /* don't free the page */ return -EFAULT; } } else { page = *pagep; *pagep = NULL; } VM_BUG_ON(PageLocked(page) || PageSwapBacked(page)); __SetPageLocked(page); __SetPageSwapBacked(page); __SetPageUptodate(page); ret = mem_cgroup_try_charge(page, dst_mm, gfp, &memcg, false); if (ret) goto out_release; ret = radix_tree_maybe_preload(gfp & GFP_RECLAIM_MASK); if (!ret) { ret = shmem_add_to_page_cache(page, mapping, pgoff, NULL); radix_tree_preload_end(); } if (ret) goto out_release_uncharge; mem_cgroup_commit_charge(page, memcg, false, false); _dst_pte = mk_pte(page, dst_vma->vm_page_prot); if (dst_vma->vm_flags & VM_WRITE) _dst_pte = pte_mkwrite(pte_mkdirty(_dst_pte)); ret = -EEXIST; dst_pte = pte_offset_map_lock(dst_mm, dst_pmd, dst_addr, &ptl); if (!pte_none(*dst_pte)) goto out_release_uncharge_unlock; lru_cache_add_anon(page); spin_lock(&info->lock); info->alloced++; inode->i_blocks += BLOCKS_PER_PAGE; shmem_recalc_inode(inode); spin_unlock(&info->lock); inc_mm_counter(dst_mm, mm_counter_file(page)); page_add_file_rmap(page, false); set_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte); /* No need to invalidate - it was non-present before */ update_mmu_cache(dst_vma, dst_addr, dst_pte); unlock_page(page); pte_unmap_unlock(dst_pte, ptl); ret = 0; out: return ret; out_release_uncharge_unlock: pte_unmap_unlock(dst_pte, ptl); out_release_uncharge: mem_cgroup_cancel_charge(page, memcg, false); out_release: unlock_page(page); put_page(page); out_dec_used_blocks: if (sbinfo->max_blocks) percpu_counter_add(&sbinfo->used_blocks, -1); out_unacct_blocks: shmem_unacct_blocks(info->flags, 1); goto out; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport54689.22%125.00%
Andrea Arcangeli6610.78%375.00%
Total612100.00%4100.00%

#ifdef CONFIG_TMPFS static const struct inode_operations shmem_symlink_inode_operations; static const struct inode_operations shmem_short_symlink_operations; #ifdef CONFIG_TMPFS_XATTR static int shmem_initxattrs(struct inode *, const struct xattr *, void *); #else #define shmem_initxattrs NULL #endif
static int shmem_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata) { struct inode *inode = mapping->host; struct shmem_inode_info *info = SHMEM_I(inode); pgoff_t index = pos >> PAGE_SHIFT; /* i_mutex is held by caller */ if (unlikely(info->seals & (F_SEAL_WRITE | F_SEAL_GROW))) { if (info->seals & F_SEAL_WRITE) return -EPERM; if ((info->seals & F_SEAL_GROW) && pos + len > inode->i_size) return -EPERM; } return shmem_getpage(inode, index, pagep, SGP_WRITE); }

Contributors

PersonTokensPropCommitsCommitProp
David Herrmann5644.44%116.67%
Andrew Morton3628.57%116.67%
Nicholas Piggin2620.63%116.67%
Steven Rostedt64.76%116.67%
Hugh Dickins10.79%116.67%
Kirill A. Shutemov10.79%116.67%
Total126100.00%6100.00%


static int shmem_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata) { struct inode *inode = mapping->host; if (pos + copied > inode->i_size) i_size_write(inode, pos + copied); if (!PageUptodate(page)) { struct page *head = compound_head(page); if (PageTransCompound(page)) { int i; for (i = 0; i < HPAGE_PMD_NR; i++) { if (head + i == page) continue; clear_highpage(head + i); flush_dcache_page(head + i); } } if (copied < PAGE_SIZE) { unsigned from = pos & (PAGE_SIZE - 1); zero_user_segments(page, 0, from, from + copied, PAGE_SIZE); } SetPageUptodate(head); } set_page_dirty(page); unlock_page(page); put_page(page); return copied; }

Contributors

PersonTokensPropCommitsCommitProp
Kirill A. Shutemov6534.39%228.57%
Nicholas Piggin6433.86%114.29%
Hugh Dickins5730.16%228.57%
Fengguang Wu21.06%114.29%
Linus Torvalds10.53%114.29%
Total189100.00%7100.00%


static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to) { struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); struct address_space *mapping = inode->i_mapping; pgoff_t index; unsigned long offset; enum sgp_type sgp = SGP_READ; int error = 0; ssize_t retval = 0; loff_t *ppos = &iocb->ki_pos; /* * Might this read be for a stacking filesystem? Then when reading * holes of a sparse file, we actually need to allocate those pages, * and even mark them dirty, so it cannot exceed the max_blocks limit. */ if (!iter_is_iovec(to)) sgp = SGP_CACHE; index = *ppos >> PAGE_SHIFT; offset = *ppos & ~PAGE_MASK; for (;;) { struct page *page = NULL; pgoff_t end_index; unsigned long nr, ret; loff_t i_size = i_size_read(inode); end_index = i_size >> PAGE_SHIFT; if (index > end_index) break; if (index == end_index) { nr = i_size & ~PAGE_MASK; if (nr <= offset) break; } error = shmem_getpage(inode, index, &page, sgp); if (error) { if (error == -EINVAL) error = 0; break; } if (page) { if (sgp == SGP_CACHE) set_page_dirty(page); unlock_page(page); } /* * We must evaluate after, since reads (unlike writes) * are called without i_mutex protection against truncate */ nr = PAGE_SIZE; i_size = i_size_read(inode); end_index = i_size >> PAGE_SHIFT; if (index == end_index) { nr = i_size & ~PAGE_MASK; if (nr <= offset) { if (page) put_page(page); break; } } nr -= offset; if (page) { /* * If users can be writing to this page using arbitrary * virtual addresses, take care about potential aliasing * before reading the page on the kernel side. */ if (mapping_writably_mapped(mapping)) flush_dcache_page(page); /* * Mark the page accessed if we read the beginning. */ if (!offset) mark_page_accessed(page); } else { page = ZERO_PAGE(0); get_page(page); } /* * Ok, we have the page, and it's up-to-date, so * now we can copy it to user space... */ ret = copy_page_to_iter(page, offset, nr, to); retval += ret; offset += ret; index += offset >> PAGE_SHIFT; offset &= ~PAGE_MASK; put_page(page); if (!iov_iter_count(to)) break; if (ret < nr) { error = -EFAULT; break; } cond_resched(); } *ppos = ((loff_t) index << PAGE_SHIFT) + offset; file_accessed(file); return retval ? retval : error; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds14535.02%13.70%
Hugh Dickins11828.50%725.93%
Al Viro6716.18%622.22%
Andrew Morton6014.49%829.63%
Kirill A. Shutemov133.14%13.70%
Nicholas Piggin61.45%13.70%
Ingo Molnar20.48%13.70%
Geert Uytterhoeven20.48%13.70%
Jes Sorensen10.24%13.70%
Total414100.00%27100.00%

/* * llseek SEEK_DATA or SEEK_HOLE through the radix_tree. */
static pgoff_t shmem_seek_hole_data(struct address_space *mapping, pgoff_t index, pgoff_t end, int whence) { struct page *page; struct pagevec pvec; pgoff_t indices[PAGEVEC_SIZE]; bool done = false; int i; pagevec_init(&pvec, 0); pvec.nr = 1; /* start small: we may be there already */ while (!done) { pvec.nr = find_get_entries(mapping, index, pvec.nr, pvec.pages, indices); if (!pvec.nr) { if (whence == SEEK_DATA) index = end; break; } for (i = 0; i < pvec.nr; i++, index++) { if (index < indices[i]) { if (whence == SEEK_HOLE) { done = true; break; } index = indices[i]; } page = pvec.pages[i]; if (page && !radix_tree_exceptional_entry(page)) { if (!PageUptodate(page)) page = NULL; } if (index >= end || (page && whence == SEEK_DATA) || (!page && whence == SEEK_HOLE)) { done = true; break; } } pagevec_remove_exceptionals(&pvec); pagevec_release(&pvec); pvec.nr = PAGEVEC_SIZE; cond_resched(); } return index; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins23797.13%133.33%
Andrew Morton52.05%133.33%
Johannes Weiner20.82%133.33%
Total244100.00%3100.00%


static loff_t shmem_file_llseek(struct file *file, loff_t offset, int whence) { struct address_space *mapping = file->f_mapping; struct inode *inode = mapping->host; pgoff_t start, end; loff_t new_offset; if (whence != SEEK_DATA && whence != SEEK_HOLE) return generic_file_llseek_size(file, offset, whence, MAX_LFS_FILESIZE, i_size_read(inode)); inode_lock(inode); /* We're holding i_mutex so we can access i_size directly */ if (offset < 0) offset = -EINVAL; else if (offset >= inode->i_size) offset = -ENXIO; else { start = offset >> PAGE_SHIFT; end = (inode->i_size + PAGE_SIZE - 1) >> PAGE_SHIFT; new_offset = shmem_seek_hole_data(mapping, start, end, whence); new_offset <<= PAGE_SHIFT; if (new_offset > offset) { if (new_offset < inode->i_size) offset = new_offset; else if (whence == SEEK_DATA) offset = -ENXIO; else offset = inode->i_size; } } if (offset >= 0) offset = vfs_setpos(file, offset, MAX_LFS_FILESIZE); inode_unlock(inode); return offset; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins18690.73%233.33%
Jie Liu73.41%116.67%
Andrew Morton62.93%116.67%
Kirill A. Shutemov41.95%116.67%
Al Viro20.98%116.67%
Total205100.00%6100.00%

/* * We need a tag: a new tag would expand every radix_tree_node by 8 bytes, * so reuse a tag which we firmly believe is never set or cleared on shmem. */ #define SHMEM_TAG_PINNED PAGECACHE_TAG_TOWRITE #define LAST_SCAN 4 /* about 150ms max */
static void shmem_tag_pins(struct address_space *mapping) { struct radix_tree_iter iter; void **slot; pgoff_t start; struct page *page; lru_add_drain(); start = 0; rcu_read_lock(); radix_tree_for_each_slot(slot, &mapping->page_tree, &iter, start) { page = radix_tree_deref_slot(slot); if (!page || radix_tree_exception(page)) { if (radix_tree_deref_retry(page)) { slot = radix_tree_iter_retry(&iter); continue; } } else if (page_count(page) - page_mapcount(page) > 1) { spin_lock_irq(&mapping->tree_lock); radix_tree_tag_set(&mapping->page_tree, iter.index, SHMEM_TAG_PINNED); spin_unlock_irq(&mapping->tree_lock); } if (need_resched()) { slot = radix_tree_iter_resume(slot, &iter); cond_resched_rcu(); } } rcu_read_unlock(); }

Contributors

PersonTokensPropCommitsCommitProp
David Herrmann13686.62%125.00%
Matthew Wilcox2113.38%375.00%
Total157100.00%4100.00%

/* * Setting SEAL_WRITE requires us to verify there's no pending writer. However, * via get_user_pages(), drivers might have some pending I/O without any active * user-space mappings (eg., direct-IO, AIO). Therefore, we look at all pages * and see whether it has an elevated ref-count. If so, we tag them and wait for * them to be dropped. * The caller must guarantee that no new user will acquire writable references * to those pages to avoid races. */
static int shmem_wait_for_pins(struct address_space *mapping) { struct radix_tree_iter iter; void **slot; pgoff_t start; struct page *page; int error, scan; shmem_tag_pins(mapping); error = 0; for (scan = 0; scan <= LAST_SCAN; scan++) { if (!radix_tree_tagged(&mapping->page_tree, SHMEM_TAG_PINNED)) break; if (!scan) lru_add_drain_all(); else if (schedule_timeout_killable((HZ << scan) / 200)) scan = LAST_SCAN; start = 0; rcu_read_lock(); radix_tree_for_each_tagged(slot, &mapping->page_tree, &iter, start, SHMEM_TAG_PINNED) { page = radix_tree_deref_slot(slot); if (radix_tree_exception(page)) { if (radix_tree_deref_retry(page)) { slot = radix_tree_iter_retry(&iter); continue; } page = NULL; } if (page && page_count(page) - page_mapcount(page) != 1) { if (scan < LAST_SCAN) goto continue_resched; /* * On the last scan, we clean up all those tags * we inserted; but make a note that we still * found pages pinned. */ error = -EBUSY; } spin_lock_irq(&mapping->tree_lock); radix_tree_tag_clear(&mapping->page_tree, iter.index, SHMEM_TAG_PINNED); spin_unlock_irq(&mapping->tree_lock); continue_resched: if (need_resched()) { slot = radix_tree_iter_resume(slot, &iter); cond_resched_rcu(); } } rcu_read_unlock(); } return error; }

Contributors

PersonTokensPropCommitsCommitProp
David Herrmann22691.50%240.00%
Matthew Wilcox218.50%360.00%
Total247100.00%5100.00%

#define F_ALL_SEALS (F_SEAL_SEAL | \ F_SEAL_SHRINK | \ F_SEAL_GROW | \ F_SEAL_WRITE)
int shmem_add_seals(struct file *file, unsigned int seals) { struct inode *inode = file_inode(file); struct shmem_inode_info *info = SHMEM_I(inode); int error; /* * SEALING * Sealing allows multiple parties to share a shmem-file but restrict * access to a specific subset of file operations. Seals can only be * added, but never removed. This way, mutually untrusted parties can * share common memory regions with a well-defined policy. A malicious * peer can thus never perform unwanted operations on a shared object. * * Seals are only supported on special shmem-files and always affect * the whole underlying inode. Once a seal is set, it may prevent some * kinds of access to the file. Currently, the following seals are * defined: * SEAL_SEAL: Prevent further seals from being set on this file * SEAL_SHRINK: Prevent the file from shrinking * SEAL_GROW: Prevent the file from growing * SEAL_WRITE: Prevent write access to the file * * As we don't require any trust relationship between two parties, we * must prevent seals from being removed. Therefore, sealing a file * only adds a given set of seals to the file, it never touches * existing seals. Furthermore, the "setting seals"-operation can be * sealed itself, which basically prevents any further seal from being * added. * * Semantics of sealing are only defined on volatile files. Only * anonymous shmem files support sealing. More importantly, seals are * never written to disk. Therefore, there's no plan to support it on * other file types. */ if (file->f_op != &shmem_file_operations) return -EINVAL; if (!(file->f_mode & FMODE_WRITE)) return -EPERM; if (seals & ~(unsigned int)F_ALL_SEALS) return -EINVAL; inode_lock(inode); if (info->seals & F_SEAL_SEAL) { error = -EPERM; goto unlock; } if ((seals & F_SEAL_WRITE) && !(info->seals & F_SEAL_WRITE)) { error = mapping_deny_writable(file->f_mapping); if (error) goto unlock; error = shmem_wait_for_pins(file->f_mapping); if (error) { mapping_allow_writable(file->f_mapping); goto unlock; } } info->seals |= seals; error = 0; unlock: inode_unlock(inode); return error; }

Contributors

PersonTokensPropCommitsCommitProp
David Herrmann18298.91%150.00%
Al Viro21.09%150.00%
Total184100.00%2100.00%

EXPORT_SYMBOL_GPL(shmem_add_seals);
int shmem_get_seals(struct file *file) { if (file->f_op != &shmem_file_operations) return -EINVAL; return SHMEM_I(file_inode(file))->seals; }

Contributors

PersonTokensPropCommitsCommitProp
David Herrmann34100.00%1100.00%
Total34100.00%1100.00%

EXPORT_SYMBOL_GPL(shmem_get_seals);
long shmem_fcntl(struct file *file, unsigned int cmd, unsigned long arg) { long error; switch (cmd) { case F_ADD_SEALS: /* disallow upper 32bit */ if (arg > UINT_MAX) return -EINVAL; error = shmem_add_seals(file, arg); break; case F_GET_SEALS: error = shmem_get_seals(file); break; default: error = -EINVAL; break; } return error; }

Contributors

PersonTokensPropCommitsCommitProp
David Herrmann72100.00%1100.00%
Total72100.00%1100.00%


static long shmem_fallocate(struct file *file, int mode, loff_t offset, loff_t len) { struct inode *inode = file_inode(file); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_falloc shmem_falloc; pgoff_t start, index, end; int error; if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) return -EOPNOTSUPP; inode_lock(inode); if (mode & FALLOC_FL_PUNCH_HOLE) { struct address_space *mapping = file->f_mapping; loff_t unmap_start = round_up(offset, PAGE_SIZE); loff_t unmap_end = round_down(offset + len, PAGE_SIZE) - 1; DECLARE_WAIT_QUEUE_HEAD_ONSTACK(shmem_falloc_waitq); /* protected by i_mutex */ if (info->seals & F_SEAL_WRITE) { error = -EPERM; goto out; } shmem_falloc.waitq = &shmem_falloc_waitq; shmem_falloc.start = unmap_start >> PAGE_SHIFT; shmem_falloc.next = (unmap_end + 1) >> PAGE_SHIFT; spin_lock(&inode->i_lock); inode->i_private = &shmem_falloc; spin_unlock(&inode->i_lock); if ((u64)unmap_end > (u64)unmap_start) unmap_mapping_range(mapping, unmap_start, 1 + unmap_end - unmap_start, 0); shmem_truncate_range(inode, offset, offset + len - 1); /* No need to unmap again: hole-punching leaves COWed pages */ spin_lock(&inode->i_lock); inode->i_private = NULL; wake_up_all(&shmem_falloc_waitq); WARN_ON_ONCE(!list_empty(&shmem_falloc_waitq.head)); spin_unlock(&inode->i_lock); error = 0; goto out; } /* We need to check rlimit even when FALLOC_FL_KEEP_SIZE */ error = inode_newsize_ok(inode, offset + len); if (error) goto out; if ((info->seals & F_SEAL_GROW) && offset + len > inode->i_size) { error = -EPERM; goto out; } start = offset >> PAGE_SHIFT; end = (offset + len + PAGE_SIZE - 1) >> PAGE_SHIFT; /* Try to avoid a swapstorm if len is impossible to satisfy */ if (sbinfo->max_blocks && end - start > sbinfo->max_blocks) { error = -ENOSPC; goto out; } shmem_falloc.waitq = NULL; shmem_falloc.start = start; shmem_falloc.next = start; shmem_falloc.nr_falloced = 0; shmem_falloc.nr_unswapped = 0; spin_lock(&inode->i_lock); inode->i_private = &shmem_falloc; spin_unlock(&inode->i_lock); for (index = start; index < end; index++) { struct page *page; /* * Good, the fallocate(2) manpage permits EINTR: we may have * been interrupted because we are using up too much memory. */ if (signal_pending(current)) error = -EINTR; else if (shmem_falloc.nr_unswapped > shmem_falloc.nr_falloced) error = -ENOMEM; else error = shmem_getpage(inode, index, &page, SGP_FALLOC); if (error) { /* Remove the !PageUptodate pages we added */ if (index > start) { shmem_undo_range(inode, (loff_t)start << PAGE_SHIFT, ((loff_t)index << PAGE_SHIFT) - 1, true); } goto undone; } /* * Inform shmem_writepage() how far we have reached. * No need for lock or barrier: we have the page lock. */ shmem_falloc.next++; if (!PageUptodate(page)) shmem_falloc.nr_falloced++; /* * If !PageUptodate, leave it that way so that freeable pages * can be recognized if we need to rollback on error later. * But set_page_dirty so that memory pressure will swap rather * than free the pages we are allocating (and SGP_CACHE pages * might still be clean: we now need to mark those dirty too). */ set_page_dirty(page); unlock_page(page); put_page(page); cond_resched(); } if (!(mode & FALLOC_FL_KEEP_SIZE) && offset + len > inode->i_size) i_size_write(inode, offset + len); inode->i_ctime = current_time(inode); undone: spin_lock(&inode->i_lock); inode->i_private = NULL; spin_unlock(&inode->i_lock); out: inode_unlock(inode); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Hugh Dickins56086.42%850.00%
David Herrmann578.80%16.25%
Linus Torvalds111.70%16.25%
Kirill A. Shutemov60.93%16.25%
Al Viro50.77%212.50%
Deepa Dinamani40.62%16.25%
Anthony Romano40.62%16.25%
Ingo Molnar10.15%16.25%
Total648100.00%16100.00%


static int shmem_statfs(struct dentry *dentry, struct kstatfs *buf) { struct shmem_sb_info *sbinfo = SHMEM_SB(dentry->d_sb); buf->f_type = TMPFS_MAGIC; buf->f_bsize = PAGE_SIZE; buf->f_namelen = NAME_MAX; if (sbinfo->max_blocks) { buf->f_blocks = sbinfo->max_blocks; buf->f_bavail = buf->f_bfree = sbinfo->max_blocks - percpu_counter_sum(&sbinfo->used_blocks); } if (sbinfo->max_inodes) { buf->f_files = sbinfo->max_inodes; buf->f_ffree = sbinfo->free_inodes; } /* else leave those fields 0 like simple_statfs */ return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds7164.55%225.00%
Hugh Dickins2320.91%225.00%
Tim Chen98.18%112.50%
David Howells54.55%112.50%
Andrew Morton10.91%112.50%
Kirill A. Shutemov10.91%112.50%
Total110100.00%8100.00%

/* * File creation. Allocate an inode, and we're done.. */
static int shmem_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { struct inode *inode; int error = -ENOSPC; inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE); if (inode) { error = simple_acl_create(dir, inode); if (error) goto out_iput; error = security_inode_init_security(inode, dir, &dentry->d_name, shmem_initxattrs, NULL); if (error && error != -EOPNOTSUPP) goto out_iput; error = 0; dir->i_size += BOGO_DIRENT_SIZE; dir->i_ctime = dir->i_mtime = current_time(dir); d_instantiate(dentry, inode); dget(dentry); /* Extra count - pin the dentry in core */ } return error; out_iput: iput(inode); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds6140.40%17.69%
Hugh Dickins2214.57%323.08%
Christoph Hellwig2013.25%17.69%
Mimi Zohar159.93%17.69%
Stephen D. Smalley127.95%17.69%
Andreas Gruenbacher95.96%17.69%
Al Viro53.31%215.38%
Deepa Dinamani42.65%17.69%
Dmitriy Monakhov21.32%17.69%
Andries E. Brouwer10.66%17.69%
Total151100.00%13100.00%


static int shmem_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; int error = -ENOSPC; inode = shmem_get_inode(dir->i_sb, dir, mode, 0, VM_NORESERVE); if (inode) { error = security_inode_init_security(inode, dir, NULL, shmem_initxattrs, NULL); if (error && error != -EOPNOTSUPP) goto out_iput; error = simple_acl_create(dir, inode); if (error) goto out_iput; d_tmpfile(dentry, inode); } return error; out_iput: iput(inode); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro9985.34%150.00%
Christoph Hellwig1714.66%150.00%
Total116100.00%2100.00%


static int shmem_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int error; if ((error = shmem_mknod(dir, dentry, mode | S_IFDIR, 0))) return error; inc_nlink(dir); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds4586.54%125.00%
Dave Hansen35.77%125.00%
Linus Torvalds (pre-git)35.77%125.00%
Al Viro11.92%125.00%
Total52100.00%4100.00%


static int shmem_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) { return shmem_mknod(dir, dentry, mode | S_IFREG, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds1850.00%120.00%
Linus Torvalds (pre-git)1438.89%120.00%
Al Viro38.33%240.00%
Trond Myklebust12.78%120.00%
Total36100.00%5100.00%

/* * Link a file.. */
static int shmem_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) { struct inode *inode = d_inode(old_dentry); int ret; /* * No ordinary (disk based) filesystem counts links as inodes; * but each new link needs a new dentry, pinning lowmem, and * tmpfs dentries cannot be pruned until they are unlinked. */ ret = shmem_reserve_inode(inode->i_sb); if (ret) goto out; dir->i_size += BOGO_DIRENT_SIZE; inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode); inc_nlink(inode); ihold(inode); /* New dentry reference */ dget(dentry); /* Extra pinning count for the created dentry */ d_instantiate(dentry, inode); out: return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds6361.17%222.22%
Hugh Dickins1514.56%222.22%
Pavel Emelyanov1413.59%111.11%
Deepa Dinamani43.88%111.11%
Dave Hansen32.91%111.11%
David Howells32.91%111.11%
Al Viro10.97%111.11%
Total103100.00%9100.00%


static int shmem_unlink(struct inode *dir, struct dentry *dentry) { struct inode *inode = d_inode(dentry); if (inode->i_nlink > 1 && !S_ISDIR(inode->i_mode)) shmem_free_inode(inode->i_sb); dir->i_size -= BOGO_DIRENT_SIZE; inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode); drop_nlink(inode); dput(dentry); /* Undo the count from "create" - this does all the work */ return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds2832.56%112.50%
Hugh Dickins2832.56%225.00%
Linus Torvalds (pre-git)1922.09%112.50%
Deepa Dinamani44.65%112.50%
Dave Hansen33.49%112.50%
David Howells33.49%112.50%
Pavel Emelyanov11.16%112.50%
Total86100.00%8100.00%


static int shmem_rmdir(struct inode *dir, struct dentry *dentry) { if (!simple_empty(dentry)) return -ENOTEMPTY; drop_nlink(d_inode(dentry)); drop_nlink(dir); return shmem_unlink(dir, dentry); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)2244.90%116.67%
Linus Torvalds1530.61%116.67%
Dave Hansen612.24%116.67%
David Howells36.12%116.67%
Sergey Vlasov24.08%116.67%
Andrew Morton12.04%116.67%
Total49100.00%6100.00%


static int shmem_exchange(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { bool old_is_dir = d_is_dir(old_dentry); bool new_is_dir = d_is_dir(new_dentry); if (old_dir != new_dir && old_is_dir != new_is_dir) { if (old_is_dir) { drop_nlink(old_dir); inc_nlink(new_dir); } else { drop_nlink(new_dir); inc_nlink(old_dir); } } old_dir->i_ctime = old_dir->i_mtime = new_dir->i_ctime = new_dir->i_mtime = d_inode(old_dentry)->i_ctime = d_inode(new_dentry)->i_ctime = current_time(old_dir); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Miklos Szeredi10990.08%125.00%
David Howells86.61%250.00%
Deepa Dinamani43.31%125.00%
Total121100.00%4100.00%


static int shmem_whiteout(struct inode *old_dir, struct dentry *old_dentry) { struct dentry *whiteout; int error; whiteout = d_alloc(old_dentry->d_parent, &old_dentry->d_name); if (!whiteout) return -ENOMEM; error = shmem_mknod(old_dir, whiteout, S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV); dput(whiteout); if (error) return error; /* * Cheat and hash the whiteout while the old dentry is still in * place, instead of playing games with FS_RENAME_DOES_D_MOVE. * * d_lookup() will consistently find one of them at this point, * not sure which one, but that isn't even important. */ d_rehash(whiteout); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Miklos Szeredi83100.00%1100.00%
Total83100.00%1100.00%

/* * The VFS layer already does all the dentry stuff for rename, * we just have to decrement the usage count for the target if * it exists so that the VFS layer correctly free's it when it * gets overwritten. */
static int shmem_rename2(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { struct inode *inode = d_inode(old_dentry); int they_are_dirs = S_ISDIR(inode->i_mode); if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT)) return -EINVAL; if (flags & RENAME_EXCHANGE) return shmem_exchange(old_dir, old_dentry, new_dir, new_dentry); if (!simple_empty(new_dentry)) return -ENOTEMPTY; if (flags & RENAME_WHITEOUT) { int error; error = shmem_whiteout(old_dir, old_dentry); if (error) return error; } if (d_really_is_positive(new_dentry)) { (void) shmem_unlink(new_dir, new_dentry); if (they_are_dirs) { drop_nlink(d_inode(new_dentry)); drop_nlink(old_dir); } } else if (they_are_dirs) { drop_nlink(old_dir); inc_nlink(new_dir); } old_dir->i_size -= BOGO_DIRENT_SIZE; new_dir->i_size += BOGO_DIRENT_SIZE; old_dir->i_ctime = old_dir->i_mtime = new_dir->i_ctime = new_dir->i_mtime = inode->i_ctime = current_time(old_dir); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Miklos Szeredi7433.79%428.57%
Hugh Dickins7132.42%321.43%
Linus Torvalds2611.87%17.14%
Linus Torvalds (pre-git)2511.42%17.14%
Dave Hansen94.11%214.29%
David Howells94.11%17.14%
Deepa Dinamani41.83%17.14%
Andrew Morton10.46%17.14%
Total219100.00%14100.00%


static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *symname) { int error; int len; struct inode *inode; struct page *page; struct shmem_inode_info *info; len = strlen(symname) + 1; if (len > PAGE_SIZE) return -ENAMETOOLONG; inode = shmem_get_inode(dir->i_sb, dir, S_IFLNK|S_IRWXUGO, 0, VM_NORESERVE); if (!inode) return -ENOSPC; error = security_inode_init_security(inode, dir, &dentry->d_name, shmem_initxattrs, NULL); if (error) { if (error != -EOPNOTSUPP) { iput(inode); return error; } error = 0; } info = SHMEM_I(inode); inode->i_size = len-1; if (len <= SHORT_SYMLINK_LEN) { inode->i_link = kmemdup(symname, len, GFP_KERNEL); if (!inode->i_link) { iput(inode); return -ENOMEM; } inode->i_op = &shmem_short_symlink_operations; } else { inode_nohighmem(inode); error = shmem_getpage(inode, 0, &page, SGP_WRITE); if (error) { iput(inode); return error; } inode->i_mapping->a_ops = &shmem_aops; inode->i_op = &shmem_symlink_inode_operations; memcpy(page_address(page), symname, len); SetPageUptodate(page); set_page_dirty(page); unlock_page(page); put_page(page); } dir->i_size += BOGO_DIRENT_SIZE; dir->i_ctime = dir->i_mtime = current_time(dir); d_instantiate(dentry, inode); dget(dentry); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds10232.59%311.54%
Hugh Dickins6219.81%726.92%
Stephen D. Smalley3912.46%13.85%
Al Viro3812.14%415.38%
Linus Torvalds (pre-git)319.90%13.85%
Christoph Rohland185.75%13.85%
Eric Paris61.92%27.69%
Fengguang Wu51.60%13.85%
Deepa Dinamani41.28%13.85%
Andrew Morton30.96%27.69%
Kirill A. Shutemov20.64%13.85%
Dmitriy Monakhov20.64%13.85%
Jarkko Sakkinen10.32%13.85%
Total313100.00%26100.00%


static void shmem_put_link(void *arg) { mark_page_accessed