Commit f9fe48be authored by Ross Zwisler's avatar Ross Zwisler Committed by Linus Torvalds

dax: support dirty DAX entries in radix tree

Add support for tracking dirty DAX entries in the struct address_space
radix tree.  This tree is already used for dirty page writeback, and it
already supports the use of exceptional (non struct page*) entries.

In order to properly track dirty DAX pages we will insert new
exceptional entries into the radix tree that represent dirty DAX PTE or
PMD pages.  These exceptional entries will also contain the writeback
addresses for the PTE or PMD faults that we can use at fsync/msync time.

There are currently two types of exceptional entries (shmem and shadow)
that can be placed into the radix tree, and this adds a third.  We rely
on the fact that only one type of exceptional entry can be found in a
given radix tree based on its usage.  This happens for free with DAX vs
shmem but we explicitly prevent shadow entries from being added to radix
trees for DAX mappings.

The only shadow entries that would be generated for DAX radix trees
would be to track zero page mappings that were created for holes.  These
pages would receive minimal benefit from having shadow entries, and the
choice to have only one type of exceptional entry in a given radix tree
makes the logic simpler both in clear_exceptional_entry() and in the
rest of DAX.
Signed-off-by: default avatarRoss Zwisler <ross.zwisler@linux.intel.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: "J. Bruce Fields" <bfields@fieldses.org>
Cc: "Theodore Ts'o" <tytso@mit.edu>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Andreas Dilger <adilger.kernel@dilger.ca>
Cc: Dave Chinner <david@fromorbit.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jan Kara <jack@suse.com>
Cc: Jeff Layton <jlayton@poochiereds.net>
Cc: Matthew Wilcox <willy@linux.intel.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Matthew Wilcox <matthew.r.wilcox@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Hugh Dickins <hughd@google.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 3f4a2670
...@@ -75,7 +75,7 @@ void kill_bdev(struct block_device *bdev) ...@@ -75,7 +75,7 @@ void kill_bdev(struct block_device *bdev)
{ {
struct address_space *mapping = bdev->bd_inode->i_mapping; struct address_space *mapping = bdev->bd_inode->i_mapping;
if (mapping->nrpages == 0 && mapping->nrshadows == 0) if (mapping->nrpages == 0 && mapping->nrexceptional == 0)
return; return;
invalidate_bh_lrus(); invalidate_bh_lrus();
......
...@@ -495,7 +495,7 @@ void clear_inode(struct inode *inode) ...@@ -495,7 +495,7 @@ void clear_inode(struct inode *inode)
*/ */
spin_lock_irq(&inode->i_data.tree_lock); spin_lock_irq(&inode->i_data.tree_lock);
BUG_ON(inode->i_data.nrpages); BUG_ON(inode->i_data.nrpages);
BUG_ON(inode->i_data.nrshadows); BUG_ON(inode->i_data.nrexceptional);
spin_unlock_irq(&inode->i_data.tree_lock); spin_unlock_irq(&inode->i_data.tree_lock);
BUG_ON(!list_empty(&inode->i_data.private_list)); BUG_ON(!list_empty(&inode->i_data.private_list));
BUG_ON(!(inode->i_state & I_FREEING)); BUG_ON(!(inode->i_state & I_FREEING));
......
...@@ -36,4 +36,9 @@ static inline bool vma_is_dax(struct vm_area_struct *vma) ...@@ -36,4 +36,9 @@ static inline bool vma_is_dax(struct vm_area_struct *vma)
{ {
return vma->vm_file && IS_DAX(vma->vm_file->f_mapping->host); return vma->vm_file && IS_DAX(vma->vm_file->f_mapping->host);
} }
static inline bool dax_mapping(struct address_space *mapping)
{
return mapping->host && IS_DAX(mapping->host);
}
#endif #endif
...@@ -433,7 +433,8 @@ struct address_space { ...@@ -433,7 +433,8 @@ struct address_space {
struct rw_semaphore i_mmap_rwsem; /* protect tree, count, list */ struct rw_semaphore i_mmap_rwsem; /* protect tree, count, list */
/* Protected by tree_lock together with the radix tree */ /* Protected by tree_lock together with the radix tree */
unsigned long nrpages; /* number of total pages */ unsigned long nrpages; /* number of total pages */
unsigned long nrshadows; /* number of shadow entries */ /* number of shadow or DAX exceptional entries */
unsigned long nrexceptional;
pgoff_t writeback_index;/* writeback starts here */ pgoff_t writeback_index;/* writeback starts here */
const struct address_space_operations *a_ops; /* methods */ const struct address_space_operations *a_ops; /* methods */
unsigned long flags; /* error bits/gfp mask */ unsigned long flags; /* error bits/gfp mask */
......
...@@ -51,6 +51,15 @@ ...@@ -51,6 +51,15 @@
#define RADIX_TREE_EXCEPTIONAL_ENTRY 2 #define RADIX_TREE_EXCEPTIONAL_ENTRY 2
#define RADIX_TREE_EXCEPTIONAL_SHIFT 2 #define RADIX_TREE_EXCEPTIONAL_SHIFT 2
#define RADIX_DAX_MASK 0xf
#define RADIX_DAX_SHIFT 4
#define RADIX_DAX_PTE (0x4 | RADIX_TREE_EXCEPTIONAL_ENTRY)
#define RADIX_DAX_PMD (0x8 | RADIX_TREE_EXCEPTIONAL_ENTRY)
#define RADIX_DAX_TYPE(entry) ((unsigned long)entry & RADIX_DAX_MASK)
#define RADIX_DAX_SECTOR(entry) (((unsigned long)entry >> RADIX_DAX_SHIFT))
#define RADIX_DAX_ENTRY(sector, pmd) ((void *)((unsigned long)sector << \
RADIX_DAX_SHIFT | (pmd ? RADIX_DAX_PMD : RADIX_DAX_PTE)))
static inline int radix_tree_is_indirect_ptr(void *ptr) static inline int radix_tree_is_indirect_ptr(void *ptr)
{ {
return (int)((unsigned long)ptr & RADIX_TREE_INDIRECT_PTR); return (int)((unsigned long)ptr & RADIX_TREE_INDIRECT_PTR);
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
*/ */
#include <linux/export.h> #include <linux/export.h>
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/dax.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/capability.h> #include <linux/capability.h>
...@@ -123,9 +124,9 @@ static void page_cache_tree_delete(struct address_space *mapping, ...@@ -123,9 +124,9 @@ static void page_cache_tree_delete(struct address_space *mapping,
__radix_tree_lookup(&mapping->page_tree, page->index, &node, &slot); __radix_tree_lookup(&mapping->page_tree, page->index, &node, &slot);
if (shadow) { if (shadow) {
mapping->nrshadows++; mapping->nrexceptional++;
/* /*
* Make sure the nrshadows update is committed before * Make sure the nrexceptional update is committed before
* the nrpages update so that final truncate racing * the nrpages update so that final truncate racing
* with reclaim does not see both counters 0 at the * with reclaim does not see both counters 0 at the
* same time and miss a shadow entry. * same time and miss a shadow entry.
...@@ -579,9 +580,13 @@ static int page_cache_tree_insert(struct address_space *mapping, ...@@ -579,9 +580,13 @@ static int page_cache_tree_insert(struct address_space *mapping,
p = radix_tree_deref_slot_protected(slot, &mapping->tree_lock); p = radix_tree_deref_slot_protected(slot, &mapping->tree_lock);
if (!radix_tree_exceptional_entry(p)) if (!radix_tree_exceptional_entry(p))
return -EEXIST; return -EEXIST;
if (WARN_ON(dax_mapping(mapping)))
return -EINVAL;
if (shadowp) if (shadowp)
*shadowp = p; *shadowp = p;
mapping->nrshadows--; mapping->nrexceptional--;
if (node) if (node)
workingset_node_shadows_dec(node); workingset_node_shadows_dec(node);
} }
...@@ -1245,9 +1250,9 @@ unsigned find_get_entries(struct address_space *mapping, ...@@ -1245,9 +1250,9 @@ unsigned find_get_entries(struct address_space *mapping,
if (radix_tree_deref_retry(page)) if (radix_tree_deref_retry(page))
goto restart; goto restart;
/* /*
* A shadow entry of a recently evicted page, * A shadow entry of a recently evicted page, a swap
* or a swap entry from shmem/tmpfs. Return * entry from shmem/tmpfs or a DAX entry. Return it
* it without attempting to raise page count. * without attempting to raise page count.
*/ */
goto export; goto export;
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/backing-dev.h> #include <linux/backing-dev.h>
#include <linux/dax.h>
#include <linux/gfp.h> #include <linux/gfp.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/swap.h> #include <linux/swap.h>
...@@ -34,17 +35,23 @@ static void clear_exceptional_entry(struct address_space *mapping, ...@@ -34,17 +35,23 @@ static void clear_exceptional_entry(struct address_space *mapping,
return; return;
spin_lock_irq(&mapping->tree_lock); spin_lock_irq(&mapping->tree_lock);
if (dax_mapping(mapping)) {
if (radix_tree_delete_item(&mapping->page_tree, index, entry))
mapping->nrexceptional--;
} else {
/* /*
* Regular page slots are stabilized by the page lock even * Regular page slots are stabilized by the page lock even
* without the tree itself locked. These unlocked entries * without the tree itself locked. These unlocked entries
* need verification under the tree lock. * need verification under the tree lock.
*/ */
if (!__radix_tree_lookup(&mapping->page_tree, index, &node, &slot)) if (!__radix_tree_lookup(&mapping->page_tree, index, &node,
&slot))
goto unlock; goto unlock;
if (*slot != entry) if (*slot != entry)
goto unlock; goto unlock;
radix_tree_replace_slot(slot, NULL); radix_tree_replace_slot(slot, NULL);
mapping->nrshadows--; mapping->nrexceptional--;
if (!node) if (!node)
goto unlock; goto unlock;
workingset_node_shadows_dec(node); workingset_node_shadows_dec(node);
...@@ -57,8 +64,10 @@ static void clear_exceptional_entry(struct address_space *mapping, ...@@ -57,8 +64,10 @@ static void clear_exceptional_entry(struct address_space *mapping,
*/ */
if (!workingset_node_shadows(node) && if (!workingset_node_shadows(node) &&
!list_empty(&node->private_list)) !list_empty(&node->private_list))
list_lru_del(&workingset_shadow_nodes, &node->private_list); list_lru_del(&workingset_shadow_nodes,
&node->private_list);
__radix_tree_delete_node(&mapping->page_tree, node); __radix_tree_delete_node(&mapping->page_tree, node);
}
unlock: unlock:
spin_unlock_irq(&mapping->tree_lock); spin_unlock_irq(&mapping->tree_lock);
} }
...@@ -228,7 +237,7 @@ void truncate_inode_pages_range(struct address_space *mapping, ...@@ -228,7 +237,7 @@ void truncate_inode_pages_range(struct address_space *mapping,
int i; int i;
cleancache_invalidate_inode(mapping); cleancache_invalidate_inode(mapping);
if (mapping->nrpages == 0 && mapping->nrshadows == 0) if (mapping->nrpages == 0 && mapping->nrexceptional == 0)
return; return;
/* Offsets within partial pages */ /* Offsets within partial pages */
...@@ -402,7 +411,7 @@ EXPORT_SYMBOL(truncate_inode_pages); ...@@ -402,7 +411,7 @@ EXPORT_SYMBOL(truncate_inode_pages);
*/ */
void truncate_inode_pages_final(struct address_space *mapping) void truncate_inode_pages_final(struct address_space *mapping)
{ {
unsigned long nrshadows; unsigned long nrexceptional;
unsigned long nrpages; unsigned long nrpages;
/* /*
...@@ -416,14 +425,14 @@ void truncate_inode_pages_final(struct address_space *mapping) ...@@ -416,14 +425,14 @@ void truncate_inode_pages_final(struct address_space *mapping)
/* /*
* When reclaim installs eviction entries, it increases * When reclaim installs eviction entries, it increases
* nrshadows first, then decreases nrpages. Make sure we see * nrexceptional first, then decreases nrpages. Make sure we see
* this in the right order or we might miss an entry. * this in the right order or we might miss an entry.
*/ */
nrpages = mapping->nrpages; nrpages = mapping->nrpages;
smp_rmb(); smp_rmb();
nrshadows = mapping->nrshadows; nrexceptional = mapping->nrexceptional;
if (nrpages || nrshadows) { if (nrpages || nrexceptional) {
/* /*
* As truncation uses a lockless tree lookup, cycle * As truncation uses a lockless tree lookup, cycle
* the tree lock to make sure any ongoing tree * the tree lock to make sure any ongoing tree
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#include <linux/oom.h> #include <linux/oom.h>
#include <linux/prefetch.h> #include <linux/prefetch.h>
#include <linux/printk.h> #include <linux/printk.h>
#include <linux/dax.h>
#include <asm/tlbflush.h> #include <asm/tlbflush.h>
#include <asm/div64.h> #include <asm/div64.h>
...@@ -671,9 +672,15 @@ static int __remove_mapping(struct address_space *mapping, struct page *page, ...@@ -671,9 +672,15 @@ static int __remove_mapping(struct address_space *mapping, struct page *page,
* inode reclaim needs to empty out the radix tree or * inode reclaim needs to empty out the radix tree or
* the nodes are lost. Don't plant shadows behind its * the nodes are lost. Don't plant shadows behind its
* back. * back.
*
* We also don't store shadows for DAX mappings because the
* only page cache pages found in these are zero pages
* covering holes, and because we don't want to mix DAX
* exceptional entries and shadow exceptional entries in the
* same page_tree.
*/ */
if (reclaimed && page_is_file_cache(page) && if (reclaimed && page_is_file_cache(page) &&
!mapping_exiting(mapping)) !mapping_exiting(mapping) && !dax_mapping(mapping))
shadow = workingset_eviction(mapping, page); shadow = workingset_eviction(mapping, page);
__delete_from_page_cache(page, shadow, memcg); __delete_from_page_cache(page, shadow, memcg);
spin_unlock_irqrestore(&mapping->tree_lock, flags); spin_unlock_irqrestore(&mapping->tree_lock, flags);
......
...@@ -351,8 +351,8 @@ static enum lru_status shadow_lru_isolate(struct list_head *item, ...@@ -351,8 +351,8 @@ static enum lru_status shadow_lru_isolate(struct list_head *item,
node->slots[i] = NULL; node->slots[i] = NULL;
BUG_ON(node->count < (1U << RADIX_TREE_COUNT_SHIFT)); BUG_ON(node->count < (1U << RADIX_TREE_COUNT_SHIFT));
node->count -= 1U << RADIX_TREE_COUNT_SHIFT; node->count -= 1U << RADIX_TREE_COUNT_SHIFT;
BUG_ON(!mapping->nrshadows); BUG_ON(!mapping->nrexceptional);
mapping->nrshadows--; mapping->nrexceptional--;
} }
} }
BUG_ON(node->count); BUG_ON(node->count);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment