[PATCH 04/10] mm: swap: Implement generic handlers for swap-related address ops

From: Mel Gorman
Date: Fri Sep 09 2011 - 07:03:17 EST


With the introduction of swap_activate, swap_writepage and
swap_readpage, there is a number of SWP_FILE checks that call a_ops and
fallback to generic handlers. This patch clarifies things by creating
generic versions of these functions and passing in all the information
required to implement a generic handler so the same information is
available to filesystems. This removes the need for SWP_FILE and
cleans up the flow slightly. There are no functional changes.

Signed-off-by: Mel Gorman <mgorman@xxxxxxx>
---
include/linux/fs.h | 7 ++-
include/linux/swap.h | 6 ++-
mm/page_io.c | 184 +++++++++++++++++++++++++++++++++++++++-----------
mm/swapfile.c | 102 +++-------------------------
4 files changed, 162 insertions(+), 137 deletions(-)

diff --git a/include/linux/fs.h b/include/linux/fs.h
index 387b767..dd93bb1 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -581,6 +581,8 @@ typedef struct {
typedef int (*read_actor_t)(read_descriptor_t *, struct page *,
unsigned long, unsigned long);

+struct swap_info_struct;
+
struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*readpage)(struct file *, struct page *);
@@ -619,8 +621,9 @@ struct address_space_operations {
int (*error_remove_page)(struct address_space *, struct page *);

/* swapfile support */
- int (*swap_activate)(struct file *file);
- int (*swap_deactivate)(struct file *file);
+ int (*swap_activate)(struct swap_info_struct *sis, struct file *file,
+ sector_t *span);
+ void (*swap_deactivate)(struct file *file);
int (*swap_writepage)(struct file *file, struct page *page,
struct writeback_control *wbc);
int (*swap_readpage)(struct file *file, struct page *page);
diff --git a/include/linux/swap.h b/include/linux/swap.h
index a044198..195ae15 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -148,7 +148,6 @@ enum {
SWP_SOLIDSTATE = (1 << 4), /* blkdev seeks are cheap */
SWP_CONTINUED = (1 << 5), /* swap_map has count continuation */
SWP_BLKDEV = (1 << 6), /* its a block device */
- SWP_FILE = (1 << 7), /* set after swap_activate success */
/* add others here before... */
SWP_SCANNING = (1 << 8), /* refcount in scan_swap_map */
};
@@ -307,6 +306,11 @@ extern int swap_writepage(struct page *page, struct writeback_control *wbc);
extern int swap_set_page_dirty(struct page *page);
extern void end_swap_bio_read(struct bio *bio, int err);

+int add_swap_extent(struct swap_info_struct *, unsigned long start_pfn,
+ unsigned long nr_pages, sector_t);
+int generic_swapfile_activate(struct swap_info_struct *, struct file *,
+ sector_t *);
+
/* linux/mm/swap_state.c */
extern struct address_space swapper_space;
#define total_swapcache_pages swapper_space.nrpages
diff --git a/mm/page_io.c b/mm/page_io.c
index 5ed5710..6ea49d3 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -86,87 +86,189 @@ void end_swap_bio_read(struct bio *bio, int err)
bio_put(bio);
}

+int generic_swap_writepage(struct page *page, struct writeback_control *wbc)
+{
+ struct bio *bio;
+ int rw = WRITE;
+
+ bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
+ if (bio == NULL) {
+ set_page_dirty(page);
+ unlock_page(page);
+ return -ENOMEM;
+ }
+ if (wbc->sync_mode == WB_SYNC_ALL)
+ rw |= REQ_SYNC;
+ count_vm_event(PSWPOUT);
+ set_page_writeback(page);
+ unlock_page(page);
+ submit_bio(rw, bio);
+
+ return 0;
+}
+
+int generic_swap_readpage(struct page *page)
+{
+ struct bio *bio;
+ bio = get_swap_bio(GFP_KERNEL, page, end_swap_bio_read);
+ if (bio == NULL) {
+ unlock_page(page);
+ return -ENOMEM;
+ }
+ count_vm_event(PSWPIN);
+ submit_bio(READ, bio);
+
+ return 0;
+}
+
+int generic_swapfile_activate(struct swap_info_struct *sis,
+ struct file *swap_file,
+ sector_t *span)
+{
+ struct address_space *mapping = swap_file->f_mapping;
+ struct inode *inode = mapping->host;
+ unsigned blocks_per_page;
+ unsigned long page_no;
+ unsigned blkbits;
+ sector_t probe_block;
+ sector_t last_block;
+ sector_t lowest_block = -1;
+ sector_t highest_block = 0;
+ int nr_extents = 0;
+ int ret;
+
+ blkbits = inode->i_blkbits;
+ blocks_per_page = PAGE_SIZE >> blkbits;
+
+ /*
+ * Map all the blocks into the extent list. This code doesn't try
+ * to be very smart.
+ */
+ probe_block = 0;
+ page_no = 0;
+ last_block = i_size_read(inode) >> blkbits;
+ while ((probe_block + blocks_per_page) <= last_block &&
+ page_no < sis->max) {
+ unsigned block_in_page;
+ sector_t first_block;
+
+ first_block = bmap(inode, probe_block);
+ if (first_block == 0)
+ goto bad_bmap;
+
+ /*
+ * It must be PAGE_SIZE aligned on-disk
+ */
+ if (first_block & (blocks_per_page - 1)) {
+ probe_block++;
+ goto reprobe;
+ }
+
+ for (block_in_page = 1; block_in_page < blocks_per_page;
+ block_in_page++) {
+ sector_t block;
+
+ block = bmap(inode, probe_block + block_in_page);
+ if (block == 0)
+ goto bad_bmap;
+ if (block != first_block + block_in_page) {
+ /* Discontiguity */
+ probe_block++;
+ goto reprobe;
+ }
+ }
+
+ first_block >>= (PAGE_SHIFT - blkbits);
+ if (page_no) { /* exclude the header page */
+ if (first_block < lowest_block)
+ lowest_block = first_block;
+ if (first_block > highest_block)
+ highest_block = first_block;
+ }
+
+ /*
+ * We found a PAGE_SIZE-length, PAGE_SIZE-aligned run of blocks
+ */
+ ret = add_swap_extent(sis, page_no, 1, first_block);
+ if (ret < 0)
+ goto out;
+ nr_extents += ret;
+ page_no++;
+ probe_block += blocks_per_page;
+reprobe:
+ continue;
+ }
+ ret = nr_extents;
+ *span = 1 + highest_block - lowest_block;
+ if (page_no == 0)
+ page_no = 1; /* force Empty message */
+ sis->max = page_no;
+ sis->pages = page_no - 1;
+ sis->highest_bit = page_no - 1;
+out:
+ return ret;
+bad_bmap:
+ printk(KERN_ERR "swapon: swapfile has holes\n");
+ ret = -EINVAL;
+ goto out;
+}
/*
* We may have stale swap cache pages in memory: notice
* them here and get rid of the unnecessary final write.
*/
int swap_writepage(struct page *page, struct writeback_control *wbc)
{
- struct bio *bio;
- int ret = 0, rw = WRITE;
+ int ret = 0;
struct swap_info_struct *sis = page_swap_info(page);
+ struct file *swap_file;
+ struct address_space *mapping;

if (try_to_free_swap(page)) {
unlock_page(page);
- goto out;
+ return ret;
}

- if (sis->flags & SWP_FILE) {
- struct file *swap_file = sis->swap_file;
- struct address_space *mapping = swap_file->f_mapping;
-
+ swap_file = sis->swap_file;
+ mapping = swap_file->f_mapping;
+ if (mapping->a_ops->swap_writepage) {
ret = mapping->a_ops->swap_writepage(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);
- unlock_page(page);
- ret = -ENOMEM;
- goto out;
- }
- if (wbc->sync_mode == WB_SYNC_ALL)
- rw |= REQ_SYNC;
- count_vm_event(PSWPOUT);
- set_page_writeback(page);
- unlock_page(page);
- submit_bio(rw, bio);
-out:
- return ret;
+ return generic_swap_writepage(page, wbc);
}

int swap_readpage(struct page *page)
{
- struct bio *bio;
int ret = 0;
struct swap_info_struct *sis = page_swap_info(page);
+ struct file *swap_file;
+ struct address_space *mapping;

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;
-
+ swap_file = sis->swap_file;
+ mapping = swap_file->f_mapping;
+ if (mapping->a_ops->swap_readpage) {
ret = mapping->a_ops->swap_readpage(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);
- ret = -ENOMEM;
- goto out;
- }
- count_vm_event(PSWPIN);
- submit_bio(READ, bio);
-out:
- return ret;
+ return generic_swap_readpage(page);
}

int swap_set_page_dirty(struct page *page)
{
struct swap_info_struct *sis = page_swap_info(page);
+ struct address_space *mapping = sis->swap_file->f_mapping;

- if (sis->flags & SWP_FILE) {
- struct address_space *mapping = sis->swap_file->f_mapping;
+ if (mapping->a_ops->set_page_dirty)
return mapping->a_ops->set_page_dirty(page);
- } else {
+ else
return __set_page_dirty_nobuffers(page);
- }
}
diff --git a/mm/swapfile.c b/mm/swapfile.c
index f181884..c49cb33 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1335,6 +1335,9 @@ sector_t map_swap_page(struct page *page, struct block_device **bdev)
*/
static void destroy_swap_extents(struct swap_info_struct *sis)
{
+ struct file *swap_file = sis->swap_file;
+ struct address_space *mapping = swap_file->f_mapping;
+
while (!list_empty(&sis->first_swap_extent.list)) {
struct swap_extent *se;

@@ -1344,13 +1347,8 @@ static void destroy_swap_extents(struct swap_info_struct *sis)
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;
+ if (mapping->a_ops->swap_deactivate)
mapping->a_ops->swap_deactivate(swap_file);
- }
}

/*
@@ -1359,7 +1357,7 @@ static void destroy_swap_extents(struct swap_info_struct *sis)
*
* This function rather assumes that it is called in ascending page order.
*/
-static int
+int
add_swap_extent(struct swap_info_struct *sis, unsigned long start_page,
unsigned long nr_pages, sector_t start_block)
{
@@ -1435,106 +1433,24 @@ static int setup_swap_extents(struct swap_info_struct *sis, sector_t *span)
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;
- sector_t probe_block;
- sector_t last_block;
- sector_t lowest_block = -1;
- sector_t highest_block = 0;
- int nr_extents = 0;
int ret;

if (S_ISBLK(inode->i_mode)) {
ret = add_swap_extent(sis, 0, sis->max, 0);
*span = sis->pages;
- goto out;
+ return ret;
}

if (mapping->a_ops->swap_activate) {
- ret = mapping->a_ops->swap_activate(swap_file);
+ ret = mapping->a_ops->swap_activate(sis, swap_file, span);
if (!ret) {
- sis->flags |= SWP_FILE;
ret = add_swap_extent(sis, 0, sis->max, 0);
*span = sis->pages;
}
- goto out;
+ return ret;
}

- blkbits = inode->i_blkbits;
- blocks_per_page = PAGE_SIZE >> blkbits;
-
- /*
- * Map all the blocks into the extent list. This code doesn't try
- * to be very smart.
- */
- probe_block = 0;
- page_no = 0;
- last_block = i_size_read(inode) >> blkbits;
- while ((probe_block + blocks_per_page) <= last_block &&
- page_no < sis->max) {
- unsigned block_in_page;
- sector_t first_block;
-
- first_block = bmap(inode, probe_block);
- if (first_block == 0)
- goto bad_bmap;
-
- /*
- * It must be PAGE_SIZE aligned on-disk
- */
- if (first_block & (blocks_per_page - 1)) {
- probe_block++;
- goto reprobe;
- }
-
- for (block_in_page = 1; block_in_page < blocks_per_page;
- block_in_page++) {
- sector_t block;
-
- block = bmap(inode, probe_block + block_in_page);
- if (block == 0)
- goto bad_bmap;
- if (block != first_block + block_in_page) {
- /* Discontiguity */
- probe_block++;
- goto reprobe;
- }
- }
-
- first_block >>= (PAGE_SHIFT - blkbits);
- if (page_no) { /* exclude the header page */
- if (first_block < lowest_block)
- lowest_block = first_block;
- if (first_block > highest_block)
- highest_block = first_block;
- }
-
- /*
- * We found a PAGE_SIZE-length, PAGE_SIZE-aligned run of blocks
- */
- ret = add_swap_extent(sis, page_no, 1, first_block);
- if (ret < 0)
- goto out;
- nr_extents += ret;
- page_no++;
- probe_block += blocks_per_page;
-reprobe:
- continue;
- }
- ret = nr_extents;
- *span = 1 + highest_block - lowest_block;
- if (page_no == 0)
- page_no = 1; /* force Empty message */
- sis->max = page_no;
- sis->pages = page_no - 1;
- sis->highest_bit = page_no - 1;
-out:
- return ret;
-bad_bmap:
- printk(KERN_ERR "swapon: swapfile has holes\n");
- ret = -EINVAL;
- goto out;
+ return generic_swapfile_activate(sis, swap_file, span);
}

static void enable_swap_info(struct swap_info_struct *p, int prio,
--
1.7.3.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/