2010-07-07 11:12:45 +00:00
|
|
|
From: Peter Zijlstra <a.p.zijlstra@chello.nl>
|
|
|
|
Subject: [PATCH 23/31] mm: add support for non block device backed swap files
|
|
|
|
Patch-mainline: not yet
|
|
|
|
|
|
|
|
New addres_space_operations methods are added:
|
|
|
|
int swapon(struct file *);
|
|
|
|
int swapoff(struct file *);
|
|
|
|
int swap_out(struct file *, struct page *, struct writeback_control *);
|
|
|
|
int swap_in(struct file *, struct page *);
|
|
|
|
|
|
|
|
When during sys_swapon() the ->swapon() method is found and returns no error
|
|
|
|
the swapper_space.a_ops will proxy to sis->swap_file->f_mapping->a_ops, and
|
|
|
|
make use of ->swap_{out,in}() to write/read swapcache pages.
|
|
|
|
|
|
|
|
The ->swapon() method will be used to communicate to the file that the VM
|
|
|
|
relies on it, and the address_space should take adequate measures (like
|
|
|
|
reserving memory for mempools or the like). The ->swapoff() method will be
|
|
|
|
called on sys_swapoff() when ->swapon() was found and returned no error.
|
|
|
|
|
|
|
|
This new interface can be used to obviate the need for ->bmap in the swapfile
|
|
|
|
code. A filesystem would need to load (and maybe even allocate) the full block
|
|
|
|
map for a file into memory and pin it there on ->swapon() so that
|
|
|
|
->swap_{out,in}() have instant access to it. It can be released on ->swapoff().
|
|
|
|
|
|
|
|
The reason to provide ->swap_{out,in}() over using {write,read}page() is to
|
|
|
|
1) make a distinction between swapcache and pagecache pages, and
|
|
|
|
2) to provide a struct file * for credential context (normally not needed
|
|
|
|
in the context of writepage, as the page content is normally dirtied
|
|
|
|
using either of the following interfaces:
|
|
|
|
write_{begin,end}()
|
|
|
|
{prepare,commit}_write()
|
|
|
|
page_mkwrite()
|
|
|
|
which do have the file context.
|
|
|
|
|
|
|
|
[miklos@szeredi.hu: cleanups]
|
|
|
|
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
|
|
|
|
Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
|
|
|
|
---
|
|
|
|
Documentation/filesystems/Locking | 22 ++++++++++++++++
|
|
|
|
Documentation/filesystems/vfs.txt | 18 +++++++++++++
|
|
|
|
include/linux/buffer_head.h | 1
|
|
|
|
include/linux/fs.h | 9 ++++++
|
|
|
|
include/linux/swap.h | 4 ++
|
|
|
|
mm/page_io.c | 52 ++++++++++++++++++++++++++++++++++++++
|
|
|
|
mm/swap_state.c | 4 +-
|
|
|
|
mm/swapfile.c | 30 ++++++++++++++++++++-
|
|
|
|
8 files changed, 136 insertions(+), 4 deletions(-)
|
|
|
|
|
|
|
|
--- a/Documentation/filesystems/Locking
|
|
|
|
+++ b/Documentation/filesystems/Locking
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -198,6 +198,10 @@ prototypes:
|
|
|
|
int (*launder_page)(struct page *);
|
|
|
|
int (*is_partially_uptodate)(struct page *, read_descriptor_t *, unsigned long);
|
|
|
|
int (*error_remove_page)(struct address_space *, struct page *);
|
2010-07-07 11:12:45 +00:00
|
|
|
+ int (*swapon) (struct file *);
|
|
|
|
+ int (*swapoff) (struct file *);
|
|
|
|
+ int (*swap_out) (struct file *, struct page *, struct writeback_control *);
|
|
|
|
+ int (*swap_in) (struct file *, struct page *);
|
|
|
|
|
|
|
|
locking rules:
|
2011-04-19 20:09:59 +00:00
|
|
|
All except set_page_dirty and freepage may block
|
|
|
|
@@ -221,6 +225,10 @@ migratepage: yes (both)
|
|
|
|
launder_page: yes
|
|
|
|
is_partially_uptodate: yes
|
|
|
|
error_remove_page: yes
|
2010-07-07 11:12:45 +00:00
|
|
|
+swapon no
|
|
|
|
+swapoff no
|
|
|
|
+swap_out no yes, unlocks
|
|
|
|
+swap_in no yes, unlocks
|
|
|
|
|
|
|
|
->write_begin(), ->write_end(), ->sync_page() and ->readpage()
|
|
|
|
may be called from the request handler (/dev/loop).
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -322,6 +330,20 @@ cleaned, or an error value if not. Note
|
2010-07-07 11:12:45 +00:00
|
|
|
getting mapped back in and redirtied, it needs to be kept locked
|
|
|
|
across the entire operation.
|
|
|
|
|
|
|
|
+ ->swapon() will be called with a non-zero argument on files backing
|
|
|
|
+(non block device backed) swapfiles. A return value of zero indicates success,
|
|
|
|
+in which case this file can be used for backing swapspace. The swapspace
|
|
|
|
+operations will be proxied to the address space operations.
|
|
|
|
+
|
|
|
|
+ ->swapoff() will be called in the sys_swapoff() path when ->swapon()
|
|
|
|
+returned success.
|
|
|
|
+
|
|
|
|
+ ->swap_out() when swapon() returned success, this method is used to
|
|
|
|
+write the swap page.
|
|
|
|
+
|
|
|
|
+ ->swap_in() when swapon() returned success, this method is used to
|
|
|
|
+read the swap page.
|
|
|
|
+
|
2011-04-19 20:09:59 +00:00
|
|
|
----------------------- file_lock_operations ------------------------------
|
|
|
|
prototypes:
|
|
|
|
void (*fl_copy_lock)(struct file_lock *, struct file_lock *);
|
2010-07-07 11:12:45 +00:00
|
|
|
--- a/Documentation/filesystems/vfs.txt
|
|
|
|
+++ b/Documentation/filesystems/vfs.txt
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -543,6 +543,11 @@ struct address_space_operations {
|
2010-07-07 11:12:45 +00:00
|
|
|
int (*migratepage) (struct page *, struct page *);
|
|
|
|
int (*launder_page) (struct page *);
|
|
|
|
int (*error_remove_page) (struct mapping *mapping, struct page *page);
|
|
|
|
+ int (*swapon)(struct file *);
|
|
|
|
+ int (*swapoff)(struct file *);
|
|
|
|
+ int (*swap_out)(struct file *file, struct page *page,
|
|
|
|
+ struct writeback_control *wbc);
|
|
|
|
+ int (*swap_in)(struct file *file, struct page *page);
|
|
|
|
};
|
|
|
|
|
|
|
|
writepage: called by the VM to write a dirty page to backing store.
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -712,6 +717,19 @@ struct address_space_operations {
|
2010-07-07 11:12:45 +00:00
|
|
|
unless you have them locked or reference counts increased.
|
|
|
|
|
|
|
|
|
|
|
|
+ swapon: Called when swapon is used on a file. A
|
|
|
|
+ return value of zero indicates success, in which case this
|
|
|
|
+ file can be used to back swapspace. The swapspace operations
|
|
|
|
+ will be proxied to this address space's ->swap_{out,in} methods.
|
|
|
|
+
|
|
|
|
+ swapoff: Called during swapoff on files where swapon was successfull.
|
|
|
|
+
|
|
|
|
+ swap_out: Called to write a swapcache page to a backing store, similar to
|
|
|
|
+ writepage.
|
|
|
|
+
|
|
|
|
+ swap_in: Called to read a swapcache page from a backing store, similar to
|
|
|
|
+ readpage.
|
|
|
|
+
|
|
|
|
The File Object
|
|
|
|
===============
|
|
|
|
|
|
|
|
--- a/include/linux/buffer_head.h
|
|
|
|
+++ b/include/linux/buffer_head.h
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -330,6 +330,7 @@ static inline int inode_has_buffers(stru
|
2010-07-07 11:12:45 +00:00
|
|
|
static inline void invalidate_inode_buffers(struct inode *inode) {}
|
|
|
|
static inline int remove_inode_buffers(struct inode *inode) { return 1; }
|
|
|
|
static inline int sync_mapping_buffers(struct address_space *mapping) { return 0; }
|
|
|
|
+static inline void block_sync_page(struct page *) { }
|
|
|
|
|
|
|
|
#endif /* CONFIG_BLOCK */
|
|
|
|
#endif /* _LINUX_BUFFER_HEAD_H */
|
|
|
|
--- a/include/linux/fs.h
|
|
|
|
+++ b/include/linux/fs.h
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -622,6 +622,15 @@ struct address_space_operations {
|
2010-07-07 11:12:45 +00:00
|
|
|
int (*is_partially_uptodate) (struct page *, read_descriptor_t *,
|
|
|
|
unsigned long);
|
|
|
|
int (*error_remove_page)(struct address_space *, struct page *);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * swapfile support
|
|
|
|
+ */
|
|
|
|
+ int (*swapon)(struct file *file);
|
|
|
|
+ int (*swapoff)(struct file *file);
|
|
|
|
+ int (*swap_out)(struct file *file, struct page *page,
|
|
|
|
+ struct writeback_control *wbc);
|
|
|
|
+ int (*swap_in)(struct file *file, struct page *page);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
--- a/include/linux/swap.h
|
|
|
|
+++ b/include/linux/swap.h
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -148,6 +148,7 @@ enum {
|
2010-07-07 11:12:45 +00:00
|
|
|
SWP_SOLIDSTATE = (1 << 4), /* blkdev seeks are cheap */
|
|
|
|
SWP_CONTINUED = (1 << 5), /* swap_map has count continuation */
|
2011-04-19 20:09:59 +00:00
|
|
|
SWP_BLKDEV = (1 << 6), /* its a block device */
|
|
|
|
+ SWP_FILE = (1 << 7), /* file swap area */
|
2010-07-07 11:12:45 +00:00
|
|
|
/* add others here before... */
|
|
|
|
SWP_SCANNING = (1 << 8), /* refcount in scan_swap_map */
|
|
|
|
};
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -303,6 +304,8 @@ extern void swap_unplug_io_fn(struct bac
|
2010-07-07 11:12:45 +00:00
|
|
|
/* linux/mm/page_io.c */
|
|
|
|
extern int swap_readpage(struct page *);
|
|
|
|
extern int swap_writepage(struct page *page, struct writeback_control *wbc);
|
|
|
|
+extern void swap_sync_page(struct page *page);
|
|
|
|
+extern int swap_set_page_dirty(struct page *page);
|
|
|
|
extern void end_swap_bio_read(struct bio *bio, int err);
|
|
|
|
|
|
|
|
/* linux/mm/swap_state.c */
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -339,6 +342,7 @@ extern int swap_type_of(dev_t, sector_t,
|
2010-07-07 11:12:45 +00:00
|
|
|
extern unsigned int count_swap_pages(int, int);
|
|
|
|
extern sector_t map_swap_page(struct page *, struct block_device **);
|
|
|
|
extern sector_t swapdev_block(int, pgoff_t);
|
|
|
|
+extern struct swap_info_struct *page_swap_info(struct page *);
|
|
|
|
extern int reuse_swap_page(struct page *);
|
|
|
|
extern int try_to_free_swap(struct page *);
|
|
|
|
struct backing_dev_info;
|
|
|
|
--- a/mm/page_io.c
|
|
|
|
+++ b/mm/page_io.c
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -17,6 +17,7 @@
|
2010-07-07 11:12:45 +00:00
|
|
|
#include <linux/swap.h>
|
|
|
|
#include <linux/bio.h>
|
|
|
|
#include <linux/swapops.h>
|
|
|
|
+#include <linux/buffer_head.h>
|
|
|
|
#include <linux/writeback.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
|
|
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -93,11 +94,23 @@ int swap_writepage(struct page *page, st
|
2010-07-07 11:12:45 +00:00
|
|
|
{
|
|
|
|
struct bio *bio;
|
|
|
|
int ret = 0, rw = WRITE;
|
|
|
|
+ struct swap_info_struct *sis = page_swap_info(page);
|
|
|
|
|
|
|
|
if (try_to_free_swap(page)) {
|
|
|
|
unlock_page(page);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
+
|
|
|
|
+ if (sis->flags & SWP_FILE) {
|
|
|
|
+ struct file *swap_file = sis->swap_file;
|
|
|
|
+ struct address_space *mapping = swap_file->f_mapping;
|
|
|
|
+
|
|
|
|
+ ret = mapping->a_ops->swap_out(swap_file, page, wbc);
|
|
|
|
+ if (!ret)
|
|
|
|
+ count_vm_event(PSWPOUT);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
|
|
|
|
if (bio == NULL) {
|
|
|
|
set_page_dirty(page);
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -115,13 +128,52 @@ out:
|
2010-07-07 11:12:45 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
+void swap_sync_page(struct page *page)
|
|
|
|
+{
|
|
|
|
+ struct swap_info_struct *sis = page_swap_info(page);
|
|
|
|
+
|
|
|
|
+ if (sis->flags & SWP_FILE) {
|
|
|
|
+ struct address_space *mapping = sis->swap_file->f_mapping;
|
|
|
|
+
|
|
|
|
+ if (mapping->a_ops->sync_page)
|
|
|
|
+ mapping->a_ops->sync_page(page);
|
|
|
|
+ } else {
|
|
|
|
+ block_sync_page(page);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int swap_set_page_dirty(struct page *page)
|
|
|
|
+{
|
|
|
|
+ struct swap_info_struct *sis = page_swap_info(page);
|
|
|
|
+
|
|
|
|
+ if (sis->flags & SWP_FILE) {
|
|
|
|
+ struct address_space *mapping = sis->swap_file->f_mapping;
|
|
|
|
+
|
|
|
|
+ return mapping->a_ops->set_page_dirty(page);
|
|
|
|
+ } else {
|
|
|
|
+ return __set_page_dirty_nobuffers(page);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
int swap_readpage(struct page *page)
|
|
|
|
{
|
|
|
|
struct bio *bio;
|
|
|
|
int ret = 0;
|
|
|
|
+ struct swap_info_struct *sis = page_swap_info(page);
|
|
|
|
|
|
|
|
VM_BUG_ON(!PageLocked(page));
|
|
|
|
VM_BUG_ON(PageUptodate(page));
|
|
|
|
+
|
|
|
|
+ if (sis->flags & SWP_FILE) {
|
|
|
|
+ struct file *swap_file = sis->swap_file;
|
|
|
|
+ struct address_space *mapping = swap_file->f_mapping;
|
|
|
|
+
|
|
|
|
+ ret = mapping->a_ops->swap_in(swap_file, page);
|
|
|
|
+ if (!ret)
|
|
|
|
+ count_vm_event(PSWPIN);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
bio = get_swap_bio(GFP_KERNEL, page, end_swap_bio_read);
|
|
|
|
if (bio == NULL) {
|
|
|
|
unlock_page(page);
|
|
|
|
--- a/mm/swap_state.c
|
|
|
|
+++ b/mm/swap_state.c
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -29,8 +29,8 @@
|
2010-07-07 11:12:45 +00:00
|
|
|
*/
|
|
|
|
static const struct address_space_operations swap_aops = {
|
|
|
|
.writepage = swap_writepage,
|
|
|
|
- .sync_page = block_sync_page,
|
|
|
|
- .set_page_dirty = __set_page_dirty_nobuffers,
|
|
|
|
+ .sync_page = swap_sync_page,
|
|
|
|
+ .set_page_dirty = swap_set_page_dirty,
|
|
|
|
.migratepage = migrate_page,
|
|
|
|
};
|
|
|
|
|
|
|
|
--- a/mm/swapfile.c
|
|
|
|
+++ b/mm/swapfile.c
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -1373,6 +1373,14 @@ static void destroy_swap_extents(struct
|
2010-07-07 11:12:45 +00:00
|
|
|
list_del(&se->list);
|
|
|
|
kfree(se);
|
|
|
|
}
|
|
|
|
+
|
|
|
|
+ if (sis->flags & SWP_FILE) {
|
|
|
|
+ struct file *swap_file = sis->swap_file;
|
|
|
|
+ struct address_space *mapping = swap_file->f_mapping;
|
|
|
|
+
|
|
|
|
+ sis->flags &= ~SWP_FILE;
|
|
|
|
+ mapping->a_ops->swapoff(swap_file);
|
|
|
|
+ }
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -1454,7 +1462,9 @@ add_swap_extent(struct swap_info_struct
|
2010-07-07 11:12:45 +00:00
|
|
|
*/
|
|
|
|
static int setup_swap_extents(struct swap_info_struct *sis, sector_t *span)
|
|
|
|
{
|
|
|
|
- struct inode *inode;
|
|
|
|
+ struct file *swap_file = sis->swap_file;
|
|
|
|
+ struct address_space *mapping = swap_file->f_mapping;
|
|
|
|
+ struct inode *inode = mapping->host;
|
|
|
|
unsigned blocks_per_page;
|
|
|
|
unsigned long page_no;
|
|
|
|
unsigned blkbits;
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -1465,13 +1475,22 @@ static int setup_swap_extents(struct swa
|
2010-07-07 11:12:45 +00:00
|
|
|
int nr_extents = 0;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
- inode = sis->swap_file->f_mapping->host;
|
|
|
|
if (S_ISBLK(inode->i_mode)) {
|
|
|
|
ret = add_swap_extent(sis, 0, sis->max, 0);
|
|
|
|
*span = sis->pages;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ if (mapping->a_ops->swapon) {
|
|
|
|
+ ret = mapping->a_ops->swapon(swap_file);
|
|
|
|
+ if (!ret) {
|
|
|
|
+ sis->flags |= SWP_FILE;
|
|
|
|
+ ret = add_swap_extent(sis, 0, sis->max, 0);
|
|
|
|
+ *span = sis->pages;
|
|
|
|
+ }
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
blkbits = inode->i_blkbits;
|
|
|
|
blocks_per_page = PAGE_SIZE >> blkbits;
|
|
|
|
|
2011-04-19 20:09:59 +00:00
|
|
|
@@ -2290,6 +2309,13 @@ int swapcache_prepare(swp_entry_t entry)
|
2010-07-07 11:12:45 +00:00
|
|
|
return __swap_duplicate(entry, SWAP_HAS_CACHE);
|
|
|
|
}
|
|
|
|
|
|
|
|
+struct swap_info_struct *page_swap_info(struct page *page)
|
|
|
|
+{
|
|
|
|
+ swp_entry_t swap = { .val = page_private(page) };
|
|
|
|
+ BUG_ON(!PageSwapCache(page));
|
|
|
|
+ return swap_info[swp_type(swap)];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
/*
|
|
|
|
* swap_lock prevents swap_map being freed. Don't grab an extra
|
|
|
|
* reference on the swaphandle, it doesn't matter if it becomes unused.
|