]> git.ipfire.org Git - people/arne_f/kernel.git/commitdiff
mm: non-atomically mark page accessed during page cache allocation where possible
authorMel Gorman <mgorman@suse.de>
Wed, 4 Jun 2014 23:10:31 +0000 (16:10 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 30 Jan 2015 01:40:52 +0000 (17:40 -0800)
commit 2457aec63745e235bcafb7ef312b182d8682f0fc upstream.

aops->write_begin may allocate a new page and make it visible only to have
mark_page_accessed called almost immediately after.  Once the page is
visible the atomic operations are necessary which is noticable overhead
when writing to an in-memory filesystem like tmpfs but should also be
noticable with fast storage.  The objective of the patch is to initialse
the accessed information with non-atomic operations before the page is
visible.

The bulk of filesystems directly or indirectly use
grab_cache_page_write_begin or find_or_create_page for the initial
allocation of a page cache page.  This patch adds an init_page_accessed()
helper which behaves like the first call to mark_page_accessed() but may
called before the page is visible and can be done non-atomically.

The primary APIs of concern in this care are the following and are used
by most filesystems.

find_get_page
find_lock_page
find_or_create_page
grab_cache_page_nowait
grab_cache_page_write_begin

All of them are very similar in detail to the patch creates a core helper
pagecache_get_page() which takes a flags parameter that affects its
behavior such as whether the page should be marked accessed or not.  Then
old API is preserved but is basically a thin wrapper around this core
function.

Each of the filesystems are then updated to avoid calling
mark_page_accessed when it is known that the VM interfaces have already
done the job.  There is a slight snag in that the timing of the
mark_page_accessed() has now changed so in rare cases it's possible a page
gets to the end of the LRU as PageReferenced where as previously it might
have been repromoted.  This is expected to be rare but it's worth the
filesystem people thinking about it in case they see a problem with the
timing change.  It is also the case that some filesystems may be marking
pages accessed that previously did not but it makes sense that filesystems
have consistent behaviour in this regard.

The test case used to evaulate this is a simple dd of a large file done
multiple times with the file deleted on each iterations.  The size of the
file is 1/10th physical memory to avoid dirty page balancing.  In the
async case it will be possible that the workload completes without even
hitting the disk and will have variable results but highlight the impact
of mark_page_accessed for async IO.  The sync results are expected to be
more stable.  The exception is tmpfs where the normal case is for the "IO"
to not hit the disk.

The test machine was single socket and UMA to avoid any scheduling or NUMA
artifacts.  Throughput and wall times are presented for sync IO, only wall
times are shown for async as the granularity reported by dd and the
variability is unsuitable for comparison.  As async results were variable
do to writback timings, I'm only reporting the maximum figures.  The sync
results were stable enough to make the mean and stddev uninteresting.

The performance results are reported based on a run with no profiling.
Profile data is based on a separate run with oprofile running.

async dd
                                    3.15.0-rc3            3.15.0-rc3
                                       vanilla           accessed-v2
ext3    Max      elapsed     13.9900 (  0.00%)     11.5900 ( 17.16%)
tmpfs Max      elapsed      0.5100 (  0.00%)      0.4900 (  3.92%)
btrfs   Max      elapsed     12.8100 (  0.00%)     12.7800 (  0.23%)
ext4 Max      elapsed     18.6000 (  0.00%)     13.3400 ( 28.28%)
xfs Max      elapsed     12.5600 (  0.00%)      2.0900 ( 83.36%)

The XFS figure is a bit strange as it managed to avoid a worst case by
sheer luck but the average figures looked reasonable.

        samples percentage
ext3       86107    0.9783  vmlinux-3.15.0-rc4-vanilla        mark_page_accessed
ext3       23833    0.2710  vmlinux-3.15.0-rc4-accessed-v3r25 mark_page_accessed
ext3        5036    0.0573  vmlinux-3.15.0-rc4-accessed-v3r25 init_page_accessed
ext4       64566    0.8961  vmlinux-3.15.0-rc4-vanilla        mark_page_accessed
ext4        5322    0.0713  vmlinux-3.15.0-rc4-accessed-v3r25 mark_page_accessed
ext4        2869    0.0384  vmlinux-3.15.0-rc4-accessed-v3r25 init_page_accessed
xfs        62126    1.7675  vmlinux-3.15.0-rc4-vanilla        mark_page_accessed
xfs         1904    0.0554  vmlinux-3.15.0-rc4-accessed-v3r25 init_page_accessed
xfs          103    0.0030  vmlinux-3.15.0-rc4-accessed-v3r25 mark_page_accessed
btrfs      10655    0.1338  vmlinux-3.15.0-rc4-vanilla        mark_page_accessed
btrfs       2020    0.0273  vmlinux-3.15.0-rc4-accessed-v3r25 init_page_accessed
btrfs        587    0.0079  vmlinux-3.15.0-rc4-accessed-v3r25 mark_page_accessed
tmpfs      59562    3.2628  vmlinux-3.15.0-rc4-vanilla        mark_page_accessed
tmpfs       1210    0.0696  vmlinux-3.15.0-rc4-accessed-v3r25 init_page_accessed
tmpfs         94    0.0054  vmlinux-3.15.0-rc4-accessed-v3r25 mark_page_accessed

[akpm@linux-foundation.org: don't run init_page_accessed() against an uninitialised pointer]
Signed-off-by: Mel Gorman <mgorman@suse.de>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Jan Kara <jack@suse.cz>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: Hugh Dickins <hughd@google.com>
Cc: Dave Hansen <dave.hansen@intel.com>
Cc: Theodore Ts'o <tytso@mit.edu>
Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Tested-by: Prabhakar Lad <prabhakar.csengg@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Mel Gorman <mgorman@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
17 files changed:
fs/btrfs/extent_io.c
fs/btrfs/file.c
fs/buffer.c
fs/ext4/mballoc.c
fs/f2fs/checkpoint.c
fs/f2fs/node.c
fs/fuse/file.c
fs/gfs2/aops.c
fs/gfs2/meta_io.c
fs/ntfs/attrib.c
fs/ntfs/file.c
include/linux/page-flags.h
include/linux/pagemap.h
include/linux/swap.h
mm/filemap.c
mm/shmem.c
mm/swap.c

index 1a858947006e942199d0fe9fb291f36e0ef27fa3..fa9f90049099f244af8e3f5ca307ca8979fff6b2 100644 (file)
@@ -4507,7 +4507,8 @@ static void check_buffer_tree_ref(struct extent_buffer *eb)
        spin_unlock(&eb->refs_lock);
 }
 
-static void mark_extent_buffer_accessed(struct extent_buffer *eb)
+static void mark_extent_buffer_accessed(struct extent_buffer *eb,
+               struct page *accessed)
 {
        unsigned long num_pages, i;
 
@@ -4516,7 +4517,8 @@ static void mark_extent_buffer_accessed(struct extent_buffer *eb)
        num_pages = num_extent_pages(eb->start, eb->len);
        for (i = 0; i < num_pages; i++) {
                struct page *p = extent_buffer_page(eb, i);
-               mark_page_accessed(p);
+               if (p != accessed)
+                       mark_page_accessed(p);
        }
 }
 
@@ -4530,7 +4532,7 @@ struct extent_buffer *find_extent_buffer(struct btrfs_fs_info *fs_info,
                               start >> PAGE_CACHE_SHIFT);
        if (eb && atomic_inc_not_zero(&eb->refs)) {
                rcu_read_unlock();
-               mark_extent_buffer_accessed(eb);
+               mark_extent_buffer_accessed(eb, NULL);
                return eb;
        }
        rcu_read_unlock();
@@ -4578,7 +4580,7 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info,
                                spin_unlock(&mapping->private_lock);
                                unlock_page(p);
                                page_cache_release(p);
-                               mark_extent_buffer_accessed(exists);
+                               mark_extent_buffer_accessed(exists, p);
                                goto free_eb;
                        }
 
@@ -4593,7 +4595,6 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info,
                attach_extent_buffer_page(eb, p);
                spin_unlock(&mapping->private_lock);
                WARN_ON(PageDirty(p));
-               mark_page_accessed(p);
                eb->pages[i] = p;
                if (!PageUptodate(p))
                        uptodate = 0;
index f6d00df99a8c458bbbc1144b054efd0eec07c746..279b06ef5522f70e07bbb401cfb0babc2dced37d 100644 (file)
@@ -470,11 +470,12 @@ static void btrfs_drop_pages(struct page **pages, size_t num_pages)
        for (i = 0; i < num_pages; i++) {
                /* page checked is some magic around finding pages that
                 * have been modified without going through btrfs_set_page_dirty
-                * clear it here
+                * clear it here. There should be no need to mark the pages
+                * accessed as prepare_pages should have marked them accessed
+                * in prepare_pages via find_or_create_page()
                 */
                ClearPageChecked(pages[i]);
                unlock_page(pages[i]);
-               mark_page_accessed(pages[i]);
                page_cache_release(pages[i]);
        }
 }
index 9c16cc70dd5da5433a89110df9ea6a40726b42d0..eef21c69f2d748af45bbd660284f455651e48944 100644 (file)
@@ -227,7 +227,7 @@ __find_get_block_slow(struct block_device *bdev, sector_t block)
        int all_mapped = 1;
 
        index = block >> (PAGE_CACHE_SHIFT - bd_inode->i_blkbits);
-       page = find_get_page(bd_mapping, index);
+       page = find_get_page_flags(bd_mapping, index, FGP_ACCESSED);
        if (!page)
                goto out;
 
@@ -1368,12 +1368,13 @@ __find_get_block(struct block_device *bdev, sector_t block, unsigned size)
        struct buffer_head *bh = lookup_bh_lru(bdev, block, size);
 
        if (bh == NULL) {
+               /* __find_get_block_slow will mark the page accessed */
                bh = __find_get_block_slow(bdev, block);
                if (bh)
                        bh_lru_install(bh);
-       }
-       if (bh)
+       } else
                touch_buffer(bh);
+
        return bh;
 }
 EXPORT_SYMBOL(__find_get_block);
index 242226a87be7430788f8f6aaf4230a87a2b034b0..7620133f78bfc69dadbeb67f3b013aae2a978735 100644 (file)
@@ -1044,6 +1044,8 @@ int ext4_mb_init_group(struct super_block *sb, ext4_group_t group)
         * allocating. If we are looking at the buddy cache we would
         * have taken a reference using ext4_mb_load_buddy and that
         * would have pinned buddy page to page cache.
+        * The call to ext4_mb_get_buddy_page_lock will mark the
+        * page accessed.
         */
        ret = ext4_mb_get_buddy_page_lock(sb, group, &e4b);
        if (ret || !EXT4_MB_GRP_NEED_INIT(this_grp)) {
@@ -1062,7 +1064,6 @@ int ext4_mb_init_group(struct super_block *sb, ext4_group_t group)
                ret = -EIO;
                goto err;
        }
-       mark_page_accessed(page);
 
        if (e4b.bd_buddy_page == NULL) {
                /*
@@ -1082,7 +1083,6 @@ int ext4_mb_init_group(struct super_block *sb, ext4_group_t group)
                ret = -EIO;
                goto err;
        }
-       mark_page_accessed(page);
 err:
        ext4_mb_put_buddy_page_lock(&e4b);
        return ret;
@@ -1141,7 +1141,7 @@ ext4_mb_load_buddy(struct super_block *sb, ext4_group_t group,
 
        /* we could use find_or_create_page(), but it locks page
         * what we'd like to avoid in fast path ... */
-       page = find_get_page(inode->i_mapping, pnum);
+       page = find_get_page_flags(inode->i_mapping, pnum, FGP_ACCESSED);
        if (page == NULL || !PageUptodate(page)) {
                if (page)
                        /*
@@ -1172,15 +1172,16 @@ ext4_mb_load_buddy(struct super_block *sb, ext4_group_t group,
                ret = -EIO;
                goto err;
        }
+
+       /* Pages marked accessed already */
        e4b->bd_bitmap_page = page;
        e4b->bd_bitmap = page_address(page) + (poff * sb->s_blocksize);
-       mark_page_accessed(page);
 
        block++;
        pnum = block / blocks_per_page;
        poff = block % blocks_per_page;
 
-       page = find_get_page(inode->i_mapping, pnum);
+       page = find_get_page_flags(inode->i_mapping, pnum, FGP_ACCESSED);
        if (page == NULL || !PageUptodate(page)) {
                if (page)
                        page_cache_release(page);
@@ -1201,9 +1202,10 @@ ext4_mb_load_buddy(struct super_block *sb, ext4_group_t group,
                ret = -EIO;
                goto err;
        }
+
+       /* Pages marked accessed already */
        e4b->bd_buddy_page = page;
        e4b->bd_buddy = page_address(page) + (poff * sb->s_blocksize);
-       mark_page_accessed(page);
 
        BUG_ON(e4b->bd_bitmap_page == NULL);
        BUG_ON(e4b->bd_buddy_page == NULL);
index 293d0486a40f7e8560b25d012a7a2398ef4e955c..5c6fe278fb63942e2c1e5743d467ad41147852c6 100644 (file)
@@ -71,7 +71,6 @@ repeat:
                goto repeat;
        }
 out:
-       mark_page_accessed(page);
        return page;
 }
 
index b0649b76eb4f390bea393344af07a9e65dd32211..bb6478acb369fdf550ce130e80a779d58534ac72 100644 (file)
@@ -969,7 +969,6 @@ repeat:
        }
 got_it:
        f2fs_bug_on(nid != nid_of_node(page));
-       mark_page_accessed(page);
        return page;
 }
 
@@ -1024,7 +1023,6 @@ page_hit:
                f2fs_put_page(page, 1);
                return ERR_PTR(-EIO);
        }
-       mark_page_accessed(page);
        return page;
 }
 
index a91d3b4d32f36c55aee5e860f89ea048f42acf6c..d8a60270581c7ff9bc434627e64de6882c411abe 100644 (file)
@@ -1006,8 +1006,6 @@ static ssize_t fuse_fill_write_pages(struct fuse_req *req,
                tmp = iov_iter_copy_from_user_atomic(page, ii, offset, bytes);
                flush_dcache_page(page);
 
-               mark_page_accessed(page);
-
                if (!tmp) {
                        unlock_page(page);
                        page_cache_release(page);
index 49436fa7cd4fdcf87fb9f9dae13743173df2ab52..4ccb60d943bb1a91139ab5c6f2f49394a18ab720 100644 (file)
@@ -517,7 +517,6 @@ int gfs2_internal_read(struct gfs2_inode *ip, char *buf, loff_t *pos,
                p = kmap_atomic(page);
                memcpy(buf + copied, p + offset, amt);
                kunmap_atomic(p);
-               mark_page_accessed(page);
                page_cache_release(page);
                copied += amt;
                index++;
index b82a9c99e18b6dd7dbcda16718318df4a3b6bf82..e7b149614f5ed17eb64dc983c08b118cf1098b09 100644 (file)
@@ -136,7 +136,8 @@ struct buffer_head *gfs2_getbuf(struct gfs2_glock *gl, u64 blkno, int create)
                        yield();
                }
        } else {
-               page = find_lock_page(mapping, index);
+               page = find_get_page_flags(mapping, index,
+                                               FGP_LOCK|FGP_ACCESSED);
                if (!page)
                        return NULL;
        }
@@ -153,7 +154,6 @@ struct buffer_head *gfs2_getbuf(struct gfs2_glock *gl, u64 blkno, int create)
                map_bh(bh, sdp->sd_vfs, blkno);
 
        unlock_page(page);
-       mark_page_accessed(page);
        page_cache_release(page);
 
        return bh;
index a27e3fecefaf817fc03c8a4cd57eae1969cdbf5f..250ed5b20c8fbed1b9afcf19154a088983986527 100644 (file)
@@ -1748,7 +1748,6 @@ int ntfs_attr_make_non_resident(ntfs_inode *ni, const u32 data_size)
        if (page) {
                set_page_dirty(page);
                unlock_page(page);
-               mark_page_accessed(page);
                page_cache_release(page);
        }
        ntfs_debug("Done.");
index db9bd8a31725477eb9130bb6868d25bda510aa4a..86ddab916b6607e3cab28c276359b8b98971a46c 100644 (file)
@@ -2060,7 +2060,6 @@ static ssize_t ntfs_file_buffered_write(struct kiocb *iocb,
                }
                do {
                        unlock_page(pages[--do_pages]);
-                       mark_page_accessed(pages[do_pages]);
                        page_cache_release(pages[do_pages]);
                } while (do_pages);
                if (unlikely(status))
index cffeaa9aea21559e48a0a5d9f5fcffef9cf6daf4..3c545b48aeabdd177a09920fceda7b550baae54d 100644 (file)
@@ -198,6 +198,7 @@ struct page;        /* forward declaration */
 TESTPAGEFLAG(Locked, locked)
 PAGEFLAG(Error, error) TESTCLEARFLAG(Error, error)
 PAGEFLAG(Referenced, referenced) TESTCLEARFLAG(Referenced, referenced)
+       __SETPAGEFLAG(Referenced, referenced)
 PAGEFLAG(Dirty, dirty) TESTSCFLAG(Dirty, dirty) __CLEARPAGEFLAG(Dirty, dirty)
 PAGEFLAG(LRU, lru) __CLEARPAGEFLAG(LRU, lru)
 PAGEFLAG(Active, active) __CLEARPAGEFLAG(Active, active)
index 0ab50001137751e6eda34d391f5ce323858fb995..ec054b4917dff6c4a0bb1070e8563ce75c9394c5 100644 (file)
@@ -248,12 +248,109 @@ pgoff_t page_cache_next_hole(struct address_space *mapping,
 pgoff_t page_cache_prev_hole(struct address_space *mapping,
                             pgoff_t index, unsigned long max_scan);
 
+#define FGP_ACCESSED           0x00000001
+#define FGP_LOCK               0x00000002
+#define FGP_CREAT              0x00000004
+#define FGP_WRITE              0x00000008
+#define FGP_NOFS               0x00000010
+#define FGP_NOWAIT             0x00000020
+
+struct page *pagecache_get_page(struct address_space *mapping, pgoff_t offset,
+               int fgp_flags, gfp_t cache_gfp_mask, gfp_t radix_gfp_mask);
+
+/**
+ * find_get_page - find and get a page reference
+ * @mapping: the address_space to search
+ * @offset: the page index
+ *
+ * Looks up the page cache slot at @mapping & @offset.  If there is a
+ * page cache page, it is returned with an increased refcount.
+ *
+ * Otherwise, %NULL is returned.
+ */
+static inline struct page *find_get_page(struct address_space *mapping,
+                                       pgoff_t offset)
+{
+       return pagecache_get_page(mapping, offset, 0, 0, 0);
+}
+
+static inline struct page *find_get_page_flags(struct address_space *mapping,
+                                       pgoff_t offset, int fgp_flags)
+{
+       return pagecache_get_page(mapping, offset, fgp_flags, 0, 0);
+}
+
+/**
+ * find_lock_page - locate, pin and lock a pagecache page
+ * pagecache_get_page - find and get a page reference
+ * @mapping: the address_space to search
+ * @offset: the page index
+ *
+ * Looks up the page cache slot at @mapping & @offset.  If there is a
+ * page cache page, it is returned locked and with an increased
+ * refcount.
+ *
+ * Otherwise, %NULL is returned.
+ *
+ * find_lock_page() may sleep.
+ */
+static inline struct page *find_lock_page(struct address_space *mapping,
+                                       pgoff_t offset)
+{
+       return pagecache_get_page(mapping, offset, FGP_LOCK, 0, 0);
+}
+
+/**
+ * find_or_create_page - locate or add a pagecache page
+ * @mapping: the page's address_space
+ * @index: the page's index into the mapping
+ * @gfp_mask: page allocation mode
+ *
+ * Looks up the page cache slot at @mapping & @offset.  If there is a
+ * page cache page, it is returned locked and with an increased
+ * refcount.
+ *
+ * If the page is not present, a new page is allocated using @gfp_mask
+ * and added to the page cache and the VM's LRU list.  The page is
+ * returned locked and with an increased refcount.
+ *
+ * On memory exhaustion, %NULL is returned.
+ *
+ * find_or_create_page() may sleep, even if @gfp_flags specifies an
+ * atomic allocation!
+ */
+static inline struct page *find_or_create_page(struct address_space *mapping,
+                                       pgoff_t offset, gfp_t gfp_mask)
+{
+       return pagecache_get_page(mapping, offset,
+                                       FGP_LOCK|FGP_ACCESSED|FGP_CREAT,
+                                       gfp_mask, gfp_mask & GFP_RECLAIM_MASK);
+}
+
+/**
+ * grab_cache_page_nowait - returns locked page at given index in given cache
+ * @mapping: target address_space
+ * @index: the page index
+ *
+ * Same as grab_cache_page(), but do not wait if the page is unavailable.
+ * This is intended for speculative data generators, where the data can
+ * be regenerated if the page couldn't be grabbed.  This routine should
+ * be safe to call while holding the lock for another page.
+ *
+ * Clear __GFP_FS when allocating the page to avoid recursion into the fs
+ * and deadlock against the caller's locked page.
+ */
+static inline struct page *grab_cache_page_nowait(struct address_space *mapping,
+                               pgoff_t index)
+{
+       return pagecache_get_page(mapping, index,
+                       FGP_LOCK|FGP_CREAT|FGP_NOFS|FGP_NOWAIT,
+                       mapping_gfp_mask(mapping),
+                       GFP_NOFS);
+}
+
 struct page *find_get_entry(struct address_space *mapping, pgoff_t offset);
-struct page *find_get_page(struct address_space *mapping, pgoff_t offset);
 struct page *find_lock_entry(struct address_space *mapping, pgoff_t offset);
-struct page *find_lock_page(struct address_space *mapping, pgoff_t offset);
-struct page *find_or_create_page(struct address_space *mapping, pgoff_t index,
-                                gfp_t gfp_mask);
 unsigned find_get_entries(struct address_space *mapping, pgoff_t start,
                          unsigned int nr_entries, struct page **entries,
                          pgoff_t *indices);
@@ -276,8 +373,6 @@ static inline struct page *grab_cache_page(struct address_space *mapping,
        return find_or_create_page(mapping, index, mapping_gfp_mask(mapping));
 }
 
-extern struct page * grab_cache_page_nowait(struct address_space *mapping,
-                               pgoff_t index);
 extern struct page * read_cache_page(struct address_space *mapping,
                                pgoff_t index, filler_t *filler, void *data);
 extern struct page * read_cache_page_gfp(struct address_space *mapping,
index c8beed191dd175ae0bddeddeb082c957f7a08883..241bf092277067bacdfc8e9c5e50b25b44807cd5 100644 (file)
@@ -275,6 +275,7 @@ extern void lru_add_page_tail(struct page *page, struct page *page_tail,
                         struct lruvec *lruvec, struct list_head *head);
 extern void activate_page(struct page *);
 extern void mark_page_accessed(struct page *);
+extern void init_page_accessed(struct page *page);
 extern void lru_add_drain(void);
 extern void lru_add_drain_cpu(int cpu);
 extern void lru_add_drain_all(void);
index bdaa21555abe7dd3057a059da62eba98a9902032..b170a68fad355776a92bac824db909ccc6b03f84 100644 (file)
@@ -847,26 +847,6 @@ out:
 }
 EXPORT_SYMBOL(find_get_entry);
 
-/**
- * find_get_page - find and get a page reference
- * @mapping: the address_space to search
- * @offset: the page index
- *
- * Looks up the page cache slot at @mapping & @offset.  If there is a
- * page cache page, it is returned with an increased refcount.
- *
- * Otherwise, %NULL is returned.
- */
-struct page *find_get_page(struct address_space *mapping, pgoff_t offset)
-{
-       struct page *page = find_get_entry(mapping, offset);
-
-       if (radix_tree_exceptional_entry(page))
-               page = NULL;
-       return page;
-}
-EXPORT_SYMBOL(find_get_page);
-
 /**
  * find_lock_entry - locate, pin and lock a page cache entry
  * @mapping: the address_space to search
@@ -904,66 +884,84 @@ repeat:
 EXPORT_SYMBOL(find_lock_entry);
 
 /**
- * find_lock_page - locate, pin and lock a pagecache page
+ * pagecache_get_page - find and get a page reference
  * @mapping: the address_space to search
  * @offset: the page index
+ * @fgp_flags: PCG flags
+ * @gfp_mask: gfp mask to use if a page is to be allocated
  *
- * Looks up the page cache slot at @mapping & @offset.  If there is a
- * page cache page, it is returned locked and with an increased
- * refcount.
+ * Looks up the page cache slot at @mapping & @offset.
  *
- * Otherwise, %NULL is returned.
+ * PCG flags modify how the page is returned
  *
- * find_lock_page() may sleep.
- */
-struct page *find_lock_page(struct address_space *mapping, pgoff_t offset)
-{
-       struct page *page = find_lock_entry(mapping, offset);
-
-       if (radix_tree_exceptional_entry(page))
-               page = NULL;
-       return page;
-}
-EXPORT_SYMBOL(find_lock_page);
-
-/**
- * find_or_create_page - locate or add a pagecache page
- * @mapping: the page's address_space
- * @index: the page's index into the mapping
- * @gfp_mask: page allocation mode
- *
- * Looks up the page cache slot at @mapping & @offset.  If there is a
- * page cache page, it is returned locked and with an increased
- * refcount.
- *
- * If the page is not present, a new page is allocated using @gfp_mask
- * and added to the page cache and the VM's LRU list.  The page is
- * returned locked and with an increased refcount.
+ * FGP_ACCESSED: the page will be marked accessed
+ * FGP_LOCK: Page is return locked
+ * FGP_CREAT: If page is not present then a new page is allocated using
+ *             @gfp_mask and added to the page cache and the VM's LRU
+ *             list. The page is returned locked and with an increased
+ *             refcount. Otherwise, %NULL is returned.
  *
- * On memory exhaustion, %NULL is returned.
+ * If FGP_LOCK or FGP_CREAT are specified then the function may sleep even
+ * if the GFP flags specified for FGP_CREAT are atomic.
  *
- * find_or_create_page() may sleep, even if @gfp_flags specifies an
- * atomic allocation!
+ * If there is a page cache page, it is returned with an increased refcount.
  */
-struct page *find_or_create_page(struct address_space *mapping,
-               pgoff_t index, gfp_t gfp_mask)
+struct page *pagecache_get_page(struct address_space *mapping, pgoff_t offset,
+       int fgp_flags, gfp_t cache_gfp_mask, gfp_t radix_gfp_mask)
 {
        struct page *page;
-       int err;
+
 repeat:
-       page = find_lock_page(mapping, index);
-       if (!page) {
-               page = __page_cache_alloc(gfp_mask);
+       page = find_get_entry(mapping, offset);
+       if (radix_tree_exceptional_entry(page))
+               page = NULL;
+       if (!page)
+               goto no_page;
+
+       if (fgp_flags & FGP_LOCK) {
+               if (fgp_flags & FGP_NOWAIT) {
+                       if (!trylock_page(page)) {
+                               page_cache_release(page);
+                               return NULL;
+                       }
+               } else {
+                       lock_page(page);
+               }
+
+               /* Has the page been truncated? */
+               if (unlikely(page->mapping != mapping)) {
+                       unlock_page(page);
+                       page_cache_release(page);
+                       goto repeat;
+               }
+               VM_BUG_ON(page->index != offset);
+       }
+
+       if (page && (fgp_flags & FGP_ACCESSED))
+               mark_page_accessed(page);
+
+no_page:
+       if (!page && (fgp_flags & FGP_CREAT)) {
+               int err;
+               if ((fgp_flags & FGP_WRITE) && mapping_cap_account_dirty(mapping))
+                       cache_gfp_mask |= __GFP_WRITE;
+               if (fgp_flags & FGP_NOFS) {
+                       cache_gfp_mask &= ~__GFP_FS;
+                       radix_gfp_mask &= ~__GFP_FS;
+               }
+
+               page = __page_cache_alloc(cache_gfp_mask);
                if (!page)
                        return NULL;
-               /*
-                * We want a regular kernel memory (not highmem or DMA etc)
-                * allocation for the radix tree nodes, but we need to honour
-                * the context-specific requirements the caller has asked for.
-                * GFP_RECLAIM_MASK collects those requirements.
-                */
-               err = add_to_page_cache_lru(page, mapping, index,
-                       (gfp_mask & GFP_RECLAIM_MASK));
+
+               if (WARN_ON_ONCE(!(fgp_flags & FGP_LOCK)))
+                       fgp_flags |= FGP_LOCK;
+
+               /* Init accessed so avoit atomic mark_page_accessed later */
+               if (fgp_flags & FGP_ACCESSED)
+                       init_page_accessed(page);
+
+               err = add_to_page_cache_lru(page, mapping, offset, radix_gfp_mask);
                if (unlikely(err)) {
                        page_cache_release(page);
                        page = NULL;
@@ -971,9 +969,10 @@ repeat:
                                goto repeat;
                }
        }
+
        return page;
 }
-EXPORT_SYMBOL(find_or_create_page);
+EXPORT_SYMBOL(pagecache_get_page);
 
 /**
  * find_get_entries - gang pagecache lookup
@@ -1263,39 +1262,6 @@ repeat:
 }
 EXPORT_SYMBOL(find_get_pages_tag);
 
-/**
- * grab_cache_page_nowait - returns locked page at given index in given cache
- * @mapping: target address_space
- * @index: the page index
- *
- * Same as grab_cache_page(), but do not wait if the page is unavailable.
- * This is intended for speculative data generators, where the data can
- * be regenerated if the page couldn't be grabbed.  This routine should
- * be safe to call while holding the lock for another page.
- *
- * Clear __GFP_FS when allocating the page to avoid recursion into the fs
- * and deadlock against the caller's locked page.
- */
-struct page *
-grab_cache_page_nowait(struct address_space *mapping, pgoff_t index)
-{
-       struct page *page = find_get_page(mapping, index);
-
-       if (page) {
-               if (trylock_page(page))
-                       return page;
-               page_cache_release(page);
-               return NULL;
-       }
-       page = __page_cache_alloc(mapping_gfp_mask(mapping) & ~__GFP_FS);
-       if (page && add_to_page_cache_lru(page, mapping, index, GFP_NOFS)) {
-               page_cache_release(page);
-               page = NULL;
-       }
-       return page;
-}
-EXPORT_SYMBOL(grab_cache_page_nowait);
-
 /*
  * CD/DVDs are error prone. When a medium error occurs, the driver may fail
  * a _large_ part of the i/o request. Imagine the worst scenario:
@@ -2397,7 +2363,6 @@ int pagecache_write_end(struct file *file, struct address_space *mapping,
 {
        const struct address_space_operations *aops = mapping->a_ops;
 
-       mark_page_accessed(page);
        return aops->write_end(file, mapping, pos, len, copied, page, fsdata);
 }
 EXPORT_SYMBOL(pagecache_write_end);
@@ -2479,34 +2444,18 @@ EXPORT_SYMBOL(generic_file_direct_write);
 struct page *grab_cache_page_write_begin(struct address_space *mapping,
                                        pgoff_t index, unsigned flags)
 {
-       int status;
-       gfp_t gfp_mask;
        struct page *page;
-       gfp_t gfp_notmask = 0;
+       int fgp_flags = FGP_LOCK|FGP_ACCESSED|FGP_WRITE|FGP_CREAT;
 
-       gfp_mask = mapping_gfp_mask(mapping);
-       if (mapping_cap_account_dirty(mapping))
-               gfp_mask |= __GFP_WRITE;
        if (flags & AOP_FLAG_NOFS)
-               gfp_notmask = __GFP_FS;
-repeat:
-       page = find_lock_page(mapping, index);
+               fgp_flags |= FGP_NOFS;
+
+       page = pagecache_get_page(mapping, index, fgp_flags,
+                       mapping_gfp_mask(mapping),
+                       GFP_KERNEL);
        if (page)
-               goto found;
+               wait_for_stable_page(page);
 
-       page = __page_cache_alloc(gfp_mask & ~gfp_notmask);
-       if (!page)
-               return NULL;
-       status = add_to_page_cache_lru(page, mapping, index,
-                                               GFP_KERNEL & ~gfp_notmask);
-       if (unlikely(status)) {
-               page_cache_release(page);
-               if (status == -EEXIST)
-                       goto repeat;
-               return NULL;
-       }
-found:
-       wait_for_stable_page(page);
        return page;
 }
 EXPORT_SYMBOL(grab_cache_page_write_begin);
@@ -2555,7 +2504,7 @@ again:
 
                status = a_ops->write_begin(file, mapping, pos, bytes, flags,
                                                &page, &fsdata);
-               if (unlikely(status))
+               if (unlikely(status < 0))
                        break;
 
                if (mapping_writably_mapped(mapping))
@@ -2564,7 +2513,6 @@ again:
                copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
                flush_dcache_page(page);
 
-               mark_page_accessed(page);
                status = a_ops->write_end(file, mapping, pos, bytes, copied,
                                                page, fsdata);
                if (unlikely(status < 0))
index 706331e9ee0ae473f87e245e63f77d6ac1a9851a..fe1c488012bc417be9b7194972e72cc951757a04 100644 (file)
@@ -1438,9 +1438,13 @@ shmem_write_begin(struct file *file, struct address_space *mapping,
                        loff_t pos, unsigned len, unsigned flags,
                        struct page **pagep, void **fsdata)
 {
+       int ret;
        struct inode *inode = mapping->host;
        pgoff_t index = pos >> PAGE_CACHE_SHIFT;
-       return shmem_getpage(inode, index, pagep, SGP_WRITE, NULL);
+       ret = shmem_getpage(inode, index, pagep, SGP_WRITE, NULL);
+       if (ret == 0 && *pagep)
+               init_page_accessed(*pagep);
+       return ret;
 }
 
 static int
index 9649d99db5f925cd4bf3423fee5d6e034d1ad0bc..674af557f4f66d0a3be91116090c4a80eb8ad035 100644 (file)
--- a/mm/swap.c
+++ b/mm/swap.c
@@ -580,6 +580,17 @@ void mark_page_accessed(struct page *page)
 }
 EXPORT_SYMBOL(mark_page_accessed);
 
+/*
+ * Used to mark_page_accessed(page) that is not visible yet and when it is
+ * still safe to use non-atomic ops
+ */
+void init_page_accessed(struct page *page)
+{
+       if (!PageReferenced(page))
+               __SetPageReferenced(page);
+}
+EXPORT_SYMBOL(init_page_accessed);
+
 static void __lru_cache_add(struct page *page)
 {
        struct pagevec *pvec = &get_cpu_var(lru_add_pvec);