linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv2 0/9] zswap: compressed swap caching
@ 2013-01-07 20:24 Seth Jennings
  2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
                   ` (9 more replies)
  0 siblings, 10 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

Changelog:

v2:
* Rename zswap_fs_* functions to zswap_frontswap_* to avoid
  confusion with "filesystem"
* Add comment about what the tree lock protects
* Remove "#if 0" code (should have been done before)
* Break out changes to existing swap code into separate patch
* Fix blank line EOF warning on documentation file
* Rebase to next-20130107

Zswap Overview:

Zswap is a lightweight compressed cache for swap pages. It takes
pages that are in the process of being swapped out and attempts to
compress them into a dynamically allocated RAM-based memory pool.
If this process is successful, the writeback to the swap device is
deferred and, in many cases, avoided completely.  This results in
a significant I/O reduction and performance gains for systems that
are swapping. The results of a kernel building benchmark indicate a
runtime reduction of 53% and an I/O reduction 76% with zswap vs normal
swapping with a kernel build under heavy memory pressure (see
Performance section for more).

Patchset Structure:
1-4: improvements/changes to zsmalloc
5:   add atomic_t get/set to debugfs
6:   promote zsmalloc to /lib
7-9: add zswap and documentation

Targeting this for linux-next.

Rationale:

Zswap provides compressed swap caching that basically trades CPU cycles
for reduced swap I/O.  This trade-off can result in a significant
performance improvement as reads to/writes from to the compressed
cache almost always faster that reading from a swap device
which incurs the latency of an asynchronous block I/O read.

Some potential benefits:
* Desktop/laptop users with limited RAM capacities can mitigate the
    performance impact of swapping.
* Overcommitted guests that share a common I/O resource can
    dramatically reduce their swap I/O pressure, avoiding heavy
    handed I/O throttling by the hypervisor.  This allows more work
    to get done with less impact to the guest workload and guests
    sharing the I/O subsystem
* Users with SSDs as swap devices can extend the life of the device by
    drastically reducing life-shortening writes.

Zswap evicts pages from compressed cache on an LRU basis to the backing
swap device when the compress pool reaches it size limit or the pool is
unable to obtain additional pages from the buddy allocator.  This
requirement had been identified in prior community discussions.

Compressed swap is also provided in zcache, along with page cache
compression and RAM clustering through RAMSter. Zswap seeks to deliver
the benefit of swap  compression to users in a discrete function.
This design decision is akin to Unix design philosophy of doing one
thing well, it leaves file cache compression and other features
for separate code.

Design:

Zswap receives pages for compression through the Frontswap API and
is able to evict pages from its own compressed pool on an LRU basis
and write them back to the backing swap device in the case that the
compressed pool is full or unable to secure additional pages from
the buddy allocator.

Zswap makes use of zsmalloc for the managing the compressed memory
pool.  This is because zsmalloc is specifically designed to minimize
fragmentation on large (> PAGE_SIZE/2) allocation sizes.  Each
allocation in zsmalloc is not directly accessible by address.
Rather, a handle is return by the allocation routine and that handle
must be mapped before being accessed.  The compressed memory pool grows
on demand and shrinks as compressed pages are freed.  The pool is
not preallocated.

When a swap page is passed from frontswap to zswap, zswap maintains
a mapping of the swap entry, a combination of the swap type and swap
offset, to the zsmalloc handle that references that compressed swap
page.  This mapping is achieved with a red-black tree per swap type.
The swap offset is the search key for the tree nodes.

Zswap seeks to be simple in its policies.  Sysfs attributes allow for
two user controlled policies:
* max_compression_ratio - Maximum compression ratio, as as percentage,
    for an acceptable compressed page. Any page that does not compress
    by at least this ratio will be rejected.
* max_pool_percent - The maximum percentage of memory that the compressed
    pool can occupy.

To enabled zswap, the "enabled" attribute must be set to 1 at boot time.

Zswap allows the compressor to be selected at kernel boot time by
setting the “compressor” attribute.  The default compressor is lzo.

A debugfs interface is provided for various statistic about pool size,
number of pages stored, and various counters for the reasons pages
are rejected.

Performance, Kernel Building:

Setup
========
Gentoo w/ kernel v3.7-rc7
Quad-core i5-2500 @ 3.3GHz
512MB DDR3 1600MHz (limited with mem=512m on boot)
Filesystem and swap on 80GB HDD (about 58MB/s with hdparm -t)
majflt are major page faults reported by the time command
pswpin/out is the delta of pswpin/out from /proc/vmstat before and after
the make -jN

Summary
========
* Zswap reduces I/O and improves performance at all swap pressure levels.

* Under heavy swaping at 24 threads, zswap reduced I/O by 76%, saving
  over 1.5GB of I/O, and cut runtime in half.

Details
========
I/O (in pages)
	base				zswap				change	change
N	pswpin	pswpout	majflt	I/O sum	pswpin	pswpout	majflt	I/O sum	%I/O	MB
8	1	335	291	627	0	0	249	249	-60%	1
12	3688	14315	5290	23293	123	860	5954	6937	-70%	64
16	12711	46179	16803	75693	2936	7390	46092	56418	-25%	75
20	42178	133781	49898	225857	9460	28382	92951	130793	-42%	371
24	96079	357280	105242	558601	7719	18484	109309	135512	-76%	1653

Runtime (in seconds)
N	base	zswap	%change
8	107	107	0%
12	128	110	-14%
16	191	179	-6%
20	371	240	-35%
24	570	267	-53%

%CPU utilization (out of 400% on 4 cpus)
N	base	zswap	%change
8	317	319	1%
12	267	311	16%
16	179	191	7%
20	94	143	52%
24	60	128	113%

Seth Jennings (9):
  staging: zsmalloc: add gfp flags to zs_create_pool
  staging: zsmalloc: remove unsed pool name
  staging: zsmalloc: add page alloc/free callbacks
  staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
  debugfs: add get/set for atomic types
  zsmalloc: promote to lib/
  mm: break up swap_writepage() for frontswap backends
  zswap: add to mm/
  zswap: add documentation

 Documentation/vm/zswap.txt               |   73 ++
 drivers/staging/Kconfig                  |    2 -
 drivers/staging/Makefile                 |    1 -
 drivers/staging/zcache/zcache-main.c     |    7 +-
 drivers/staging/zram/zram_drv.c          |    4 +-
 drivers/staging/zram/zram_drv.h          |    3 +-
 drivers/staging/zsmalloc/Kconfig         |   10 -
 drivers/staging/zsmalloc/Makefile        |    3 -
 drivers/staging/zsmalloc/zsmalloc-main.c | 1064 -----------------------------
 drivers/staging/zsmalloc/zsmalloc.h      |   43 --
 fs/debugfs/file.c                        |   42 ++
 include/linux/debugfs.h                  |    2 +
 include/linux/swap.h                     |    4 +
 include/linux/zsmalloc.h                 |   49 ++
 lib/Kconfig                              |   18 +
 lib/Makefile                             |    1 +
 lib/zsmalloc.c                           | 1076 ++++++++++++++++++++++++++++++
 mm/Kconfig                               |   15 +
 mm/Makefile                              |    1 +
 mm/page_io.c                             |   22 +-
 mm/swap_state.c                          |    2 +-
 mm/zswap.c                               | 1066 +++++++++++++++++++++++++++++
 22 files changed, 2371 insertions(+), 1137 deletions(-)
 create mode 100644 Documentation/vm/zswap.txt
 delete mode 100644 drivers/staging/zsmalloc/Kconfig
 delete mode 100644 drivers/staging/zsmalloc/Makefile
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
 create mode 100644 include/linux/zsmalloc.h
 create mode 100644 lib/zsmalloc.c
 create mode 100644 mm/zswap.c

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 43+ messages in thread

* [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-25  0:08   ` Nitin Gupta
                     ` (2 more replies)
  2013-01-07 20:24 ` [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name Seth Jennings
                   ` (8 subsequent siblings)
  9 siblings, 3 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

zs_create_pool() currently takes a gfp flags argument
that is used when growing the memory pool.  However
it is not used in allocating the metadata for the pool
itself.  That is currently hardcoded to GFP_KERNEL.

zswap calls zs_create_pool() at swapon time which is done
in atomic context, resulting in a "might sleep" warning.

This patch changes the meaning of the flags argument in
zs_create_pool() to mean the flags for the metadata allocation,
and adds a flags argument to zs_malloc that will be used for
memory pool growth if required.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zcache/zcache-main.c     |    4 ++--
 drivers/staging/zram/zram_drv.c          |    4 ++--
 drivers/staging/zsmalloc/zsmalloc-main.c |    9 +++------
 drivers/staging/zsmalloc/zsmalloc.h      |    2 +-
 4 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index 52b43b7..674c754 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -711,7 +711,7 @@ static unsigned long zv_create(struct zs_pool *pool, uint32_t pool_id,
 
 	BUG_ON(!irqs_disabled());
 	BUG_ON(chunks >= NCHUNKS);
-	handle = zs_malloc(pool, size);
+	handle = zs_malloc(pool, size, ZCACHE_GFP_MASK);
 	if (!handle)
 		goto out;
 	atomic_inc(&zv_curr_dist_counts[chunks]);
@@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
 		goto out;
 	cli->allocated = 1;
 #ifdef CONFIG_FRONTSWAP
-	cli->zspool = zs_create_pool("zcache", ZCACHE_GFP_MASK);
+	cli->zspool = zs_create_pool("zcache", GFP_KERNEL);
 	if (cli->zspool == NULL)
 		goto out;
 	idr_init(&cli->tmem_pools);
diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
index fb4a7c9..13e9b4b 100644
--- a/drivers/staging/zram/zram_drv.c
+++ b/drivers/staging/zram/zram_drv.c
@@ -336,7 +336,7 @@ static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec, u32 index,
 		clen = PAGE_SIZE;
 	}
 
-	handle = zs_malloc(zram->mem_pool, clen);
+	handle = zs_malloc(zram->mem_pool, clen, GFP_NOIO | __GFP_HIGHMEM);
 	if (!handle) {
 		pr_info("Error allocating memory for compressed "
 			"page: %u, size=%zu\n", index, clen);
@@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
 	/* zram devices sort of resembles non-rotational disks */
 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
 
-	zram->mem_pool = zs_create_pool("zram", GFP_NOIO | __GFP_HIGHMEM);
+	zram->mem_pool = zs_create_pool("zram", GFP_KERNEL);
 	if (!zram->mem_pool) {
 		pr_err("Error creating memory pool\n");
 		ret = -ENOMEM;
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 09a9d35..6ff380e 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -205,8 +205,6 @@ struct link_free {
 
 struct zs_pool {
 	struct size_class size_class[ZS_SIZE_CLASSES];
-
-	gfp_t flags;	/* allocation flags used when growing pool */
 	const char *name;
 };
 
@@ -807,7 +805,7 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
 		return NULL;
 
 	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
-	pool = kzalloc(ovhd_size, GFP_KERNEL);
+	pool = kzalloc(ovhd_size, flags);
 	if (!pool)
 		return NULL;
 
@@ -827,7 +825,6 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
 
 	}
 
-	pool->flags = flags;
 	pool->name = name;
 
 	return pool;
@@ -863,7 +860,7 @@ EXPORT_SYMBOL_GPL(zs_destroy_pool);
  * otherwise 0.
  * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
  */
-unsigned long zs_malloc(struct zs_pool *pool, size_t size)
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
 {
 	unsigned long obj;
 	struct link_free *link;
@@ -885,7 +882,7 @@ unsigned long zs_malloc(struct zs_pool *pool, size_t size)
 
 	if (!first_page) {
 		spin_unlock(&class->lock);
-		first_page = alloc_zspage(class, pool->flags);
+		first_page = alloc_zspage(class, flags);
 		if (unlikely(!first_page))
 			return 0;
 
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
index de2e8bf..907ff03 100644
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ b/drivers/staging/zsmalloc/zsmalloc.h
@@ -31,7 +31,7 @@ struct zs_pool;
 struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
 void zs_destroy_pool(struct zs_pool *pool);
 
-unsigned long zs_malloc(struct zs_pool *pool, size_t size);
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
 void zs_free(struct zs_pool *pool, unsigned long obj);
 
 void *zs_map_object(struct zs_pool *pool, unsigned long handle,
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
  2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-25  0:09   ` Nitin Gupta
  2013-01-25 21:50   ` Rik van Riel
  2013-01-07 20:24 ` [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
                   ` (7 subsequent siblings)
  9 siblings, 2 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

zs_create_pool() currently takes a name argument which is
never used in any useful way.

This patch removes it.

Signed-off-by: Seth Jennnings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zcache/zcache-main.c     |    2 +-
 drivers/staging/zram/zram_drv.c          |    2 +-
 drivers/staging/zsmalloc/zsmalloc-main.c |    7 +------
 drivers/staging/zsmalloc/zsmalloc.h      |    2 +-
 4 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index 674c754..6fa9f9a 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
 		goto out;
 	cli->allocated = 1;
 #ifdef CONFIG_FRONTSWAP
-	cli->zspool = zs_create_pool("zcache", GFP_KERNEL);
+	cli->zspool = zs_create_pool(GFP_KERNEL);
 	if (cli->zspool == NULL)
 		goto out;
 	idr_init(&cli->tmem_pools);
diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
index 13e9b4b..13d9f6d 100644
--- a/drivers/staging/zram/zram_drv.c
+++ b/drivers/staging/zram/zram_drv.c
@@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
 	/* zram devices sort of resembles non-rotational disks */
 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
 
-	zram->mem_pool = zs_create_pool("zram", GFP_KERNEL);
+	zram->mem_pool = zs_create_pool(GFP_KERNEL);
 	if (!zram->mem_pool) {
 		pr_err("Error creating memory pool\n");
 		ret = -ENOMEM;
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 6ff380e..5e212c0 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -796,14 +796,11 @@ fail:
 	return notifier_to_errno(ret);
 }
 
-struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
+struct zs_pool *zs_create_pool(gfp_t flags)
 {
 	int i, ovhd_size;
 	struct zs_pool *pool;
 
-	if (!name)
-		return NULL;
-
 	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
 	pool = kzalloc(ovhd_size, flags);
 	if (!pool)
@@ -825,8 +822,6 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
 
 	}
 
-	pool->name = name;
-
 	return pool;
 }
 EXPORT_SYMBOL_GPL(zs_create_pool);
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
index 907ff03..25a4b4d 100644
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ b/drivers/staging/zsmalloc/zsmalloc.h
@@ -28,7 +28,7 @@ enum zs_mapmode {
 
 struct zs_pool;
 
-struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
+struct zs_pool *zs_create_pool(gfp_t flags);
 void zs_destroy_pool(struct zs_pool *pool);
 
 unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
  2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
  2013-01-07 20:24 ` [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-25  0:11   ` Nitin Gupta
  2013-01-25 21:55   ` Rik van Riel
  2013-01-07 20:24 ` [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
                   ` (6 subsequent siblings)
  9 siblings, 2 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

This patch allows users of zsmalloc to register the
allocation and free routines used by zsmalloc to obtain
more pages for the memory pool.  This allows the user
more control over zsmalloc pool policy and behavior.

If the user does not wish to control this, alloc_page() and
__free_page() are used by default.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zcache/zcache-main.c     |    2 +-
 drivers/staging/zram/zram_drv.c          |    2 +-
 drivers/staging/zsmalloc/zsmalloc-main.c |   43 ++++++++++++++++++++++--------
 drivers/staging/zsmalloc/zsmalloc.h      |    8 +++++-
 4 files changed, 41 insertions(+), 14 deletions(-)

diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index 6fa9f9a..e3e533b 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
 		goto out;
 	cli->allocated = 1;
 #ifdef CONFIG_FRONTSWAP
-	cli->zspool = zs_create_pool(GFP_KERNEL);
+	cli->zspool = zs_create_pool(GFP_KERNEL, NULL);
 	if (cli->zspool == NULL)
 		goto out;
 	idr_init(&cli->tmem_pools);
diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
index 13d9f6d..0357903 100644
--- a/drivers/staging/zram/zram_drv.c
+++ b/drivers/staging/zram/zram_drv.c
@@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
 	/* zram devices sort of resembles non-rotational disks */
 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
 
-	zram->mem_pool = zs_create_pool(GFP_KERNEL);
+	zram->mem_pool = zs_create_pool(GFP_KERNEL, NULL);
 	if (!zram->mem_pool) {
 		pr_err("Error creating memory pool\n");
 		ret = -ENOMEM;
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 5e212c0..825e124 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -205,7 +205,7 @@ struct link_free {
 
 struct zs_pool {
 	struct size_class size_class[ZS_SIZE_CLASSES];
-	const char *name;
+	struct zs_ops *ops;
 };
 
 /*
@@ -240,6 +240,21 @@ struct mapping_area {
 	enum zs_mapmode vm_mm; /* mapping mode */
 };
 
+/* default page alloc/free ops */
+struct page *zs_alloc_page(gfp_t flags)
+{
+	return alloc_page(flags);
+}
+
+void zs_free_page(struct page *page)
+{
+	__free_page(page);
+}
+
+struct zs_ops zs_default_ops = {
+	.alloc = zs_alloc_page,
+	.free = zs_free_page
+};
 
 /* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
 static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
@@ -476,7 +491,7 @@ static void reset_page(struct page *page)
 	reset_page_mapcount(page);
 }
 
-static void free_zspage(struct page *first_page)
+static void free_zspage(struct zs_ops *ops, struct page *first_page)
 {
 	struct page *nextp, *tmp, *head_extra;
 
@@ -486,7 +501,7 @@ static void free_zspage(struct page *first_page)
 	head_extra = (struct page *)page_private(first_page);
 
 	reset_page(first_page);
-	__free_page(first_page);
+	ops->free(first_page);
 
 	/* zspage with only 1 system page */
 	if (!head_extra)
@@ -495,10 +510,10 @@ static void free_zspage(struct page *first_page)
 	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
 		list_del(&nextp->lru);
 		reset_page(nextp);
-		__free_page(nextp);
+		ops->free(nextp);
 	}
 	reset_page(head_extra);
-	__free_page(head_extra);
+	ops->free(head_extra);
 }
 
 /* Initialize a newly allocated zspage */
@@ -550,7 +565,8 @@ static void init_zspage(struct page *first_page, struct size_class *class)
 /*
  * Allocate a zspage for the given size class
  */
-static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
+static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
+				gfp_t flags)
 {
 	int i, error;
 	struct page *first_page = NULL, *uninitialized_var(prev_page);
@@ -570,7 +586,7 @@ static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
 	for (i = 0; i < class->pages_per_zspage; i++) {
 		struct page *page;
 
-		page = alloc_page(flags);
+		page = ops->alloc(flags);
 		if (!page)
 			goto cleanup;
 
@@ -602,7 +618,7 @@ static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
 
 cleanup:
 	if (unlikely(error) && first_page) {
-		free_zspage(first_page);
+		free_zspage(ops, first_page);
 		first_page = NULL;
 	}
 
@@ -796,7 +812,7 @@ fail:
 	return notifier_to_errno(ret);
 }
 
-struct zs_pool *zs_create_pool(gfp_t flags)
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
 {
 	int i, ovhd_size;
 	struct zs_pool *pool;
@@ -822,6 +838,11 @@ struct zs_pool *zs_create_pool(gfp_t flags)
 
 	}
 
+	if (ops)
+		pool->ops = ops;
+	else
+		pool->ops = &zs_default_ops;
+
 	return pool;
 }
 EXPORT_SYMBOL_GPL(zs_create_pool);
@@ -877,7 +898,7 @@ unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
 
 	if (!first_page) {
 		spin_unlock(&class->lock);
-		first_page = alloc_zspage(class, flags);
+		first_page = alloc_zspage(pool->ops, class, flags);
 		if (unlikely(!first_page))
 			return 0;
 
@@ -943,7 +964,7 @@ void zs_free(struct zs_pool *pool, unsigned long obj)
 	spin_unlock(&class->lock);
 
 	if (fullness == ZS_EMPTY)
-		free_zspage(first_page);
+		free_zspage(pool->ops, first_page);
 }
 EXPORT_SYMBOL_GPL(zs_free);
 
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
index 25a4b4d..eb6efb6 100644
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ b/drivers/staging/zsmalloc/zsmalloc.h
@@ -14,6 +14,7 @@
 #define _ZS_MALLOC_H_
 
 #include <linux/types.h>
+#include <linux/mm_types.h>
 
 /*
  * zsmalloc mapping modes
@@ -26,9 +27,14 @@ enum zs_mapmode {
 	ZS_MM_WO /* write-only (no copy-in at map time) */
 };
 
+struct zs_ops {
+	struct page * (*alloc)(gfp_t);
+	void (*free)(struct page *);
+};
+
 struct zs_pool;
 
-struct zs_pool *zs_create_pool(gfp_t flags);
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
 void zs_destroy_pool(struct zs_pool *pool);
 
 unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (2 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-25  0:17   ` Nitin Gupta
  2013-01-07 20:24 ` [PATCHv2 5/9] debugfs: add get/set for atomic types Seth Jennings
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

Right now ZS_SIZE_CLASS_DELTA is hardcoded to be 16.  This
creates 254 classes for systems with 4k pages. However, on
PPC64 with 64k pages, it creates 4095 classes which is far
too many.

This patch makes ZS_SIZE_CLASS_DELTA relative to PAGE_SIZE
so that regardless of the page size, there will be the same
number of classes.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zsmalloc/zsmalloc-main.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 825e124..3543047 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -141,7 +141,7 @@
  *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
  *  (reason above)
  */
-#define ZS_SIZE_CLASS_DELTA	16
+#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
 #define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
 					ZS_SIZE_CLASS_DELTA + 1)
 
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 5/9] debugfs: add get/set for atomic types
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (3 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-07 20:32   ` Greg Kroah-Hartman
  2013-01-07 20:24 ` [PATCHv2 6/9] zsmalloc: promote to lib/ Seth Jennings
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

debugfs currently lack the ability to create attributes
that set/get atomic_t values.

This patch adds support for this through a new
debugfs_create_atomic_t() function.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 fs/debugfs/file.c       |   42 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/debugfs.h |    2 ++
 2 files changed, 44 insertions(+)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index c5ca6ae..fa26d5b 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -21,6 +21,7 @@
 #include <linux/debugfs.h>
 #include <linux/io.h>
 #include <linux/slab.h>
+#include <linux/atomic.h>
 
 static ssize_t default_read_file(struct file *file, char __user *buf,
 				 size_t count, loff_t *ppos)
@@ -403,6 +404,47 @@ struct dentry *debugfs_create_size_t(const char *name, umode_t mode,
 }
 EXPORT_SYMBOL_GPL(debugfs_create_size_t);
 
+static int debugfs_atomic_t_set(void *data, u64 val)
+{
+	atomic_set((atomic_t *)data, val);
+	return 0;
+}
+static int debugfs_atomic_t_get(void *data, u64 *val)
+{
+	*val = atomic_read((atomic_t *)data);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t, debugfs_atomic_t_get,
+			debugfs_atomic_t_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_ro, debugfs_atomic_t_get, NULL, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_wo, NULL, debugfs_atomic_t_set, "%llu\n");
+
+/**
+ * debugfs_create_atomic_t - create a debugfs file that is used to read and
+ * write an atomic_t value
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file.  This should be a
+ *          directory dentry if set.  If this parameter is %NULL, then the
+ *          file will be created in the root of the debugfs filesystem.
+ * @value: a pointer to the variable that the file should read to and write
+ *         from.
+ */
+struct dentry *debugfs_create_atomic_t(const char *name, umode_t mode,
+				 struct dentry *parent, atomic_t *value)
+{
+	/* if there are no write bits set, make read only */
+	if (!(mode & S_IWUGO))
+		return debugfs_create_file(name, mode, parent, value,
+					&fops_atomic_t_ro);
+	/* if there are no read bits set, make write only */
+	if (!(mode & S_IRUGO))
+		return debugfs_create_file(name, mode, parent, value,
+					&fops_atomic_t_wo);
+
+	return debugfs_create_file(name, mode, parent, value, &fops_atomic_t);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_atomic_t);
 
 static ssize_t read_file_bool(struct file *file, char __user *user_buf,
 			      size_t count, loff_t *ppos)
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index 66c434f..51fea70 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -79,6 +79,8 @@ struct dentry *debugfs_create_x64(const char *name, umode_t mode,
 				  struct dentry *parent, u64 *value);
 struct dentry *debugfs_create_size_t(const char *name, umode_t mode,
 				     struct dentry *parent, size_t *value);
+struct dentry *debugfs_create_atomic_t(const char *name, umode_t mode,
+				     struct dentry *parent, atomic_t *value);
 struct dentry *debugfs_create_bool(const char *name, umode_t mode,
 				  struct dentry *parent, u32 *value);
 
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 6/9] zsmalloc: promote to lib/
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (4 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 5/9] debugfs: add get/set for atomic types Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-28  4:01   ` Minchan Kim
  2013-01-07 20:24 ` [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends Seth Jennings
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

This patch promotes the slab-based zsmalloc memory allocator
from the staging tree to lib/

zswap depends on this allocator for storing compressed RAM pages
in an efficient way under system wide memory pressure where
high-order (greater than 0) page allocation are very likely to
fail.

For more information on zsmalloc and its internals, read the
documentation at the top of the zsmalloc.c file.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
--
This patch is similar to a patch Minchan has on out on the list
to promote for use in zram.
---
 drivers/staging/Kconfig                  |    2 -
 drivers/staging/Makefile                 |    1 -
 drivers/staging/zcache/zcache-main.c     |    3 +-
 drivers/staging/zram/zram_drv.h          |    3 +-
 drivers/staging/zsmalloc/Kconfig         |   10 -
 drivers/staging/zsmalloc/Makefile        |    3 -
 drivers/staging/zsmalloc/zsmalloc-main.c | 1077 ------------------------------
 drivers/staging/zsmalloc/zsmalloc.h      |   49 --
 include/linux/zsmalloc.h                 |   49 ++
 lib/Kconfig                              |   18 +
 lib/Makefile                             |    1 +
 lib/zsmalloc.c                           | 1076 +++++++++++++++++++++++++++++
 12 files changed, 1146 insertions(+), 1146 deletions(-)
 delete mode 100644 drivers/staging/zsmalloc/Kconfig
 delete mode 100644 drivers/staging/zsmalloc/Makefile
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
 create mode 100644 include/linux/zsmalloc.h
 create mode 100644 lib/zsmalloc.c

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 329bdb4..c0a7918 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -76,8 +76,6 @@ source "drivers/staging/zram/Kconfig"
 
 source "drivers/staging/zcache/Kconfig"
 
-source "drivers/staging/zsmalloc/Kconfig"
-
 source "drivers/staging/wlags49_h2/Kconfig"
 
 source "drivers/staging/wlags49_h25/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index c7ec486..1572fe5 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -32,7 +32,6 @@ obj-$(CONFIG_DX_SEP)            += sep/
 obj-$(CONFIG_IIO)		+= iio/
 obj-$(CONFIG_ZRAM)		+= zram/
 obj-$(CONFIG_ZCACHE)		+= zcache/
-obj-$(CONFIG_ZSMALLOC)		+= zsmalloc/
 obj-$(CONFIG_WLAGS49_H2)	+= wlags49_h2/
 obj-$(CONFIG_WLAGS49_H25)	+= wlags49_h25/
 obj-$(CONFIG_FB_SM7XX)		+= sm7xxfb/
diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index e3e533b..08e412a 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -32,10 +32,9 @@
 #include <linux/crypto.h>
 #include <linux/string.h>
 #include <linux/idr.h>
+#include <linux/zsmalloc.h>
 #include "tmem.h"
 
-#include "../zsmalloc/zsmalloc.h"
-
 #ifdef CONFIG_CLEANCACHE
 #include <linux/cleancache.h>
 #endif
diff --git a/drivers/staging/zram/zram_drv.h b/drivers/staging/zram/zram_drv.h
index df2eec4..1e72965 100644
--- a/drivers/staging/zram/zram_drv.h
+++ b/drivers/staging/zram/zram_drv.h
@@ -17,8 +17,7 @@
 
 #include <linux/spinlock.h>
 #include <linux/mutex.h>
-
-#include "../zsmalloc/zsmalloc.h"
+#include <linux/zsmalloc.h>
 
 /*
  * Some arbitrary value. This is just to catch
diff --git a/drivers/staging/zsmalloc/Kconfig b/drivers/staging/zsmalloc/Kconfig
deleted file mode 100644
index 9084565..0000000
--- a/drivers/staging/zsmalloc/Kconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-config ZSMALLOC
-	tristate "Memory allocator for compressed pages"
-	default n
-	help
-	  zsmalloc is a slab-based memory allocator designed to store
-	  compressed RAM pages.  zsmalloc uses virtual memory mapping
-	  in order to reduce fragmentation.  However, this results in a
-	  non-standard allocator interface where a handle, not a pointer, is
-	  returned by an alloc().  This handle must be mapped in order to
-	  access the allocated space.
diff --git a/drivers/staging/zsmalloc/Makefile b/drivers/staging/zsmalloc/Makefile
deleted file mode 100644
index b134848..0000000
--- a/drivers/staging/zsmalloc/Makefile
+++ /dev/null
@@ -1,3 +0,0 @@
-zsmalloc-y 		:= zsmalloc-main.o
-
-obj-$(CONFIG_ZSMALLOC)	+= zsmalloc.o
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
deleted file mode 100644
index 3543047..0000000
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ /dev/null
@@ -1,1077 +0,0 @@
-/*
- * zsmalloc memory allocator
- *
- * Copyright (C) 2011  Nitin Gupta
- *
- * This code is released using a dual license strategy: BSD/GPL
- * You can choose the license that better fits your requirements.
- *
- * Released under the terms of 3-clause BSD License
- * Released under the terms of GNU General Public License Version 2.0
- */
-
-
-/*
- * This allocator is designed for use with zcache and zram. Thus, the
- * allocator is supposed to work well under low memory conditions. In
- * particular, it never attempts higher order page allocation which is
- * very likely to fail under memory pressure. On the other hand, if we
- * just use single (0-order) pages, it would suffer from very high
- * fragmentation -- any object of size PAGE_SIZE/2 or larger would occupy
- * an entire page. This was one of the major issues with its predecessor
- * (xvmalloc).
- *
- * To overcome these issues, zsmalloc allocates a bunch of 0-order pages
- * and links them together using various 'struct page' fields. These linked
- * pages act as a single higher-order page i.e. an object can span 0-order
- * page boundaries. The code refers to these linked pages as a single entity
- * called zspage.
- *
- * Following is how we use various fields and flags of underlying
- * struct page(s) to form a zspage.
- *
- * Usage of struct page fields:
- *	page->first_page: points to the first component (0-order) page
- *	page->index (union with page->freelist): offset of the first object
- *		starting in this page. For the first page, this is
- *		always 0, so we use this field (aka freelist) to point
- *		to the first free object in zspage.
- *	page->lru: links together all component pages (except the first page)
- *		of a zspage
- *
- *	For _first_ page only:
- *
- *	page->private (union with page->first_page): refers to the
- *		component page after the first page
- *	page->freelist: points to the first free object in zspage.
- *		Free objects are linked together using in-place
- *		metadata.
- *	page->objects: maximum number of objects we can store in this
- *		zspage (class->zspage_order * PAGE_SIZE / class->size)
- *	page->lru: links together first pages of various zspages.
- *		Basically forming list of zspages in a fullness group.
- *	page->mapping: class index and fullness group of the zspage
- *
- * Usage of struct page flags:
- *	PG_private: identifies the first component page
- *	PG_private2: identifies the last component page
- *
- */
-
-#ifdef CONFIG_ZSMALLOC_DEBUG
-#define DEBUG
-#endif
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/bitops.h>
-#include <linux/errno.h>
-#include <linux/highmem.h>
-#include <linux/init.h>
-#include <linux/string.h>
-#include <linux/slab.h>
-#include <asm/tlbflush.h>
-#include <asm/pgtable.h>
-#include <linux/cpumask.h>
-#include <linux/cpu.h>
-#include <linux/vmalloc.h>
-#include <linux/hardirq.h>
-#include <linux/spinlock.h>
-#include <linux/types.h>
-
-#include "zsmalloc.h"
-
-/*
- * This must be power of 2 and greater than of equal to sizeof(link_free).
- * These two conditions ensure that any 'struct link_free' itself doesn't
- * span more than 1 page which avoids complex case of mapping 2 pages simply
- * to restore link_free pointer values.
- */
-#define ZS_ALIGN		8
-
-/*
- * A single 'zspage' is composed of up to 2^N discontiguous 0-order (single)
- * pages. ZS_MAX_ZSPAGE_ORDER defines upper limit on N.
- */
-#define ZS_MAX_ZSPAGE_ORDER 2
-#define ZS_MAX_PAGES_PER_ZSPAGE (_AC(1, UL) << ZS_MAX_ZSPAGE_ORDER)
-
-/*
- * Object location (<PFN>, <obj_idx>) is encoded as
- * as single (void *) handle value.
- *
- * Note that object index <obj_idx> is relative to system
- * page <PFN> it is stored in, so for each sub-page belonging
- * to a zspage, obj_idx starts with 0.
- *
- * This is made more complicated by various memory models and PAE.
- */
-
-#ifndef MAX_PHYSMEM_BITS
-#ifdef CONFIG_HIGHMEM64G
-#define MAX_PHYSMEM_BITS 36
-#else /* !CONFIG_HIGHMEM64G */
-/*
- * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just
- * be PAGE_SHIFT
- */
-#define MAX_PHYSMEM_BITS BITS_PER_LONG
-#endif
-#endif
-#define _PFN_BITS		(MAX_PHYSMEM_BITS - PAGE_SHIFT)
-#define OBJ_INDEX_BITS	(BITS_PER_LONG - _PFN_BITS)
-#define OBJ_INDEX_MASK	((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
-
-#define MAX(a, b) ((a) >= (b) ? (a) : (b))
-/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
-#define ZS_MIN_ALLOC_SIZE \
-	MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS))
-#define ZS_MAX_ALLOC_SIZE	PAGE_SIZE
-
-/*
- * On systems with 4K page size, this gives 254 size classes! There is a
- * trader-off here:
- *  - Large number of size classes is potentially wasteful as free page are
- *    spread across these classes
- *  - Small number of size classes causes large internal fragmentation
- *  - Probably its better to use specific size classes (empirically
- *    determined). NOTE: all those class sizes must be set as multiple of
- *    ZS_ALIGN to make sure link_free itself never has to span 2 pages.
- *
- *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
- *  (reason above)
- */
-#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
-#define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
-					ZS_SIZE_CLASS_DELTA + 1)
-
-/*
- * We do not maintain any list for completely empty or full pages
- */
-enum fullness_group {
-	ZS_ALMOST_FULL,
-	ZS_ALMOST_EMPTY,
-	_ZS_NR_FULLNESS_GROUPS,
-
-	ZS_EMPTY,
-	ZS_FULL
-};
-
-/*
- * We assign a page to ZS_ALMOST_EMPTY fullness group when:
- *	n <= N / f, where
- * n = number of allocated objects
- * N = total number of objects zspage can store
- * f = 1/fullness_threshold_frac
- *
- * Similarly, we assign zspage to:
- *	ZS_ALMOST_FULL	when n > N / f
- *	ZS_EMPTY	when n == 0
- *	ZS_FULL		when n == N
- *
- * (see: fix_fullness_group())
- */
-static const int fullness_threshold_frac = 4;
-
-struct size_class {
-	/*
-	 * Size of objects stored in this class. Must be multiple
-	 * of ZS_ALIGN.
-	 */
-	int size;
-	unsigned int index;
-
-	/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
-	int pages_per_zspage;
-
-	spinlock_t lock;
-
-	/* stats */
-	u64 pages_allocated;
-
-	struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
-};
-
-/*
- * Placed within free objects to form a singly linked list.
- * For every zspage, first_page->freelist gives head of this list.
- *
- * This must be power of 2 and less than or equal to ZS_ALIGN
- */
-struct link_free {
-	/* Handle of next free chunk (encodes <PFN, obj_idx>) */
-	void *next;
-};
-
-struct zs_pool {
-	struct size_class size_class[ZS_SIZE_CLASSES];
-	struct zs_ops *ops;
-};
-
-/*
- * A zspage's class index and fullness group
- * are encoded in its (first)page->mapping
- */
-#define CLASS_IDX_BITS	28
-#define FULLNESS_BITS	4
-#define CLASS_IDX_MASK	((1 << CLASS_IDX_BITS) - 1)
-#define FULLNESS_MASK	((1 << FULLNESS_BITS) - 1)
-
-/*
- * By default, zsmalloc uses a copy-based object mapping method to access
- * allocations that span two pages. However, if a particular architecture
- * 1) Implements local_flush_tlb_kernel_range() and 2) Performs VM mapping
- * faster than copying, then it should be added here so that
- * USE_PGTABLE_MAPPING is defined. This causes zsmalloc to use page table
- * mapping rather than copying
- * for object mapping.
-*/
-#if defined(CONFIG_ARM)
-#define USE_PGTABLE_MAPPING
-#endif
-
-struct mapping_area {
-#ifdef USE_PGTABLE_MAPPING
-	struct vm_struct *vm; /* vm area for mapping object that span pages */
-#else
-	char *vm_buf; /* copy buffer for objects that span pages */
-#endif
-	char *vm_addr; /* address of kmap_atomic()'ed pages */
-	enum zs_mapmode vm_mm; /* mapping mode */
-};
-
-/* default page alloc/free ops */
-struct page *zs_alloc_page(gfp_t flags)
-{
-	return alloc_page(flags);
-}
-
-void zs_free_page(struct page *page)
-{
-	__free_page(page);
-}
-
-struct zs_ops zs_default_ops = {
-	.alloc = zs_alloc_page,
-	.free = zs_free_page
-};
-
-/* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
-static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
-
-static int is_first_page(struct page *page)
-{
-	return PagePrivate(page);
-}
-
-static int is_last_page(struct page *page)
-{
-	return PagePrivate2(page);
-}
-
-static void get_zspage_mapping(struct page *page, unsigned int *class_idx,
-				enum fullness_group *fullness)
-{
-	unsigned long m;
-	BUG_ON(!is_first_page(page));
-
-	m = (unsigned long)page->mapping;
-	*fullness = m & FULLNESS_MASK;
-	*class_idx = (m >> FULLNESS_BITS) & CLASS_IDX_MASK;
-}
-
-static void set_zspage_mapping(struct page *page, unsigned int class_idx,
-				enum fullness_group fullness)
-{
-	unsigned long m;
-	BUG_ON(!is_first_page(page));
-
-	m = ((class_idx & CLASS_IDX_MASK) << FULLNESS_BITS) |
-			(fullness & FULLNESS_MASK);
-	page->mapping = (struct address_space *)m;
-}
-
-static int get_size_class_index(int size)
-{
-	int idx = 0;
-
-	if (likely(size > ZS_MIN_ALLOC_SIZE))
-		idx = DIV_ROUND_UP(size - ZS_MIN_ALLOC_SIZE,
-				ZS_SIZE_CLASS_DELTA);
-
-	return idx;
-}
-
-static enum fullness_group get_fullness_group(struct page *page)
-{
-	int inuse, max_objects;
-	enum fullness_group fg;
-	BUG_ON(!is_first_page(page));
-
-	inuse = page->inuse;
-	max_objects = page->objects;
-
-	if (inuse == 0)
-		fg = ZS_EMPTY;
-	else if (inuse == max_objects)
-		fg = ZS_FULL;
-	else if (inuse <= max_objects / fullness_threshold_frac)
-		fg = ZS_ALMOST_EMPTY;
-	else
-		fg = ZS_ALMOST_FULL;
-
-	return fg;
-}
-
-static void insert_zspage(struct page *page, struct size_class *class,
-				enum fullness_group fullness)
-{
-	struct page **head;
-
-	BUG_ON(!is_first_page(page));
-
-	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
-		return;
-
-	head = &class->fullness_list[fullness];
-	if (*head)
-		list_add_tail(&page->lru, &(*head)->lru);
-
-	*head = page;
-}
-
-static void remove_zspage(struct page *page, struct size_class *class,
-				enum fullness_group fullness)
-{
-	struct page **head;
-
-	BUG_ON(!is_first_page(page));
-
-	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
-		return;
-
-	head = &class->fullness_list[fullness];
-	BUG_ON(!*head);
-	if (list_empty(&(*head)->lru))
-		*head = NULL;
-	else if (*head == page)
-		*head = (struct page *)list_entry((*head)->lru.next,
-					struct page, lru);
-
-	list_del_init(&page->lru);
-}
-
-static enum fullness_group fix_fullness_group(struct zs_pool *pool,
-						struct page *page)
-{
-	int class_idx;
-	struct size_class *class;
-	enum fullness_group currfg, newfg;
-
-	BUG_ON(!is_first_page(page));
-
-	get_zspage_mapping(page, &class_idx, &currfg);
-	newfg = get_fullness_group(page);
-	if (newfg == currfg)
-		goto out;
-
-	class = &pool->size_class[class_idx];
-	remove_zspage(page, class, currfg);
-	insert_zspage(page, class, newfg);
-	set_zspage_mapping(page, class_idx, newfg);
-
-out:
-	return newfg;
-}
-
-/*
- * We have to decide on how many pages to link together
- * to form a zspage for each size class. This is important
- * to reduce wastage due to unusable space left at end of
- * each zspage which is given as:
- *	wastage = Zp - Zp % size_class
- * where Zp = zspage size = k * PAGE_SIZE where k = 1, 2, ...
- *
- * For example, for size class of 3/8 * PAGE_SIZE, we should
- * link together 3 PAGE_SIZE sized pages to form a zspage
- * since then we can perfectly fit in 8 such objects.
- */
-static int get_pages_per_zspage(int class_size)
-{
-	int i, max_usedpc = 0;
-	/* zspage order which gives maximum used size per KB */
-	int max_usedpc_order = 1;
-
-	for (i = 1; i <= ZS_MAX_PAGES_PER_ZSPAGE; i++) {
-		int zspage_size;
-		int waste, usedpc;
-
-		zspage_size = i * PAGE_SIZE;
-		waste = zspage_size % class_size;
-		usedpc = (zspage_size - waste) * 100 / zspage_size;
-
-		if (usedpc > max_usedpc) {
-			max_usedpc = usedpc;
-			max_usedpc_order = i;
-		}
-	}
-
-	return max_usedpc_order;
-}
-
-/*
- * A single 'zspage' is composed of many system pages which are
- * linked together using fields in struct page. This function finds
- * the first/head page, given any component page of a zspage.
- */
-static struct page *get_first_page(struct page *page)
-{
-	if (is_first_page(page))
-		return page;
-	else
-		return page->first_page;
-}
-
-static struct page *get_next_page(struct page *page)
-{
-	struct page *next;
-
-	if (is_last_page(page))
-		next = NULL;
-	else if (is_first_page(page))
-		next = (struct page *)page->private;
-	else
-		next = list_entry(page->lru.next, struct page, lru);
-
-	return next;
-}
-
-/* Encode <page, obj_idx> as a single handle value */
-static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
-{
-	unsigned long handle;
-
-	if (!page) {
-		BUG_ON(obj_idx);
-		return NULL;
-	}
-
-	handle = page_to_pfn(page) << OBJ_INDEX_BITS;
-	handle |= (obj_idx & OBJ_INDEX_MASK);
-
-	return (void *)handle;
-}
-
-/* Decode <page, obj_idx> pair from the given object handle */
-static void obj_handle_to_location(unsigned long handle, struct page **page,
-				unsigned long *obj_idx)
-{
-	*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
-	*obj_idx = handle & OBJ_INDEX_MASK;
-}
-
-static unsigned long obj_idx_to_offset(struct page *page,
-				unsigned long obj_idx, int class_size)
-{
-	unsigned long off = 0;
-
-	if (!is_first_page(page))
-		off = page->index;
-
-	return off + obj_idx * class_size;
-}
-
-static void reset_page(struct page *page)
-{
-	clear_bit(PG_private, &page->flags);
-	clear_bit(PG_private_2, &page->flags);
-	set_page_private(page, 0);
-	page->mapping = NULL;
-	page->freelist = NULL;
-	reset_page_mapcount(page);
-}
-
-static void free_zspage(struct zs_ops *ops, struct page *first_page)
-{
-	struct page *nextp, *tmp, *head_extra;
-
-	BUG_ON(!is_first_page(first_page));
-	BUG_ON(first_page->inuse);
-
-	head_extra = (struct page *)page_private(first_page);
-
-	reset_page(first_page);
-	ops->free(first_page);
-
-	/* zspage with only 1 system page */
-	if (!head_extra)
-		return;
-
-	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
-		list_del(&nextp->lru);
-		reset_page(nextp);
-		ops->free(nextp);
-	}
-	reset_page(head_extra);
-	ops->free(head_extra);
-}
-
-/* Initialize a newly allocated zspage */
-static void init_zspage(struct page *first_page, struct size_class *class)
-{
-	unsigned long off = 0;
-	struct page *page = first_page;
-
-	BUG_ON(!is_first_page(first_page));
-	while (page) {
-		struct page *next_page;
-		struct link_free *link;
-		unsigned int i, objs_on_page;
-
-		/*
-		 * page->index stores offset of first object starting
-		 * in the page. For the first page, this is always 0,
-		 * so we use first_page->index (aka ->freelist) to store
-		 * head of corresponding zspage's freelist.
-		 */
-		if (page != first_page)
-			page->index = off;
-
-		link = (struct link_free *)kmap_atomic(page) +
-						off / sizeof(*link);
-		objs_on_page = (PAGE_SIZE - off) / class->size;
-
-		for (i = 1; i <= objs_on_page; i++) {
-			off += class->size;
-			if (off < PAGE_SIZE) {
-				link->next = obj_location_to_handle(page, i);
-				link += class->size / sizeof(*link);
-			}
-		}
-
-		/*
-		 * We now come to the last (full or partial) object on this
-		 * page, which must point to the first object on the next
-		 * page (if present)
-		 */
-		next_page = get_next_page(page);
-		link->next = obj_location_to_handle(next_page, 0);
-		kunmap_atomic(link);
-		page = next_page;
-		off = (off + class->size) % PAGE_SIZE;
-	}
-}
-
-/*
- * Allocate a zspage for the given size class
- */
-static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
-				gfp_t flags)
-{
-	int i, error;
-	struct page *first_page = NULL, *uninitialized_var(prev_page);
-
-	/*
-	 * Allocate individual pages and link them together as:
-	 * 1. first page->private = first sub-page
-	 * 2. all sub-pages are linked together using page->lru
-	 * 3. each sub-page is linked to the first page using page->first_page
-	 *
-	 * For each size class, First/Head pages are linked together using
-	 * page->lru. Also, we set PG_private to identify the first page
-	 * (i.e. no other sub-page has this flag set) and PG_private_2 to
-	 * identify the last page.
-	 */
-	error = -ENOMEM;
-	for (i = 0; i < class->pages_per_zspage; i++) {
-		struct page *page;
-
-		page = ops->alloc(flags);
-		if (!page)
-			goto cleanup;
-
-		INIT_LIST_HEAD(&page->lru);
-		if (i == 0) {	/* first page */
-			SetPagePrivate(page);
-			set_page_private(page, 0);
-			first_page = page;
-			first_page->inuse = 0;
-		}
-		if (i == 1)
-			first_page->private = (unsigned long)page;
-		if (i >= 1)
-			page->first_page = first_page;
-		if (i >= 2)
-			list_add(&page->lru, &prev_page->lru);
-		if (i == class->pages_per_zspage - 1)	/* last page */
-			SetPagePrivate2(page);
-		prev_page = page;
-	}
-
-	init_zspage(first_page, class);
-
-	first_page->freelist = obj_location_to_handle(first_page, 0);
-	/* Maximum number of objects we can store in this zspage */
-	first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
-
-	error = 0; /* Success */
-
-cleanup:
-	if (unlikely(error) && first_page) {
-		free_zspage(ops, first_page);
-		first_page = NULL;
-	}
-
-	return first_page;
-}
-
-static struct page *find_get_zspage(struct size_class *class)
-{
-	int i;
-	struct page *page;
-
-	for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
-		page = class->fullness_list[i];
-		if (page)
-			break;
-	}
-
-	return page;
-}
-
-#ifdef USE_PGTABLE_MAPPING
-static inline int __zs_cpu_up(struct mapping_area *area)
-{
-	/*
-	 * Make sure we don't leak memory if a cpu UP notification
-	 * and zs_init() race and both call zs_cpu_up() on the same cpu
-	 */
-	if (area->vm)
-		return 0;
-	area->vm = alloc_vm_area(PAGE_SIZE * 2, NULL);
-	if (!area->vm)
-		return -ENOMEM;
-	return 0;
-}
-
-static inline void __zs_cpu_down(struct mapping_area *area)
-{
-	if (area->vm)
-		free_vm_area(area->vm);
-	area->vm = NULL;
-}
-
-static inline void *__zs_map_object(struct mapping_area *area,
-				struct page *pages[2], int off, int size)
-{
-	BUG_ON(map_vm_area(area->vm, PAGE_KERNEL, &pages));
-	area->vm_addr = area->vm->addr;
-	return area->vm_addr + off;
-}
-
-static inline void __zs_unmap_object(struct mapping_area *area,
-				struct page *pages[2], int off, int size)
-{
-	unsigned long addr = (unsigned long)area->vm_addr;
-	unsigned long end = addr + (PAGE_SIZE * 2);
-
-	flush_cache_vunmap(addr, end);
-	unmap_kernel_range_noflush(addr, PAGE_SIZE * 2);
-	local_flush_tlb_kernel_range(addr, end);
-}
-
-#else /* USE_PGTABLE_MAPPING */
-
-static inline int __zs_cpu_up(struct mapping_area *area)
-{
-	/*
-	 * Make sure we don't leak memory if a cpu UP notification
-	 * and zs_init() race and both call zs_cpu_up() on the same cpu
-	 */
-	if (area->vm_buf)
-		return 0;
-	area->vm_buf = (char *)__get_free_page(GFP_KERNEL);
-	if (!area->vm_buf)
-		return -ENOMEM;
-	return 0;
-}
-
-static inline void __zs_cpu_down(struct mapping_area *area)
-{
-	if (area->vm_buf)
-		free_page((unsigned long)area->vm_buf);
-	area->vm_buf = NULL;
-}
-
-static void *__zs_map_object(struct mapping_area *area,
-			struct page *pages[2], int off, int size)
-{
-	int sizes[2];
-	void *addr;
-	char *buf = area->vm_buf;
-
-	/* disable page faults to match kmap_atomic() return conditions */
-	pagefault_disable();
-
-	/* no read fastpath */
-	if (area->vm_mm == ZS_MM_WO)
-		goto out;
-
-	sizes[0] = PAGE_SIZE - off;
-	sizes[1] = size - sizes[0];
-
-	/* copy object to per-cpu buffer */
-	addr = kmap_atomic(pages[0]);
-	memcpy(buf, addr + off, sizes[0]);
-	kunmap_atomic(addr);
-	addr = kmap_atomic(pages[1]);
-	memcpy(buf + sizes[0], addr, sizes[1]);
-	kunmap_atomic(addr);
-out:
-	return area->vm_buf;
-}
-
-static void __zs_unmap_object(struct mapping_area *area,
-			struct page *pages[2], int off, int size)
-{
-	int sizes[2];
-	void *addr;
-	char *buf = area->vm_buf;
-
-	/* no write fastpath */
-	if (area->vm_mm == ZS_MM_RO)
-		goto out;
-
-	sizes[0] = PAGE_SIZE - off;
-	sizes[1] = size - sizes[0];
-
-	/* copy per-cpu buffer to object */
-	addr = kmap_atomic(pages[0]);
-	memcpy(addr + off, buf, sizes[0]);
-	kunmap_atomic(addr);
-	addr = kmap_atomic(pages[1]);
-	memcpy(addr, buf + sizes[0], sizes[1]);
-	kunmap_atomic(addr);
-
-out:
-	/* enable page faults to match kunmap_atomic() return conditions */
-	pagefault_enable();
-}
-
-#endif /* USE_PGTABLE_MAPPING */
-
-static int zs_cpu_notifier(struct notifier_block *nb, unsigned long action,
-				void *pcpu)
-{
-	int ret, cpu = (long)pcpu;
-	struct mapping_area *area;
-
-	switch (action) {
-	case CPU_UP_PREPARE:
-		area = &per_cpu(zs_map_area, cpu);
-		ret = __zs_cpu_up(area);
-		if (ret)
-			return notifier_from_errno(ret);
-		break;
-	case CPU_DEAD:
-	case CPU_UP_CANCELED:
-		area = &per_cpu(zs_map_area, cpu);
-		__zs_cpu_down(area);
-		break;
-	}
-
-	return NOTIFY_OK;
-}
-
-static struct notifier_block zs_cpu_nb = {
-	.notifier_call = zs_cpu_notifier
-};
-
-static void zs_exit(void)
-{
-	int cpu;
-
-	for_each_online_cpu(cpu)
-		zs_cpu_notifier(NULL, CPU_DEAD, (void *)(long)cpu);
-	unregister_cpu_notifier(&zs_cpu_nb);
-}
-
-static int zs_init(void)
-{
-	int cpu, ret;
-
-	register_cpu_notifier(&zs_cpu_nb);
-	for_each_online_cpu(cpu) {
-		ret = zs_cpu_notifier(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
-		if (notifier_to_errno(ret))
-			goto fail;
-	}
-	return 0;
-fail:
-	zs_exit();
-	return notifier_to_errno(ret);
-}
-
-struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
-{
-	int i, ovhd_size;
-	struct zs_pool *pool;
-
-	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
-	pool = kzalloc(ovhd_size, flags);
-	if (!pool)
-		return NULL;
-
-	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
-		int size;
-		struct size_class *class;
-
-		size = ZS_MIN_ALLOC_SIZE + i * ZS_SIZE_CLASS_DELTA;
-		if (size > ZS_MAX_ALLOC_SIZE)
-			size = ZS_MAX_ALLOC_SIZE;
-
-		class = &pool->size_class[i];
-		class->size = size;
-		class->index = i;
-		spin_lock_init(&class->lock);
-		class->pages_per_zspage = get_pages_per_zspage(size);
-
-	}
-
-	if (ops)
-		pool->ops = ops;
-	else
-		pool->ops = &zs_default_ops;
-
-	return pool;
-}
-EXPORT_SYMBOL_GPL(zs_create_pool);
-
-void zs_destroy_pool(struct zs_pool *pool)
-{
-	int i;
-
-	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
-		int fg;
-		struct size_class *class = &pool->size_class[i];
-
-		for (fg = 0; fg < _ZS_NR_FULLNESS_GROUPS; fg++) {
-			if (class->fullness_list[fg]) {
-				pr_info("Freeing non-empty class with size "
-					"%db, fullness group %d\n",
-					class->size, fg);
-			}
-		}
-	}
-	kfree(pool);
-}
-EXPORT_SYMBOL_GPL(zs_destroy_pool);
-
-/**
- * zs_malloc - Allocate block of given size from pool.
- * @pool: pool to allocate from
- * @size: size of block to allocate
- *
- * On success, handle to the allocated object is returned,
- * otherwise 0.
- * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
- */
-unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
-{
-	unsigned long obj;
-	struct link_free *link;
-	int class_idx;
-	struct size_class *class;
-
-	struct page *first_page, *m_page;
-	unsigned long m_objidx, m_offset;
-
-	if (unlikely(!size || size > ZS_MAX_ALLOC_SIZE))
-		return 0;
-
-	class_idx = get_size_class_index(size);
-	class = &pool->size_class[class_idx];
-	BUG_ON(class_idx != class->index);
-
-	spin_lock(&class->lock);
-	first_page = find_get_zspage(class);
-
-	if (!first_page) {
-		spin_unlock(&class->lock);
-		first_page = alloc_zspage(pool->ops, class, flags);
-		if (unlikely(!first_page))
-			return 0;
-
-		set_zspage_mapping(first_page, class->index, ZS_EMPTY);
-		spin_lock(&class->lock);
-		class->pages_allocated += class->pages_per_zspage;
-	}
-
-	obj = (unsigned long)first_page->freelist;
-	obj_handle_to_location(obj, &m_page, &m_objidx);
-	m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
-
-	link = (struct link_free *)kmap_atomic(m_page) +
-					m_offset / sizeof(*link);
-	first_page->freelist = link->next;
-	memset(link, POISON_INUSE, sizeof(*link));
-	kunmap_atomic(link);
-
-	first_page->inuse++;
-	/* Now move the zspage to another fullness group, if required */
-	fix_fullness_group(pool, first_page);
-	spin_unlock(&class->lock);
-
-	return obj;
-}
-EXPORT_SYMBOL_GPL(zs_malloc);
-
-void zs_free(struct zs_pool *pool, unsigned long obj)
-{
-	struct link_free *link;
-	struct page *first_page, *f_page;
-	unsigned long f_objidx, f_offset;
-
-	int class_idx;
-	struct size_class *class;
-	enum fullness_group fullness;
-
-	if (unlikely(!obj))
-		return;
-
-	obj_handle_to_location(obj, &f_page, &f_objidx);
-	first_page = get_first_page(f_page);
-
-	get_zspage_mapping(first_page, &class_idx, &fullness);
-	class = &pool->size_class[class_idx];
-	f_offset = obj_idx_to_offset(f_page, f_objidx, class->size);
-
-	spin_lock(&class->lock);
-
-	/* Insert this object in containing zspage's freelist */
-	link = (struct link_free *)((unsigned char *)kmap_atomic(f_page)
-							+ f_offset);
-	link->next = first_page->freelist;
-	kunmap_atomic(link);
-	first_page->freelist = (void *)obj;
-
-	first_page->inuse--;
-	fullness = fix_fullness_group(pool, first_page);
-
-	if (fullness == ZS_EMPTY)
-		class->pages_allocated -= class->pages_per_zspage;
-
-	spin_unlock(&class->lock);
-
-	if (fullness == ZS_EMPTY)
-		free_zspage(pool->ops, first_page);
-}
-EXPORT_SYMBOL_GPL(zs_free);
-
-/**
- * zs_map_object - get address of allocated object from handle.
- * @pool: pool from which the object was allocated
- * @handle: handle returned from zs_malloc
- *
- * Before using an object allocated from zs_malloc, it must be mapped using
- * this function. When done with the object, it must be unmapped using
- * zs_unmap_object.
- *
- * Only one object can be mapped per cpu at a time. There is no protection
- * against nested mappings.
- *
- * This function returns with preemption and page faults disabled.
-*/
-void *zs_map_object(struct zs_pool *pool, unsigned long handle,
-			enum zs_mapmode mm)
-{
-	struct page *page;
-	unsigned long obj_idx, off;
-
-	unsigned int class_idx;
-	enum fullness_group fg;
-	struct size_class *class;
-	struct mapping_area *area;
-	struct page *pages[2];
-
-	BUG_ON(!handle);
-
-	/*
-	 * Because we use per-cpu mapping areas shared among the
-	 * pools/users, we can't allow mapping in interrupt context
-	 * because it can corrupt another users mappings.
-	 */
-	BUG_ON(in_interrupt());
-
-	obj_handle_to_location(handle, &page, &obj_idx);
-	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
-	class = &pool->size_class[class_idx];
-	off = obj_idx_to_offset(page, obj_idx, class->size);
-
-	area = &get_cpu_var(zs_map_area);
-	area->vm_mm = mm;
-	if (off + class->size <= PAGE_SIZE) {
-		/* this object is contained entirely within a page */
-		area->vm_addr = kmap_atomic(page);
-		return area->vm_addr + off;
-	}
-
-	/* this object spans two pages */
-	pages[0] = page;
-	pages[1] = get_next_page(page);
-	BUG_ON(!pages[1]);
-
-	return __zs_map_object(area, pages, off, class->size);
-}
-EXPORT_SYMBOL_GPL(zs_map_object);
-
-void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
-{
-	struct page *page;
-	unsigned long obj_idx, off;
-
-	unsigned int class_idx;
-	enum fullness_group fg;
-	struct size_class *class;
-	struct mapping_area *area;
-
-	BUG_ON(!handle);
-
-	obj_handle_to_location(handle, &page, &obj_idx);
-	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
-	class = &pool->size_class[class_idx];
-	off = obj_idx_to_offset(page, obj_idx, class->size);
-
-	area = &__get_cpu_var(zs_map_area);
-	if (off + class->size <= PAGE_SIZE)
-		kunmap_atomic(area->vm_addr);
-	else {
-		struct page *pages[2];
-
-		pages[0] = page;
-		pages[1] = get_next_page(page);
-		BUG_ON(!pages[1]);
-
-		__zs_unmap_object(area, pages, off, class->size);
-	}
-	put_cpu_var(zs_map_area);
-}
-EXPORT_SYMBOL_GPL(zs_unmap_object);
-
-u64 zs_get_total_size_bytes(struct zs_pool *pool)
-{
-	int i;
-	u64 npages = 0;
-
-	for (i = 0; i < ZS_SIZE_CLASSES; i++)
-		npages += pool->size_class[i].pages_allocated;
-
-	return npages << PAGE_SHIFT;
-}
-EXPORT_SYMBOL_GPL(zs_get_total_size_bytes);
-
-module_init(zs_init);
-module_exit(zs_exit);
-
-MODULE_LICENSE("Dual BSD/GPL");
-MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
deleted file mode 100644
index eb6efb6..0000000
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * zsmalloc memory allocator
- *
- * Copyright (C) 2011  Nitin Gupta
- *
- * This code is released using a dual license strategy: BSD/GPL
- * You can choose the license that better fits your requirements.
- *
- * Released under the terms of 3-clause BSD License
- * Released under the terms of GNU General Public License Version 2.0
- */
-
-#ifndef _ZS_MALLOC_H_
-#define _ZS_MALLOC_H_
-
-#include <linux/types.h>
-#include <linux/mm_types.h>
-
-/*
- * zsmalloc mapping modes
- *
- * NOTE: These only make a difference when a mapped object spans pages
-*/
-enum zs_mapmode {
-	ZS_MM_RW, /* normal read-write mapping */
-	ZS_MM_RO, /* read-only (no copy-out at unmap time) */
-	ZS_MM_WO /* write-only (no copy-in at map time) */
-};
-
-struct zs_ops {
-	struct page * (*alloc)(gfp_t);
-	void (*free)(struct page *);
-};
-
-struct zs_pool;
-
-struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
-void zs_destroy_pool(struct zs_pool *pool);
-
-unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
-void zs_free(struct zs_pool *pool, unsigned long obj);
-
-void *zs_map_object(struct zs_pool *pool, unsigned long handle,
-			enum zs_mapmode mm);
-void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
-
-u64 zs_get_total_size_bytes(struct zs_pool *pool);
-
-#endif
diff --git a/include/linux/zsmalloc.h b/include/linux/zsmalloc.h
new file mode 100644
index 0000000..eb6efb6
--- /dev/null
+++ b/include/linux/zsmalloc.h
@@ -0,0 +1,49 @@
+/*
+ * zsmalloc memory allocator
+ *
+ * Copyright (C) 2011  Nitin Gupta
+ *
+ * This code is released using a dual license strategy: BSD/GPL
+ * You can choose the license that better fits your requirements.
+ *
+ * Released under the terms of 3-clause BSD License
+ * Released under the terms of GNU General Public License Version 2.0
+ */
+
+#ifndef _ZS_MALLOC_H_
+#define _ZS_MALLOC_H_
+
+#include <linux/types.h>
+#include <linux/mm_types.h>
+
+/*
+ * zsmalloc mapping modes
+ *
+ * NOTE: These only make a difference when a mapped object spans pages
+*/
+enum zs_mapmode {
+	ZS_MM_RW, /* normal read-write mapping */
+	ZS_MM_RO, /* read-only (no copy-out at unmap time) */
+	ZS_MM_WO /* write-only (no copy-in at map time) */
+};
+
+struct zs_ops {
+	struct page * (*alloc)(gfp_t);
+	void (*free)(struct page *);
+};
+
+struct zs_pool;
+
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
+void zs_destroy_pool(struct zs_pool *pool);
+
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
+void zs_free(struct zs_pool *pool, unsigned long obj);
+
+void *zs_map_object(struct zs_pool *pool, unsigned long handle,
+			enum zs_mapmode mm);
+void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
+
+u64 zs_get_total_size_bytes(struct zs_pool *pool);
+
+#endif
diff --git a/lib/Kconfig b/lib/Kconfig
index 75cdb77..fdab273 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -219,6 +219,24 @@ config DECOMPRESS_LZO
 config GENERIC_ALLOCATOR
 	boolean
 
+config ZSMALLOC
+	tristate "Memory allocator for compressed pages"
+	default n
+	help
+	  zsmalloc is a slab-based memory allocator designed to store
+	  compressed RAM pages.  zsmalloc uses a memory pool that combines
+	  single pages into higher order pages by linking them together
+	  using the fields of the struct page. Allocations are then
+	  mapped through copy buffers or VM mapping, in order to reduce
+	  memory pool fragmentation and increase allocation success rate under
+	  memory pressure.
+
+	  This results in a non-standard allocator interface where
+	  a handle, not a pointer, is returned by the allocation function.
+	  This handle must be mapped in order to access the allocated space.
+
+	  If unsure, say N.
+
 #
 # reed solomon support is select'ed if needed
 #
diff --git a/lib/Makefile b/lib/Makefile
index 5e08f6a..851b653 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_CRC7)	+= crc7.o
 obj-$(CONFIG_LIBCRC32C)	+= libcrc32c.o
 obj-$(CONFIG_CRC8)	+= crc8.o
 obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o
+obj-$(CONFIG_ZSMALLOC) += zsmalloc.o
 
 obj-$(CONFIG_ZLIB_INFLATE) += zlib_inflate/
 obj-$(CONFIG_ZLIB_DEFLATE) += zlib_deflate/
diff --git a/lib/zsmalloc.c b/lib/zsmalloc.c
new file mode 100644
index 0000000..3aaf086
--- /dev/null
+++ b/lib/zsmalloc.c
@@ -0,0 +1,1076 @@
+/*
+ * zsmalloc memory allocator
+ *
+ * Copyright (C) 2011  Nitin Gupta
+ *
+ * This code is released using a dual license strategy: BSD/GPL
+ * You can choose the license that better fits your requirements.
+ *
+ * Released under the terms of 3-clause BSD License
+ * Released under the terms of GNU General Public License Version 2.0
+ */
+
+
+/*
+ * This allocator is designed for use with zcache and zram. Thus, the
+ * allocator is supposed to work well under low memory conditions. In
+ * particular, it never attempts higher order page allocation which is
+ * very likely to fail under memory pressure. On the other hand, if we
+ * just use single (0-order) pages, it would suffer from very high
+ * fragmentation -- any object of size PAGE_SIZE/2 or larger would occupy
+ * an entire page. This was one of the major issues with its predecessor
+ * (xvmalloc).
+ *
+ * To overcome these issues, zsmalloc allocates a bunch of 0-order pages
+ * and links them together using various 'struct page' fields. These linked
+ * pages act as a single higher-order page i.e. an object can span 0-order
+ * page boundaries. The code refers to these linked pages as a single entity
+ * called zspage.
+ *
+ * Following is how we use various fields and flags of underlying
+ * struct page(s) to form a zspage.
+ *
+ * Usage of struct page fields:
+ *	page->first_page: points to the first component (0-order) page
+ *	page->index (union with page->freelist): offset of the first object
+ *		starting in this page. For the first page, this is
+ *		always 0, so we use this field (aka freelist) to point
+ *		to the first free object in zspage.
+ *	page->lru: links together all component pages (except the first page)
+ *		of a zspage
+ *
+ *	For _first_ page only:
+ *
+ *	page->private (union with page->first_page): refers to the
+ *		component page after the first page
+ *	page->freelist: points to the first free object in zspage.
+ *		Free objects are linked together using in-place
+ *		metadata.
+ *	page->objects: maximum number of objects we can store in this
+ *		zspage (class->zspage_order * PAGE_SIZE / class->size)
+ *	page->lru: links together first pages of various zspages.
+ *		Basically forming list of zspages in a fullness group.
+ *	page->mapping: class index and fullness group of the zspage
+ *
+ * Usage of struct page flags:
+ *	PG_private: identifies the first component page
+ *	PG_private2: identifies the last component page
+ *
+ */
+
+#ifdef CONFIG_ZSMALLOC_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/highmem.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <asm/tlbflush.h>
+#include <asm/pgtable.h>
+#include <linux/cpumask.h>
+#include <linux/cpu.h>
+#include <linux/vmalloc.h>
+#include <linux/hardirq.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/zsmalloc.h>
+
+/*
+ * This must be power of 2 and greater than of equal to sizeof(link_free).
+ * These two conditions ensure that any 'struct link_free' itself doesn't
+ * span more than 1 page which avoids complex case of mapping 2 pages simply
+ * to restore link_free pointer values.
+ */
+#define ZS_ALIGN		8
+
+/*
+ * A single 'zspage' is composed of up to 2^N discontiguous 0-order (single)
+ * pages. ZS_MAX_ZSPAGE_ORDER defines upper limit on N.
+ */
+#define ZS_MAX_ZSPAGE_ORDER 2
+#define ZS_MAX_PAGES_PER_ZSPAGE (_AC(1, UL) << ZS_MAX_ZSPAGE_ORDER)
+
+/*
+ * Object location (<PFN>, <obj_idx>) is encoded as
+ * as single (void *) handle value.
+ *
+ * Note that object index <obj_idx> is relative to system
+ * page <PFN> it is stored in, so for each sub-page belonging
+ * to a zspage, obj_idx starts with 0.
+ *
+ * This is made more complicated by various memory models and PAE.
+ */
+
+#ifndef MAX_PHYSMEM_BITS
+#ifdef CONFIG_HIGHMEM64G
+#define MAX_PHYSMEM_BITS 36
+#else /* !CONFIG_HIGHMEM64G */
+/*
+ * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just
+ * be PAGE_SHIFT
+ */
+#define MAX_PHYSMEM_BITS BITS_PER_LONG
+#endif
+#endif
+#define _PFN_BITS		(MAX_PHYSMEM_BITS - PAGE_SHIFT)
+#define OBJ_INDEX_BITS	(BITS_PER_LONG - _PFN_BITS)
+#define OBJ_INDEX_MASK	((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
+
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
+#define ZS_MIN_ALLOC_SIZE \
+	MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS))
+#define ZS_MAX_ALLOC_SIZE	PAGE_SIZE
+
+/*
+ * On systems with 4K page size, this gives 254 size classes! There is a
+ * trader-off here:
+ *  - Large number of size classes is potentially wasteful as free page are
+ *    spread across these classes
+ *  - Small number of size classes causes large internal fragmentation
+ *  - Probably its better to use specific size classes (empirically
+ *    determined). NOTE: all those class sizes must be set as multiple of
+ *    ZS_ALIGN to make sure link_free itself never has to span 2 pages.
+ *
+ *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
+ *  (reason above)
+ */
+#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
+#define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
+					ZS_SIZE_CLASS_DELTA + 1)
+
+/*
+ * We do not maintain any list for completely empty or full pages
+ */
+enum fullness_group {
+	ZS_ALMOST_FULL,
+	ZS_ALMOST_EMPTY,
+	_ZS_NR_FULLNESS_GROUPS,
+
+	ZS_EMPTY,
+	ZS_FULL
+};
+
+/*
+ * We assign a page to ZS_ALMOST_EMPTY fullness group when:
+ *	n <= N / f, where
+ * n = number of allocated objects
+ * N = total number of objects zspage can store
+ * f = 1/fullness_threshold_frac
+ *
+ * Similarly, we assign zspage to:
+ *	ZS_ALMOST_FULL	when n > N / f
+ *	ZS_EMPTY	when n == 0
+ *	ZS_FULL		when n == N
+ *
+ * (see: fix_fullness_group())
+ */
+static const int fullness_threshold_frac = 4;
+
+struct size_class {
+	/*
+	 * Size of objects stored in this class. Must be multiple
+	 * of ZS_ALIGN.
+	 */
+	int size;
+	unsigned int index;
+
+	/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
+	int pages_per_zspage;
+
+	spinlock_t lock;
+
+	/* stats */
+	u64 pages_allocated;
+
+	struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
+};
+
+/*
+ * Placed within free objects to form a singly linked list.
+ * For every zspage, first_page->freelist gives head of this list.
+ *
+ * This must be power of 2 and less than or equal to ZS_ALIGN
+ */
+struct link_free {
+	/* Handle of next free chunk (encodes <PFN, obj_idx>) */
+	void *next;
+};
+
+struct zs_pool {
+	struct size_class size_class[ZS_SIZE_CLASSES];
+	struct zs_ops *ops;
+};
+
+/*
+ * A zspage's class index and fullness group
+ * are encoded in its (first)page->mapping
+ */
+#define CLASS_IDX_BITS	28
+#define FULLNESS_BITS	4
+#define CLASS_IDX_MASK	((1 << CLASS_IDX_BITS) - 1)
+#define FULLNESS_MASK	((1 << FULLNESS_BITS) - 1)
+
+/*
+ * By default, zsmalloc uses a copy-based object mapping method to access
+ * allocations that span two pages. However, if a particular architecture
+ * 1) Implements local_flush_tlb_kernel_range() and 2) Performs VM mapping
+ * faster than copying, then it should be added here so that
+ * USE_PGTABLE_MAPPING is defined. This causes zsmalloc to use page table
+ * mapping rather than copying
+ * for object mapping.
+*/
+#if defined(CONFIG_ARM)
+#define USE_PGTABLE_MAPPING
+#endif
+
+struct mapping_area {
+#ifdef USE_PGTABLE_MAPPING
+	struct vm_struct *vm; /* vm area for mapping object that span pages */
+#else
+	char *vm_buf; /* copy buffer for objects that span pages */
+#endif
+	char *vm_addr; /* address of kmap_atomic()'ed pages */
+	enum zs_mapmode vm_mm; /* mapping mode */
+};
+
+/* default page alloc/free ops */
+struct page *zs_alloc_page(gfp_t flags)
+{
+	return alloc_page(flags);
+}
+
+void zs_free_page(struct page *page)
+{
+	__free_page(page);
+}
+
+struct zs_ops zs_default_ops = {
+	.alloc = zs_alloc_page,
+	.free = zs_free_page
+};
+
+/* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
+static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
+
+static int is_first_page(struct page *page)
+{
+	return PagePrivate(page);
+}
+
+static int is_last_page(struct page *page)
+{
+	return PagePrivate2(page);
+}
+
+static void get_zspage_mapping(struct page *page, unsigned int *class_idx,
+				enum fullness_group *fullness)
+{
+	unsigned long m;
+	BUG_ON(!is_first_page(page));
+
+	m = (unsigned long)page->mapping;
+	*fullness = m & FULLNESS_MASK;
+	*class_idx = (m >> FULLNESS_BITS) & CLASS_IDX_MASK;
+}
+
+static void set_zspage_mapping(struct page *page, unsigned int class_idx,
+				enum fullness_group fullness)
+{
+	unsigned long m;
+	BUG_ON(!is_first_page(page));
+
+	m = ((class_idx & CLASS_IDX_MASK) << FULLNESS_BITS) |
+			(fullness & FULLNESS_MASK);
+	page->mapping = (struct address_space *)m;
+}
+
+static int get_size_class_index(int size)
+{
+	int idx = 0;
+
+	if (likely(size > ZS_MIN_ALLOC_SIZE))
+		idx = DIV_ROUND_UP(size - ZS_MIN_ALLOC_SIZE,
+				ZS_SIZE_CLASS_DELTA);
+
+	return idx;
+}
+
+static enum fullness_group get_fullness_group(struct page *page)
+{
+	int inuse, max_objects;
+	enum fullness_group fg;
+	BUG_ON(!is_first_page(page));
+
+	inuse = page->inuse;
+	max_objects = page->objects;
+
+	if (inuse == 0)
+		fg = ZS_EMPTY;
+	else if (inuse == max_objects)
+		fg = ZS_FULL;
+	else if (inuse <= max_objects / fullness_threshold_frac)
+		fg = ZS_ALMOST_EMPTY;
+	else
+		fg = ZS_ALMOST_FULL;
+
+	return fg;
+}
+
+static void insert_zspage(struct page *page, struct size_class *class,
+				enum fullness_group fullness)
+{
+	struct page **head;
+
+	BUG_ON(!is_first_page(page));
+
+	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
+		return;
+
+	head = &class->fullness_list[fullness];
+	if (*head)
+		list_add_tail(&page->lru, &(*head)->lru);
+
+	*head = page;
+}
+
+static void remove_zspage(struct page *page, struct size_class *class,
+				enum fullness_group fullness)
+{
+	struct page **head;
+
+	BUG_ON(!is_first_page(page));
+
+	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
+		return;
+
+	head = &class->fullness_list[fullness];
+	BUG_ON(!*head);
+	if (list_empty(&(*head)->lru))
+		*head = NULL;
+	else if (*head == page)
+		*head = (struct page *)list_entry((*head)->lru.next,
+					struct page, lru);
+
+	list_del_init(&page->lru);
+}
+
+static enum fullness_group fix_fullness_group(struct zs_pool *pool,
+						struct page *page)
+{
+	int class_idx;
+	struct size_class *class;
+	enum fullness_group currfg, newfg;
+
+	BUG_ON(!is_first_page(page));
+
+	get_zspage_mapping(page, &class_idx, &currfg);
+	newfg = get_fullness_group(page);
+	if (newfg == currfg)
+		goto out;
+
+	class = &pool->size_class[class_idx];
+	remove_zspage(page, class, currfg);
+	insert_zspage(page, class, newfg);
+	set_zspage_mapping(page, class_idx, newfg);
+
+out:
+	return newfg;
+}
+
+/*
+ * We have to decide on how many pages to link together
+ * to form a zspage for each size class. This is important
+ * to reduce wastage due to unusable space left at end of
+ * each zspage which is given as:
+ *	wastage = Zp - Zp % size_class
+ * where Zp = zspage size = k * PAGE_SIZE where k = 1, 2, ...
+ *
+ * For example, for size class of 3/8 * PAGE_SIZE, we should
+ * link together 3 PAGE_SIZE sized pages to form a zspage
+ * since then we can perfectly fit in 8 such objects.
+ */
+static int get_pages_per_zspage(int class_size)
+{
+	int i, max_usedpc = 0;
+	/* zspage order which gives maximum used size per KB */
+	int max_usedpc_order = 1;
+
+	for (i = 1; i <= ZS_MAX_PAGES_PER_ZSPAGE; i++) {
+		int zspage_size;
+		int waste, usedpc;
+
+		zspage_size = i * PAGE_SIZE;
+		waste = zspage_size % class_size;
+		usedpc = (zspage_size - waste) * 100 / zspage_size;
+
+		if (usedpc > max_usedpc) {
+			max_usedpc = usedpc;
+			max_usedpc_order = i;
+		}
+	}
+
+	return max_usedpc_order;
+}
+
+/*
+ * A single 'zspage' is composed of many system pages which are
+ * linked together using fields in struct page. This function finds
+ * the first/head page, given any component page of a zspage.
+ */
+static struct page *get_first_page(struct page *page)
+{
+	if (is_first_page(page))
+		return page;
+	else
+		return page->first_page;
+}
+
+static struct page *get_next_page(struct page *page)
+{
+	struct page *next;
+
+	if (is_last_page(page))
+		next = NULL;
+	else if (is_first_page(page))
+		next = (struct page *)page->private;
+	else
+		next = list_entry(page->lru.next, struct page, lru);
+
+	return next;
+}
+
+/* Encode <page, obj_idx> as a single handle value */
+static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
+{
+	unsigned long handle;
+
+	if (!page) {
+		BUG_ON(obj_idx);
+		return NULL;
+	}
+
+	handle = page_to_pfn(page) << OBJ_INDEX_BITS;
+	handle |= (obj_idx & OBJ_INDEX_MASK);
+
+	return (void *)handle;
+}
+
+/* Decode <page, obj_idx> pair from the given object handle */
+static void obj_handle_to_location(unsigned long handle, struct page **page,
+				unsigned long *obj_idx)
+{
+	*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
+	*obj_idx = handle & OBJ_INDEX_MASK;
+}
+
+static unsigned long obj_idx_to_offset(struct page *page,
+				unsigned long obj_idx, int class_size)
+{
+	unsigned long off = 0;
+
+	if (!is_first_page(page))
+		off = page->index;
+
+	return off + obj_idx * class_size;
+}
+
+static void reset_page(struct page *page)
+{
+	clear_bit(PG_private, &page->flags);
+	clear_bit(PG_private_2, &page->flags);
+	set_page_private(page, 0);
+	page->mapping = NULL;
+	page->freelist = NULL;
+	reset_page_mapcount(page);
+}
+
+static void free_zspage(struct zs_ops *ops, struct page *first_page)
+{
+	struct page *nextp, *tmp, *head_extra;
+
+	BUG_ON(!is_first_page(first_page));
+	BUG_ON(first_page->inuse);
+
+	head_extra = (struct page *)page_private(first_page);
+
+	reset_page(first_page);
+	ops->free(first_page);
+
+	/* zspage with only 1 system page */
+	if (!head_extra)
+		return;
+
+	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
+		list_del(&nextp->lru);
+		reset_page(nextp);
+		ops->free(nextp);
+	}
+	reset_page(head_extra);
+	ops->free(head_extra);
+}
+
+/* Initialize a newly allocated zspage */
+static void init_zspage(struct page *first_page, struct size_class *class)
+{
+	unsigned long off = 0;
+	struct page *page = first_page;
+
+	BUG_ON(!is_first_page(first_page));
+	while (page) {
+		struct page *next_page;
+		struct link_free *link;
+		unsigned int i, objs_on_page;
+
+		/*
+		 * page->index stores offset of first object starting
+		 * in the page. For the first page, this is always 0,
+		 * so we use first_page->index (aka ->freelist) to store
+		 * head of corresponding zspage's freelist.
+		 */
+		if (page != first_page)
+			page->index = off;
+
+		link = (struct link_free *)kmap_atomic(page) +
+						off / sizeof(*link);
+		objs_on_page = (PAGE_SIZE - off) / class->size;
+
+		for (i = 1; i <= objs_on_page; i++) {
+			off += class->size;
+			if (off < PAGE_SIZE) {
+				link->next = obj_location_to_handle(page, i);
+				link += class->size / sizeof(*link);
+			}
+		}
+
+		/*
+		 * We now come to the last (full or partial) object on this
+		 * page, which must point to the first object on the next
+		 * page (if present)
+		 */
+		next_page = get_next_page(page);
+		link->next = obj_location_to_handle(next_page, 0);
+		kunmap_atomic(link);
+		page = next_page;
+		off = (off + class->size) % PAGE_SIZE;
+	}
+}
+
+/*
+ * Allocate a zspage for the given size class
+ */
+static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
+				gfp_t flags)
+{
+	int i, error;
+	struct page *first_page = NULL, *uninitialized_var(prev_page);
+
+	/*
+	 * Allocate individual pages and link them together as:
+	 * 1. first page->private = first sub-page
+	 * 2. all sub-pages are linked together using page->lru
+	 * 3. each sub-page is linked to the first page using page->first_page
+	 *
+	 * For each size class, First/Head pages are linked together using
+	 * page->lru. Also, we set PG_private to identify the first page
+	 * (i.e. no other sub-page has this flag set) and PG_private_2 to
+	 * identify the last page.
+	 */
+	error = -ENOMEM;
+	for (i = 0; i < class->pages_per_zspage; i++) {
+		struct page *page;
+
+		page = ops->alloc(flags);
+		if (!page)
+			goto cleanup;
+
+		INIT_LIST_HEAD(&page->lru);
+		if (i == 0) {	/* first page */
+			SetPagePrivate(page);
+			set_page_private(page, 0);
+			first_page = page;
+			first_page->inuse = 0;
+		}
+		if (i == 1)
+			first_page->private = (unsigned long)page;
+		if (i >= 1)
+			page->first_page = first_page;
+		if (i >= 2)
+			list_add(&page->lru, &prev_page->lru);
+		if (i == class->pages_per_zspage - 1)	/* last page */
+			SetPagePrivate2(page);
+		prev_page = page;
+	}
+
+	init_zspage(first_page, class);
+
+	first_page->freelist = obj_location_to_handle(first_page, 0);
+	/* Maximum number of objects we can store in this zspage */
+	first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
+
+	error = 0; /* Success */
+
+cleanup:
+	if (unlikely(error) && first_page) {
+		free_zspage(ops, first_page);
+		first_page = NULL;
+	}
+
+	return first_page;
+}
+
+static struct page *find_get_zspage(struct size_class *class)
+{
+	int i;
+	struct page *page;
+
+	for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
+		page = class->fullness_list[i];
+		if (page)
+			break;
+	}
+
+	return page;
+}
+
+#ifdef USE_PGTABLE_MAPPING
+static inline int __zs_cpu_up(struct mapping_area *area)
+{
+	/*
+	 * Make sure we don't leak memory if a cpu UP notification
+	 * and zs_init() race and both call zs_cpu_up() on the same cpu
+	 */
+	if (area->vm)
+		return 0;
+	area->vm = alloc_vm_area(PAGE_SIZE * 2, NULL);
+	if (!area->vm)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void __zs_cpu_down(struct mapping_area *area)
+{
+	if (area->vm)
+		free_vm_area(area->vm);
+	area->vm = NULL;
+}
+
+static inline void *__zs_map_object(struct mapping_area *area,
+				struct page *pages[2], int off, int size)
+{
+	BUG_ON(map_vm_area(area->vm, PAGE_KERNEL, &pages));
+	area->vm_addr = area->vm->addr;
+	return area->vm_addr + off;
+}
+
+static inline void __zs_unmap_object(struct mapping_area *area,
+				struct page *pages[2], int off, int size)
+{
+	unsigned long addr = (unsigned long)area->vm_addr;
+	unsigned long end = addr + (PAGE_SIZE * 2);
+
+	flush_cache_vunmap(addr, end);
+	unmap_kernel_range_noflush(addr, PAGE_SIZE * 2);
+	local_flush_tlb_kernel_range(addr, end);
+}
+
+#else /* USE_PGTABLE_MAPPING */
+
+static inline int __zs_cpu_up(struct mapping_area *area)
+{
+	/*
+	 * Make sure we don't leak memory if a cpu UP notification
+	 * and zs_init() race and both call zs_cpu_up() on the same cpu
+	 */
+	if (area->vm_buf)
+		return 0;
+	area->vm_buf = (char *)__get_free_page(GFP_KERNEL);
+	if (!area->vm_buf)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void __zs_cpu_down(struct mapping_area *area)
+{
+	if (area->vm_buf)
+		free_page((unsigned long)area->vm_buf);
+	area->vm_buf = NULL;
+}
+
+static void *__zs_map_object(struct mapping_area *area,
+			struct page *pages[2], int off, int size)
+{
+	int sizes[2];
+	void *addr;
+	char *buf = area->vm_buf;
+
+	/* disable page faults to match kmap_atomic() return conditions */
+	pagefault_disable();
+
+	/* no read fastpath */
+	if (area->vm_mm == ZS_MM_WO)
+		goto out;
+
+	sizes[0] = PAGE_SIZE - off;
+	sizes[1] = size - sizes[0];
+
+	/* copy object to per-cpu buffer */
+	addr = kmap_atomic(pages[0]);
+	memcpy(buf, addr + off, sizes[0]);
+	kunmap_atomic(addr);
+	addr = kmap_atomic(pages[1]);
+	memcpy(buf + sizes[0], addr, sizes[1]);
+	kunmap_atomic(addr);
+out:
+	return area->vm_buf;
+}
+
+static void __zs_unmap_object(struct mapping_area *area,
+			struct page *pages[2], int off, int size)
+{
+	int sizes[2];
+	void *addr;
+	char *buf = area->vm_buf;
+
+	/* no write fastpath */
+	if (area->vm_mm == ZS_MM_RO)
+		goto out;
+
+	sizes[0] = PAGE_SIZE - off;
+	sizes[1] = size - sizes[0];
+
+	/* copy per-cpu buffer to object */
+	addr = kmap_atomic(pages[0]);
+	memcpy(addr + off, buf, sizes[0]);
+	kunmap_atomic(addr);
+	addr = kmap_atomic(pages[1]);
+	memcpy(addr, buf + sizes[0], sizes[1]);
+	kunmap_atomic(addr);
+
+out:
+	/* enable page faults to match kunmap_atomic() return conditions */
+	pagefault_enable();
+}
+
+#endif /* USE_PGTABLE_MAPPING */
+
+static int zs_cpu_notifier(struct notifier_block *nb, unsigned long action,
+				void *pcpu)
+{
+	int ret, cpu = (long)pcpu;
+	struct mapping_area *area;
+
+	switch (action) {
+	case CPU_UP_PREPARE:
+		area = &per_cpu(zs_map_area, cpu);
+		ret = __zs_cpu_up(area);
+		if (ret)
+			return notifier_from_errno(ret);
+		break;
+	case CPU_DEAD:
+	case CPU_UP_CANCELED:
+		area = &per_cpu(zs_map_area, cpu);
+		__zs_cpu_down(area);
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block zs_cpu_nb = {
+	.notifier_call = zs_cpu_notifier
+};
+
+static void zs_exit(void)
+{
+	int cpu;
+
+	for_each_online_cpu(cpu)
+		zs_cpu_notifier(NULL, CPU_DEAD, (void *)(long)cpu);
+	unregister_cpu_notifier(&zs_cpu_nb);
+}
+
+static int zs_init(void)
+{
+	int cpu, ret;
+
+	register_cpu_notifier(&zs_cpu_nb);
+	for_each_online_cpu(cpu) {
+		ret = zs_cpu_notifier(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
+		if (notifier_to_errno(ret))
+			goto fail;
+	}
+	return 0;
+fail:
+	zs_exit();
+	return notifier_to_errno(ret);
+}
+
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
+{
+	int i, ovhd_size;
+	struct zs_pool *pool;
+
+	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
+	pool = kzalloc(ovhd_size, flags);
+	if (!pool)
+		return NULL;
+
+	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
+		int size;
+		struct size_class *class;
+
+		size = ZS_MIN_ALLOC_SIZE + i * ZS_SIZE_CLASS_DELTA;
+		if (size > ZS_MAX_ALLOC_SIZE)
+			size = ZS_MAX_ALLOC_SIZE;
+
+		class = &pool->size_class[i];
+		class->size = size;
+		class->index = i;
+		spin_lock_init(&class->lock);
+		class->pages_per_zspage = get_pages_per_zspage(size);
+
+	}
+
+	if (ops)
+		pool->ops = ops;
+	else
+		pool->ops = &zs_default_ops;
+
+	return pool;
+}
+EXPORT_SYMBOL_GPL(zs_create_pool);
+
+void zs_destroy_pool(struct zs_pool *pool)
+{
+	int i;
+
+	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
+		int fg;
+		struct size_class *class = &pool->size_class[i];
+
+		for (fg = 0; fg < _ZS_NR_FULLNESS_GROUPS; fg++) {
+			if (class->fullness_list[fg]) {
+				pr_info("Freeing non-empty class with size "
+					"%db, fullness group %d\n",
+					class->size, fg);
+			}
+		}
+	}
+	kfree(pool);
+}
+EXPORT_SYMBOL_GPL(zs_destroy_pool);
+
+/**
+ * zs_malloc - Allocate block of given size from pool.
+ * @pool: pool to allocate from
+ * @size: size of block to allocate
+ *
+ * On success, handle to the allocated object is returned,
+ * otherwise 0.
+ * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
+ */
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
+{
+	unsigned long obj;
+	struct link_free *link;
+	int class_idx;
+	struct size_class *class;
+
+	struct page *first_page, *m_page;
+	unsigned long m_objidx, m_offset;
+
+	if (unlikely(!size || size > ZS_MAX_ALLOC_SIZE))
+		return 0;
+
+	class_idx = get_size_class_index(size);
+	class = &pool->size_class[class_idx];
+	BUG_ON(class_idx != class->index);
+
+	spin_lock(&class->lock);
+	first_page = find_get_zspage(class);
+
+	if (!first_page) {
+		spin_unlock(&class->lock);
+		first_page = alloc_zspage(pool->ops, class, flags);
+		if (unlikely(!first_page))
+			return 0;
+
+		set_zspage_mapping(first_page, class->index, ZS_EMPTY);
+		spin_lock(&class->lock);
+		class->pages_allocated += class->pages_per_zspage;
+	}
+
+	obj = (unsigned long)first_page->freelist;
+	obj_handle_to_location(obj, &m_page, &m_objidx);
+	m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
+
+	link = (struct link_free *)kmap_atomic(m_page) +
+					m_offset / sizeof(*link);
+	first_page->freelist = link->next;
+	memset(link, POISON_INUSE, sizeof(*link));
+	kunmap_atomic(link);
+
+	first_page->inuse++;
+	/* Now move the zspage to another fullness group, if required */
+	fix_fullness_group(pool, first_page);
+	spin_unlock(&class->lock);
+
+	return obj;
+}
+EXPORT_SYMBOL_GPL(zs_malloc);
+
+void zs_free(struct zs_pool *pool, unsigned long obj)
+{
+	struct link_free *link;
+	struct page *first_page, *f_page;
+	unsigned long f_objidx, f_offset;
+
+	int class_idx;
+	struct size_class *class;
+	enum fullness_group fullness;
+
+	if (unlikely(!obj))
+		return;
+
+	obj_handle_to_location(obj, &f_page, &f_objidx);
+	first_page = get_first_page(f_page);
+
+	get_zspage_mapping(first_page, &class_idx, &fullness);
+	class = &pool->size_class[class_idx];
+	f_offset = obj_idx_to_offset(f_page, f_objidx, class->size);
+
+	spin_lock(&class->lock);
+
+	/* Insert this object in containing zspage's freelist */
+	link = (struct link_free *)((unsigned char *)kmap_atomic(f_page)
+							+ f_offset);
+	link->next = first_page->freelist;
+	kunmap_atomic(link);
+	first_page->freelist = (void *)obj;
+
+	first_page->inuse--;
+	fullness = fix_fullness_group(pool, first_page);
+
+	if (fullness == ZS_EMPTY)
+		class->pages_allocated -= class->pages_per_zspage;
+
+	spin_unlock(&class->lock);
+
+	if (fullness == ZS_EMPTY)
+		free_zspage(pool->ops, first_page);
+}
+EXPORT_SYMBOL_GPL(zs_free);
+
+/**
+ * zs_map_object - get address of allocated object from handle.
+ * @pool: pool from which the object was allocated
+ * @handle: handle returned from zs_malloc
+ *
+ * Before using an object allocated from zs_malloc, it must be mapped using
+ * this function. When done with the object, it must be unmapped using
+ * zs_unmap_object.
+ *
+ * Only one object can be mapped per cpu at a time. There is no protection
+ * against nested mappings.
+ *
+ * This function returns with preemption and page faults disabled.
+*/
+void *zs_map_object(struct zs_pool *pool, unsigned long handle,
+			enum zs_mapmode mm)
+{
+	struct page *page;
+	unsigned long obj_idx, off;
+
+	unsigned int class_idx;
+	enum fullness_group fg;
+	struct size_class *class;
+	struct mapping_area *area;
+	struct page *pages[2];
+
+	BUG_ON(!handle);
+
+	/*
+	 * Because we use per-cpu mapping areas shared among the
+	 * pools/users, we can't allow mapping in interrupt context
+	 * because it can corrupt another users mappings.
+	 */
+	BUG_ON(in_interrupt());
+
+	obj_handle_to_location(handle, &page, &obj_idx);
+	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
+	class = &pool->size_class[class_idx];
+	off = obj_idx_to_offset(page, obj_idx, class->size);
+
+	area = &get_cpu_var(zs_map_area);
+	area->vm_mm = mm;
+	if (off + class->size <= PAGE_SIZE) {
+		/* this object is contained entirely within a page */
+		area->vm_addr = kmap_atomic(page);
+		return area->vm_addr + off;
+	}
+
+	/* this object spans two pages */
+	pages[0] = page;
+	pages[1] = get_next_page(page);
+	BUG_ON(!pages[1]);
+
+	return __zs_map_object(area, pages, off, class->size);
+}
+EXPORT_SYMBOL_GPL(zs_map_object);
+
+void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
+{
+	struct page *page;
+	unsigned long obj_idx, off;
+
+	unsigned int class_idx;
+	enum fullness_group fg;
+	struct size_class *class;
+	struct mapping_area *area;
+
+	BUG_ON(!handle);
+
+	obj_handle_to_location(handle, &page, &obj_idx);
+	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
+	class = &pool->size_class[class_idx];
+	off = obj_idx_to_offset(page, obj_idx, class->size);
+
+	area = &__get_cpu_var(zs_map_area);
+	if (off + class->size <= PAGE_SIZE)
+		kunmap_atomic(area->vm_addr);
+	else {
+		struct page *pages[2];
+
+		pages[0] = page;
+		pages[1] = get_next_page(page);
+		BUG_ON(!pages[1]);
+
+		__zs_unmap_object(area, pages, off, class->size);
+	}
+	put_cpu_var(zs_map_area);
+}
+EXPORT_SYMBOL_GPL(zs_unmap_object);
+
+u64 zs_get_total_size_bytes(struct zs_pool *pool)
+{
+	int i;
+	u64 npages = 0;
+
+	for (i = 0; i < ZS_SIZE_CLASSES; i++)
+		npages += pool->size_class[i].pages_allocated;
+
+	return npages << PAGE_SHIFT;
+}
+EXPORT_SYMBOL_GPL(zs_get_total_size_bytes);
+
+module_init(zs_init);
+module_exit(zs_exit);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (5 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 6/9] zsmalloc: promote to lib/ Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-28  4:22   ` Minchan Kim
  2013-01-07 20:24 ` [PATCHv2 8/9] zswap: add to mm/ Seth Jennings
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

swap_writepage() is currently where frontswap hooks into the swap
write path to capture pages with the frontswap_store() function.
However, if a frontswap backend wants to "resume" the writeback of
a page to the swap device, it can't call swap_writepage() as
the page will simply reenter the backend.

This patch separates swap_writepage() into a top and bottom half, the
bottom half named __swap_writepage() to allow a frontswap backend,
like zswap, to resume writeback beyond the frontswap_store() hook and
by notified when the writeback completes.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 include/linux/swap.h |    4 ++++
 mm/page_io.c         |   22 +++++++++++++++++-----
 mm/swap_state.c      |    2 +-
 3 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8c66486..a3da829 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -321,6 +321,9 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t ent)
 /* linux/mm/page_io.c */
 extern int swap_readpage(struct page *);
 extern int swap_writepage(struct page *page, struct writeback_control *wbc);
+extern void end_swap_bio_write(struct bio *bio, int err);
+extern int __swap_writepage(struct page *page, struct writeback_control *wbc,
+	void (*end_write_func)(struct bio *, int));
 extern int swap_set_page_dirty(struct page *page);
 extern void end_swap_bio_read(struct bio *bio, int err);
 
@@ -335,6 +338,7 @@ extern struct address_space swapper_space;
 extern void show_swap_cache_info(void);
 extern int add_to_swap(struct page *);
 extern int add_to_swap_cache(struct page *, swp_entry_t, gfp_t);
+extern int __add_to_swap_cache(struct page *page, swp_entry_t entry);
 extern void __delete_from_swap_cache(struct page *);
 extern void delete_from_swap_cache(struct page *);
 extern void free_page_and_swap_cache(struct page *);
diff --git a/mm/page_io.c b/mm/page_io.c
index c535d39..806085e 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -43,7 +43,7 @@ static struct bio *get_swap_bio(gfp_t gfp_flags,
 	return bio;
 }
 
-static void end_swap_bio_write(struct bio *bio, int err)
+void end_swap_bio_write(struct bio *bio, int err)
 {
 	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
 	struct page *page = bio->bi_io_vec[0].bv_page;
@@ -180,15 +180,16 @@ bad_bmap:
 	goto out;
 }
 
+int __swap_writepage(struct page *page, struct writeback_control *wbc,
+	void (*end_write_func)(struct bio *, int));
+
 /*
  * 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;
-	struct swap_info_struct *sis = page_swap_info(page);
+	int ret = 0;
 
 	if (try_to_free_swap(page)) {
 		unlock_page(page);
@@ -200,6 +201,17 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
 		end_page_writeback(page);
 		goto out;
 	}
+	ret = __swap_writepage(page, wbc, end_swap_bio_write);
+out:
+	return ret;
+}
+
+int __swap_writepage(struct page *page, struct writeback_control *wbc,
+	void (*end_write_func)(struct bio *, int))
+{
+	struct bio *bio;
+	int ret = 0, rw = WRITE;
+	struct swap_info_struct *sis = page_swap_info(page);
 
 	if (sis->flags & SWP_FILE) {
 		struct kiocb kiocb;
@@ -227,7 +239,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
 		return ret;
 	}
 
-	bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
+	bio = get_swap_bio(GFP_NOIO, page, end_write_func);
 	if (bio == NULL) {
 		set_page_dirty(page);
 		unlock_page(page);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 0cb36fb..7eded9c 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -67,7 +67,7 @@ void show_swap_cache_info(void)
  * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,
  * but sets SwapCache flag and private instead of mapping and index.
  */
-static int __add_to_swap_cache(struct page *page, swp_entry_t entry)
+int __add_to_swap_cache(struct page *page, swp_entry_t entry)
 {
 	int error;
 
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 8/9] zswap: add to mm/
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (6 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-08 17:15   ` Dave Hansen
  2013-01-25 22:44   ` Rik van Riel
  2013-01-07 20:24 ` [PATCHv2 9/9] zswap: add documentation Seth Jennings
  2013-01-22 18:10 ` [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
  9 siblings, 2 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

zswap is a thin compression backend for frontswap. It receives
pages from frontswap and attempts to store them in a compressed
memory pool, resulting in an effective partial memory reclaim and
dramatically reduced swap device I/O.

Additional, in most cases, pages can be retrieved from this
compressed store much more quickly than reading from tradition
swap devices resulting in faster performance for many workloads.

This patch adds the zswap driver to mm/

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 mm/Kconfig  |   15 +
 mm/Makefile |    1 +
 mm/zswap.c  | 1066 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1082 insertions(+)
 create mode 100644 mm/zswap.c

diff --git a/mm/Kconfig b/mm/Kconfig
index 278e3ab..14b9acb 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -446,3 +446,18 @@ config FRONTSWAP
 	  and swap data is stored as normal on the matching swap device.
 
 	  If unsure, say Y to enable frontswap.
+
+config ZSWAP
+	bool "In-kernel swap page compression"
+	depends on FRONTSWAP && CRYPTO
+	select CRYPTO_LZO
+	select ZSMALLOC
+	default n
+	help
+	  Zswap is a backend for the frontswap mechanism in the VMM.
+	  It receives pages from frontswap and attempts to store them
+	  in a compressed memory pool, resulting in an effective
+	  partial memory reclaim.  In addition, pages and be retrieved
+	  from this compressed store much faster than most tradition
+	  swap devices resulting in reduced I/O and faster performance
+	  for many workloads.
diff --git a/mm/Makefile b/mm/Makefile
index 3a46287..1b1ed5c 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_HAVE_MEMBLOCK) += memblock.o
 obj-$(CONFIG_BOUNCE)	+= bounce.o
 obj-$(CONFIG_SWAP)	+= page_io.o swap_state.o swapfile.o
 obj-$(CONFIG_FRONTSWAP)	+= frontswap.o
+obj-$(CONFIG_ZSWAP)	+= zswap.o
 obj-$(CONFIG_HAS_DMA)	+= dmapool.o
 obj-$(CONFIG_HUGETLBFS)	+= hugetlb.o
 obj-$(CONFIG_NUMA) 	+= mempolicy.o
diff --git a/mm/zswap.c b/mm/zswap.c
new file mode 100644
index 0000000..e76dd0d
--- /dev/null
+++ b/mm/zswap.c
@@ -0,0 +1,1066 @@
+/*
+ * zswap-drv.c - zswap driver file
+ *
+ * zswap is a backend for frontswap that takes pages that are in the
+ * process of being swapped out and attempts to compress them and store
+ * them in a RAM-based memory pool.  This results in a significant I/O
+ * reduction on the real swap device and, in the case of a slow swap
+ * device, can also improve workload performance.
+ *
+ * Copyright (C) 2012  Seth Jennings <sjenning@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+*/
+
+#include <linux/module.h>
+#include <linux/cpu.h>
+#include <linux/highmem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/frontswap.h>
+#include <linux/rbtree.h>
+#include <linux/swap.h>
+#include <linux/crypto.h>
+#include <linux/mempool.h>
+#include <linux/zsmalloc.h>
+
+#include <linux/mm_types.h>
+#include <linux/page-flags.h>
+#include <linux/swapops.h>
+#include <linux/writeback.h>
+#include <linux/pagemap.h>
+
+/*********************************
+* statistics
+**********************************/
+/* Number of memory pages used by the compressed pool */
+static atomic_t zswap_pool_pages = ATOMIC_INIT(0);
+/* The number of compressed pages currently stored in zswap */
+static atomic_t zswap_stored_pages = ATOMIC_INIT(0);
+/* The number of outstanding pages awaiting writeback */
+static atomic_t zswap_outstanding_flushes = ATOMIC_INIT(0);
+
+/*
+ * The statistics below are not protected from concurrent access for
+ * performance reasons so they may not be a 100% accurate.  However,
+ * the do provide useful information on roughly how many times a
+ * certain event is occurring.
+*/
+static u64 zswap_flushed_pages;
+static u64 zswap_reject_compress_poor;
+static u64 zswap_flush_attempted;
+static u64 zswap_reject_tmppage_fail;
+static u64 zswap_reject_flush_fail;
+static u64 zswap_reject_zsmalloc_fail;
+static u64 zswap_reject_kmemcache_fail;
+static u64 zswap_saved_by_flush;
+static u64 zswap_duplicate_entry;
+
+/*********************************
+* tunables
+**********************************/
+/* Enable/disable zswap (enabled by default, fixed at boot for now) */
+static bool zswap_enabled;
+module_param_named(enabled, zswap_enabled, bool, 0);
+
+/* Compressor to be used by zswap (fixed at boot for now) */
+#define ZSWAP_COMPRESSOR_DEFAULT "lzo"
+static char *zswap_compressor = ZSWAP_COMPRESSOR_DEFAULT;
+module_param_named(compressor, zswap_compressor, charp, 0);
+
+/* The maximum percentage of memory that the compressed pool can occupy */
+static unsigned int zswap_max_pool_percent = 20;
+module_param_named(max_pool_percent,
+			zswap_max_pool_percent, uint, 0644);
+
+/*
+ * Maximum compression ratio, as as percentage, for an acceptable
+ * compressed page. Any pages that do not compress by at least
+ * this ratio will be rejected.
+*/
+static unsigned int zswap_max_compression_ratio = 80;
+module_param_named(max_compression_ratio,
+			zswap_max_compression_ratio, uint, 0644);
+
+/*********************************
+* compression functions
+**********************************/
+/* per-cpu compression transforms */
+static struct crypto_comp * __percpu *zswap_comp_pcpu_tfms;
+
+enum comp_op {
+	ZSWAP_COMPOP_COMPRESS,
+	ZSWAP_COMPOP_DECOMPRESS
+};
+
+static int zswap_comp_op(enum comp_op op, const u8 *src, unsigned int slen,
+				u8 *dst, unsigned int *dlen)
+{
+	struct crypto_comp *tfm;
+	int ret;
+
+	tfm = *per_cpu_ptr(zswap_comp_pcpu_tfms, get_cpu());
+	switch (op) {
+	case ZSWAP_COMPOP_COMPRESS:
+		ret = crypto_comp_compress(tfm, src, slen, dst, dlen);
+		break;
+	case ZSWAP_COMPOP_DECOMPRESS:
+		ret = crypto_comp_decompress(tfm, src, slen, dst, dlen);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	put_cpu();
+	return ret;
+}
+
+static int __init zswap_comp_init(void)
+{
+	if (!crypto_has_comp(zswap_compressor, 0, 0)) {
+		pr_info("zswap: %s compressor not available\n",
+			zswap_compressor);
+		/* fall back to default compressor */
+		zswap_compressor = ZSWAP_COMPRESSOR_DEFAULT;
+		if (!crypto_has_comp(zswap_compressor, 0, 0))
+			/* can't even load the default compressor */
+			return -ENODEV;
+	}
+	pr_info("zswap: using %s compressor\n", zswap_compressor);
+
+	/* alloc percpu transforms */
+	zswap_comp_pcpu_tfms = alloc_percpu(struct crypto_comp *);
+	if (!zswap_comp_pcpu_tfms)
+		return -ENOMEM;
+	return 0;
+}
+
+static void zswap_comp_exit(void)
+{
+	/* free percpu transforms */
+	if (zswap_comp_pcpu_tfms)
+		free_percpu(zswap_comp_pcpu_tfms);
+}
+
+/*********************************
+* data structures
+**********************************/
+struct zswap_entry {
+	struct rb_node rbnode;
+	struct list_head lru;
+	int refcount;
+	unsigned type;
+	pgoff_t offset;
+	unsigned long handle;
+	unsigned int length;
+};
+
+/*
+ * The tree lock in the zswap_tree struct protects a few things:
+ * - the rbtree
+ * - the lru list
+ * - the refcount field of each entry in the tree
+ */
+struct zswap_tree {
+	struct rb_root rbroot;
+	struct list_head lru;
+	spinlock_t lock;
+	struct zs_pool *pool;
+};
+
+static struct zswap_tree *zswap_trees[MAX_SWAPFILES];
+
+/*********************************
+* zswap entry functions
+**********************************/
+#define ZSWAP_KMEM_CACHE_NAME "zswap_entry_cache"
+static struct kmem_cache *zswap_entry_cache;
+
+static inline int zswap_entry_cache_create(void)
+{
+	zswap_entry_cache =
+		kmem_cache_create(ZSWAP_KMEM_CACHE_NAME,
+			sizeof(struct zswap_entry), 0, 0, NULL);
+	return (zswap_entry_cache == NULL);
+}
+
+static inline void zswap_entry_cache_destory(void)
+{
+	kmem_cache_destroy(zswap_entry_cache);
+}
+
+static inline struct zswap_entry *zswap_entry_cache_alloc(gfp_t gfp)
+{
+	struct zswap_entry *entry;
+	entry = kmem_cache_alloc(zswap_entry_cache, gfp);
+	if (!entry)
+		return NULL;
+	INIT_LIST_HEAD(&entry->lru);
+	entry->refcount = 1;
+	return entry;
+}
+
+static inline void zswap_entry_cache_free(struct zswap_entry *entry)
+{
+	kmem_cache_free(zswap_entry_cache, entry);
+}
+
+static inline void zswap_entry_get(struct zswap_entry *entry)
+{
+	entry->refcount++;
+}
+
+static inline int zswap_entry_put(struct zswap_entry *entry)
+{
+	entry->refcount--;
+	return entry->refcount;
+}
+
+/*********************************
+* rbtree functions
+**********************************/
+static struct zswap_entry *zswap_rb_search(struct rb_root *root, pgoff_t offset)
+{
+	struct rb_node *node = root->rb_node;
+	struct zswap_entry *entry;
+
+	while (node) {
+		entry = rb_entry(node, struct zswap_entry, rbnode);
+		if (entry->offset > offset)
+			node = node->rb_left;
+		else if (entry->offset < offset)
+			node = node->rb_right;
+		else
+			return entry;
+	}
+	return NULL;
+}
+
+/*
+ * In the case that a entry with the same offset is found, it a pointer to
+ * the existing entry is stored in dupentry and the function returns -EEXIST
+*/
+static int zswap_rb_insert(struct rb_root *root, struct zswap_entry *entry,
+			struct zswap_entry **dupentry)
+{
+	struct rb_node **link = &root->rb_node, *parent = NULL;
+	struct zswap_entry *myentry;
+
+	while (*link) {
+		parent = *link;
+		myentry = rb_entry(parent, struct zswap_entry, rbnode);
+		if (myentry->offset > entry->offset)
+			link = &(*link)->rb_left;
+		else if (myentry->offset < entry->offset)
+			link = &(*link)->rb_right;
+		else {
+			*dupentry = myentry;
+			return -EEXIST;
+		}
+	}
+	rb_link_node(&entry->rbnode, parent, link);
+	rb_insert_color(&entry->rbnode, root);
+	return 0;
+}
+
+/*********************************
+* per-cpu code
+**********************************/
+static DEFINE_PER_CPU(u8 *, zswap_dstmem);
+
+static int __zswap_cpu_notifier(unsigned long action, unsigned long cpu)
+{
+	struct crypto_comp *tfm;
+	u8 *dst;
+
+	switch (action) {
+	case CPU_UP_PREPARE:
+		tfm = crypto_alloc_comp(zswap_compressor, 0, 0);
+		if (IS_ERR(tfm)) {
+			pr_err("zswap: can't allocate compressor transform\n");
+			return NOTIFY_BAD;
+		}
+		*per_cpu_ptr(zswap_comp_pcpu_tfms, cpu) = tfm;
+		dst = (u8 *)__get_free_pages(GFP_KERNEL, 1);
+		if (!dst) {
+			pr_err("zswap: can't allocate compressor buffer\n");
+			crypto_free_comp(tfm);
+			*per_cpu_ptr(zswap_comp_pcpu_tfms, cpu) = NULL;
+			return NOTIFY_BAD;
+		}
+		per_cpu(zswap_dstmem, cpu) = dst;
+		break;
+	case CPU_DEAD:
+	case CPU_UP_CANCELED:
+		tfm = *per_cpu_ptr(zswap_comp_pcpu_tfms, cpu);
+		if (tfm) {
+			crypto_free_comp(tfm);
+			*per_cpu_ptr(zswap_comp_pcpu_tfms, cpu) = NULL;
+		}
+		dst = per_cpu(zswap_dstmem, cpu);
+		if (dst) {
+			free_pages((unsigned long)dst, 1);
+			per_cpu(zswap_dstmem, cpu) = NULL;
+		}
+		break;
+	default:
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+static int zswap_cpu_notifier(struct notifier_block *nb,
+				unsigned long action, void *pcpu)
+{
+	unsigned long cpu = (unsigned long)pcpu;
+	return __zswap_cpu_notifier(action, cpu);
+}
+
+static struct notifier_block zswap_cpu_notifier_block = {
+	.notifier_call = zswap_cpu_notifier
+};
+
+static int zswap_cpu_init(void)
+{
+	unsigned long cpu;
+
+	get_online_cpus();
+	for_each_online_cpu(cpu)
+		if (__zswap_cpu_notifier(CPU_UP_PREPARE, cpu) != NOTIFY_OK)
+			goto cleanup;
+	register_cpu_notifier(&zswap_cpu_notifier_block);
+	put_online_cpus();
+	return 0;
+
+cleanup:
+	for_each_online_cpu(cpu)
+		__zswap_cpu_notifier(CPU_UP_CANCELED, cpu);
+	put_online_cpus();
+	return -ENOMEM;
+}
+
+/*********************************
+* zsmalloc callbacks
+**********************************/
+static mempool_t *zswap_page_pool;
+
+static u64 zswap_pool_limit_hit;
+
+static inline unsigned int zswap_max_pool_pages(void)
+{
+	return zswap_max_pool_percent * totalram_pages / 100;
+}
+
+static inline int zswap_page_pool_create(void)
+{
+	zswap_page_pool = mempool_create_page_pool(256, 0);
+	if (!zswap_page_pool)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void zswap_page_pool_destroy(void)
+{
+	mempool_destroy(zswap_page_pool);
+}
+
+static struct page *zswap_alloc_page(gfp_t flags)
+{
+	struct page *page;
+
+	if (atomic_read(&zswap_pool_pages) >= zswap_max_pool_pages()) {
+		zswap_pool_limit_hit++;
+		return NULL;
+	}
+	page = mempool_alloc(zswap_page_pool, flags);
+	if (page)
+		atomic_inc(&zswap_pool_pages);
+	return page;
+}
+
+static void zswap_free_page(struct page *page)
+{
+	mempool_free(page, zswap_page_pool);
+	atomic_dec(&zswap_pool_pages);
+}
+
+static struct zs_ops zswap_zs_ops = {
+	.alloc = zswap_alloc_page,
+	.free = zswap_free_page
+};
+
+/*********************************
+* flush code
+**********************************/
+static void zswap_end_swap_write(struct bio *bio, int err)
+{
+	end_swap_bio_write(bio, err);
+	atomic_dec(&zswap_outstanding_flushes);
+	zswap_flushed_pages++;
+}
+
+/*
+ * zswap_get_swap_cache_page
+ *
+ * This is an adaption of read_swap_cache_async()
+ *
+ * If success, page is returned in retpage
+ * Returns 0 if page was already in the swap cache, page is not locked
+ * Returns 1 if the new page needs to be populated, page is locked
+ */
+static int zswap_get_swap_cache_page(swp_entry_t entry,
+				struct page **retpage)
+{
+	struct page *found_page, *new_page = NULL;
+	int err;
+
+	*retpage = NULL;
+	do {
+		/*
+		 * First check the swap cache.  Since this is normally
+		 * called after lookup_swap_cache() failed, re-calling
+		 * that would confuse statistics.
+		 */
+		found_page = find_get_page(&swapper_space, entry.val);
+		if (found_page)
+			break;
+
+		/*
+		 * Get a new page to read into from swap.
+		 */
+		if (!new_page) {
+			new_page = alloc_page(GFP_KERNEL);
+			if (!new_page)
+				break; /* Out of memory */
+		}
+
+		/*
+		 * call radix_tree_preload() while we can wait.
+		 */
+		err = radix_tree_preload(GFP_KERNEL);
+		if (err)
+			break;
+
+		/*
+		 * Swap entry may have been freed since our caller observed it.
+		 */
+		err = swapcache_prepare(entry);
+		if (err == -EEXIST) { /* seems racy */
+			radix_tree_preload_end();
+			continue;
+		}
+		if (err) { /* swp entry is obsolete ? */
+			radix_tree_preload_end();
+			break;
+		}
+
+		/* May fail (-ENOMEM) if radix-tree node allocation failed. */
+		__set_page_locked(new_page);
+		SetPageSwapBacked(new_page);
+		err = __add_to_swap_cache(new_page, entry);
+		if (likely(!err)) {
+			radix_tree_preload_end();
+			lru_cache_add_anon(new_page);
+			*retpage = new_page;
+			return 1;
+		}
+		radix_tree_preload_end();
+		ClearPageSwapBacked(new_page);
+		__clear_page_locked(new_page);
+		/*
+		 * add_to_swap_cache() doesn't return -EEXIST, so we can safely
+		 * clear SWAP_HAS_CACHE flag.
+		 */
+		swapcache_free(entry, NULL);
+	} while (err != -ENOMEM);
+
+	if (new_page)
+		page_cache_release(new_page);
+	if (!found_page)
+		return -ENOMEM;
+	*retpage = found_page;
+	return 0;
+}
+
+static int zswap_flush_entry(struct zswap_entry *entry)
+{
+	unsigned long type = entry->type;
+	struct zswap_tree *tree = zswap_trees[type];
+	struct page *page;
+	swp_entry_t swpentry;
+	u8 *src, *dst;
+	unsigned int dlen;
+	int ret, refcount;
+	struct writeback_control wbc = {
+		.sync_mode = WB_SYNC_NONE,
+	};
+
+	/* get/allocate page in the swap cache */
+	swpentry = swp_entry(type, entry->offset);
+	ret = zswap_get_swap_cache_page(swpentry, &page);
+	if (ret < 0)
+		return ret;
+	else if (ret) {
+		/* decompress */
+		dlen = PAGE_SIZE;
+		src = zs_map_object(tree->pool, entry->handle, ZS_MM_RO);
+		dst = kmap_atomic(page);
+		ret = zswap_comp_op(ZSWAP_COMPOP_DECOMPRESS, src, entry->length,
+				dst, &dlen);
+		kunmap_atomic(dst);
+		zs_unmap_object(tree->pool, entry->handle);
+		BUG_ON(ret);
+		BUG_ON(dlen != PAGE_SIZE);
+		SetPageUptodate(page);
+	} else {
+		/* page is already in the swap cache, ignore for now */
+		spin_lock(&tree->lock);
+		refcount = zswap_entry_put(entry);
+		spin_unlock(&tree->lock);
+
+		if (likely(refcount))
+			return 0;
+
+		/* if the refcount is zero, invalidate must have come in */
+		/* free */
+		zs_free(tree->pool, entry->handle);
+		zswap_entry_cache_free(entry);
+		atomic_dec(&zswap_stored_pages);
+
+		return 0;
+	}
+
+	/* start writeback */
+	SetPageReclaim(page);
+	/*
+	 * Return value is ignored here because it doesn't change anything
+	 * for us.  Page is returned unlocked.
+	 */
+	(void)__swap_writepage(page, &wbc, zswap_end_swap_write);
+	page_cache_release(page);
+	atomic_inc(&zswap_outstanding_flushes);
+
+	/* remove */
+	spin_lock(&tree->lock);
+	refcount = zswap_entry_put(entry);
+	if (refcount > 1) {
+		/* load in progress, load will free */
+		spin_unlock(&tree->lock);
+		return 0;
+	}
+	if (refcount == 1)
+		/* no invalidate yet, remove from rbtree */
+		rb_erase(&entry->rbnode, &tree->rbroot);
+	spin_unlock(&tree->lock);
+
+	/* free */
+	zs_free(tree->pool, entry->handle);
+	zswap_entry_cache_free(entry);
+	atomic_dec(&zswap_stored_pages);
+
+	return 0;
+}
+
+static void zswap_flush_entries(unsigned type, int nr)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry;
+	int i, ret;
+
+/*
+ * This limits is arbitrary for now until a better
+ * policy can be implemented. This is so we don't
+ * eat all of RAM decompressing pages for writeback.
+ */
+#define ZSWAP_MAX_OUTSTANDING_FLUSHES 64
+	if (atomic_read(&zswap_outstanding_flushes) >
+		ZSWAP_MAX_OUTSTANDING_FLUSHES)
+		return;
+
+	for (i = 0; i < nr; i++) {
+		/* dequeue from lru */
+		spin_lock(&tree->lock);
+		if (list_empty(&tree->lru)) {
+			spin_unlock(&tree->lock);
+			break;
+		}
+		entry = list_first_entry(&tree->lru,
+				struct zswap_entry, lru);
+		list_del(&entry->lru);
+		zswap_entry_get(entry);
+		spin_unlock(&tree->lock);
+		ret = zswap_flush_entry(entry);
+		if (ret) {
+			/* put back on the lru */
+			spin_lock(&tree->lock);
+			list_add(&entry->lru, &tree->lru);
+			spin_unlock(&tree->lock);
+		} else {
+			if (atomic_read(&zswap_outstanding_flushes) >
+				ZSWAP_MAX_OUTSTANDING_FLUSHES)
+				break;
+		}
+	}
+}
+
+/*******************************************
+* page pool for temporary compression result
+********************************************/
+#define ZSWAP_TMPPAGE_POOL_PAGES 16
+static LIST_HEAD(zswap_tmppage_list);
+static DEFINE_SPINLOCK(zswap_tmppage_lock);
+
+static void zswap_tmppage_pool_destroy(void)
+{
+	struct page *page, *tmppage;
+
+	spin_lock(&zswap_tmppage_lock);
+	list_for_each_entry_safe(page, tmppage, &zswap_tmppage_list, lru) {
+		list_del(&page->lru);
+		__free_pages(page, 1);
+	}
+	spin_unlock(&zswap_tmppage_lock);
+}
+
+static int zswap_tmppage_pool_create(void)
+{
+	int i;
+	struct page *page;
+
+	for (i = 0; i < ZSWAP_TMPPAGE_POOL_PAGES; i++) {
+		page = alloc_pages(GFP_KERNEL, 1);
+		if (!page) {
+			zswap_tmppage_pool_destroy();
+			return -ENOMEM;
+		}
+		spin_lock(&zswap_tmppage_lock);
+		list_add(&page->lru, &zswap_tmppage_list);
+		spin_unlock(&zswap_tmppage_lock);
+	}
+	return 0;
+}
+
+static inline struct page *zswap_tmppage_alloc(void)
+{
+	struct page *page;
+
+	spin_lock(&zswap_tmppage_lock);
+	if (list_empty(&zswap_tmppage_list)) {
+		spin_unlock(&zswap_tmppage_lock);
+		return NULL;
+	}
+	page = list_first_entry(&zswap_tmppage_list, struct page, lru);
+	list_del(&page->lru);
+	spin_unlock(&zswap_tmppage_lock);
+	return page;
+}
+
+static inline void zswap_tmppage_free(struct page *page)
+{
+	spin_lock(&zswap_tmppage_lock);
+	list_add(&page->lru, &zswap_tmppage_list);
+	spin_unlock(&zswap_tmppage_lock);
+}
+
+/*********************************
+* frontswap hooks
+**********************************/
+/* attempts to compress and store an single page */
+static int zswap_frontswap_store(unsigned type, pgoff_t offset, struct page *page)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry, *dupentry;
+	int ret;
+	unsigned int dlen = PAGE_SIZE;
+	unsigned long handle;
+	char *buf;
+	u8 *src, *dst, *tmpdst;
+	struct page *tmppage;
+	bool flush_attempted = 0;
+
+	if (!tree) {
+		ret = -ENODEV;
+		goto reject;
+	}
+
+	/* compress */
+	dst = get_cpu_var(zswap_dstmem);
+	src = kmap_atomic(page);
+	ret = zswap_comp_op(ZSWAP_COMPOP_COMPRESS, src, PAGE_SIZE, dst, &dlen);
+	kunmap_atomic(src);
+	if (ret) {
+		ret = -EINVAL;
+		goto freepage;
+	}
+	if ((dlen * 100 / PAGE_SIZE) > zswap_max_compression_ratio) {
+		zswap_reject_compress_poor++;
+		ret = -E2BIG;
+		goto freepage;
+	}
+
+	/* store */
+	handle = zs_malloc(tree->pool, dlen,
+		__GFP_NORETRY | __GFP_HIGHMEM | __GFP_NOMEMALLOC |
+			__GFP_NOWARN);
+	if (!handle) {
+		zswap_flush_attempted++;
+		/*
+		 * Copy compressed buffer out of per-cpu storage so
+		 * we can re-enable preemption.
+		*/
+		tmppage = zswap_tmppage_alloc();
+		if (!tmppage) {
+			zswap_reject_tmppage_fail++;
+			ret = -ENOMEM;
+			goto freepage;
+		}
+		flush_attempted = 1;
+		tmpdst = page_address(tmppage);
+		memcpy(tmpdst, dst, dlen);
+		dst = tmpdst;
+		put_cpu_var(zswap_dstmem);
+
+		/* try to free up some space */
+		/* TODO: replace with more targeted policy */
+		zswap_flush_entries(type, 16);
+		/* try again, allowing wait */
+		handle = zs_malloc(tree->pool, dlen,
+			__GFP_NORETRY | __GFP_HIGHMEM | __GFP_NOMEMALLOC |
+				__GFP_NOWARN);
+		if (!handle) {
+			/* still no space, fail */
+			zswap_reject_zsmalloc_fail++;
+			ret = -ENOMEM;
+			goto freepage;
+		}
+		zswap_saved_by_flush++;
+	}
+
+	buf = zs_map_object(tree->pool, handle, ZS_MM_WO);
+	memcpy(buf, dst, dlen);
+	zs_unmap_object(tree->pool, handle);
+	if (flush_attempted)
+		zswap_tmppage_free(tmppage);
+	else
+		put_cpu_var(zswap_dstmem);
+
+	/* allocate entry */
+	entry = zswap_entry_cache_alloc(GFP_KERNEL);
+	if (!entry) {
+		zswap_reject_kmemcache_fail++;
+		ret = -ENOMEM;
+		goto reject;
+	}
+
+	/* populate entry */
+	entry->type = type;
+	entry->offset = offset;
+	entry->handle = handle;
+	entry->length = dlen;
+
+	/* map */
+	spin_lock(&tree->lock);
+	do {
+		ret = zswap_rb_insert(&tree->rbroot, entry, &dupentry);
+		if (ret == -EEXIST) {
+			zswap_duplicate_entry++;
+			/* remove from rbtree and lru */
+			rb_erase(&dupentry->rbnode, &tree->rbroot);
+			if (dupentry->lru.next != LIST_POISON1)
+				list_del(&dupentry->lru);
+			if (!zswap_entry_put(dupentry)) {
+				/* free */
+				zs_free(tree->pool, dupentry->handle);
+				zswap_entry_cache_free(dupentry);
+				atomic_dec(&zswap_stored_pages);
+			}
+		}
+	} while (ret == -EEXIST);
+	list_add_tail(&entry->lru, &tree->lru);
+	spin_unlock(&tree->lock);
+
+	/* update stats */
+	atomic_inc(&zswap_stored_pages);
+
+	return 0;
+
+freepage:
+	if (flush_attempted)
+		zswap_tmppage_free(tmppage);
+	else
+		put_cpu_var(zswap_dstmem);
+reject:
+	return ret;
+}
+
+/*
+ * returns 0 if the page was successfully decompressed
+ * return -1 on entry not found or error
+*/
+static int zswap_frontswap_load(unsigned type, pgoff_t offset, struct page *page)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry;
+	u8 *src, *dst;
+	unsigned int dlen;
+	int refcount;
+
+	/* find */
+	spin_lock(&tree->lock);
+	entry = zswap_rb_search(&tree->rbroot, offset);
+	if (!entry) {
+		/* entry was flushed */
+		spin_unlock(&tree->lock);
+		return -1;
+	}
+	zswap_entry_get(entry);
+
+	/* remove from lru */
+	if (entry->lru.next != LIST_POISON1)
+		list_del(&entry->lru);
+	spin_unlock(&tree->lock);
+
+	/* decompress */
+	dlen = PAGE_SIZE;
+	src = zs_map_object(tree->pool, entry->handle, ZS_MM_RO);
+	dst = kmap_atomic(page);
+	zswap_comp_op(ZSWAP_COMPOP_DECOMPRESS, src, entry->length,
+		dst, &dlen);
+	kunmap_atomic(dst);
+	zs_unmap_object(tree->pool, entry->handle);
+
+	spin_lock(&tree->lock);
+	refcount = zswap_entry_put(entry);
+	if (likely(refcount)) {
+		list_add_tail(&entry->lru, &tree->lru);
+		spin_unlock(&tree->lock);
+		return 0;
+	}
+	spin_unlock(&tree->lock);
+
+	/*
+	 * We don't have to unlink from the rbtree because zswap_flush_entry()
+	 * or zswap_frontswap_invalidate page() has already done this for us if we
+	 * are the last reference.
+	 */
+	/* free */
+	zs_free(tree->pool, entry->handle);
+	zswap_entry_cache_free(entry);
+	atomic_dec(&zswap_stored_pages);
+
+	return 0;
+}
+
+/* invalidates a single page */
+static void zswap_frontswap_invalidate_page(unsigned type, pgoff_t offset)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry;
+	int refcount;
+
+	if (!tree)
+		return;
+
+	/* find */
+	spin_lock(&tree->lock);
+	entry = zswap_rb_search(&tree->rbroot, offset);
+	if (!entry) {
+		/* entry was flushed */
+		spin_unlock(&tree->lock);
+		return;
+	}
+
+	/* remove from rbtree and lru */
+	rb_erase(&entry->rbnode, &tree->rbroot);
+	if (entry->lru.next != LIST_POISON1)
+		list_del(&entry->lru);
+	refcount = zswap_entry_put(entry);
+	spin_unlock(&tree->lock);
+	if (refcount) {
+		/* must be flushing */
+		return;
+	}
+
+	/* free */
+	zs_free(tree->pool, entry->handle);
+	zswap_entry_cache_free(entry);
+	atomic_dec(&zswap_stored_pages);
+}
+
+/* invalidates all pages for the given swap type */
+static void zswap_frontswap_invalidate_area(unsigned type)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct rb_node *node, *next;
+	struct zswap_entry *entry;
+
+	if (!tree)
+		return;
+
+	/* walk the tree and free everything */
+	spin_lock(&tree->lock);
+	node = rb_first(&tree->rbroot);
+	while (node) {
+		entry = rb_entry(node, struct zswap_entry, rbnode);
+		zs_free(tree->pool, entry->handle);
+		next = rb_next(node);
+		zswap_entry_cache_free(entry);
+		node = next;
+	}
+	tree->rbroot = RB_ROOT;
+	INIT_LIST_HEAD(&tree->lru);
+	spin_unlock(&tree->lock);
+}
+
+/* NOTE: this is called in atomic context from swapon and must not sleep */
+static void zswap_frontswap_init(unsigned type)
+{
+	struct zswap_tree *tree;
+
+	tree = kzalloc(sizeof(struct zswap_tree), GFP_NOWAIT);
+	if (!tree)
+		goto err;
+	tree->pool = zs_create_pool(GFP_NOWAIT, &zswap_zs_ops);
+	if (!tree->pool)
+		goto freetree;
+	tree->rbroot = RB_ROOT;
+	INIT_LIST_HEAD(&tree->lru);
+	spin_lock_init(&tree->lock);
+	zswap_trees[type] = tree;
+	return;
+
+freetree:
+	kfree(tree);
+err:
+	pr_err("zswap: alloc failed, zswap disabled for swap type %d\n", type);
+}
+
+static struct frontswap_ops zswap_frontswap_ops = {
+	.store = zswap_frontswap_store,
+	.load = zswap_frontswap_load,
+	.invalidate_page = zswap_frontswap_invalidate_page,
+	.invalidate_area = zswap_frontswap_invalidate_area,
+	.init = zswap_frontswap_init
+};
+
+/*********************************
+* debugfs functions
+**********************************/
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static struct dentry *zswap_debugfs_root;
+
+static int __init zswap_debugfs_init(void)
+{
+	if (!debugfs_initialized())
+		return -ENODEV;
+
+	zswap_debugfs_root = debugfs_create_dir("zswap", NULL);
+	if (!zswap_debugfs_root)
+		return -ENOMEM;
+
+	debugfs_create_u64("saved_by_flush", S_IRUGO,
+			zswap_debugfs_root, &zswap_saved_by_flush);
+	debugfs_create_u64("pool_limit_hit", S_IRUGO,
+			zswap_debugfs_root, &zswap_pool_limit_hit);
+	debugfs_create_u64("reject_flush_attempted", S_IRUGO,
+			zswap_debugfs_root, &zswap_flush_attempted);
+	debugfs_create_u64("reject_tmppage_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_tmppage_fail);
+	debugfs_create_u64("reject_flush_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_flush_fail);
+	debugfs_create_u64("reject_zsmalloc_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_zsmalloc_fail);
+	debugfs_create_u64("reject_kmemcache_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_kmemcache_fail);
+	debugfs_create_u64("reject_compress_poor", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_compress_poor);
+	debugfs_create_u64("flushed_pages", S_IRUGO,
+			zswap_debugfs_root, &zswap_flushed_pages);
+	debugfs_create_u64("duplicate_entry", S_IRUGO,
+			zswap_debugfs_root, &zswap_duplicate_entry);
+	debugfs_create_atomic_t("pool_pages", S_IRUGO,
+			zswap_debugfs_root, &zswap_pool_pages);
+	debugfs_create_atomic_t("stored_pages", S_IRUGO,
+			zswap_debugfs_root, &zswap_stored_pages);
+	debugfs_create_atomic_t("outstanding_flushes", S_IRUGO,
+			zswap_debugfs_root, &zswap_outstanding_flushes);
+
+	return 0;
+}
+
+static void __exit zswap_debugfs_exit(void)
+{
+	if (zswap_debugfs_root)
+		debugfs_remove_recursive(zswap_debugfs_root);
+}
+#else
+static inline int __init zswap_debugfs_init(void)
+{
+	return 0;
+}
+
+static inline void __exit zswap_debugfs_exit(void) { }
+#endif
+
+/*********************************
+* module init and exit
+**********************************/
+static int __init init_zswap(void)
+{
+	if (!zswap_enabled)
+		return 0;
+
+	pr_info("loading zswap\n");
+	if (zswap_entry_cache_create()) {
+		pr_err("zswap: entry cache creation failed\n");
+		goto error;
+	}
+	if (zswap_page_pool_create()) {
+		pr_err("zswap: page pool initialization failed\n");
+		goto pagepoolfail;
+	}
+	if (zswap_tmppage_pool_create()) {
+		pr_err("zswap: workmem pool initialization failed\n");
+		goto tmppoolfail;
+	}
+	if (zswap_comp_init()) {
+		pr_err("zswap: compressor initialization failed\n");
+		goto compfail;
+	}
+	if (zswap_cpu_init()) {
+		pr_err("zswap: per-cpu initialization failed\n");
+		goto pcpufail;
+	}
+	frontswap_register_ops(&zswap_frontswap_ops);
+	if (zswap_debugfs_init())
+		pr_warn("zswap: debugfs initialization failed\n");
+	return 0;
+pcpufail:
+	zswap_comp_exit();
+compfail:
+	zswap_tmppage_pool_destroy();
+tmppoolfail:
+	zswap_page_pool_destroy();
+pagepoolfail:
+	zswap_entry_cache_destory();
+error:
+	return -ENOMEM;
+}
+/* must be late so crypto has time to come up */
+late_initcall(init_zswap);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Seth Jennings <sjenning@linux.vnet.ibm.com>");
+MODULE_DESCRIPTION("Compression backend for frontswap pages");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCHv2 9/9] zswap: add documentation
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (7 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 8/9] zswap: add to mm/ Seth Jennings
@ 2013-01-07 20:24 ` Seth Jennings
  2013-01-22 18:10 ` [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
  9 siblings, 0 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

This patch adds the documentation file for the zswap functionality

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 Documentation/vm/zswap.txt |   73 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 73 insertions(+)
 create mode 100644 Documentation/vm/zswap.txt

diff --git a/Documentation/vm/zswap.txt b/Documentation/vm/zswap.txt
new file mode 100644
index 0000000..5d00ce9
--- /dev/null
+++ b/Documentation/vm/zswap.txt
@@ -0,0 +1,73 @@
+Overview:
+
+Zswap is a lightweight compressed cache for swap pages. It takes
+pages that are in the process of being swapped out and attempts to
+compress them into a dynamically allocated RAM-based memory pool.
+If this process is successful, the writeback to the swap device is
+deferred and, in many cases, avoided completely.  This results in
+a significant I/O reduction and performance gains for systems that
+are swapping.
+
+Zswap provides compressed swap caching that basically trades CPU cycles
+for reduced swap I/O.  This trade-off can result in a significant
+performance improvement as reads to/writes from to the compressed
+cache almost always faster that reading from a swap device
+which incurs the latency of an asynchronous block I/O read.
+
+Some potential benefits:
+* Desktop/laptop users with limited RAM capacities can mitigate the
+    performance impact of swapping.
+* Overcommitted guests that share a common I/O resource can
+    dramatically reduce their swap I/O pressure, avoiding heavy
+    handed I/O throttling by the hypervisor.  This allows more work
+    to get done with less impact to the guest workload and guests
+    sharing the I/O subsystem
+* Users with SSDs as swap devices can extend the life of the device by
+    drastically reducing life-shortening writes.
+
+Zswap evicts pages from compressed cache on an LRU basis to the backing
+swap device when the compress pool reaches it size limit or the pool is
+unable to obtain additional pages from the buddy allocator.  This
+requirement had been identified in prior community discussions.
+
+To enabled zswap, the "enabled" attribute must be set to 1 at boot time.
+e.g. zswap.enabled=1
+
+Design:
+
+Zswap receives pages for compression through the Frontswap API and
+is able to evict pages from its own compressed pool on an LRU basis
+and write them back to the backing swap device in the case that the
+compressed pool is full or unable to secure additional pages from
+the buddy allocator.
+
+Zswap makes use of zsmalloc for the managing the compressed memory
+pool.  This is because zsmalloc is specifically designed to minimize
+fragmentation on large (> PAGE_SIZE/2) allocation sizes.  Each
+allocation in zsmalloc is not directly accessible by address.
+Rather, a handle is return by the allocation routine and that handle
+must be mapped before being accessed.  The compressed memory pool grows
+on demand and shrinks as compressed pages are freed.  The pool is
+not preallocated.
+
+When a swap page is passed from frontswap to zswap, zswap maintains
+a mapping of the swap entry, a combination of the swap type and swap
+offset, to the zsmalloc handle that references that compressed swap
+page.  This mapping is achieved with a red-black tree per swap type.
+The swap offset is the search key for the tree nodes.
+
+Zswap seeks to be simple in its policies.  Sysfs attributes allow for
+two user controlled policies:
+* max_compression_ratio - Maximum compression ratio, as as percentage,
+    for an acceptable compressed page. Any page that does not compress
+    by at least this ratio will be rejected.
+* max_pool_percent - The maximum percentage of memory that the compressed
+    pool can occupy.
+
+Zswap allows the compressor to be selected at kernel boot time by
+setting the “compressor” attribute.  The default compressor is lzo.
+e.g. zswap.compressor=deflate
+
+A debugfs interface is provided for various statistic about pool size,
+number of pages stored, and various counters for the reasons pages
+are rejected.
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 5/9] debugfs: add get/set for atomic types
  2013-01-07 20:24 ` [PATCHv2 5/9] debugfs: add get/set for atomic types Seth Jennings
@ 2013-01-07 20:32   ` Greg Kroah-Hartman
  2013-01-07 20:41     ` Seth Jennings
  0 siblings, 1 reply; 43+ messages in thread
From: Greg Kroah-Hartman @ 2013-01-07 20:32 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Andrew Morton, devel, Dan Magenheimer, Konrad Rzeszutek Wilk,
	linux-kernel, Rik van Riel, Larry Woodman, linux-mm, Minchan Kim,
	Mel Gorman, Robert Jennings, Johannes Weiner, Nitin Gupta,
	Jenifer Hopper

On Mon, Jan 07, 2013 at 02:24:36PM -0600, Seth Jennings wrote:
> debugfs currently lack the ability to create attributes
> that set/get atomic_t values.

I hate to ask, but why would you ever want to do such a thing?

greg k-h

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 5/9] debugfs: add get/set for atomic types
  2013-01-07 20:32   ` Greg Kroah-Hartman
@ 2013-01-07 20:41     ` Seth Jennings
  2013-01-25 16:45       ` Seth Jennings
  0 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-07 20:41 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Andrew Morton, devel, Dan Magenheimer, Konrad Rzeszutek Wilk,
	linux-kernel, Rik van Riel, Larry Woodman, linux-mm, Minchan Kim,
	Mel Gorman, Robert Jennings, Johannes Weiner, Nitin Gupta,
	Jenifer Hopper

On 01/07/2013 02:32 PM, Greg Kroah-Hartman wrote:
> On Mon, Jan 07, 2013 at 02:24:36PM -0600, Seth Jennings wrote:
>> debugfs currently lack the ability to create attributes
>> that set/get atomic_t values.
> 
> I hate to ask, but why would you ever want to do such a thing?

There are a few atomic_t statistics in zswap that are valuable to have
in the debugfs attributes.  Rather than have non-atomic mirrors of all
of them, as is done in zcache right now (see
drivers/staging/ramster/zcache-main.c:131), I thought this to be a
cleaner solution.

Granted, I personally have no use for the setting part; only the
getting part.  I only included the setting operations to keep the
balance and conform with the rest of the debugfs implementation.

Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 8/9] zswap: add to mm/
  2013-01-07 20:24 ` [PATCHv2 8/9] zswap: add to mm/ Seth Jennings
@ 2013-01-08 17:15   ` Dave Hansen
  2013-01-08 17:54     ` Dan Magenheimer
  2013-01-25 22:44   ` Rik van Riel
  1 sibling, 1 reply; 43+ messages in thread
From: Dave Hansen @ 2013-01-08 17:15 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/07/2013 12:24 PM, Seth Jennings wrote:
> +struct zswap_tree {
> +	struct rb_root rbroot;
> +	struct list_head lru;
> +	spinlock_t lock;
> +	struct zs_pool *pool;
> +};

BTW, I spent some time trying to get this lock contended.  You thought
the anon_vma locks would dominate and this spinlock would not end up
very contended.

I figured that if I hit zswap from a bunch of CPUs that _didn't_ use
anonymous memory (and thus the anon_vma locks) that some more contention
would pop up.  I did that with a bunch of CPUs writing to tmpfs, and
this lock was still well down below anon_vma.  The anon_vma contention
was obviously coming from _other_ anonymous memory around.

IOW, I feel a bit better about this lock.  I only tested on 16 cores on
a system with relatively light NUMA characteristics, and it might be the
bottleneck if all the anonymous memory on the system is mlock()'d and
you're pounding on tmpfs, but that's pretty contrived.


^ permalink raw reply	[flat|nested] 43+ messages in thread

* RE: [PATCHv2 8/9] zswap: add to mm/
  2013-01-08 17:15   ` Dave Hansen
@ 2013-01-08 17:54     ` Dan Magenheimer
  0 siblings, 0 replies; 43+ messages in thread
From: Dan Magenheimer @ 2013-01-08 17:54 UTC (permalink / raw)
  To: Dave Hansen, Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

> From: Dave Hansen [mailto:dave@linux.vnet.ibm.com]
> Sent: Tuesday, January 08, 2013 10:15 AM
> To: Seth Jennings
> Cc: Greg Kroah-Hartman; Andrew Morton; Nitin Gupta; Minchan Kim; Konrad Rzeszutek Wilk; Dan
> Magenheimer; Robert Jennings; Jenifer Hopper; Mel Gorman; Johannes Weiner; Rik van Riel; Larry
> Woodman; linux-mm@kvack.org; linux-kernel@vger.kernel.org; devel@driverdev.osuosl.org
> Subject: Re: [PATCHv2 8/9] zswap: add to mm/
> 
> On 01/07/2013 12:24 PM, Seth Jennings wrote:
> > +struct zswap_tree {
> > +	struct rb_root rbroot;
> > +	struct list_head lru;
> > +	spinlock_t lock;
> > +	struct zs_pool *pool;
> > +};
> 
> BTW, I spent some time trying to get this lock contended.  You thought
> the anon_vma locks would dominate and this spinlock would not end up
> very contended.
> 
> I figured that if I hit zswap from a bunch of CPUs that _didn't_ use
> anonymous memory (and thus the anon_vma locks) that some more contention
> would pop up.  I did that with a bunch of CPUs writing to tmpfs, and
> this lock was still well down below anon_vma.  The anon_vma contention
> was obviously coming from _other_ anonymous memory around.
> 
> IOW, I feel a bit better about this lock.  I only tested on 16 cores on
> a system with relatively light NUMA characteristics, and it might be the
> bottleneck if all the anonymous memory on the system is mlock()'d and
> you're pounding on tmpfs, but that's pretty contrived.

IIUC, Seth's current "flush" code only gets called when in the context
of a frontswap_store and is very limited in what it does, whereas the
goal will be for flushing to run both as an independent thread and do
more complex things (e.g. so that wholepages can be reclaimed rather
than random zpages).

So it will be interesting to re-test contention when zswap is complete.

Dan

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 0/9] zswap: compressed swap caching
  2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
                   ` (8 preceding siblings ...)
  2013-01-07 20:24 ` [PATCHv2 9/9] zswap: add documentation Seth Jennings
@ 2013-01-22 18:10 ` Seth Jennings
  9 siblings, 0 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-22 18:10 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

I forgot to include the link to the performance information we have
gathered on zswap from the first patchset.

Here are some addition performance metrics regarding the performance
improvements and I/O reductions that can be achieved using zswap as
measured by SPECjbb.

http://ibm.co/VCgHvM

These results include runs on x86 and new results on Power7+ with
hardware compression acceleration.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
@ 2013-01-25  0:08   ` Nitin Gupta
  2013-01-25  1:33   ` Minchan Kim
  2013-01-25 21:26   ` Rik van Riel
  2 siblings, 0 replies; 43+ messages in thread
From: Nitin Gupta @ 2013-01-25  0:08 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 7, 2013 at 12:24 PM, Seth Jennings
<sjenning@linux.vnet.ibm.com> wrote:
> zs_create_pool() currently takes a gfp flags argument
> that is used when growing the memory pool.  However
> it is not used in allocating the metadata for the pool
> itself.  That is currently hardcoded to GFP_KERNEL.
>
> zswap calls zs_create_pool() at swapon time which is done
> in atomic context, resulting in a "might sleep" warning.
>
> This patch changes the meaning of the flags argument in
> zs_create_pool() to mean the flags for the metadata allocation,
> and adds a flags argument to zs_malloc that will be used for
> memory pool growth if required.
>
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> ---
>  drivers/staging/zcache/zcache-main.c     |    4 ++--
>  drivers/staging/zram/zram_drv.c          |    4 ++--
>  drivers/staging/zsmalloc/zsmalloc-main.c |    9 +++------
>  drivers/staging/zsmalloc/zsmalloc.h      |    2 +-
>  4 files changed, 8 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
> index 52b43b7..674c754 100644
> --- a/drivers/staging/zcache/zcache-main.c
> +++ b/drivers/staging/zcache/zcache-main.c
> @@ -711,7 +711,7 @@ static unsigned long zv_create(struct zs_pool *pool, uint32_t pool_id,
>
>         BUG_ON(!irqs_disabled());
>         BUG_ON(chunks >= NCHUNKS);
> -       handle = zs_malloc(pool, size);
> +       handle = zs_malloc(pool, size, ZCACHE_GFP_MASK);
>         if (!handle)
>                 goto out;
>         atomic_inc(&zv_curr_dist_counts[chunks]);
> @@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
>                 goto out;
>         cli->allocated = 1;
>  #ifdef CONFIG_FRONTSWAP
> -       cli->zspool = zs_create_pool("zcache", ZCACHE_GFP_MASK);
> +       cli->zspool = zs_create_pool("zcache", GFP_KERNEL);
>         if (cli->zspool == NULL)
>                 goto out;
>         idr_init(&cli->tmem_pools);
> diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
> index fb4a7c9..13e9b4b 100644
> --- a/drivers/staging/zram/zram_drv.c
> +++ b/drivers/staging/zram/zram_drv.c
> @@ -336,7 +336,7 @@ static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec, u32 index,
>                 clen = PAGE_SIZE;
>         }
>
> -       handle = zs_malloc(zram->mem_pool, clen);
> +       handle = zs_malloc(zram->mem_pool, clen, GFP_NOIO | __GFP_HIGHMEM);
>         if (!handle) {
>                 pr_info("Error allocating memory for compressed "
>                         "page: %u, size=%zu\n", index, clen);
> @@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
>         /* zram devices sort of resembles non-rotational disks */
>         queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
>
> -       zram->mem_pool = zs_create_pool("zram", GFP_NOIO | __GFP_HIGHMEM);
> +       zram->mem_pool = zs_create_pool("zram", GFP_KERNEL);
>         if (!zram->mem_pool) {
>                 pr_err("Error creating memory pool\n");
>                 ret = -ENOMEM;
> diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
> index 09a9d35..6ff380e 100644
> --- a/drivers/staging/zsmalloc/zsmalloc-main.c
> +++ b/drivers/staging/zsmalloc/zsmalloc-main.c
> @@ -205,8 +205,6 @@ struct link_free {
>
>  struct zs_pool {
>         struct size_class size_class[ZS_SIZE_CLASSES];
> -
> -       gfp_t flags;    /* allocation flags used when growing pool */
>         const char *name;
>  };
>
> @@ -807,7 +805,7 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
>                 return NULL;
>
>         ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
> -       pool = kzalloc(ovhd_size, GFP_KERNEL);
> +       pool = kzalloc(ovhd_size, flags);
>         if (!pool)
>                 return NULL;
>
> @@ -827,7 +825,6 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
>
>         }
>
> -       pool->flags = flags;
>         pool->name = name;
>
>         return pool;
> @@ -863,7 +860,7 @@ EXPORT_SYMBOL_GPL(zs_destroy_pool);
>   * otherwise 0.
>   * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
>   */
> -unsigned long zs_malloc(struct zs_pool *pool, size_t size)
> +unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
>  {
>         unsigned long obj;
>         struct link_free *link;
> @@ -885,7 +882,7 @@ unsigned long zs_malloc(struct zs_pool *pool, size_t size)
>
>         if (!first_page) {
>                 spin_unlock(&class->lock);
> -               first_page = alloc_zspage(class, pool->flags);
> +               first_page = alloc_zspage(class, flags);
>                 if (unlikely(!first_page))
>                         return 0;
>
> diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
> index de2e8bf..907ff03 100644
> --- a/drivers/staging/zsmalloc/zsmalloc.h
> +++ b/drivers/staging/zsmalloc/zsmalloc.h
> @@ -31,7 +31,7 @@ struct zs_pool;
>  struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
>  void zs_destroy_pool(struct zs_pool *pool);
>
> -unsigned long zs_malloc(struct zs_pool *pool, size_t size);
> +unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
>  void zs_free(struct zs_pool *pool, unsigned long obj);
>
>  void *zs_map_object(struct zs_pool *pool, unsigned long handle,
> --
> 1.7.9.5
>
> --


The additional of flags, especially for zs_create_pool seems not so
obvious so should be documented as function comment. Otherwise,
looks good to me.

Acked-by: Nitin Gupta <ngupta@vflare.org>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name
  2013-01-07 20:24 ` [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name Seth Jennings
@ 2013-01-25  0:09   ` Nitin Gupta
  2013-01-25 21:50   ` Rik van Riel
  1 sibling, 0 replies; 43+ messages in thread
From: Nitin Gupta @ 2013-01-25  0:09 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 7, 2013 at 12:24 PM, Seth Jennings
<sjenning@linux.vnet.ibm.com> wrote:
> zs_create_pool() currently takes a name argument which is
> never used in any useful way.
>
> This patch removes it.
>
> Signed-off-by: Seth Jennnings <sjenning@linux.vnet.ibm.com>
> ---
>  drivers/staging/zcache/zcache-main.c     |    2 +-
>  drivers/staging/zram/zram_drv.c          |    2 +-
>  drivers/staging/zsmalloc/zsmalloc-main.c |    7 +------
>  drivers/staging/zsmalloc/zsmalloc.h      |    2 +-
>  4 files changed, 4 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
> index 674c754..6fa9f9a 100644
> --- a/drivers/staging/zcache/zcache-main.c
> +++ b/drivers/staging/zcache/zcache-main.c
> @@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
>                 goto out;
>         cli->allocated = 1;
>  #ifdef CONFIG_FRONTSWAP
> -       cli->zspool = zs_create_pool("zcache", GFP_KERNEL);
> +       cli->zspool = zs_create_pool(GFP_KERNEL);
>         if (cli->zspool == NULL)
>                 goto out;
>         idr_init(&cli->tmem_pools);
> diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
> index 13e9b4b..13d9f6d 100644
> --- a/drivers/staging/zram/zram_drv.c
> +++ b/drivers/staging/zram/zram_drv.c
> @@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
>         /* zram devices sort of resembles non-rotational disks */
>         queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
>
> -       zram->mem_pool = zs_create_pool("zram", GFP_KERNEL);
> +       zram->mem_pool = zs_create_pool(GFP_KERNEL);
>         if (!zram->mem_pool) {
>                 pr_err("Error creating memory pool\n");
>                 ret = -ENOMEM;
> diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
> index 6ff380e..5e212c0 100644
> --- a/drivers/staging/zsmalloc/zsmalloc-main.c
> +++ b/drivers/staging/zsmalloc/zsmalloc-main.c
> @@ -796,14 +796,11 @@ fail:
>         return notifier_to_errno(ret);
>  }
>
> -struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
> +struct zs_pool *zs_create_pool(gfp_t flags)
>  {
>         int i, ovhd_size;
>         struct zs_pool *pool;
>
> -       if (!name)
> -               return NULL;
> -
>         ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
>         pool = kzalloc(ovhd_size, flags);
>         if (!pool)
> @@ -825,8 +822,6 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
>
>         }
>
> -       pool->name = name;
> -
>         return pool;
>  }
>  EXPORT_SYMBOL_GPL(zs_create_pool);
> diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
> index 907ff03..25a4b4d 100644
> --- a/drivers/staging/zsmalloc/zsmalloc.h
> +++ b/drivers/staging/zsmalloc/zsmalloc.h
> @@ -28,7 +28,7 @@ enum zs_mapmode {
>
>  struct zs_pool;
>
> -struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
> +struct zs_pool *zs_create_pool(gfp_t flags);
>  void zs_destroy_pool(struct zs_pool *pool);
>
>  unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
> --
> 1.7.9.5
>

Acked-by: Nitin Gupta <ngupta@vflare.org>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks
  2013-01-07 20:24 ` [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
@ 2013-01-25  0:11   ` Nitin Gupta
  2013-01-25 21:55   ` Rik van Riel
  1 sibling, 0 replies; 43+ messages in thread
From: Nitin Gupta @ 2013-01-25  0:11 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 7, 2013 at 12:24 PM, Seth Jennings
<sjenning@linux.vnet.ibm.com> wrote:
> This patch allows users of zsmalloc to register the
> allocation and free routines used by zsmalloc to obtain
> more pages for the memory pool.  This allows the user
> more control over zsmalloc pool policy and behavior.
>
> If the user does not wish to control this, alloc_page() and
> __free_page() are used by default.
>
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> ---
>  drivers/staging/zcache/zcache-main.c     |    2 +-
>  drivers/staging/zram/zram_drv.c          |    2 +-
>  drivers/staging/zsmalloc/zsmalloc-main.c |   43 ++++++++++++++++++++++--------
>  drivers/staging/zsmalloc/zsmalloc.h      |    8 +++++-
>  4 files changed, 41 insertions(+), 14 deletions(-)
>

Some documentation about zs_ops in zs_create_pool() would be useful.
Otherwise, looks good to me.

Acked-by: Nitin Gupta <ngupta@vflare.org>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
  2013-01-07 20:24 ` [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
@ 2013-01-25  0:17   ` Nitin Gupta
  2013-01-25 16:38     ` Seth Jennings
  0 siblings, 1 reply; 43+ messages in thread
From: Nitin Gupta @ 2013-01-25  0:17 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 7, 2013 at 12:24 PM, Seth Jennings
<sjenning@linux.vnet.ibm.com> wrote:
> Right now ZS_SIZE_CLASS_DELTA is hardcoded to be 16.  This
> creates 254 classes for systems with 4k pages. However, on
> PPC64 with 64k pages, it creates 4095 classes which is far
> too many.
>
> This patch makes ZS_SIZE_CLASS_DELTA relative to PAGE_SIZE
> so that regardless of the page size, there will be the same
> number of classes.
>
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> ---
>  drivers/staging/zsmalloc/zsmalloc-main.c |    2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
> index 825e124..3543047 100644
> --- a/drivers/staging/zsmalloc/zsmalloc-main.c
> +++ b/drivers/staging/zsmalloc/zsmalloc-main.c
> @@ -141,7 +141,7 @@
>   *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
>   *  (reason above)
>   */
> -#define ZS_SIZE_CLASS_DELTA    16
> +#define ZS_SIZE_CLASS_DELTA    (PAGE_SIZE >> 8)
>  #define ZS_SIZE_CLASSES                ((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
>                                         ZS_SIZE_CLASS_DELTA + 1)
>

Actually, there is no point creating size classes beyond [M/(M+1)] * PAGE_SIZE
where M is the maximum number of system pages in a zspage. All size classes
beyond this size can be collapsed with PAGE_SIZE size class.  This can
significantly reduce number of size classes created but I think changes needed
to do this would be more involved, so perhaps, should be done in another
patch.


Can you please resend part of this series  (patch 1  to patch 4) which deals
just with zsmalloc separately?  I haven't yet looked into zswap itself so would
help with zsmalloc bits are separated out.

Acked-by: Nitin Gupta <ngupta@vflare.org>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
  2013-01-25  0:08   ` Nitin Gupta
@ 2013-01-25  1:33   ` Minchan Kim
  2013-01-25 15:07     ` Seth Jennings
  2013-01-25 21:26   ` Rik van Riel
  2 siblings, 1 reply; 43+ messages in thread
From: Minchan Kim @ 2013-01-25  1:33 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

Hi Seth, frontswap guys

On Tue, Jan 8, 2013 at 5:24 AM, Seth Jennings
<sjenning@linux.vnet.ibm.com> wrote:
> zs_create_pool() currently takes a gfp flags argument
> that is used when growing the memory pool.  However
> it is not used in allocating the metadata for the pool
> itself.  That is currently hardcoded to GFP_KERNEL.
>
> zswap calls zs_create_pool() at swapon time which is done
> in atomic context, resulting in a "might sleep" warning.

I didn't review this all series, really sorry but totday I saw Nitin
added Acked-by so I'm afraid Greg might get it under my radar. I'm not
strong against but I would like know why we should call frontswap_init
under swap_lock? Is there special reason?

Thanks.
-- 
Kind regards,
Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-25  1:33   ` Minchan Kim
@ 2013-01-25 15:07     ` Seth Jennings
  2013-01-25 15:56       ` Dan Magenheimer
  0 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-25 15:07 UTC (permalink / raw)
  To: Minchan Kim
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/24/2013 07:33 PM, Minchan Kim wrote:
> Hi Seth, frontswap guys
> 
> On Tue, Jan 8, 2013 at 5:24 AM, Seth Jennings
> <sjenning@linux.vnet.ibm.com> wrote:
>> zs_create_pool() currently takes a gfp flags argument
>> that is used when growing the memory pool.  However
>> it is not used in allocating the metadata for the pool
>> itself.  That is currently hardcoded to GFP_KERNEL.
>>
>> zswap calls zs_create_pool() at swapon time which is done
>> in atomic context, resulting in a "might sleep" warning.
> 
> I didn't review this all series, really sorry but totday I saw Nitin
> added Acked-by so I'm afraid Greg might get it under my radar. I'm not
> strong against but I would like know why we should call frontswap_init
> under swap_lock? Is there special reason?

The call stack is:

SYSCALL_DEFINE2(swapon.. <-- swapon_mutex taken here
enable_swap_info() <-- swap_lock taken here
frontswap_init()
__frontswap_init()
zswap_frontswap_init()
zs_create_pool()

It isn't entirely clear to me why frontswap_init() is called under
lock.  Then again, I'm not entirely sure what the swap_lock protects.
 There are no comments near the swap_lock definition to tell me.

I would guess that the intent is to block any writes to the swap
device until frontswap_init() has completed.

Dan care to weigh in?

Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* RE: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-25 15:07     ` Seth Jennings
@ 2013-01-25 15:56       ` Dan Magenheimer
  2013-01-28  2:59         ` Minchan Kim
  0 siblings, 1 reply; 43+ messages in thread
From: Dan Magenheimer @ 2013-01-25 15:56 UTC (permalink / raw)
  To: Seth Jennings, Minchan Kim
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Konrad Wilk,
	Robert Jennings, Jenifer Hopper, Mel Gorman, Johannes Weiner,
	Rik van Riel, Larry Woodman, linux-mm, linux-kernel, devel

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
> 
> On 01/24/2013 07:33 PM, Minchan Kim wrote:
> > Hi Seth, frontswap guys
> >
> > On Tue, Jan 8, 2013 at 5:24 AM, Seth Jennings
> > <sjenning@linux.vnet.ibm.com> wrote:
> >> zs_create_pool() currently takes a gfp flags argument
> >> that is used when growing the memory pool.  However
> >> it is not used in allocating the metadata for the pool
> >> itself.  That is currently hardcoded to GFP_KERNEL.
> >>
> >> zswap calls zs_create_pool() at swapon time which is done
> >> in atomic context, resulting in a "might sleep" warning.
> >
> > I didn't review this all series, really sorry but totday I saw Nitin
> > added Acked-by so I'm afraid Greg might get it under my radar. I'm not
> > strong against but I would like know why we should call frontswap_init
> > under swap_lock? Is there special reason?
> 
> The call stack is:
> 
> SYSCALL_DEFINE2(swapon.. <-- swapon_mutex taken here
> enable_swap_info() <-- swap_lock taken here
> frontswap_init()
> __frontswap_init()
> zswap_frontswap_init()
> zs_create_pool()
> 
> It isn't entirely clear to me why frontswap_init() is called under
> lock.  Then again, I'm not entirely sure what the swap_lock protects.
>  There are no comments near the swap_lock definition to tell me.
> 
> I would guess that the intent is to block any writes to the swap
> device until frontswap_init() has completed.
> 
> Dan care to weigh in?

I think frontswap's first appearance needs to be atomic, i.e.
the transition from (a) frontswap is not present and will fail
all calls, to (b) frontswap is fully functional... that transition
must be atomic.  And, once Konrad's module patches are in, the
opposite transition must be atomic also.  But there are most
likely other ways to do those transitions atomically that
don't need to hold swap_lock.

Honestly, I never really focused on the initialization code
so I am very open to improvements as long as they work for
all the various frontswap backends.

Dan

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
  2013-01-25  0:17   ` Nitin Gupta
@ 2013-01-25 16:38     ` Seth Jennings
  0 siblings, 0 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-25 16:38 UTC (permalink / raw)
  To: Nitin Gupta
  Cc: Greg Kroah-Hartman, Andrew Morton, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/24/2013 06:17 PM, Nitin Gupta wrote:
> On Mon, Jan 7, 2013 at 12:24 PM, Seth Jennings
> <sjenning@linux.vnet.ibm.com> wrote:
>> Right now ZS_SIZE_CLASS_DELTA is hardcoded to be 16.  This
>> creates 254 classes for systems with 4k pages. However, on
>> PPC64 with 64k pages, it creates 4095 classes which is far
>> too many.
>>
>> This patch makes ZS_SIZE_CLASS_DELTA relative to PAGE_SIZE
>> so that regardless of the page size, there will be the same
>> number of classes.
>>
>> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
>> ---
>>  drivers/staging/zsmalloc/zsmalloc-main.c |    2 +-
>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
>> index 825e124..3543047 100644
>> --- a/drivers/staging/zsmalloc/zsmalloc-main.c
>> +++ b/drivers/staging/zsmalloc/zsmalloc-main.c
>> @@ -141,7 +141,7 @@
>>   *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
>>   *  (reason above)
>>   */
>> -#define ZS_SIZE_CLASS_DELTA    16
>> +#define ZS_SIZE_CLASS_DELTA    (PAGE_SIZE >> 8)
>>  #define ZS_SIZE_CLASSES                ((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
>>                                         ZS_SIZE_CLASS_DELTA + 1)
>>
> 
> Actually, there is no point creating size classes beyond [M/(M+1)] * PAGE_SIZE
> where M is the maximum number of system pages in a zspage.

Agreed.

> All size classes
> beyond this size can be collapsed with PAGE_SIZE size class.  This can
> significantly reduce number of size classes created but I think changes needed
> to do this would be more involved, so perhaps, should be done in another
> patch.

I agree there could be some optimization here, but those extra classes
really aren't doing any harm that I can see.

> Can you please resend part of this series  (patch 1  to patch 4) which deals
> just with zsmalloc separately?  I haven't yet looked into zswap itself so would
> help with zsmalloc bits are separated out.

Working it now.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 5/9] debugfs: add get/set for atomic types
  2013-01-07 20:41     ` Seth Jennings
@ 2013-01-25 16:45       ` Seth Jennings
  2013-01-25 21:35         ` Greg Kroah-Hartman
  0 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-25 16:45 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Andrew Morton, devel, Dan Magenheimer, Konrad Rzeszutek Wilk,
	linux-kernel, Rik van Riel, Larry Woodman, linux-mm, Minchan Kim,
	Mel Gorman, Robert Jennings, Johannes Weiner, Nitin Gupta,
	Jenifer Hopper

On 01/07/2013 02:41 PM, Seth Jennings wrote:
> On 01/07/2013 02:32 PM, Greg Kroah-Hartman wrote:
>> On Mon, Jan 07, 2013 at 02:24:36PM -0600, Seth Jennings wrote:
>>> debugfs currently lack the ability to create attributes
>>> that set/get atomic_t values.
>>
>> I hate to ask, but why would you ever want to do such a thing?
> 
> There are a few atomic_t statistics in zswap that are valuable to have
> in the debugfs attributes.  Rather than have non-atomic mirrors of all
> of them, as is done in zcache right now (see
> drivers/staging/ramster/zcache-main.c:131), I thought this to be a
> cleaner solution.
> 
> Granted, I personally have no use for the setting part; only the
> getting part.  I only included the setting operations to keep the
> balance and conform with the rest of the debugfs implementation.

Greg, I never did get your ack or rejection here.  Are you ok with
this patch?

Thanks,
Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
  2013-01-25  0:08   ` Nitin Gupta
  2013-01-25  1:33   ` Minchan Kim
@ 2013-01-25 21:26   ` Rik van Riel
  2 siblings, 0 replies; 43+ messages in thread
From: Rik van Riel @ 2013-01-25 21:26 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Larry Woodman,
	linux-mm, linux-kernel, devel

On 01/07/2013 03:24 PM, Seth Jennings wrote:
> zs_create_pool() currently takes a gfp flags argument
> that is used when growing the memory pool.  However
> it is not used in allocating the metadata for the pool
> itself.  That is currently hardcoded to GFP_KERNEL.
>
> zswap calls zs_create_pool() at swapon time which is done
> in atomic context, resulting in a "might sleep" warning.
>
> This patch changes the meaning of the flags argument in
> zs_create_pool() to mean the flags for the metadata allocation,
> and adds a flags argument to zs_malloc that will be used for
> memory pool growth if required.
>
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>

Acked-by: Rik van Riel <riel@redhat.com>

-- 
All rights reversed

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 5/9] debugfs: add get/set for atomic types
  2013-01-25 16:45       ` Seth Jennings
@ 2013-01-25 21:35         ` Greg Kroah-Hartman
  0 siblings, 0 replies; 43+ messages in thread
From: Greg Kroah-Hartman @ 2013-01-25 21:35 UTC (permalink / raw)
  To: Seth Jennings
  Cc: devel, Rik van Riel, Konrad Rzeszutek Wilk, Minchan Kim,
	linux-kernel, Johannes Weiner, linux-mm, Mel Gorman,
	Andrew Morton, Robert Jennings, Dan Magenheimer, Larry Woodman,
	Nitin Gupta, Jenifer Hopper

On Fri, Jan 25, 2013 at 10:45:04AM -0600, Seth Jennings wrote:
> On 01/07/2013 02:41 PM, Seth Jennings wrote:
> > On 01/07/2013 02:32 PM, Greg Kroah-Hartman wrote:
> >> On Mon, Jan 07, 2013 at 02:24:36PM -0600, Seth Jennings wrote:
> >>> debugfs currently lack the ability to create attributes
> >>> that set/get atomic_t values.
> >>
> >> I hate to ask, but why would you ever want to do such a thing?
> > 
> > There are a few atomic_t statistics in zswap that are valuable to have
> > in the debugfs attributes.  Rather than have non-atomic mirrors of all
> > of them, as is done in zcache right now (see
> > drivers/staging/ramster/zcache-main.c:131), I thought this to be a
> > cleaner solution.
> > 
> > Granted, I personally have no use for the setting part; only the
> > getting part.  I only included the setting operations to keep the
> > balance and conform with the rest of the debugfs implementation.
> 
> Greg, I never did get your ack or rejection here.  Are you ok with
> this patch?

Some patches you just hold your breath and hope the sender goes away and
never asks about again, this was one of them :)

Seriously, it's fine, feel free to take it through whatever tree it
depends on.

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

greg k-h

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name
  2013-01-07 20:24 ` [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name Seth Jennings
  2013-01-25  0:09   ` Nitin Gupta
@ 2013-01-25 21:50   ` Rik van Riel
  1 sibling, 0 replies; 43+ messages in thread
From: Rik van Riel @ 2013-01-25 21:50 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Larry Woodman,
	linux-mm, linux-kernel, devel

On 01/07/2013 03:24 PM, Seth Jennings wrote:
> zs_create_pool() currently takes a name argument which is
> never used in any useful way.
>
> This patch removes it.
>
> Signed-off-by: Seth Jennnings <sjenning@linux.vnet.ibm.com>

Acked-by: Rik van Riel <riel@redhat.com>

-- 
All rights reversed

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks
  2013-01-07 20:24 ` [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
  2013-01-25  0:11   ` Nitin Gupta
@ 2013-01-25 21:55   ` Rik van Riel
  1 sibling, 0 replies; 43+ messages in thread
From: Rik van Riel @ 2013-01-25 21:55 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Larry Woodman,
	linux-mm, linux-kernel, devel

On 01/07/2013 03:24 PM, Seth Jennings wrote:
> This patch allows users of zsmalloc to register the
> allocation and free routines used by zsmalloc to obtain
> more pages for the memory pool.  This allows the user
> more control over zsmalloc pool policy and behavior.
>
> If the user does not wish to control this, alloc_page() and
> __free_page() are used by default.

Acked-by: Rik van Riel <riel@redhat.com>


-- 
All rights reversed

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 8/9] zswap: add to mm/
  2013-01-07 20:24 ` [PATCHv2 8/9] zswap: add to mm/ Seth Jennings
  2013-01-08 17:15   ` Dave Hansen
@ 2013-01-25 22:44   ` Rik van Riel
  2013-01-25 23:15     ` Dan Magenheimer
  2013-01-28 15:27     ` Seth Jennings
  1 sibling, 2 replies; 43+ messages in thread
From: Rik van Riel @ 2013-01-25 22:44 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Larry Woodman,
	linux-mm, linux-kernel, devel

On 01/07/2013 03:24 PM, Seth Jennings wrote:
> zswap is a thin compression backend for frontswap. It receives
> pages from frontswap and attempts to store them in a compressed
> memory pool, resulting in an effective partial memory reclaim and
> dramatically reduced swap device I/O.
>
> Additional, in most cases, pages can be retrieved from this
> compressed store much more quickly than reading from tradition
> swap devices resulting in faster performance for many workloads.
>
> This patch adds the zswap driver to mm/
>
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>

I like the approach of flushing pages into actual disk based
swap when compressed swap is full.  I would like it if that
was advertised more prominently in the changelog :)

The code looks mostly good, complaints are at the nitpick level.

One worry is that the pool can grow to whatever maximum was
decided, and there is no way to shrink it when memory is
required for something else.

Would it be an idea to add a shrinker for the zcache pool,
that can also shrink the zcache pool when required?

Of course, that does lead to the question of how to balance
the pressure from that shrinker, with the new memory entering
zcache from the swap side. I have no clear answers here, just
something to think about...


> +static void zswap_flush_entries(unsigned type, int nr)
> +{
> +	struct zswap_tree *tree = zswap_trees[type];
> +	struct zswap_entry *entry;
> +	int i, ret;
> +
> +/*
> + * This limits is arbitrary for now until a better
> + * policy can be implemented. This is so we don't
> + * eat all of RAM decompressing pages for writeback.
> + */
> +#define ZSWAP_MAX_OUTSTANDING_FLUSHES 64
> +	if (atomic_read(&zswap_outstanding_flushes) >
> +		ZSWAP_MAX_OUTSTANDING_FLUSHES)
> +		return;

Having this #define right in the middle of the function is
rather ugly.  Might be worth moving it to the top.

> +static int __init zswap_debugfs_init(void)
> +{
> +	if (!debugfs_initialized())
> +		return -ENODEV;
> +
> +	zswap_debugfs_root = debugfs_create_dir("zswap", NULL);
> +	if (!zswap_debugfs_root)
> +		return -ENOMEM;
> +
> +	debugfs_create_u64("saved_by_flush", S_IRUGO,
> +			zswap_debugfs_root, &zswap_saved_by_flush);
> +	debugfs_create_u64("pool_limit_hit", S_IRUGO,
> +			zswap_debugfs_root, &zswap_pool_limit_hit);
> +	debugfs_create_u64("reject_flush_attempted", S_IRUGO,
> +			zswap_debugfs_root, &zswap_flush_attempted);
> +	debugfs_create_u64("reject_tmppage_fail", S_IRUGO,
> +			zswap_debugfs_root, &zswap_reject_tmppage_fail);
> +	debugfs_create_u64("reject_flush_fail", S_IRUGO,
> +			zswap_debugfs_root, &zswap_reject_flush_fail);
> +	debugfs_create_u64("reject_zsmalloc_fail", S_IRUGO,
> +			zswap_debugfs_root, &zswap_reject_zsmalloc_fail);
> +	debugfs_create_u64("reject_kmemcache_fail", S_IRUGO,
> +			zswap_debugfs_root, &zswap_reject_kmemcache_fail);
> +	debugfs_create_u64("reject_compress_poor", S_IRUGO,
> +			zswap_debugfs_root, &zswap_reject_compress_poor);
> +	debugfs_create_u64("flushed_pages", S_IRUGO,
> +			zswap_debugfs_root, &zswap_flushed_pages);
> +	debugfs_create_u64("duplicate_entry", S_IRUGO,
> +			zswap_debugfs_root, &zswap_duplicate_entry);
> +	debugfs_create_atomic_t("pool_pages", S_IRUGO,
> +			zswap_debugfs_root, &zswap_pool_pages);
> +	debugfs_create_atomic_t("stored_pages", S_IRUGO,
> +			zswap_debugfs_root, &zswap_stored_pages);
> +	debugfs_create_atomic_t("outstanding_flushes", S_IRUGO,
> +			zswap_debugfs_root, &zswap_outstanding_flushes);
> +

Some of these statistics would be very useful to system
administrators, who will not be mounting debugfs on
production systems.

Would it make sense to export some of these statistics
through sysfs?

-- 
All rights reversed

^ permalink raw reply	[flat|nested] 43+ messages in thread

* RE: [PATCHv2 8/9] zswap: add to mm/
  2013-01-25 22:44   ` Rik van Riel
@ 2013-01-25 23:15     ` Dan Magenheimer
  2013-01-28 15:27     ` Seth Jennings
  1 sibling, 0 replies; 43+ messages in thread
From: Dan Magenheimer @ 2013-01-25 23:15 UTC (permalink / raw)
  To: Rik van Riel, Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Larry Woodman, linux-mm, linux-kernel, devel

> From: Rik van Riel [mailto:riel@redhat.com]
> Subject: Re: [PATCHv2 8/9] zswap: add to mm/
> 
> On 01/07/2013 03:24 PM, Seth Jennings wrote:
> > zswap is a thin compression backend for frontswap. It receives
> > pages from frontswap and attempts to store them in a compressed
> > memory pool, resulting in an effective partial memory reclaim and
> > dramatically reduced swap device I/O.
> >
> > Additional, in most cases, pages can be retrieved from this
> > compressed store much more quickly than reading from tradition
> > swap devices resulting in faster performance for many workloads.
> >
> > This patch adds the zswap driver to mm/
> >
> > Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> 
> I like the approach of flushing pages into actual disk based
> swap when compressed swap is full.  I would like it if that
> was advertised more prominently in the changelog :)
> 
> The code looks mostly good, complaints are at the nitpick level.
> 
> One worry is that the pool can grow to whatever maximum was
> decided, and there is no way to shrink it when memory is
> required for something else.
> 
> Would it be an idea to add a shrinker for the zcache pool,
> that can also shrink the zcache pool when required?
> 
> Of course, that does lead to the question of how to balance
> the pressure from that shrinker, with the new memory entering
> zcache from the swap side. I have no clear answers here, just
> something to think about...

Hey Rik --

A shrinker needs to be able to free up whole pages.
I think Seth is working on this with zsmalloc but
it's quite a bit harder when pursuing high density
and page crossing which are the benefits, but also
part of the curse, of zsmalloc.

I have some ideas on how to do pressure balancing
and plan to propose a topic for LSF/MM to discuss
various questions involving in-kernel compression,
with this sub-topic included.  Hopefully all the
developers contributing various in-kernel compression
solutions will be able to attend and participate
and we can start converging on upstreaming (and/or
promoting) some of them.

Dan

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-25 15:56       ` Dan Magenheimer
@ 2013-01-28  2:59         ` Minchan Kim
  2013-01-30 16:11           ` Konrad Rzeszutek Wilk
  0 siblings, 1 reply; 43+ messages in thread
From: Minchan Kim @ 2013-01-28  2:59 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Seth Jennings, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

On Fri, Jan 25, 2013 at 07:56:29AM -0800, Dan Magenheimer wrote:
> > From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> > Subject: Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
> > 
> > On 01/24/2013 07:33 PM, Minchan Kim wrote:
> > > Hi Seth, frontswap guys
> > >
> > > On Tue, Jan 8, 2013 at 5:24 AM, Seth Jennings
> > > <sjenning@linux.vnet.ibm.com> wrote:
> > >> zs_create_pool() currently takes a gfp flags argument
> > >> that is used when growing the memory pool.  However
> > >> it is not used in allocating the metadata for the pool
> > >> itself.  That is currently hardcoded to GFP_KERNEL.
> > >>
> > >> zswap calls zs_create_pool() at swapon time which is done
> > >> in atomic context, resulting in a "might sleep" warning.
> > >
> > > I didn't review this all series, really sorry but totday I saw Nitin
> > > added Acked-by so I'm afraid Greg might get it under my radar. I'm not
> > > strong against but I would like know why we should call frontswap_init
> > > under swap_lock? Is there special reason?
> > 
> > The call stack is:
> > 
> > SYSCALL_DEFINE2(swapon.. <-- swapon_mutex taken here
> > enable_swap_info() <-- swap_lock taken here
> > frontswap_init()
> > __frontswap_init()
> > zswap_frontswap_init()
> > zs_create_pool()
> > 
> > It isn't entirely clear to me why frontswap_init() is called under
> > lock.  Then again, I'm not entirely sure what the swap_lock protects.
> >  There are no comments near the swap_lock definition to tell me.
> > 
> > I would guess that the intent is to block any writes to the swap
> > device until frontswap_init() has completed.
> > 
> > Dan care to weigh in?
> 
> I think frontswap's first appearance needs to be atomic, i.e.
> the transition from (a) frontswap is not present and will fail
> all calls, to (b) frontswap is fully functional... that transition
> must be atomic.  And, once Konrad's module patches are in, the
> opposite transition must be atomic also.  But there are most
> likely other ways to do those transitions atomically that
> don't need to hold swap_lock.

It could be raced once swap_info is registered.
But what's the problem if we call frontswap_init before calling
_enable_swap_info out of lock?
Swap subsystem never do I/O before it register new swap_info_struct.

And IMHO, if frontswap is to be atomic, it would be better to have
own scheme without dependency of swap_lock if it's possible.
> 
> Honestly, I never really focused on the initialization code
> so I am very open to improvements as long as they work for
> all the various frontswap backends.

How about this?

>From 157a3edf49feb93be0595574beb153b322ddf7d2 Mon Sep 17 00:00:00 2001
From: Minchan Kim <minchan@kernel.org>
Date: Mon, 28 Jan 2013 11:34:00 +0900
Subject: [PATCH] frontswap: Get rid of swap_lock dependency

Frontswap initialization routine depends on swap_lock, which want
to be atomic about frontswap's first appearance.
IOW, frontswap is not present and will fail all calls OR frontswap is
fully functional but if new swap_info_struct isn't registered
by enable_swap_info, swap subsystem doesn't start I/O so there is no race
between init procedure and page I/O working on frontswap.

So let's remove unncessary swap_lock dependency.

Cc: Dan Magenheimer <dan.magenheimer@oracle.com>
Cc: Konrad Rzeszutek Wilk <konrad@darnok.org>
Signed-off-by: Minchan Kim <minchan@kernel.org>
---
 include/linux/frontswap.h |    6 +++---
 mm/frontswap.c            |    7 ++++---
 mm/swapfile.c             |   11 +++++------
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/include/linux/frontswap.h b/include/linux/frontswap.h
index 3044254..b7e238e 100644
--- a/include/linux/frontswap.h
+++ b/include/linux/frontswap.h
@@ -22,7 +22,7 @@ extern void frontswap_writethrough(bool);
 #define FRONTSWAP_HAS_EXCLUSIVE_GETS
 extern void frontswap_tmem_exclusive_gets(bool);
 
-extern void __frontswap_init(unsigned type);
+extern void __frontswap_init(unsigned type, unsigned long *map);
 extern int __frontswap_store(struct page *page);
 extern int __frontswap_load(struct page *page);
 extern void __frontswap_invalidate_page(unsigned, pgoff_t);
@@ -120,10 +120,10 @@ static inline void frontswap_invalidate_area(unsigned type)
 		__frontswap_invalidate_area(type);
 }
 
-static inline void frontswap_init(unsigned type)
+static inline void frontswap_init(unsigned type, unsigned long *map)
 {
 	if (frontswap_enabled)
-		__frontswap_init(type);
+		__frontswap_init(type, map);
 }
 
 #endif /* _LINUX_FRONTSWAP_H */
diff --git a/mm/frontswap.c b/mm/frontswap.c
index 2890e67..bad21b0 100644
--- a/mm/frontswap.c
+++ b/mm/frontswap.c
@@ -115,13 +115,14 @@ EXPORT_SYMBOL(frontswap_tmem_exclusive_gets);
 /*
  * Called when a swap device is swapon'd.
  */
-void __frontswap_init(unsigned type)
+void __frontswap_init(unsigned type, unsigned long *map)
 {
 	struct swap_info_struct *sis = swap_info[type];
 
 	BUG_ON(sis == NULL);
-	if (sis->frontswap_map == NULL)
-		return;
+	BUG_ON(sis->frontswap_map);
+
+	frontswap_map_set(sis, map);
 	frontswap_ops.init(type);
 }
 EXPORT_SYMBOL(__frontswap_init);
diff --git a/mm/swapfile.c b/mm/swapfile.c
index dfaff5f..652e4fc 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1497,8 +1497,7 @@ static int setup_swap_extents(struct swap_info_struct *sis, sector_t *span)
 }
 
 static void _enable_swap_info(struct swap_info_struct *p, int prio,
-				unsigned char *swap_map,
-				unsigned long *frontswap_map)
+				unsigned char *swap_map)
 {
 	int i, prev;
 
@@ -1507,7 +1506,6 @@ static void _enable_swap_info(struct swap_info_struct *p, int prio,
 	else
 		p->prio = --least_priority;
 	p->swap_map = swap_map;
-	frontswap_map_set(p, frontswap_map);
 	p->flags |= SWP_WRITEOK;
 	atomic_long_add(p->pages, &nr_swap_pages);
 	total_swap_pages += p->pages;
@@ -1530,10 +1528,10 @@ static void enable_swap_info(struct swap_info_struct *p, int prio,
 				unsigned char *swap_map,
 				unsigned long *frontswap_map)
 {
+	frontswap_init(p->type, frontswap_map);
 	spin_lock(&swap_lock);
 	spin_lock(&p->lock);
-	_enable_swap_info(p, prio, swap_map, frontswap_map);
-	frontswap_init(p->type);
+	_enable_swap_info(p, prio, swap_map);
 	spin_unlock(&p->lock);
 	spin_unlock(&swap_lock);
 }
@@ -1542,7 +1540,7 @@ static void reinsert_swap_info(struct swap_info_struct *p)
 {
 	spin_lock(&swap_lock);
 	spin_lock(&p->lock);
-	_enable_swap_info(p, p->prio, p->swap_map, frontswap_map_get(p));
+	_enable_swap_info(p, p->prio, p->swap_map);
 	spin_unlock(&p->lock);
 	spin_unlock(&swap_lock);
 }
@@ -1651,6 +1649,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
 	p->swap_map = NULL;
 	p->flags = 0;
 	frontswap_invalidate_area(type);
+	frontswap_map_set(p, NULL);
 	spin_unlock(&p->lock);
 	spin_unlock(&swap_lock);
 	mutex_unlock(&swapon_mutex);
-- 
1.7.9.5


> 
> Dan
> 
> --
> To unsubscribe, send a message with 'unsubscribe linux-mm' in
> the body to majordomo@kvack.org.  For more info on Linux MM,
> see: http://www.linux-mm.org/ .
> Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>

-- 
Kind regards,
Minchan Kim

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 6/9] zsmalloc: promote to lib/
  2013-01-07 20:24 ` [PATCHv2 6/9] zsmalloc: promote to lib/ Seth Jennings
@ 2013-01-28  4:01   ` Minchan Kim
  2013-01-28  4:32     ` Minchan Kim
  0 siblings, 1 reply; 43+ messages in thread
From: Minchan Kim @ 2013-01-28  4:01 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 07, 2013 at 02:24:37PM -0600, Seth Jennings wrote:
> This patch promotes the slab-based zsmalloc memory allocator
> from the staging tree to lib/
> 
> zswap depends on this allocator for storing compressed RAM pages
> in an efficient way under system wide memory pressure where
> high-order (greater than 0) page allocation are very likely to
> fail.
> 
> For more information on zsmalloc and its internals, read the
> documentation at the top of the zsmalloc.c file.
> 
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>

Seth, zsmalloc has a bug[1], I sent a patch totay. If it want't known,
it mighte be no problem to promote but it's known bug so let's fix it
before promoting.

Another question. Why do you promote zsmalloc in this patchset?
It might make you hard to merge even zswap into staging.

[1] http://marc.info/?l=linux-mm&m=135933481517809&w=3

> --
> This patch is similar to a patch Minchan has on out on the list
> to promote for use in zram.
> ---
>  drivers/staging/Kconfig                  |    2 -
>  drivers/staging/Makefile                 |    1 -
>  drivers/staging/zcache/zcache-main.c     |    3 +-
>  drivers/staging/zram/zram_drv.h          |    3 +-
>  drivers/staging/zsmalloc/Kconfig         |   10 -
>  drivers/staging/zsmalloc/Makefile        |    3 -
>  drivers/staging/zsmalloc/zsmalloc-main.c | 1077 ------------------------------
>  drivers/staging/zsmalloc/zsmalloc.h      |   49 --
>  include/linux/zsmalloc.h                 |   49 ++
>  lib/Kconfig                              |   18 +
>  lib/Makefile                             |    1 +
>  lib/zsmalloc.c                           | 1076 +++++++++++++++++++++++++++++
>  12 files changed, 1146 insertions(+), 1146 deletions(-)
>  delete mode 100644 drivers/staging/zsmalloc/Kconfig
>  delete mode 100644 drivers/staging/zsmalloc/Makefile
>  delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
>  delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
>  create mode 100644 include/linux/zsmalloc.h
>  create mode 100644 lib/zsmalloc.c
> 
> diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
> index 329bdb4..c0a7918 100644
> --- a/drivers/staging/Kconfig
> +++ b/drivers/staging/Kconfig
> @@ -76,8 +76,6 @@ source "drivers/staging/zram/Kconfig"
>  
>  source "drivers/staging/zcache/Kconfig"
>  
> -source "drivers/staging/zsmalloc/Kconfig"
> -
>  source "drivers/staging/wlags49_h2/Kconfig"
>  
>  source "drivers/staging/wlags49_h25/Kconfig"
> diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
> index c7ec486..1572fe5 100644
> --- a/drivers/staging/Makefile
> +++ b/drivers/staging/Makefile
> @@ -32,7 +32,6 @@ obj-$(CONFIG_DX_SEP)            += sep/
>  obj-$(CONFIG_IIO)		+= iio/
>  obj-$(CONFIG_ZRAM)		+= zram/
>  obj-$(CONFIG_ZCACHE)		+= zcache/
> -obj-$(CONFIG_ZSMALLOC)		+= zsmalloc/
>  obj-$(CONFIG_WLAGS49_H2)	+= wlags49_h2/
>  obj-$(CONFIG_WLAGS49_H25)	+= wlags49_h25/
>  obj-$(CONFIG_FB_SM7XX)		+= sm7xxfb/
> diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
> index e3e533b..08e412a 100644
> --- a/drivers/staging/zcache/zcache-main.c
> +++ b/drivers/staging/zcache/zcache-main.c
> @@ -32,10 +32,9 @@
>  #include <linux/crypto.h>
>  #include <linux/string.h>
>  #include <linux/idr.h>
> +#include <linux/zsmalloc.h>
>  #include "tmem.h"
>  
> -#include "../zsmalloc/zsmalloc.h"
> -
>  #ifdef CONFIG_CLEANCACHE
>  #include <linux/cleancache.h>
>  #endif
> diff --git a/drivers/staging/zram/zram_drv.h b/drivers/staging/zram/zram_drv.h
> index df2eec4..1e72965 100644
> --- a/drivers/staging/zram/zram_drv.h
> +++ b/drivers/staging/zram/zram_drv.h
> @@ -17,8 +17,7 @@
>  
>  #include <linux/spinlock.h>
>  #include <linux/mutex.h>
> -
> -#include "../zsmalloc/zsmalloc.h"
> +#include <linux/zsmalloc.h>
>  
>  /*
>   * Some arbitrary value. This is just to catch
> diff --git a/drivers/staging/zsmalloc/Kconfig b/drivers/staging/zsmalloc/Kconfig
> deleted file mode 100644
> index 9084565..0000000
> --- a/drivers/staging/zsmalloc/Kconfig
> +++ /dev/null
> @@ -1,10 +0,0 @@
> -config ZSMALLOC
> -	tristate "Memory allocator for compressed pages"
> -	default n
> -	help
> -	  zsmalloc is a slab-based memory allocator designed to store
> -	  compressed RAM pages.  zsmalloc uses virtual memory mapping
> -	  in order to reduce fragmentation.  However, this results in a
> -	  non-standard allocator interface where a handle, not a pointer, is
> -	  returned by an alloc().  This handle must be mapped in order to
> -	  access the allocated space.
> diff --git a/drivers/staging/zsmalloc/Makefile b/drivers/staging/zsmalloc/Makefile
> deleted file mode 100644
> index b134848..0000000
> --- a/drivers/staging/zsmalloc/Makefile
> +++ /dev/null
> @@ -1,3 +0,0 @@
> -zsmalloc-y 		:= zsmalloc-main.o
> -
> -obj-$(CONFIG_ZSMALLOC)	+= zsmalloc.o
> diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
> deleted file mode 100644
> index 3543047..0000000
> --- a/drivers/staging/zsmalloc/zsmalloc-main.c
> +++ /dev/null
> @@ -1,1077 +0,0 @@
> -/*
> - * zsmalloc memory allocator
> - *
> - * Copyright (C) 2011  Nitin Gupta
> - *
> - * This code is released using a dual license strategy: BSD/GPL
> - * You can choose the license that better fits your requirements.
> - *
> - * Released under the terms of 3-clause BSD License
> - * Released under the terms of GNU General Public License Version 2.0
> - */
> -
> -
> -/*
> - * This allocator is designed for use with zcache and zram. Thus, the
> - * allocator is supposed to work well under low memory conditions. In
> - * particular, it never attempts higher order page allocation which is
> - * very likely to fail under memory pressure. On the other hand, if we
> - * just use single (0-order) pages, it would suffer from very high
> - * fragmentation -- any object of size PAGE_SIZE/2 or larger would occupy
> - * an entire page. This was one of the major issues with its predecessor
> - * (xvmalloc).
> - *
> - * To overcome these issues, zsmalloc allocates a bunch of 0-order pages
> - * and links them together using various 'struct page' fields. These linked
> - * pages act as a single higher-order page i.e. an object can span 0-order
> - * page boundaries. The code refers to these linked pages as a single entity
> - * called zspage.
> - *
> - * Following is how we use various fields and flags of underlying
> - * struct page(s) to form a zspage.
> - *
> - * Usage of struct page fields:
> - *	page->first_page: points to the first component (0-order) page
> - *	page->index (union with page->freelist): offset of the first object
> - *		starting in this page. For the first page, this is
> - *		always 0, so we use this field (aka freelist) to point
> - *		to the first free object in zspage.
> - *	page->lru: links together all component pages (except the first page)
> - *		of a zspage
> - *
> - *	For _first_ page only:
> - *
> - *	page->private (union with page->first_page): refers to the
> - *		component page after the first page
> - *	page->freelist: points to the first free object in zspage.
> - *		Free objects are linked together using in-place
> - *		metadata.
> - *	page->objects: maximum number of objects we can store in this
> - *		zspage (class->zspage_order * PAGE_SIZE / class->size)
> - *	page->lru: links together first pages of various zspages.
> - *		Basically forming list of zspages in a fullness group.
> - *	page->mapping: class index and fullness group of the zspage
> - *
> - * Usage of struct page flags:
> - *	PG_private: identifies the first component page
> - *	PG_private2: identifies the last component page
> - *
> - */
> -
> -#ifdef CONFIG_ZSMALLOC_DEBUG
> -#define DEBUG
> -#endif
> -
> -#include <linux/module.h>
> -#include <linux/kernel.h>
> -#include <linux/bitops.h>
> -#include <linux/errno.h>
> -#include <linux/highmem.h>
> -#include <linux/init.h>
> -#include <linux/string.h>
> -#include <linux/slab.h>
> -#include <asm/tlbflush.h>
> -#include <asm/pgtable.h>
> -#include <linux/cpumask.h>
> -#include <linux/cpu.h>
> -#include <linux/vmalloc.h>
> -#include <linux/hardirq.h>
> -#include <linux/spinlock.h>
> -#include <linux/types.h>
> -
> -#include "zsmalloc.h"
> -
> -/*
> - * This must be power of 2 and greater than of equal to sizeof(link_free).
> - * These two conditions ensure that any 'struct link_free' itself doesn't
> - * span more than 1 page which avoids complex case of mapping 2 pages simply
> - * to restore link_free pointer values.
> - */
> -#define ZS_ALIGN		8
> -
> -/*
> - * A single 'zspage' is composed of up to 2^N discontiguous 0-order (single)
> - * pages. ZS_MAX_ZSPAGE_ORDER defines upper limit on N.
> - */
> -#define ZS_MAX_ZSPAGE_ORDER 2
> -#define ZS_MAX_PAGES_PER_ZSPAGE (_AC(1, UL) << ZS_MAX_ZSPAGE_ORDER)
> -
> -/*
> - * Object location (<PFN>, <obj_idx>) is encoded as
> - * as single (void *) handle value.
> - *
> - * Note that object index <obj_idx> is relative to system
> - * page <PFN> it is stored in, so for each sub-page belonging
> - * to a zspage, obj_idx starts with 0.
> - *
> - * This is made more complicated by various memory models and PAE.
> - */
> -
> -#ifndef MAX_PHYSMEM_BITS
> -#ifdef CONFIG_HIGHMEM64G
> -#define MAX_PHYSMEM_BITS 36
> -#else /* !CONFIG_HIGHMEM64G */
> -/*
> - * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just
> - * be PAGE_SHIFT
> - */
> -#define MAX_PHYSMEM_BITS BITS_PER_LONG
> -#endif
> -#endif
> -#define _PFN_BITS		(MAX_PHYSMEM_BITS - PAGE_SHIFT)
> -#define OBJ_INDEX_BITS	(BITS_PER_LONG - _PFN_BITS)
> -#define OBJ_INDEX_MASK	((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
> -
> -#define MAX(a, b) ((a) >= (b) ? (a) : (b))
> -/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
> -#define ZS_MIN_ALLOC_SIZE \
> -	MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS))
> -#define ZS_MAX_ALLOC_SIZE	PAGE_SIZE
> -
> -/*
> - * On systems with 4K page size, this gives 254 size classes! There is a
> - * trader-off here:
> - *  - Large number of size classes is potentially wasteful as free page are
> - *    spread across these classes
> - *  - Small number of size classes causes large internal fragmentation
> - *  - Probably its better to use specific size classes (empirically
> - *    determined). NOTE: all those class sizes must be set as multiple of
> - *    ZS_ALIGN to make sure link_free itself never has to span 2 pages.
> - *
> - *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
> - *  (reason above)
> - */
> -#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
> -#define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
> -					ZS_SIZE_CLASS_DELTA + 1)
> -
> -/*
> - * We do not maintain any list for completely empty or full pages
> - */
> -enum fullness_group {
> -	ZS_ALMOST_FULL,
> -	ZS_ALMOST_EMPTY,
> -	_ZS_NR_FULLNESS_GROUPS,
> -
> -	ZS_EMPTY,
> -	ZS_FULL
> -};
> -
> -/*
> - * We assign a page to ZS_ALMOST_EMPTY fullness group when:
> - *	n <= N / f, where
> - * n = number of allocated objects
> - * N = total number of objects zspage can store
> - * f = 1/fullness_threshold_frac
> - *
> - * Similarly, we assign zspage to:
> - *	ZS_ALMOST_FULL	when n > N / f
> - *	ZS_EMPTY	when n == 0
> - *	ZS_FULL		when n == N
> - *
> - * (see: fix_fullness_group())
> - */
> -static const int fullness_threshold_frac = 4;
> -
> -struct size_class {
> -	/*
> -	 * Size of objects stored in this class. Must be multiple
> -	 * of ZS_ALIGN.
> -	 */
> -	int size;
> -	unsigned int index;
> -
> -	/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
> -	int pages_per_zspage;
> -
> -	spinlock_t lock;
> -
> -	/* stats */
> -	u64 pages_allocated;
> -
> -	struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
> -};
> -
> -/*
> - * Placed within free objects to form a singly linked list.
> - * For every zspage, first_page->freelist gives head of this list.
> - *
> - * This must be power of 2 and less than or equal to ZS_ALIGN
> - */
> -struct link_free {
> -	/* Handle of next free chunk (encodes <PFN, obj_idx>) */
> -	void *next;
> -};
> -
> -struct zs_pool {
> -	struct size_class size_class[ZS_SIZE_CLASSES];
> -	struct zs_ops *ops;
> -};
> -
> -/*
> - * A zspage's class index and fullness group
> - * are encoded in its (first)page->mapping
> - */
> -#define CLASS_IDX_BITS	28
> -#define FULLNESS_BITS	4
> -#define CLASS_IDX_MASK	((1 << CLASS_IDX_BITS) - 1)
> -#define FULLNESS_MASK	((1 << FULLNESS_BITS) - 1)
> -
> -/*
> - * By default, zsmalloc uses a copy-based object mapping method to access
> - * allocations that span two pages. However, if a particular architecture
> - * 1) Implements local_flush_tlb_kernel_range() and 2) Performs VM mapping
> - * faster than copying, then it should be added here so that
> - * USE_PGTABLE_MAPPING is defined. This causes zsmalloc to use page table
> - * mapping rather than copying
> - * for object mapping.
> -*/
> -#if defined(CONFIG_ARM)
> -#define USE_PGTABLE_MAPPING
> -#endif
> -
> -struct mapping_area {
> -#ifdef USE_PGTABLE_MAPPING
> -	struct vm_struct *vm; /* vm area for mapping object that span pages */
> -#else
> -	char *vm_buf; /* copy buffer for objects that span pages */
> -#endif
> -	char *vm_addr; /* address of kmap_atomic()'ed pages */
> -	enum zs_mapmode vm_mm; /* mapping mode */
> -};
> -
> -/* default page alloc/free ops */
> -struct page *zs_alloc_page(gfp_t flags)
> -{
> -	return alloc_page(flags);
> -}
> -
> -void zs_free_page(struct page *page)
> -{
> -	__free_page(page);
> -}
> -
> -struct zs_ops zs_default_ops = {
> -	.alloc = zs_alloc_page,
> -	.free = zs_free_page
> -};
> -
> -/* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
> -static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
> -
> -static int is_first_page(struct page *page)
> -{
> -	return PagePrivate(page);
> -}
> -
> -static int is_last_page(struct page *page)
> -{
> -	return PagePrivate2(page);
> -}
> -
> -static void get_zspage_mapping(struct page *page, unsigned int *class_idx,
> -				enum fullness_group *fullness)
> -{
> -	unsigned long m;
> -	BUG_ON(!is_first_page(page));
> -
> -	m = (unsigned long)page->mapping;
> -	*fullness = m & FULLNESS_MASK;
> -	*class_idx = (m >> FULLNESS_BITS) & CLASS_IDX_MASK;
> -}
> -
> -static void set_zspage_mapping(struct page *page, unsigned int class_idx,
> -				enum fullness_group fullness)
> -{
> -	unsigned long m;
> -	BUG_ON(!is_first_page(page));
> -
> -	m = ((class_idx & CLASS_IDX_MASK) << FULLNESS_BITS) |
> -			(fullness & FULLNESS_MASK);
> -	page->mapping = (struct address_space *)m;
> -}
> -
> -static int get_size_class_index(int size)
> -{
> -	int idx = 0;
> -
> -	if (likely(size > ZS_MIN_ALLOC_SIZE))
> -		idx = DIV_ROUND_UP(size - ZS_MIN_ALLOC_SIZE,
> -				ZS_SIZE_CLASS_DELTA);
> -
> -	return idx;
> -}
> -
> -static enum fullness_group get_fullness_group(struct page *page)
> -{
> -	int inuse, max_objects;
> -	enum fullness_group fg;
> -	BUG_ON(!is_first_page(page));
> -
> -	inuse = page->inuse;
> -	max_objects = page->objects;
> -
> -	if (inuse == 0)
> -		fg = ZS_EMPTY;
> -	else if (inuse == max_objects)
> -		fg = ZS_FULL;
> -	else if (inuse <= max_objects / fullness_threshold_frac)
> -		fg = ZS_ALMOST_EMPTY;
> -	else
> -		fg = ZS_ALMOST_FULL;
> -
> -	return fg;
> -}
> -
> -static void insert_zspage(struct page *page, struct size_class *class,
> -				enum fullness_group fullness)
> -{
> -	struct page **head;
> -
> -	BUG_ON(!is_first_page(page));
> -
> -	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
> -		return;
> -
> -	head = &class->fullness_list[fullness];
> -	if (*head)
> -		list_add_tail(&page->lru, &(*head)->lru);
> -
> -	*head = page;
> -}
> -
> -static void remove_zspage(struct page *page, struct size_class *class,
> -				enum fullness_group fullness)
> -{
> -	struct page **head;
> -
> -	BUG_ON(!is_first_page(page));
> -
> -	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
> -		return;
> -
> -	head = &class->fullness_list[fullness];
> -	BUG_ON(!*head);
> -	if (list_empty(&(*head)->lru))
> -		*head = NULL;
> -	else if (*head == page)
> -		*head = (struct page *)list_entry((*head)->lru.next,
> -					struct page, lru);
> -
> -	list_del_init(&page->lru);
> -}
> -
> -static enum fullness_group fix_fullness_group(struct zs_pool *pool,
> -						struct page *page)
> -{
> -	int class_idx;
> -	struct size_class *class;
> -	enum fullness_group currfg, newfg;
> -
> -	BUG_ON(!is_first_page(page));
> -
> -	get_zspage_mapping(page, &class_idx, &currfg);
> -	newfg = get_fullness_group(page);
> -	if (newfg == currfg)
> -		goto out;
> -
> -	class = &pool->size_class[class_idx];
> -	remove_zspage(page, class, currfg);
> -	insert_zspage(page, class, newfg);
> -	set_zspage_mapping(page, class_idx, newfg);
> -
> -out:
> -	return newfg;
> -}
> -
> -/*
> - * We have to decide on how many pages to link together
> - * to form a zspage for each size class. This is important
> - * to reduce wastage due to unusable space left at end of
> - * each zspage which is given as:
> - *	wastage = Zp - Zp % size_class
> - * where Zp = zspage size = k * PAGE_SIZE where k = 1, 2, ...
> - *
> - * For example, for size class of 3/8 * PAGE_SIZE, we should
> - * link together 3 PAGE_SIZE sized pages to form a zspage
> - * since then we can perfectly fit in 8 such objects.
> - */
> -static int get_pages_per_zspage(int class_size)
> -{
> -	int i, max_usedpc = 0;
> -	/* zspage order which gives maximum used size per KB */
> -	int max_usedpc_order = 1;
> -
> -	for (i = 1; i <= ZS_MAX_PAGES_PER_ZSPAGE; i++) {
> -		int zspage_size;
> -		int waste, usedpc;
> -
> -		zspage_size = i * PAGE_SIZE;
> -		waste = zspage_size % class_size;
> -		usedpc = (zspage_size - waste) * 100 / zspage_size;
> -
> -		if (usedpc > max_usedpc) {
> -			max_usedpc = usedpc;
> -			max_usedpc_order = i;
> -		}
> -	}
> -
> -	return max_usedpc_order;
> -}
> -
> -/*
> - * A single 'zspage' is composed of many system pages which are
> - * linked together using fields in struct page. This function finds
> - * the first/head page, given any component page of a zspage.
> - */
> -static struct page *get_first_page(struct page *page)
> -{
> -	if (is_first_page(page))
> -		return page;
> -	else
> -		return page->first_page;
> -}
> -
> -static struct page *get_next_page(struct page *page)
> -{
> -	struct page *next;
> -
> -	if (is_last_page(page))
> -		next = NULL;
> -	else if (is_first_page(page))
> -		next = (struct page *)page->private;
> -	else
> -		next = list_entry(page->lru.next, struct page, lru);
> -
> -	return next;
> -}
> -
> -/* Encode <page, obj_idx> as a single handle value */
> -static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
> -{
> -	unsigned long handle;
> -
> -	if (!page) {
> -		BUG_ON(obj_idx);
> -		return NULL;
> -	}
> -
> -	handle = page_to_pfn(page) << OBJ_INDEX_BITS;
> -	handle |= (obj_idx & OBJ_INDEX_MASK);
> -
> -	return (void *)handle;
> -}
> -
> -/* Decode <page, obj_idx> pair from the given object handle */
> -static void obj_handle_to_location(unsigned long handle, struct page **page,
> -				unsigned long *obj_idx)
> -{
> -	*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
> -	*obj_idx = handle & OBJ_INDEX_MASK;
> -}
> -
> -static unsigned long obj_idx_to_offset(struct page *page,
> -				unsigned long obj_idx, int class_size)
> -{
> -	unsigned long off = 0;
> -
> -	if (!is_first_page(page))
> -		off = page->index;
> -
> -	return off + obj_idx * class_size;
> -}
> -
> -static void reset_page(struct page *page)
> -{
> -	clear_bit(PG_private, &page->flags);
> -	clear_bit(PG_private_2, &page->flags);
> -	set_page_private(page, 0);
> -	page->mapping = NULL;
> -	page->freelist = NULL;
> -	reset_page_mapcount(page);
> -}
> -
> -static void free_zspage(struct zs_ops *ops, struct page *first_page)
> -{
> -	struct page *nextp, *tmp, *head_extra;
> -
> -	BUG_ON(!is_first_page(first_page));
> -	BUG_ON(first_page->inuse);
> -
> -	head_extra = (struct page *)page_private(first_page);
> -
> -	reset_page(first_page);
> -	ops->free(first_page);
> -
> -	/* zspage with only 1 system page */
> -	if (!head_extra)
> -		return;
> -
> -	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
> -		list_del(&nextp->lru);
> -		reset_page(nextp);
> -		ops->free(nextp);
> -	}
> -	reset_page(head_extra);
> -	ops->free(head_extra);
> -}
> -
> -/* Initialize a newly allocated zspage */
> -static void init_zspage(struct page *first_page, struct size_class *class)
> -{
> -	unsigned long off = 0;
> -	struct page *page = first_page;
> -
> -	BUG_ON(!is_first_page(first_page));
> -	while (page) {
> -		struct page *next_page;
> -		struct link_free *link;
> -		unsigned int i, objs_on_page;
> -
> -		/*
> -		 * page->index stores offset of first object starting
> -		 * in the page. For the first page, this is always 0,
> -		 * so we use first_page->index (aka ->freelist) to store
> -		 * head of corresponding zspage's freelist.
> -		 */
> -		if (page != first_page)
> -			page->index = off;
> -
> -		link = (struct link_free *)kmap_atomic(page) +
> -						off / sizeof(*link);
> -		objs_on_page = (PAGE_SIZE - off) / class->size;
> -
> -		for (i = 1; i <= objs_on_page; i++) {
> -			off += class->size;
> -			if (off < PAGE_SIZE) {
> -				link->next = obj_location_to_handle(page, i);
> -				link += class->size / sizeof(*link);
> -			}
> -		}
> -
> -		/*
> -		 * We now come to the last (full or partial) object on this
> -		 * page, which must point to the first object on the next
> -		 * page (if present)
> -		 */
> -		next_page = get_next_page(page);
> -		link->next = obj_location_to_handle(next_page, 0);
> -		kunmap_atomic(link);
> -		page = next_page;
> -		off = (off + class->size) % PAGE_SIZE;
> -	}
> -}
> -
> -/*
> - * Allocate a zspage for the given size class
> - */
> -static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
> -				gfp_t flags)
> -{
> -	int i, error;
> -	struct page *first_page = NULL, *uninitialized_var(prev_page);
> -
> -	/*
> -	 * Allocate individual pages and link them together as:
> -	 * 1. first page->private = first sub-page
> -	 * 2. all sub-pages are linked together using page->lru
> -	 * 3. each sub-page is linked to the first page using page->first_page
> -	 *
> -	 * For each size class, First/Head pages are linked together using
> -	 * page->lru. Also, we set PG_private to identify the first page
> -	 * (i.e. no other sub-page has this flag set) and PG_private_2 to
> -	 * identify the last page.
> -	 */
> -	error = -ENOMEM;
> -	for (i = 0; i < class->pages_per_zspage; i++) {
> -		struct page *page;
> -
> -		page = ops->alloc(flags);
> -		if (!page)
> -			goto cleanup;
> -
> -		INIT_LIST_HEAD(&page->lru);
> -		if (i == 0) {	/* first page */
> -			SetPagePrivate(page);
> -			set_page_private(page, 0);
> -			first_page = page;
> -			first_page->inuse = 0;
> -		}
> -		if (i == 1)
> -			first_page->private = (unsigned long)page;
> -		if (i >= 1)
> -			page->first_page = first_page;
> -		if (i >= 2)
> -			list_add(&page->lru, &prev_page->lru);
> -		if (i == class->pages_per_zspage - 1)	/* last page */
> -			SetPagePrivate2(page);
> -		prev_page = page;
> -	}
> -
> -	init_zspage(first_page, class);
> -
> -	first_page->freelist = obj_location_to_handle(first_page, 0);
> -	/* Maximum number of objects we can store in this zspage */
> -	first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
> -
> -	error = 0; /* Success */
> -
> -cleanup:
> -	if (unlikely(error) && first_page) {
> -		free_zspage(ops, first_page);
> -		first_page = NULL;
> -	}
> -
> -	return first_page;
> -}
> -
> -static struct page *find_get_zspage(struct size_class *class)
> -{
> -	int i;
> -	struct page *page;
> -
> -	for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
> -		page = class->fullness_list[i];
> -		if (page)
> -			break;
> -	}
> -
> -	return page;
> -}
> -
> -#ifdef USE_PGTABLE_MAPPING
> -static inline int __zs_cpu_up(struct mapping_area *area)
> -{
> -	/*
> -	 * Make sure we don't leak memory if a cpu UP notification
> -	 * and zs_init() race and both call zs_cpu_up() on the same cpu
> -	 */
> -	if (area->vm)
> -		return 0;
> -	area->vm = alloc_vm_area(PAGE_SIZE * 2, NULL);
> -	if (!area->vm)
> -		return -ENOMEM;
> -	return 0;
> -}
> -
> -static inline void __zs_cpu_down(struct mapping_area *area)
> -{
> -	if (area->vm)
> -		free_vm_area(area->vm);
> -	area->vm = NULL;
> -}
> -
> -static inline void *__zs_map_object(struct mapping_area *area,
> -				struct page *pages[2], int off, int size)
> -{
> -	BUG_ON(map_vm_area(area->vm, PAGE_KERNEL, &pages));
> -	area->vm_addr = area->vm->addr;
> -	return area->vm_addr + off;
> -}
> -
> -static inline void __zs_unmap_object(struct mapping_area *area,
> -				struct page *pages[2], int off, int size)
> -{
> -	unsigned long addr = (unsigned long)area->vm_addr;
> -	unsigned long end = addr + (PAGE_SIZE * 2);
> -
> -	flush_cache_vunmap(addr, end);
> -	unmap_kernel_range_noflush(addr, PAGE_SIZE * 2);
> -	local_flush_tlb_kernel_range(addr, end);
> -}
> -
> -#else /* USE_PGTABLE_MAPPING */
> -
> -static inline int __zs_cpu_up(struct mapping_area *area)
> -{
> -	/*
> -	 * Make sure we don't leak memory if a cpu UP notification
> -	 * and zs_init() race and both call zs_cpu_up() on the same cpu
> -	 */
> -	if (area->vm_buf)
> -		return 0;
> -	area->vm_buf = (char *)__get_free_page(GFP_KERNEL);
> -	if (!area->vm_buf)
> -		return -ENOMEM;
> -	return 0;
> -}
> -
> -static inline void __zs_cpu_down(struct mapping_area *area)
> -{
> -	if (area->vm_buf)
> -		free_page((unsigned long)area->vm_buf);
> -	area->vm_buf = NULL;
> -}
> -
> -static void *__zs_map_object(struct mapping_area *area,
> -			struct page *pages[2], int off, int size)
> -{
> -	int sizes[2];
> -	void *addr;
> -	char *buf = area->vm_buf;
> -
> -	/* disable page faults to match kmap_atomic() return conditions */
> -	pagefault_disable();
> -
> -	/* no read fastpath */
> -	if (area->vm_mm == ZS_MM_WO)
> -		goto out;
> -
> -	sizes[0] = PAGE_SIZE - off;
> -	sizes[1] = size - sizes[0];
> -
> -	/* copy object to per-cpu buffer */
> -	addr = kmap_atomic(pages[0]);
> -	memcpy(buf, addr + off, sizes[0]);
> -	kunmap_atomic(addr);
> -	addr = kmap_atomic(pages[1]);
> -	memcpy(buf + sizes[0], addr, sizes[1]);
> -	kunmap_atomic(addr);
> -out:
> -	return area->vm_buf;
> -}
> -
> -static void __zs_unmap_object(struct mapping_area *area,
> -			struct page *pages[2], int off, int size)
> -{
> -	int sizes[2];
> -	void *addr;
> -	char *buf = area->vm_buf;
> -
> -	/* no write fastpath */
> -	if (area->vm_mm == ZS_MM_RO)
> -		goto out;
> -
> -	sizes[0] = PAGE_SIZE - off;
> -	sizes[1] = size - sizes[0];
> -
> -	/* copy per-cpu buffer to object */
> -	addr = kmap_atomic(pages[0]);
> -	memcpy(addr + off, buf, sizes[0]);
> -	kunmap_atomic(addr);
> -	addr = kmap_atomic(pages[1]);
> -	memcpy(addr, buf + sizes[0], sizes[1]);
> -	kunmap_atomic(addr);
> -
> -out:
> -	/* enable page faults to match kunmap_atomic() return conditions */
> -	pagefault_enable();
> -}
> -
> -#endif /* USE_PGTABLE_MAPPING */
> -
> -static int zs_cpu_notifier(struct notifier_block *nb, unsigned long action,
> -				void *pcpu)
> -{
> -	int ret, cpu = (long)pcpu;
> -	struct mapping_area *area;
> -
> -	switch (action) {
> -	case CPU_UP_PREPARE:
> -		area = &per_cpu(zs_map_area, cpu);
> -		ret = __zs_cpu_up(area);
> -		if (ret)
> -			return notifier_from_errno(ret);
> -		break;
> -	case CPU_DEAD:
> -	case CPU_UP_CANCELED:
> -		area = &per_cpu(zs_map_area, cpu);
> -		__zs_cpu_down(area);
> -		break;
> -	}
> -
> -	return NOTIFY_OK;
> -}
> -
> -static struct notifier_block zs_cpu_nb = {
> -	.notifier_call = zs_cpu_notifier
> -};
> -
> -static void zs_exit(void)
> -{
> -	int cpu;
> -
> -	for_each_online_cpu(cpu)
> -		zs_cpu_notifier(NULL, CPU_DEAD, (void *)(long)cpu);
> -	unregister_cpu_notifier(&zs_cpu_nb);
> -}
> -
> -static int zs_init(void)
> -{
> -	int cpu, ret;
> -
> -	register_cpu_notifier(&zs_cpu_nb);
> -	for_each_online_cpu(cpu) {
> -		ret = zs_cpu_notifier(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
> -		if (notifier_to_errno(ret))
> -			goto fail;
> -	}
> -	return 0;
> -fail:
> -	zs_exit();
> -	return notifier_to_errno(ret);
> -}
> -
> -struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
> -{
> -	int i, ovhd_size;
> -	struct zs_pool *pool;
> -
> -	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
> -	pool = kzalloc(ovhd_size, flags);
> -	if (!pool)
> -		return NULL;
> -
> -	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
> -		int size;
> -		struct size_class *class;
> -
> -		size = ZS_MIN_ALLOC_SIZE + i * ZS_SIZE_CLASS_DELTA;
> -		if (size > ZS_MAX_ALLOC_SIZE)
> -			size = ZS_MAX_ALLOC_SIZE;
> -
> -		class = &pool->size_class[i];
> -		class->size = size;
> -		class->index = i;
> -		spin_lock_init(&class->lock);
> -		class->pages_per_zspage = get_pages_per_zspage(size);
> -
> -	}
> -
> -	if (ops)
> -		pool->ops = ops;
> -	else
> -		pool->ops = &zs_default_ops;
> -
> -	return pool;
> -}
> -EXPORT_SYMBOL_GPL(zs_create_pool);
> -
> -void zs_destroy_pool(struct zs_pool *pool)
> -{
> -	int i;
> -
> -	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
> -		int fg;
> -		struct size_class *class = &pool->size_class[i];
> -
> -		for (fg = 0; fg < _ZS_NR_FULLNESS_GROUPS; fg++) {
> -			if (class->fullness_list[fg]) {
> -				pr_info("Freeing non-empty class with size "
> -					"%db, fullness group %d\n",
> -					class->size, fg);
> -			}
> -		}
> -	}
> -	kfree(pool);
> -}
> -EXPORT_SYMBOL_GPL(zs_destroy_pool);
> -
> -/**
> - * zs_malloc - Allocate block of given size from pool.
> - * @pool: pool to allocate from
> - * @size: size of block to allocate
> - *
> - * On success, handle to the allocated object is returned,
> - * otherwise 0.
> - * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
> - */
> -unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
> -{
> -	unsigned long obj;
> -	struct link_free *link;
> -	int class_idx;
> -	struct size_class *class;
> -
> -	struct page *first_page, *m_page;
> -	unsigned long m_objidx, m_offset;
> -
> -	if (unlikely(!size || size > ZS_MAX_ALLOC_SIZE))
> -		return 0;
> -
> -	class_idx = get_size_class_index(size);
> -	class = &pool->size_class[class_idx];
> -	BUG_ON(class_idx != class->index);
> -
> -	spin_lock(&class->lock);
> -	first_page = find_get_zspage(class);
> -
> -	if (!first_page) {
> -		spin_unlock(&class->lock);
> -		first_page = alloc_zspage(pool->ops, class, flags);
> -		if (unlikely(!first_page))
> -			return 0;
> -
> -		set_zspage_mapping(first_page, class->index, ZS_EMPTY);
> -		spin_lock(&class->lock);
> -		class->pages_allocated += class->pages_per_zspage;
> -	}
> -
> -	obj = (unsigned long)first_page->freelist;
> -	obj_handle_to_location(obj, &m_page, &m_objidx);
> -	m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
> -
> -	link = (struct link_free *)kmap_atomic(m_page) +
> -					m_offset / sizeof(*link);
> -	first_page->freelist = link->next;
> -	memset(link, POISON_INUSE, sizeof(*link));
> -	kunmap_atomic(link);
> -
> -	first_page->inuse++;
> -	/* Now move the zspage to another fullness group, if required */
> -	fix_fullness_group(pool, first_page);
> -	spin_unlock(&class->lock);
> -
> -	return obj;
> -}
> -EXPORT_SYMBOL_GPL(zs_malloc);
> -
> -void zs_free(struct zs_pool *pool, unsigned long obj)
> -{
> -	struct link_free *link;
> -	struct page *first_page, *f_page;
> -	unsigned long f_objidx, f_offset;
> -
> -	int class_idx;
> -	struct size_class *class;
> -	enum fullness_group fullness;
> -
> -	if (unlikely(!obj))
> -		return;
> -
> -	obj_handle_to_location(obj, &f_page, &f_objidx);
> -	first_page = get_first_page(f_page);
> -
> -	get_zspage_mapping(first_page, &class_idx, &fullness);
> -	class = &pool->size_class[class_idx];
> -	f_offset = obj_idx_to_offset(f_page, f_objidx, class->size);
> -
> -	spin_lock(&class->lock);
> -
> -	/* Insert this object in containing zspage's freelist */
> -	link = (struct link_free *)((unsigned char *)kmap_atomic(f_page)
> -							+ f_offset);
> -	link->next = first_page->freelist;
> -	kunmap_atomic(link);
> -	first_page->freelist = (void *)obj;
> -
> -	first_page->inuse--;
> -	fullness = fix_fullness_group(pool, first_page);
> -
> -	if (fullness == ZS_EMPTY)
> -		class->pages_allocated -= class->pages_per_zspage;
> -
> -	spin_unlock(&class->lock);
> -
> -	if (fullness == ZS_EMPTY)
> -		free_zspage(pool->ops, first_page);
> -}
> -EXPORT_SYMBOL_GPL(zs_free);
> -
> -/**
> - * zs_map_object - get address of allocated object from handle.
> - * @pool: pool from which the object was allocated
> - * @handle: handle returned from zs_malloc
> - *
> - * Before using an object allocated from zs_malloc, it must be mapped using
> - * this function. When done with the object, it must be unmapped using
> - * zs_unmap_object.
> - *
> - * Only one object can be mapped per cpu at a time. There is no protection
> - * against nested mappings.
> - *
> - * This function returns with preemption and page faults disabled.
> -*/
> -void *zs_map_object(struct zs_pool *pool, unsigned long handle,
> -			enum zs_mapmode mm)
> -{
> -	struct page *page;
> -	unsigned long obj_idx, off;
> -
> -	unsigned int class_idx;
> -	enum fullness_group fg;
> -	struct size_class *class;
> -	struct mapping_area *area;
> -	struct page *pages[2];
> -
> -	BUG_ON(!handle);
> -
> -	/*
> -	 * Because we use per-cpu mapping areas shared among the
> -	 * pools/users, we can't allow mapping in interrupt context
> -	 * because it can corrupt another users mappings.
> -	 */
> -	BUG_ON(in_interrupt());
> -
> -	obj_handle_to_location(handle, &page, &obj_idx);
> -	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
> -	class = &pool->size_class[class_idx];
> -	off = obj_idx_to_offset(page, obj_idx, class->size);
> -
> -	area = &get_cpu_var(zs_map_area);
> -	area->vm_mm = mm;
> -	if (off + class->size <= PAGE_SIZE) {
> -		/* this object is contained entirely within a page */
> -		area->vm_addr = kmap_atomic(page);
> -		return area->vm_addr + off;
> -	}
> -
> -	/* this object spans two pages */
> -	pages[0] = page;
> -	pages[1] = get_next_page(page);
> -	BUG_ON(!pages[1]);
> -
> -	return __zs_map_object(area, pages, off, class->size);
> -}
> -EXPORT_SYMBOL_GPL(zs_map_object);
> -
> -void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
> -{
> -	struct page *page;
> -	unsigned long obj_idx, off;
> -
> -	unsigned int class_idx;
> -	enum fullness_group fg;
> -	struct size_class *class;
> -	struct mapping_area *area;
> -
> -	BUG_ON(!handle);
> -
> -	obj_handle_to_location(handle, &page, &obj_idx);
> -	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
> -	class = &pool->size_class[class_idx];
> -	off = obj_idx_to_offset(page, obj_idx, class->size);
> -
> -	area = &__get_cpu_var(zs_map_area);
> -	if (off + class->size <= PAGE_SIZE)
> -		kunmap_atomic(area->vm_addr);
> -	else {
> -		struct page *pages[2];
> -
> -		pages[0] = page;
> -		pages[1] = get_next_page(page);
> -		BUG_ON(!pages[1]);
> -
> -		__zs_unmap_object(area, pages, off, class->size);
> -	}
> -	put_cpu_var(zs_map_area);
> -}
> -EXPORT_SYMBOL_GPL(zs_unmap_object);
> -
> -u64 zs_get_total_size_bytes(struct zs_pool *pool)
> -{
> -	int i;
> -	u64 npages = 0;
> -
> -	for (i = 0; i < ZS_SIZE_CLASSES; i++)
> -		npages += pool->size_class[i].pages_allocated;
> -
> -	return npages << PAGE_SHIFT;
> -}
> -EXPORT_SYMBOL_GPL(zs_get_total_size_bytes);
> -
> -module_init(zs_init);
> -module_exit(zs_exit);
> -
> -MODULE_LICENSE("Dual BSD/GPL");
> -MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
> diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
> deleted file mode 100644
> index eb6efb6..0000000
> --- a/drivers/staging/zsmalloc/zsmalloc.h
> +++ /dev/null
> @@ -1,49 +0,0 @@
> -/*
> - * zsmalloc memory allocator
> - *
> - * Copyright (C) 2011  Nitin Gupta
> - *
> - * This code is released using a dual license strategy: BSD/GPL
> - * You can choose the license that better fits your requirements.
> - *
> - * Released under the terms of 3-clause BSD License
> - * Released under the terms of GNU General Public License Version 2.0
> - */
> -
> -#ifndef _ZS_MALLOC_H_
> -#define _ZS_MALLOC_H_
> -
> -#include <linux/types.h>
> -#include <linux/mm_types.h>
> -
> -/*
> - * zsmalloc mapping modes
> - *
> - * NOTE: These only make a difference when a mapped object spans pages
> -*/
> -enum zs_mapmode {
> -	ZS_MM_RW, /* normal read-write mapping */
> -	ZS_MM_RO, /* read-only (no copy-out at unmap time) */
> -	ZS_MM_WO /* write-only (no copy-in at map time) */
> -};
> -
> -struct zs_ops {
> -	struct page * (*alloc)(gfp_t);
> -	void (*free)(struct page *);
> -};
> -
> -struct zs_pool;
> -
> -struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
> -void zs_destroy_pool(struct zs_pool *pool);
> -
> -unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
> -void zs_free(struct zs_pool *pool, unsigned long obj);
> -
> -void *zs_map_object(struct zs_pool *pool, unsigned long handle,
> -			enum zs_mapmode mm);
> -void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
> -
> -u64 zs_get_total_size_bytes(struct zs_pool *pool);
> -
> -#endif
> diff --git a/include/linux/zsmalloc.h b/include/linux/zsmalloc.h
> new file mode 100644
> index 0000000..eb6efb6
> --- /dev/null
> +++ b/include/linux/zsmalloc.h
> @@ -0,0 +1,49 @@
> +/*
> + * zsmalloc memory allocator
> + *
> + * Copyright (C) 2011  Nitin Gupta
> + *
> + * This code is released using a dual license strategy: BSD/GPL
> + * You can choose the license that better fits your requirements.
> + *
> + * Released under the terms of 3-clause BSD License
> + * Released under the terms of GNU General Public License Version 2.0
> + */
> +
> +#ifndef _ZS_MALLOC_H_
> +#define _ZS_MALLOC_H_
> +
> +#include <linux/types.h>
> +#include <linux/mm_types.h>
> +
> +/*
> + * zsmalloc mapping modes
> + *
> + * NOTE: These only make a difference when a mapped object spans pages
> +*/
> +enum zs_mapmode {
> +	ZS_MM_RW, /* normal read-write mapping */
> +	ZS_MM_RO, /* read-only (no copy-out at unmap time) */
> +	ZS_MM_WO /* write-only (no copy-in at map time) */
> +};
> +
> +struct zs_ops {
> +	struct page * (*alloc)(gfp_t);
> +	void (*free)(struct page *);
> +};
> +
> +struct zs_pool;
> +
> +struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
> +void zs_destroy_pool(struct zs_pool *pool);
> +
> +unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
> +void zs_free(struct zs_pool *pool, unsigned long obj);
> +
> +void *zs_map_object(struct zs_pool *pool, unsigned long handle,
> +			enum zs_mapmode mm);
> +void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
> +
> +u64 zs_get_total_size_bytes(struct zs_pool *pool);
> +
> +#endif
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 75cdb77..fdab273 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -219,6 +219,24 @@ config DECOMPRESS_LZO
>  config GENERIC_ALLOCATOR
>  	boolean
>  
> +config ZSMALLOC
> +	tristate "Memory allocator for compressed pages"
> +	default n
> +	help
> +	  zsmalloc is a slab-based memory allocator designed to store
> +	  compressed RAM pages.  zsmalloc uses a memory pool that combines
> +	  single pages into higher order pages by linking them together
> +	  using the fields of the struct page. Allocations are then
> +	  mapped through copy buffers or VM mapping, in order to reduce
> +	  memory pool fragmentation and increase allocation success rate under
> +	  memory pressure.
> +
> +	  This results in a non-standard allocator interface where
> +	  a handle, not a pointer, is returned by the allocation function.
> +	  This handle must be mapped in order to access the allocated space.
> +
> +	  If unsure, say N.
> +
>  #
>  # reed solomon support is select'ed if needed
>  #
> diff --git a/lib/Makefile b/lib/Makefile
> index 5e08f6a..851b653 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -65,6 +65,7 @@ obj-$(CONFIG_CRC7)	+= crc7.o
>  obj-$(CONFIG_LIBCRC32C)	+= libcrc32c.o
>  obj-$(CONFIG_CRC8)	+= crc8.o
>  obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o
> +obj-$(CONFIG_ZSMALLOC) += zsmalloc.o
>  
>  obj-$(CONFIG_ZLIB_INFLATE) += zlib_inflate/
>  obj-$(CONFIG_ZLIB_DEFLATE) += zlib_deflate/
> diff --git a/lib/zsmalloc.c b/lib/zsmalloc.c
> new file mode 100644
> index 0000000..3aaf086
> --- /dev/null
> +++ b/lib/zsmalloc.c
> @@ -0,0 +1,1076 @@
> +/*
> + * zsmalloc memory allocator
> + *
> + * Copyright (C) 2011  Nitin Gupta
> + *
> + * This code is released using a dual license strategy: BSD/GPL
> + * You can choose the license that better fits your requirements.
> + *
> + * Released under the terms of 3-clause BSD License
> + * Released under the terms of GNU General Public License Version 2.0
> + */
> +
> +
> +/*
> + * This allocator is designed for use with zcache and zram. Thus, the
> + * allocator is supposed to work well under low memory conditions. In
> + * particular, it never attempts higher order page allocation which is
> + * very likely to fail under memory pressure. On the other hand, if we
> + * just use single (0-order) pages, it would suffer from very high
> + * fragmentation -- any object of size PAGE_SIZE/2 or larger would occupy
> + * an entire page. This was one of the major issues with its predecessor
> + * (xvmalloc).
> + *
> + * To overcome these issues, zsmalloc allocates a bunch of 0-order pages
> + * and links them together using various 'struct page' fields. These linked
> + * pages act as a single higher-order page i.e. an object can span 0-order
> + * page boundaries. The code refers to these linked pages as a single entity
> + * called zspage.
> + *
> + * Following is how we use various fields and flags of underlying
> + * struct page(s) to form a zspage.
> + *
> + * Usage of struct page fields:
> + *	page->first_page: points to the first component (0-order) page
> + *	page->index (union with page->freelist): offset of the first object
> + *		starting in this page. For the first page, this is
> + *		always 0, so we use this field (aka freelist) to point
> + *		to the first free object in zspage.
> + *	page->lru: links together all component pages (except the first page)
> + *		of a zspage
> + *
> + *	For _first_ page only:
> + *
> + *	page->private (union with page->first_page): refers to the
> + *		component page after the first page
> + *	page->freelist: points to the first free object in zspage.
> + *		Free objects are linked together using in-place
> + *		metadata.
> + *	page->objects: maximum number of objects we can store in this
> + *		zspage (class->zspage_order * PAGE_SIZE / class->size)
> + *	page->lru: links together first pages of various zspages.
> + *		Basically forming list of zspages in a fullness group.
> + *	page->mapping: class index and fullness group of the zspage
> + *
> + * Usage of struct page flags:
> + *	PG_private: identifies the first component page
> + *	PG_private2: identifies the last component page
> + *
> + */
> +
> +#ifdef CONFIG_ZSMALLOC_DEBUG
> +#define DEBUG
> +#endif
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/bitops.h>
> +#include <linux/errno.h>
> +#include <linux/highmem.h>
> +#include <linux/init.h>
> +#include <linux/string.h>
> +#include <linux/slab.h>
> +#include <asm/tlbflush.h>
> +#include <asm/pgtable.h>
> +#include <linux/cpumask.h>
> +#include <linux/cpu.h>
> +#include <linux/vmalloc.h>
> +#include <linux/hardirq.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/zsmalloc.h>
> +
> +/*
> + * This must be power of 2 and greater than of equal to sizeof(link_free).
> + * These two conditions ensure that any 'struct link_free' itself doesn't
> + * span more than 1 page which avoids complex case of mapping 2 pages simply
> + * to restore link_free pointer values.
> + */
> +#define ZS_ALIGN		8
> +
> +/*
> + * A single 'zspage' is composed of up to 2^N discontiguous 0-order (single)
> + * pages. ZS_MAX_ZSPAGE_ORDER defines upper limit on N.
> + */
> +#define ZS_MAX_ZSPAGE_ORDER 2
> +#define ZS_MAX_PAGES_PER_ZSPAGE (_AC(1, UL) << ZS_MAX_ZSPAGE_ORDER)
> +
> +/*
> + * Object location (<PFN>, <obj_idx>) is encoded as
> + * as single (void *) handle value.
> + *
> + * Note that object index <obj_idx> is relative to system
> + * page <PFN> it is stored in, so for each sub-page belonging
> + * to a zspage, obj_idx starts with 0.
> + *
> + * This is made more complicated by various memory models and PAE.
> + */
> +
> +#ifndef MAX_PHYSMEM_BITS
> +#ifdef CONFIG_HIGHMEM64G
> +#define MAX_PHYSMEM_BITS 36
> +#else /* !CONFIG_HIGHMEM64G */
> +/*
> + * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just
> + * be PAGE_SHIFT
> + */
> +#define MAX_PHYSMEM_BITS BITS_PER_LONG
> +#endif
> +#endif
> +#define _PFN_BITS		(MAX_PHYSMEM_BITS - PAGE_SHIFT)
> +#define OBJ_INDEX_BITS	(BITS_PER_LONG - _PFN_BITS)
> +#define OBJ_INDEX_MASK	((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
> +
> +#define MAX(a, b) ((a) >= (b) ? (a) : (b))
> +/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
> +#define ZS_MIN_ALLOC_SIZE \
> +	MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS))
> +#define ZS_MAX_ALLOC_SIZE	PAGE_SIZE
> +
> +/*
> + * On systems with 4K page size, this gives 254 size classes! There is a
> + * trader-off here:
> + *  - Large number of size classes is potentially wasteful as free page are
> + *    spread across these classes
> + *  - Small number of size classes causes large internal fragmentation
> + *  - Probably its better to use specific size classes (empirically
> + *    determined). NOTE: all those class sizes must be set as multiple of
> + *    ZS_ALIGN to make sure link_free itself never has to span 2 pages.
> + *
> + *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
> + *  (reason above)
> + */
> +#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
> +#define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
> +					ZS_SIZE_CLASS_DELTA + 1)
> +
> +/*
> + * We do not maintain any list for completely empty or full pages
> + */
> +enum fullness_group {
> +	ZS_ALMOST_FULL,
> +	ZS_ALMOST_EMPTY,
> +	_ZS_NR_FULLNESS_GROUPS,
> +
> +	ZS_EMPTY,
> +	ZS_FULL
> +};
> +
> +/*
> + * We assign a page to ZS_ALMOST_EMPTY fullness group when:
> + *	n <= N / f, where
> + * n = number of allocated objects
> + * N = total number of objects zspage can store
> + * f = 1/fullness_threshold_frac
> + *
> + * Similarly, we assign zspage to:
> + *	ZS_ALMOST_FULL	when n > N / f
> + *	ZS_EMPTY	when n == 0
> + *	ZS_FULL		when n == N
> + *
> + * (see: fix_fullness_group())
> + */
> +static const int fullness_threshold_frac = 4;
> +
> +struct size_class {
> +	/*
> +	 * Size of objects stored in this class. Must be multiple
> +	 * of ZS_ALIGN.
> +	 */
> +	int size;
> +	unsigned int index;
> +
> +	/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
> +	int pages_per_zspage;
> +
> +	spinlock_t lock;
> +
> +	/* stats */
> +	u64 pages_allocated;
> +
> +	struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
> +};
> +
> +/*
> + * Placed within free objects to form a singly linked list.
> + * For every zspage, first_page->freelist gives head of this list.
> + *
> + * This must be power of 2 and less than or equal to ZS_ALIGN
> + */
> +struct link_free {
> +	/* Handle of next free chunk (encodes <PFN, obj_idx>) */
> +	void *next;
> +};
> +
> +struct zs_pool {
> +	struct size_class size_class[ZS_SIZE_CLASSES];
> +	struct zs_ops *ops;
> +};
> +
> +/*
> + * A zspage's class index and fullness group
> + * are encoded in its (first)page->mapping
> + */
> +#define CLASS_IDX_BITS	28
> +#define FULLNESS_BITS	4
> +#define CLASS_IDX_MASK	((1 << CLASS_IDX_BITS) - 1)
> +#define FULLNESS_MASK	((1 << FULLNESS_BITS) - 1)
> +
> +/*
> + * By default, zsmalloc uses a copy-based object mapping method to access
> + * allocations that span two pages. However, if a particular architecture
> + * 1) Implements local_flush_tlb_kernel_range() and 2) Performs VM mapping
> + * faster than copying, then it should be added here so that
> + * USE_PGTABLE_MAPPING is defined. This causes zsmalloc to use page table
> + * mapping rather than copying
> + * for object mapping.
> +*/
> +#if defined(CONFIG_ARM)
> +#define USE_PGTABLE_MAPPING
> +#endif
> +
> +struct mapping_area {
> +#ifdef USE_PGTABLE_MAPPING
> +	struct vm_struct *vm; /* vm area for mapping object that span pages */
> +#else
> +	char *vm_buf; /* copy buffer for objects that span pages */
> +#endif
> +	char *vm_addr; /* address of kmap_atomic()'ed pages */
> +	enum zs_mapmode vm_mm; /* mapping mode */
> +};
> +
> +/* default page alloc/free ops */
> +struct page *zs_alloc_page(gfp_t flags)
> +{
> +	return alloc_page(flags);
> +}
> +
> +void zs_free_page(struct page *page)
> +{
> +	__free_page(page);
> +}
> +
> +struct zs_ops zs_default_ops = {
> +	.alloc = zs_alloc_page,
> +	.free = zs_free_page
> +};
> +
> +/* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
> +static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
> +
> +static int is_first_page(struct page *page)
> +{
> +	return PagePrivate(page);
> +}
> +
> +static int is_last_page(struct page *page)
> +{
> +	return PagePrivate2(page);
> +}
> +
> +static void get_zspage_mapping(struct page *page, unsigned int *class_idx,
> +				enum fullness_group *fullness)
> +{
> +	unsigned long m;
> +	BUG_ON(!is_first_page(page));
> +
> +	m = (unsigned long)page->mapping;
> +	*fullness = m & FULLNESS_MASK;
> +	*class_idx = (m >> FULLNESS_BITS) & CLASS_IDX_MASK;
> +}
> +
> +static void set_zspage_mapping(struct page *page, unsigned int class_idx,
> +				enum fullness_group fullness)
> +{
> +	unsigned long m;
> +	BUG_ON(!is_first_page(page));
> +
> +	m = ((class_idx & CLASS_IDX_MASK) << FULLNESS_BITS) |
> +			(fullness & FULLNESS_MASK);
> +	page->mapping = (struct address_space *)m;
> +}
> +
> +static int get_size_class_index(int size)
> +{
> +	int idx = 0;
> +
> +	if (likely(size > ZS_MIN_ALLOC_SIZE))
> +		idx = DIV_ROUND_UP(size - ZS_MIN_ALLOC_SIZE,
> +				ZS_SIZE_CLASS_DELTA);
> +
> +	return idx;
> +}
> +
> +static enum fullness_group get_fullness_group(struct page *page)
> +{
> +	int inuse, max_objects;
> +	enum fullness_group fg;
> +	BUG_ON(!is_first_page(page));
> +
> +	inuse = page->inuse;
> +	max_objects = page->objects;
> +
> +	if (inuse == 0)
> +		fg = ZS_EMPTY;
> +	else if (inuse == max_objects)
> +		fg = ZS_FULL;
> +	else if (inuse <= max_objects / fullness_threshold_frac)
> +		fg = ZS_ALMOST_EMPTY;
> +	else
> +		fg = ZS_ALMOST_FULL;
> +
> +	return fg;
> +}
> +
> +static void insert_zspage(struct page *page, struct size_class *class,
> +				enum fullness_group fullness)
> +{
> +	struct page **head;
> +
> +	BUG_ON(!is_first_page(page));
> +
> +	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
> +		return;
> +
> +	head = &class->fullness_list[fullness];
> +	if (*head)
> +		list_add_tail(&page->lru, &(*head)->lru);
> +
> +	*head = page;
> +}
> +
> +static void remove_zspage(struct page *page, struct size_class *class,
> +				enum fullness_group fullness)
> +{
> +	struct page **head;
> +
> +	BUG_ON(!is_first_page(page));
> +
> +	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
> +		return;
> +
> +	head = &class->fullness_list[fullness];
> +	BUG_ON(!*head);
> +	if (list_empty(&(*head)->lru))
> +		*head = NULL;
> +	else if (*head == page)
> +		*head = (struct page *)list_entry((*head)->lru.next,
> +					struct page, lru);
> +
> +	list_del_init(&page->lru);
> +}
> +
> +static enum fullness_group fix_fullness_group(struct zs_pool *pool,
> +						struct page *page)
> +{
> +	int class_idx;
> +	struct size_class *class;
> +	enum fullness_group currfg, newfg;
> +
> +	BUG_ON(!is_first_page(page));
> +
> +	get_zspage_mapping(page, &class_idx, &currfg);
> +	newfg = get_fullness_group(page);
> +	if (newfg == currfg)
> +		goto out;
> +
> +	class = &pool->size_class[class_idx];
> +	remove_zspage(page, class, currfg);
> +	insert_zspage(page, class, newfg);
> +	set_zspage_mapping(page, class_idx, newfg);
> +
> +out:
> +	return newfg;
> +}
> +
> +/*
> + * We have to decide on how many pages to link together
> + * to form a zspage for each size class. This is important
> + * to reduce wastage due to unusable space left at end of
> + * each zspage which is given as:
> + *	wastage = Zp - Zp % size_class
> + * where Zp = zspage size = k * PAGE_SIZE where k = 1, 2, ...
> + *
> + * For example, for size class of 3/8 * PAGE_SIZE, we should
> + * link together 3 PAGE_SIZE sized pages to form a zspage
> + * since then we can perfectly fit in 8 such objects.
> + */
> +static int get_pages_per_zspage(int class_size)
> +{
> +	int i, max_usedpc = 0;
> +	/* zspage order which gives maximum used size per KB */
> +	int max_usedpc_order = 1;
> +
> +	for (i = 1; i <= ZS_MAX_PAGES_PER_ZSPAGE; i++) {
> +		int zspage_size;
> +		int waste, usedpc;
> +
> +		zspage_size = i * PAGE_SIZE;
> +		waste = zspage_size % class_size;
> +		usedpc = (zspage_size - waste) * 100 / zspage_size;
> +
> +		if (usedpc > max_usedpc) {
> +			max_usedpc = usedpc;
> +			max_usedpc_order = i;
> +		}
> +	}
> +
> +	return max_usedpc_order;
> +}
> +
> +/*
> + * A single 'zspage' is composed of many system pages which are
> + * linked together using fields in struct page. This function finds
> + * the first/head page, given any component page of a zspage.
> + */
> +static struct page *get_first_page(struct page *page)
> +{
> +	if (is_first_page(page))
> +		return page;
> +	else
> +		return page->first_page;
> +}
> +
> +static struct page *get_next_page(struct page *page)
> +{
> +	struct page *next;
> +
> +	if (is_last_page(page))
> +		next = NULL;
> +	else if (is_first_page(page))
> +		next = (struct page *)page->private;
> +	else
> +		next = list_entry(page->lru.next, struct page, lru);
> +
> +	return next;
> +}
> +
> +/* Encode <page, obj_idx> as a single handle value */
> +static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
> +{
> +	unsigned long handle;
> +
> +	if (!page) {
> +		BUG_ON(obj_idx);
> +		return NULL;
> +	}
> +
> +	handle = page_to_pfn(page) << OBJ_INDEX_BITS;
> +	handle |= (obj_idx & OBJ_INDEX_MASK);
> +
> +	return (void *)handle;
> +}
> +
> +/* Decode <page, obj_idx> pair from the given object handle */
> +static void obj_handle_to_location(unsigned long handle, struct page **page,
> +				unsigned long *obj_idx)
> +{
> +	*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
> +	*obj_idx = handle & OBJ_INDEX_MASK;
> +}
> +
> +static unsigned long obj_idx_to_offset(struct page *page,
> +				unsigned long obj_idx, int class_size)
> +{
> +	unsigned long off = 0;
> +
> +	if (!is_first_page(page))
> +		off = page->index;
> +
> +	return off + obj_idx * class_size;
> +}
> +
> +static void reset_page(struct page *page)
> +{
> +	clear_bit(PG_private, &page->flags);
> +	clear_bit(PG_private_2, &page->flags);
> +	set_page_private(page, 0);
> +	page->mapping = NULL;
> +	page->freelist = NULL;
> +	reset_page_mapcount(page);
> +}
> +
> +static void free_zspage(struct zs_ops *ops, struct page *first_page)
> +{
> +	struct page *nextp, *tmp, *head_extra;
> +
> +	BUG_ON(!is_first_page(first_page));
> +	BUG_ON(first_page->inuse);
> +
> +	head_extra = (struct page *)page_private(first_page);
> +
> +	reset_page(first_page);
> +	ops->free(first_page);
> +
> +	/* zspage with only 1 system page */
> +	if (!head_extra)
> +		return;
> +
> +	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
> +		list_del(&nextp->lru);
> +		reset_page(nextp);
> +		ops->free(nextp);
> +	}
> +	reset_page(head_extra);
> +	ops->free(head_extra);
> +}
> +
> +/* Initialize a newly allocated zspage */
> +static void init_zspage(struct page *first_page, struct size_class *class)
> +{
> +	unsigned long off = 0;
> +	struct page *page = first_page;
> +
> +	BUG_ON(!is_first_page(first_page));
> +	while (page) {
> +		struct page *next_page;
> +		struct link_free *link;
> +		unsigned int i, objs_on_page;
> +
> +		/*
> +		 * page->index stores offset of first object starting
> +		 * in the page. For the first page, this is always 0,
> +		 * so we use first_page->index (aka ->freelist) to store
> +		 * head of corresponding zspage's freelist.
> +		 */
> +		if (page != first_page)
> +			page->index = off;
> +
> +		link = (struct link_free *)kmap_atomic(page) +
> +						off / sizeof(*link);
> +		objs_on_page = (PAGE_SIZE - off) / class->size;
> +
> +		for (i = 1; i <= objs_on_page; i++) {
> +			off += class->size;
> +			if (off < PAGE_SIZE) {
> +				link->next = obj_location_to_handle(page, i);
> +				link += class->size / sizeof(*link);
> +			}
> +		}
> +
> +		/*
> +		 * We now come to the last (full or partial) object on this
> +		 * page, which must point to the first object on the next
> +		 * page (if present)
> +		 */
> +		next_page = get_next_page(page);
> +		link->next = obj_location_to_handle(next_page, 0);
> +		kunmap_atomic(link);
> +		page = next_page;
> +		off = (off + class->size) % PAGE_SIZE;
> +	}
> +}
> +
> +/*
> + * Allocate a zspage for the given size class
> + */
> +static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
> +				gfp_t flags)
> +{
> +	int i, error;
> +	struct page *first_page = NULL, *uninitialized_var(prev_page);
> +
> +	/*
> +	 * Allocate individual pages and link them together as:
> +	 * 1. first page->private = first sub-page
> +	 * 2. all sub-pages are linked together using page->lru
> +	 * 3. each sub-page is linked to the first page using page->first_page
> +	 *
> +	 * For each size class, First/Head pages are linked together using
> +	 * page->lru. Also, we set PG_private to identify the first page
> +	 * (i.e. no other sub-page has this flag set) and PG_private_2 to
> +	 * identify the last page.
> +	 */
> +	error = -ENOMEM;
> +	for (i = 0; i < class->pages_per_zspage; i++) {
> +		struct page *page;
> +
> +		page = ops->alloc(flags);
> +		if (!page)
> +			goto cleanup;
> +
> +		INIT_LIST_HEAD(&page->lru);
> +		if (i == 0) {	/* first page */
> +			SetPagePrivate(page);
> +			set_page_private(page, 0);
> +			first_page = page;
> +			first_page->inuse = 0;
> +		}
> +		if (i == 1)
> +			first_page->private = (unsigned long)page;
> +		if (i >= 1)
> +			page->first_page = first_page;
> +		if (i >= 2)
> +			list_add(&page->lru, &prev_page->lru);
> +		if (i == class->pages_per_zspage - 1)	/* last page */
> +			SetPagePrivate2(page);
> +		prev_page = page;
> +	}
> +
> +	init_zspage(first_page, class);
> +
> +	first_page->freelist = obj_location_to_handle(first_page, 0);
> +	/* Maximum number of objects we can store in this zspage */
> +	first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
> +
> +	error = 0; /* Success */
> +
> +cleanup:
> +	if (unlikely(error) && first_page) {
> +		free_zspage(ops, first_page);
> +		first_page = NULL;
> +	}
> +
> +	return first_page;
> +}
> +
> +static struct page *find_get_zspage(struct size_class *class)
> +{
> +	int i;
> +	struct page *page;
> +
> +	for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
> +		page = class->fullness_list[i];
> +		if (page)
> +			break;
> +	}
> +
> +	return page;
> +}
> +
> +#ifdef USE_PGTABLE_MAPPING
> +static inline int __zs_cpu_up(struct mapping_area *area)
> +{
> +	/*
> +	 * Make sure we don't leak memory if a cpu UP notification
> +	 * and zs_init() race and both call zs_cpu_up() on the same cpu
> +	 */
> +	if (area->vm)
> +		return 0;
> +	area->vm = alloc_vm_area(PAGE_SIZE * 2, NULL);
> +	if (!area->vm)
> +		return -ENOMEM;
> +	return 0;
> +}
> +
> +static inline void __zs_cpu_down(struct mapping_area *area)
> +{
> +	if (area->vm)
> +		free_vm_area(area->vm);
> +	area->vm = NULL;
> +}
> +
> +static inline void *__zs_map_object(struct mapping_area *area,
> +				struct page *pages[2], int off, int size)
> +{
> +	BUG_ON(map_vm_area(area->vm, PAGE_KERNEL, &pages));
> +	area->vm_addr = area->vm->addr;
> +	return area->vm_addr + off;
> +}
> +
> +static inline void __zs_unmap_object(struct mapping_area *area,
> +				struct page *pages[2], int off, int size)
> +{
> +	unsigned long addr = (unsigned long)area->vm_addr;
> +	unsigned long end = addr + (PAGE_SIZE * 2);
> +
> +	flush_cache_vunmap(addr, end);
> +	unmap_kernel_range_noflush(addr, PAGE_SIZE * 2);
> +	local_flush_tlb_kernel_range(addr, end);
> +}
> +
> +#else /* USE_PGTABLE_MAPPING */
> +
> +static inline int __zs_cpu_up(struct mapping_area *area)
> +{
> +	/*
> +	 * Make sure we don't leak memory if a cpu UP notification
> +	 * and zs_init() race and both call zs_cpu_up() on the same cpu
> +	 */
> +	if (area->vm_buf)
> +		return 0;
> +	area->vm_buf = (char *)__get_free_page(GFP_KERNEL);
> +	if (!area->vm_buf)
> +		return -ENOMEM;
> +	return 0;
> +}
> +
> +static inline void __zs_cpu_down(struct mapping_area *area)
> +{
> +	if (area->vm_buf)
> +		free_page((unsigned long)area->vm_buf);
> +	area->vm_buf = NULL;
> +}
> +
> +static void *__zs_map_object(struct mapping_area *area,
> +			struct page *pages[2], int off, int size)
> +{
> +	int sizes[2];
> +	void *addr;
> +	char *buf = area->vm_buf;
> +
> +	/* disable page faults to match kmap_atomic() return conditions */
> +	pagefault_disable();
> +
> +	/* no read fastpath */
> +	if (area->vm_mm == ZS_MM_WO)
> +		goto out;
> +
> +	sizes[0] = PAGE_SIZE - off;
> +	sizes[1] = size - sizes[0];
> +
> +	/* copy object to per-cpu buffer */
> +	addr = kmap_atomic(pages[0]);
> +	memcpy(buf, addr + off, sizes[0]);
> +	kunmap_atomic(addr);
> +	addr = kmap_atomic(pages[1]);
> +	memcpy(buf + sizes[0], addr, sizes[1]);
> +	kunmap_atomic(addr);
> +out:
> +	return area->vm_buf;
> +}
> +
> +static void __zs_unmap_object(struct mapping_area *area,
> +			struct page *pages[2], int off, int size)
> +{
> +	int sizes[2];
> +	void *addr;
> +	char *buf = area->vm_buf;
> +
> +	/* no write fastpath */
> +	if (area->vm_mm == ZS_MM_RO)
> +		goto out;
> +
> +	sizes[0] = PAGE_SIZE - off;
> +	sizes[1] = size - sizes[0];
> +
> +	/* copy per-cpu buffer to object */
> +	addr = kmap_atomic(pages[0]);
> +	memcpy(addr + off, buf, sizes[0]);
> +	kunmap_atomic(addr);
> +	addr = kmap_atomic(pages[1]);
> +	memcpy(addr, buf + sizes[0], sizes[1]);
> +	kunmap_atomic(addr);
> +
> +out:
> +	/* enable page faults to match kunmap_atomic() return conditions */
> +	pagefault_enable();
> +}
> +
> +#endif /* USE_PGTABLE_MAPPING */
> +
> +static int zs_cpu_notifier(struct notifier_block *nb, unsigned long action,
> +				void *pcpu)
> +{
> +	int ret, cpu = (long)pcpu;
> +	struct mapping_area *area;
> +
> +	switch (action) {
> +	case CPU_UP_PREPARE:
> +		area = &per_cpu(zs_map_area, cpu);
> +		ret = __zs_cpu_up(area);
> +		if (ret)
> +			return notifier_from_errno(ret);
> +		break;
> +	case CPU_DEAD:
> +	case CPU_UP_CANCELED:
> +		area = &per_cpu(zs_map_area, cpu);
> +		__zs_cpu_down(area);
> +		break;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static struct notifier_block zs_cpu_nb = {
> +	.notifier_call = zs_cpu_notifier
> +};
> +
> +static void zs_exit(void)
> +{
> +	int cpu;
> +
> +	for_each_online_cpu(cpu)
> +		zs_cpu_notifier(NULL, CPU_DEAD, (void *)(long)cpu);
> +	unregister_cpu_notifier(&zs_cpu_nb);
> +}
> +
> +static int zs_init(void)
> +{
> +	int cpu, ret;
> +
> +	register_cpu_notifier(&zs_cpu_nb);
> +	for_each_online_cpu(cpu) {
> +		ret = zs_cpu_notifier(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
> +		if (notifier_to_errno(ret))
> +			goto fail;
> +	}
> +	return 0;
> +fail:
> +	zs_exit();
> +	return notifier_to_errno(ret);
> +}
> +
> +struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
> +{
> +	int i, ovhd_size;
> +	struct zs_pool *pool;
> +
> +	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
> +	pool = kzalloc(ovhd_size, flags);
> +	if (!pool)
> +		return NULL;
> +
> +	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
> +		int size;
> +		struct size_class *class;
> +
> +		size = ZS_MIN_ALLOC_SIZE + i * ZS_SIZE_CLASS_DELTA;
> +		if (size > ZS_MAX_ALLOC_SIZE)
> +			size = ZS_MAX_ALLOC_SIZE;
> +
> +		class = &pool->size_class[i];
> +		class->size = size;
> +		class->index = i;
> +		spin_lock_init(&class->lock);
> +		class->pages_per_zspage = get_pages_per_zspage(size);
> +
> +	}
> +
> +	if (ops)
> +		pool->ops = ops;
> +	else
> +		pool->ops = &zs_default_ops;
> +
> +	return pool;
> +}
> +EXPORT_SYMBOL_GPL(zs_create_pool);
> +
> +void zs_destroy_pool(struct zs_pool *pool)
> +{
> +	int i;
> +
> +	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
> +		int fg;
> +		struct size_class *class = &pool->size_class[i];
> +
> +		for (fg = 0; fg < _ZS_NR_FULLNESS_GROUPS; fg++) {
> +			if (class->fullness_list[fg]) {
> +				pr_info("Freeing non-empty class with size "
> +					"%db, fullness group %d\n",
> +					class->size, fg);
> +			}
> +		}
> +	}
> +	kfree(pool);
> +}
> +EXPORT_SYMBOL_GPL(zs_destroy_pool);
> +
> +/**
> + * zs_malloc - Allocate block of given size from pool.
> + * @pool: pool to allocate from
> + * @size: size of block to allocate
> + *
> + * On success, handle to the allocated object is returned,
> + * otherwise 0.
> + * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
> + */
> +unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
> +{
> +	unsigned long obj;
> +	struct link_free *link;
> +	int class_idx;
> +	struct size_class *class;
> +
> +	struct page *first_page, *m_page;
> +	unsigned long m_objidx, m_offset;
> +
> +	if (unlikely(!size || size > ZS_MAX_ALLOC_SIZE))
> +		return 0;
> +
> +	class_idx = get_size_class_index(size);
> +	class = &pool->size_class[class_idx];
> +	BUG_ON(class_idx != class->index);
> +
> +	spin_lock(&class->lock);
> +	first_page = find_get_zspage(class);
> +
> +	if (!first_page) {
> +		spin_unlock(&class->lock);
> +		first_page = alloc_zspage(pool->ops, class, flags);
> +		if (unlikely(!first_page))
> +			return 0;
> +
> +		set_zspage_mapping(first_page, class->index, ZS_EMPTY);
> +		spin_lock(&class->lock);
> +		class->pages_allocated += class->pages_per_zspage;
> +	}
> +
> +	obj = (unsigned long)first_page->freelist;
> +	obj_handle_to_location(obj, &m_page, &m_objidx);
> +	m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
> +
> +	link = (struct link_free *)kmap_atomic(m_page) +
> +					m_offset / sizeof(*link);
> +	first_page->freelist = link->next;
> +	memset(link, POISON_INUSE, sizeof(*link));
> +	kunmap_atomic(link);
> +
> +	first_page->inuse++;
> +	/* Now move the zspage to another fullness group, if required */
> +	fix_fullness_group(pool, first_page);
> +	spin_unlock(&class->lock);
> +
> +	return obj;
> +}
> +EXPORT_SYMBOL_GPL(zs_malloc);
> +
> +void zs_free(struct zs_pool *pool, unsigned long obj)
> +{
> +	struct link_free *link;
> +	struct page *first_page, *f_page;
> +	unsigned long f_objidx, f_offset;
> +
> +	int class_idx;
> +	struct size_class *class;
> +	enum fullness_group fullness;
> +
> +	if (unlikely(!obj))
> +		return;
> +
> +	obj_handle_to_location(obj, &f_page, &f_objidx);
> +	first_page = get_first_page(f_page);
> +
> +	get_zspage_mapping(first_page, &class_idx, &fullness);
> +	class = &pool->size_class[class_idx];
> +	f_offset = obj_idx_to_offset(f_page, f_objidx, class->size);
> +
> +	spin_lock(&class->lock);
> +
> +	/* Insert this object in containing zspage's freelist */
> +	link = (struct link_free *)((unsigned char *)kmap_atomic(f_page)
> +							+ f_offset);
> +	link->next = first_page->freelist;
> +	kunmap_atomic(link);
> +	first_page->freelist = (void *)obj;
> +
> +	first_page->inuse--;
> +	fullness = fix_fullness_group(pool, first_page);
> +
> +	if (fullness == ZS_EMPTY)
> +		class->pages_allocated -= class->pages_per_zspage;
> +
> +	spin_unlock(&class->lock);
> +
> +	if (fullness == ZS_EMPTY)
> +		free_zspage(pool->ops, first_page);
> +}
> +EXPORT_SYMBOL_GPL(zs_free);
> +
> +/**
> + * zs_map_object - get address of allocated object from handle.
> + * @pool: pool from which the object was allocated
> + * @handle: handle returned from zs_malloc
> + *
> + * Before using an object allocated from zs_malloc, it must be mapped using
> + * this function. When done with the object, it must be unmapped using
> + * zs_unmap_object.
> + *
> + * Only one object can be mapped per cpu at a time. There is no protection
> + * against nested mappings.
> + *
> + * This function returns with preemption and page faults disabled.
> +*/
> +void *zs_map_object(struct zs_pool *pool, unsigned long handle,
> +			enum zs_mapmode mm)
> +{
> +	struct page *page;
> +	unsigned long obj_idx, off;
> +
> +	unsigned int class_idx;
> +	enum fullness_group fg;
> +	struct size_class *class;
> +	struct mapping_area *area;
> +	struct page *pages[2];
> +
> +	BUG_ON(!handle);
> +
> +	/*
> +	 * Because we use per-cpu mapping areas shared among the
> +	 * pools/users, we can't allow mapping in interrupt context
> +	 * because it can corrupt another users mappings.
> +	 */
> +	BUG_ON(in_interrupt());
> +
> +	obj_handle_to_location(handle, &page, &obj_idx);
> +	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
> +	class = &pool->size_class[class_idx];
> +	off = obj_idx_to_offset(page, obj_idx, class->size);
> +
> +	area = &get_cpu_var(zs_map_area);
> +	area->vm_mm = mm;
> +	if (off + class->size <= PAGE_SIZE) {
> +		/* this object is contained entirely within a page */
> +		area->vm_addr = kmap_atomic(page);
> +		return area->vm_addr + off;
> +	}
> +
> +	/* this object spans two pages */
> +	pages[0] = page;
> +	pages[1] = get_next_page(page);
> +	BUG_ON(!pages[1]);
> +
> +	return __zs_map_object(area, pages, off, class->size);
> +}
> +EXPORT_SYMBOL_GPL(zs_map_object);
> +
> +void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
> +{
> +	struct page *page;
> +	unsigned long obj_idx, off;
> +
> +	unsigned int class_idx;
> +	enum fullness_group fg;
> +	struct size_class *class;
> +	struct mapping_area *area;
> +
> +	BUG_ON(!handle);
> +
> +	obj_handle_to_location(handle, &page, &obj_idx);
> +	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
> +	class = &pool->size_class[class_idx];
> +	off = obj_idx_to_offset(page, obj_idx, class->size);
> +
> +	area = &__get_cpu_var(zs_map_area);
> +	if (off + class->size <= PAGE_SIZE)
> +		kunmap_atomic(area->vm_addr);
> +	else {
> +		struct page *pages[2];
> +
> +		pages[0] = page;
> +		pages[1] = get_next_page(page);
> +		BUG_ON(!pages[1]);
> +
> +		__zs_unmap_object(area, pages, off, class->size);
> +	}
> +	put_cpu_var(zs_map_area);
> +}
> +EXPORT_SYMBOL_GPL(zs_unmap_object);
> +
> +u64 zs_get_total_size_bytes(struct zs_pool *pool)
> +{
> +	int i;
> +	u64 npages = 0;
> +
> +	for (i = 0; i < ZS_SIZE_CLASSES; i++)
> +		npages += pool->size_class[i].pages_allocated;
> +
> +	return npages << PAGE_SHIFT;
> +}
> +EXPORT_SYMBOL_GPL(zs_get_total_size_bytes);
> +
> +module_init(zs_init);
> +module_exit(zs_exit);
> +
> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
> -- 
> 1.7.9.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/

-- 
Kind regards,
Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends
  2013-01-07 20:24 ` [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends Seth Jennings
@ 2013-01-28  4:22   ` Minchan Kim
  2013-01-28 17:26     ` Seth Jennings
  0 siblings, 1 reply; 43+ messages in thread
From: Minchan Kim @ 2013-01-28  4:22 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 07, 2013 at 02:24:38PM -0600, Seth Jennings wrote:
> swap_writepage() is currently where frontswap hooks into the swap
> write path to capture pages with the frontswap_store() function.
> However, if a frontswap backend wants to "resume" the writeback of
> a page to the swap device, it can't call swap_writepage() as
> the page will simply reenter the backend.
> 
> This patch separates swap_writepage() into a top and bottom half, the
> bottom half named __swap_writepage() to allow a frontswap backend,
> like zswap, to resume writeback beyond the frontswap_store() hook and
> by notified when the writeback completes.
> 
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>

Looks good to me except few nitpicks.

Acked-by: Minchan Kim <minchan@kernel.org>

> ---
>  include/linux/swap.h |    4 ++++
>  mm/page_io.c         |   22 +++++++++++++++++-----
>  mm/swap_state.c      |    2 +-
>  3 files changed, 22 insertions(+), 6 deletions(-)
> 
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index 8c66486..a3da829 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -321,6 +321,9 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t ent)
>  /* linux/mm/page_io.c */
>  extern int swap_readpage(struct page *);
>  extern int swap_writepage(struct page *page, struct writeback_control *wbc);
> +extern void end_swap_bio_write(struct bio *bio, int err);
> +extern int __swap_writepage(struct page *page, struct writeback_control *wbc,
> +	void (*end_write_func)(struct bio *, int));
>  extern int swap_set_page_dirty(struct page *page);
>  extern void end_swap_bio_read(struct bio *bio, int err);
>  
> @@ -335,6 +338,7 @@ extern struct address_space swapper_space;
>  extern void show_swap_cache_info(void);
>  extern int add_to_swap(struct page *);
>  extern int add_to_swap_cache(struct page *, swp_entry_t, gfp_t);
> +extern int __add_to_swap_cache(struct page *page, swp_entry_t entry);

What's related __add_to_swap_cache with this patch?

>  extern void __delete_from_swap_cache(struct page *);
>  extern void delete_from_swap_cache(struct page *);
>  extern void free_page_and_swap_cache(struct page *);
> diff --git a/mm/page_io.c b/mm/page_io.c
> index c535d39..806085e 100644
> --- a/mm/page_io.c
> +++ b/mm/page_io.c
> @@ -43,7 +43,7 @@ static struct bio *get_swap_bio(gfp_t gfp_flags,
>  	return bio;
>  }
>  
> -static void end_swap_bio_write(struct bio *bio, int err)
> +void end_swap_bio_write(struct bio *bio, int err)

Why do you remove static in this patch? It's not related to the patch.

>  {
>  	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
>  	struct page *page = bio->bi_io_vec[0].bv_page;
> @@ -180,15 +180,16 @@ bad_bmap:
>  	goto out;
>  }
>  
> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
> +	void (*end_write_func)(struct bio *, int));
> +
>  /*
>   * 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;
> -	struct swap_info_struct *sis = page_swap_info(page);
> +	int ret = 0;
>  
>  	if (try_to_free_swap(page)) {
>  		unlock_page(page);
> @@ -200,6 +201,17 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>  		end_page_writeback(page);
>  		goto out;
>  	}
> +	ret = __swap_writepage(page, wbc, end_swap_bio_write);
> +out:
> +	return ret;
> +}
> +
> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
> +	void (*end_write_func)(struct bio *, int))
> +{
> +	struct bio *bio;
> +	int ret = 0, rw = WRITE;
> +	struct swap_info_struct *sis = page_swap_info(page);
>  
>  	if (sis->flags & SWP_FILE) {
>  		struct kiocb kiocb;
> @@ -227,7 +239,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>  		return ret;
>  	}
>  
> -	bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
> +	bio = get_swap_bio(GFP_NOIO, page, end_write_func);
>  	if (bio == NULL) {
>  		set_page_dirty(page);
>  		unlock_page(page);
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index 0cb36fb..7eded9c 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -67,7 +67,7 @@ void show_swap_cache_info(void)
>   * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,
>   * but sets SwapCache flag and private instead of mapping and index.
>   */
> -static int __add_to_swap_cache(struct page *page, swp_entry_t entry)
> +int __add_to_swap_cache(struct page *page, swp_entry_t entry)

Ditto

>  {
>  	int error;
>  
> -- 
> 1.7.9.5
> 
> --
> To unsubscribe, send a message with 'unsubscribe linux-mm' in
> the body to majordomo@kvack.org.  For more info on Linux MM,
> see: http://www.linux-mm.org/ .
> Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>

-- 
Kind regards,
Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 6/9] zsmalloc: promote to lib/
  2013-01-28  4:01   ` Minchan Kim
@ 2013-01-28  4:32     ` Minchan Kim
  2013-01-28 17:41       ` Seth Jennings
  0 siblings, 1 reply; 43+ messages in thread
From: Minchan Kim @ 2013-01-28  4:32 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 28, 2013 at 01:01:16PM +0900, Minchan Kim wrote:
> On Mon, Jan 07, 2013 at 02:24:37PM -0600, Seth Jennings wrote:
> > This patch promotes the slab-based zsmalloc memory allocator
> > from the staging tree to lib/
> > 
> > zswap depends on this allocator for storing compressed RAM pages
> > in an efficient way under system wide memory pressure where
> > high-order (greater than 0) page allocation are very likely to
> > fail.
> > 
> > For more information on zsmalloc and its internals, read the
> > documentation at the top of the zsmalloc.c file.
> > 
> > Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> 
> Seth, zsmalloc has a bug[1], I sent a patch totay. If it want't known,
> it mighte be no problem to promote but it's known bug so let's fix it
> before promoting.
> 
> Another question. Why do you promote zsmalloc in this patchset?
> It might make you hard to merge even zswap into staging.

When I look at [8/9], I realized you are trying to merge this patch
into mm/, NOT staging. I don't know history why zsmalloc/zram/zscache was
in staging at the beginning but personally, I don't ojbect zswap into /mm
directly because I got realized staging is very deep hole to get out,
expecially related to mm stuff. ;-)

-- 
Kind regards,
Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 8/9] zswap: add to mm/
  2013-01-25 22:44   ` Rik van Riel
  2013-01-25 23:15     ` Dan Magenheimer
@ 2013-01-28 15:27     ` Seth Jennings
  2013-01-29 10:21       ` Lord Glauber Costa of Sealand
  1 sibling, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-28 15:27 UTC (permalink / raw)
  To: Rik van Riel
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Larry Woodman,
	linux-mm, linux-kernel, devel

On 01/25/2013 04:44 PM, Rik van Riel wrote:
> On 01/07/2013 03:24 PM, Seth Jennings wrote:
>> zswap is a thin compression backend for frontswap. It receives
>> pages from frontswap and attempts to store them in a compressed
>> memory pool, resulting in an effective partial memory reclaim and
>> dramatically reduced swap device I/O.
>>
>> Additional, in most cases, pages can be retrieved from this
>> compressed store much more quickly than reading from tradition
>> swap devices resulting in faster performance for many workloads.
>>
>> This patch adds the zswap driver to mm/
>>
>> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> 
> I like the approach of flushing pages into actual disk based
> swap when compressed swap is full.  I would like it if that
> was advertised more prominently in the changelog :)

Thanks so much for the review!

> The code looks mostly good, complaints are at the nitpick level.
> 
> One worry is that the pool can grow to whatever maximum was
> decided, and there is no way to shrink it when memory is
> required for something else.
> 
> Would it be an idea to add a shrinker for the zcache pool,
> that can also shrink the zcache pool when required?
> 
> Of course, that does lead to the question of how to balance
> the pressure from that shrinker, with the new memory entering
> zcache from the swap side. I have no clear answers here, just
> something to think about...

Yes, I prototyped a shrinker interface for zswap, but, as we both
figured, it shrinks the zswap compressed pool too aggressively to the
point of being useless.

Right now I'm working on a zswap thread that will "leak" pages out to
the swap device on an LRU basis over time.  That way if the page is a
rarely accessed page, it will eventually be written out to the swap
device and it's memory freed, even if the zswap pool isn't full.

Would this address your concerns?

>> +static void zswap_flush_entries(unsigned type, int nr)
>> +{
>> +    struct zswap_tree *tree = zswap_trees[type];
>> +    struct zswap_entry *entry;
>> +    int i, ret;
>> +
>> +/*
>> + * This limits is arbitrary for now until a better
>> + * policy can be implemented. This is so we don't
>> + * eat all of RAM decompressing pages for writeback.
>> + */
>> +#define ZSWAP_MAX_OUTSTANDING_FLUSHES 64
>> +    if (atomic_read(&zswap_outstanding_flushes) >
>> +        ZSWAP_MAX_OUTSTANDING_FLUSHES)
>> +        return;
> 
> Having this #define right in the middle of the function is
> rather ugly.  Might be worth moving it to the top.

Yes. In my mind, this policy was going to be replaced by a better one
soon. Checking may_write_to_queue() was my idea.  I didn't spend too
much time making that part pretty.

>> +static int __init zswap_debugfs_init(void)
>> +{
>> +    if (!debugfs_initialized())
>> +        return -ENODEV;
>> +
>> +    zswap_debugfs_root = debugfs_create_dir("zswap", NULL);
>> +    if (!zswap_debugfs_root)
>> +        return -ENOMEM;
>> +
>> +    debugfs_create_u64("saved_by_flush", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_saved_by_flush);
>> +    debugfs_create_u64("pool_limit_hit", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_pool_limit_hit);
>> +    debugfs_create_u64("reject_flush_attempted", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_flush_attempted);
>> +    debugfs_create_u64("reject_tmppage_fail", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_reject_tmppage_fail);
>> +    debugfs_create_u64("reject_flush_fail", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_reject_flush_fail);
>> +    debugfs_create_u64("reject_zsmalloc_fail", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_reject_zsmalloc_fail);
>> +    debugfs_create_u64("reject_kmemcache_fail", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_reject_kmemcache_fail);
>> +    debugfs_create_u64("reject_compress_poor", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_reject_compress_poor);
>> +    debugfs_create_u64("flushed_pages", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_flushed_pages);
>> +    debugfs_create_u64("duplicate_entry", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_duplicate_entry);
>> +    debugfs_create_atomic_t("pool_pages", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_pool_pages);
>> +    debugfs_create_atomic_t("stored_pages", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_stored_pages);
>> +    debugfs_create_atomic_t("outstanding_flushes", S_IRUGO,
>> +            zswap_debugfs_root, &zswap_outstanding_flushes);
>> +
> 
> Some of these statistics would be very useful to system
> administrators, who will not be mounting debugfs on
> production systems.
> 
> Would it make sense to export some of these statistics
> through sysfs?

That's fine.  Which of these stats do you think should be in sysfs?

Thanks again for taking time to look at this!

Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends
  2013-01-28  4:22   ` Minchan Kim
@ 2013-01-28 17:26     ` Seth Jennings
  2013-01-28 23:46       ` Minchan Kim
  0 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-01-28 17:26 UTC (permalink / raw)
  To: Minchan Kim
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/27/2013 10:22 PM, Minchan Kim wrote:
> On Mon, Jan 07, 2013 at 02:24:38PM -0600, Seth Jennings wrote:
>> swap_writepage() is currently where frontswap hooks into the swap
>> write path to capture pages with the frontswap_store() function.
>> However, if a frontswap backend wants to "resume" the writeback of
>> a page to the swap device, it can't call swap_writepage() as
>> the page will simply reenter the backend.
>>
>> This patch separates swap_writepage() into a top and bottom half, the
>> bottom half named __swap_writepage() to allow a frontswap backend,
>> like zswap, to resume writeback beyond the frontswap_store() hook and
>> by notified when the writeback completes.
>>
>> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> 
> Looks good to me except few nitpicks.

I broke these changes out from the main zswap patch (patch 8/9) in
response to a request for Dan.  You are right in that there are
changes here unrelated to the commit message.

The other changes are related to allowing zswap to do accounting on
the number of outstanding flushes to the swap device.

If I'm going to break those out though the two resulting patches would be:
1. breakup __swap_writepage() and un-static __add_to_swap_cache() to
allow resuming of swap page writeback
2. change __swap_writepage() signature to include end_write_func and
un-static end_swap_bio_write() for accounting of outstanding flushes

Is that what you'd like to see?

Seth

> 
> Acked-by: Minchan Kim <minchan@kernel.org>
> 
>> ---
>>  include/linux/swap.h |    4 ++++
>>  mm/page_io.c         |   22 +++++++++++++++++-----
>>  mm/swap_state.c      |    2 +-
>>  3 files changed, 22 insertions(+), 6 deletions(-)
>>
>> diff --git a/include/linux/swap.h b/include/linux/swap.h
>> index 8c66486..a3da829 100644
>> --- a/include/linux/swap.h
>> +++ b/include/linux/swap.h
>> @@ -321,6 +321,9 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t ent)
>>  /* linux/mm/page_io.c */
>>  extern int swap_readpage(struct page *);
>>  extern int swap_writepage(struct page *page, struct writeback_control *wbc);
>> +extern void end_swap_bio_write(struct bio *bio, int err);
>> +extern int __swap_writepage(struct page *page, struct writeback_control *wbc,
>> +	void (*end_write_func)(struct bio *, int));
>>  extern int swap_set_page_dirty(struct page *page);
>>  extern void end_swap_bio_read(struct bio *bio, int err);
>>  
>> @@ -335,6 +338,7 @@ extern struct address_space swapper_space;
>>  extern void show_swap_cache_info(void);
>>  extern int add_to_swap(struct page *);
>>  extern int add_to_swap_cache(struct page *, swp_entry_t, gfp_t);
>> +extern int __add_to_swap_cache(struct page *page, swp_entry_t entry);
> 
> What's related __add_to_swap_cache with this patch?
> 
>>  extern void __delete_from_swap_cache(struct page *);
>>  extern void delete_from_swap_cache(struct page *);
>>  extern void free_page_and_swap_cache(struct page *);
>> diff --git a/mm/page_io.c b/mm/page_io.c
>> index c535d39..806085e 100644
>> --- a/mm/page_io.c
>> +++ b/mm/page_io.c
>> @@ -43,7 +43,7 @@ static struct bio *get_swap_bio(gfp_t gfp_flags,
>>  	return bio;
>>  }
>>  
>> -static void end_swap_bio_write(struct bio *bio, int err)
>> +void end_swap_bio_write(struct bio *bio, int err)
> 
> Why do you remove static in this patch? It's not related to the patch.
> 
>>  {
>>  	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
>>  	struct page *page = bio->bi_io_vec[0].bv_page;
>> @@ -180,15 +180,16 @@ bad_bmap:
>>  	goto out;
>>  }
>>  
>> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
>> +	void (*end_write_func)(struct bio *, int));
>> +
>>  /*
>>   * 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;
>> -	struct swap_info_struct *sis = page_swap_info(page);
>> +	int ret = 0;
>>  
>>  	if (try_to_free_swap(page)) {
>>  		unlock_page(page);
>> @@ -200,6 +201,17 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>>  		end_page_writeback(page);
>>  		goto out;
>>  	}
>> +	ret = __swap_writepage(page, wbc, end_swap_bio_write);
>> +out:
>> +	return ret;
>> +}
>> +
>> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
>> +	void (*end_write_func)(struct bio *, int))
>> +{
>> +	struct bio *bio;
>> +	int ret = 0, rw = WRITE;
>> +	struct swap_info_struct *sis = page_swap_info(page);
>>  
>>  	if (sis->flags & SWP_FILE) {
>>  		struct kiocb kiocb;
>> @@ -227,7 +239,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>>  		return ret;
>>  	}
>>  
>> -	bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
>> +	bio = get_swap_bio(GFP_NOIO, page, end_write_func);
>>  	if (bio == NULL) {
>>  		set_page_dirty(page);
>>  		unlock_page(page);
>> diff --git a/mm/swap_state.c b/mm/swap_state.c
>> index 0cb36fb..7eded9c 100644
>> --- a/mm/swap_state.c
>> +++ b/mm/swap_state.c
>> @@ -67,7 +67,7 @@ void show_swap_cache_info(void)
>>   * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,
>>   * but sets SwapCache flag and private instead of mapping and index.
>>   */
>> -static int __add_to_swap_cache(struct page *page, swp_entry_t entry)
>> +int __add_to_swap_cache(struct page *page, swp_entry_t entry)
> 
> Ditto
> 
>>  {
>>  	int error;
>>  
>> -- 
>> 1.7.9.5
>>
>> --
>> To unsubscribe, send a message with 'unsubscribe linux-mm' in
>> the body to majordomo@kvack.org.  For more info on Linux MM,
>> see: http://www.linux-mm.org/ .
>> Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
> 


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 6/9] zsmalloc: promote to lib/
  2013-01-28  4:32     ` Minchan Kim
@ 2013-01-28 17:41       ` Seth Jennings
  0 siblings, 0 replies; 43+ messages in thread
From: Seth Jennings @ 2013-01-28 17:41 UTC (permalink / raw)
  To: Minchan Kim
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/27/2013 10:32 PM, Minchan Kim wrote:
> On Mon, Jan 28, 2013 at 01:01:16PM +0900, Minchan Kim wrote:
>> On Mon, Jan 07, 2013 at 02:24:37PM -0600, Seth Jennings wrote:
>>> This patch promotes the slab-based zsmalloc memory allocator
>>> from the staging tree to lib/
>>>
>>> zswap depends on this allocator for storing compressed RAM pages
>>> in an efficient way under system wide memory pressure where
>>> high-order (greater than 0) page allocation are very likely to
>>> fail.
>>>
>>> For more information on zsmalloc and its internals, read the
>>> documentation at the top of the zsmalloc.c file.
>>>
>>> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
>>
>> Seth, zsmalloc has a bug[1], I sent a patch totay. If it want't known,
>> it mighte be no problem to promote but it's known bug so let's fix it
>> before promoting.
>>
>> Another question. Why do you promote zsmalloc in this patchset?
>> It might make you hard to merge even zswap into staging.
> 
> When I look at [8/9], I realized you are trying to merge this patch
> into mm/, NOT staging. I don't know history why zsmalloc/zram/zscache was
> in staging at the beginning but personally, I don't ojbect zswap into /mm
> directly because I got realized staging is very deep hole to get out,
> expecially related to mm stuff. ;-)

Correct.

As I understand the purpose of the staging tree, it is meant for
drivers whose code doesn't adhere to the kernel coding
standards/guidelines and might have questionable stability.  The point
is to have a TODO, get the code to conform to the kernel standards,
fix known instabilities, then promote into the appropriate place in
the driver tree.

However, with the work on memory compression, it's really become a
prototyping area, which I don't think Greg likes all that much.

Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends
  2013-01-28 17:26     ` Seth Jennings
@ 2013-01-28 23:46       ` Minchan Kim
  0 siblings, 0 replies; 43+ messages in thread
From: Minchan Kim @ 2013-01-28 23:46 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Mon, Jan 28, 2013 at 11:26:43AM -0600, Seth Jennings wrote:
> On 01/27/2013 10:22 PM, Minchan Kim wrote:
> > On Mon, Jan 07, 2013 at 02:24:38PM -0600, Seth Jennings wrote:
> >> swap_writepage() is currently where frontswap hooks into the swap
> >> write path to capture pages with the frontswap_store() function.
> >> However, if a frontswap backend wants to "resume" the writeback of
> >> a page to the swap device, it can't call swap_writepage() as
> >> the page will simply reenter the backend.
> >>
> >> This patch separates swap_writepage() into a top and bottom half, the
> >> bottom half named __swap_writepage() to allow a frontswap backend,
> >> like zswap, to resume writeback beyond the frontswap_store() hook and
> >> by notified when the writeback completes.
> >>
> >> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> > 
> > Looks good to me except few nitpicks.
> 
> I broke these changes out from the main zswap patch (patch 8/9) in
> response to a request for Dan.  You are right in that there are
> changes here unrelated to the commit message.
> 
> The other changes are related to allowing zswap to do accounting on
> the number of outstanding flushes to the swap device.
> 
> If I'm going to break those out though the two resulting patches would be:
> 1. breakup __swap_writepage() and un-static __add_to_swap_cache() to
> allow resuming of swap page writeback
> 2. change __swap_writepage() signature to include end_write_func and
> un-static end_swap_bio_write() for accounting of outstanding flushes
> 

Or please add comment why you want to export some functions.

Thanks!

> Is that what you'd like to see?
> 
> Seth
> 
> > 
> > Acked-by: Minchan Kim <minchan@kernel.org>
> > 
> >> ---
> >>  include/linux/swap.h |    4 ++++
> >>  mm/page_io.c         |   22 +++++++++++++++++-----
> >>  mm/swap_state.c      |    2 +-
> >>  3 files changed, 22 insertions(+), 6 deletions(-)
> >>
> >> diff --git a/include/linux/swap.h b/include/linux/swap.h
> >> index 8c66486..a3da829 100644
> >> --- a/include/linux/swap.h
> >> +++ b/include/linux/swap.h
> >> @@ -321,6 +321,9 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t ent)
> >>  /* linux/mm/page_io.c */
> >>  extern int swap_readpage(struct page *);
> >>  extern int swap_writepage(struct page *page, struct writeback_control *wbc);
> >> +extern void end_swap_bio_write(struct bio *bio, int err);
> >> +extern int __swap_writepage(struct page *page, struct writeback_control *wbc,
> >> +	void (*end_write_func)(struct bio *, int));
> >>  extern int swap_set_page_dirty(struct page *page);
> >>  extern void end_swap_bio_read(struct bio *bio, int err);
> >>  
> >> @@ -335,6 +338,7 @@ extern struct address_space swapper_space;
> >>  extern void show_swap_cache_info(void);
> >>  extern int add_to_swap(struct page *);
> >>  extern int add_to_swap_cache(struct page *, swp_entry_t, gfp_t);
> >> +extern int __add_to_swap_cache(struct page *page, swp_entry_t entry);
> > 
> > What's related __add_to_swap_cache with this patch?
> > 
> >>  extern void __delete_from_swap_cache(struct page *);
> >>  extern void delete_from_swap_cache(struct page *);
> >>  extern void free_page_and_swap_cache(struct page *);
> >> diff --git a/mm/page_io.c b/mm/page_io.c
> >> index c535d39..806085e 100644
> >> --- a/mm/page_io.c
> >> +++ b/mm/page_io.c
> >> @@ -43,7 +43,7 @@ static struct bio *get_swap_bio(gfp_t gfp_flags,
> >>  	return bio;
> >>  }
> >>  
> >> -static void end_swap_bio_write(struct bio *bio, int err)
> >> +void end_swap_bio_write(struct bio *bio, int err)
> > 
> > Why do you remove static in this patch? It's not related to the patch.
> > 
> >>  {
> >>  	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
> >>  	struct page *page = bio->bi_io_vec[0].bv_page;
> >> @@ -180,15 +180,16 @@ bad_bmap:
> >>  	goto out;
> >>  }
> >>  
> >> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
> >> +	void (*end_write_func)(struct bio *, int));
> >> +
> >>  /*
> >>   * 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;
> >> -	struct swap_info_struct *sis = page_swap_info(page);
> >> +	int ret = 0;
> >>  
> >>  	if (try_to_free_swap(page)) {
> >>  		unlock_page(page);
> >> @@ -200,6 +201,17 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
> >>  		end_page_writeback(page);
> >>  		goto out;
> >>  	}
> >> +	ret = __swap_writepage(page, wbc, end_swap_bio_write);
> >> +out:
> >> +	return ret;
> >> +}
> >> +
> >> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
> >> +	void (*end_write_func)(struct bio *, int))
> >> +{
> >> +	struct bio *bio;
> >> +	int ret = 0, rw = WRITE;
> >> +	struct swap_info_struct *sis = page_swap_info(page);
> >>  
> >>  	if (sis->flags & SWP_FILE) {
> >>  		struct kiocb kiocb;
> >> @@ -227,7 +239,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
> >>  		return ret;
> >>  	}
> >>  
> >> -	bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
> >> +	bio = get_swap_bio(GFP_NOIO, page, end_write_func);
> >>  	if (bio == NULL) {
> >>  		set_page_dirty(page);
> >>  		unlock_page(page);
> >> diff --git a/mm/swap_state.c b/mm/swap_state.c
> >> index 0cb36fb..7eded9c 100644
> >> --- a/mm/swap_state.c
> >> +++ b/mm/swap_state.c
> >> @@ -67,7 +67,7 @@ void show_swap_cache_info(void)
> >>   * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,
> >>   * but sets SwapCache flag and private instead of mapping and index.
> >>   */
> >> -static int __add_to_swap_cache(struct page *page, swp_entry_t entry)
> >> +int __add_to_swap_cache(struct page *page, swp_entry_t entry)
> > 
> > Ditto
> > 
> >>  {
> >>  	int error;
> >>  
> >> -- 
> >> 1.7.9.5
> >>
> >> --
> >> To unsubscribe, send a message with 'unsubscribe linux-mm' in
> >> the body to majordomo@kvack.org.  For more info on Linux MM,
> >> see: http://www.linux-mm.org/ .
> >> Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
> > 
> 
> --
> To unsubscribe, send a message with 'unsubscribe linux-mm' in
> the body to majordomo@kvack.org.  For more info on Linux MM,
> see: http://www.linux-mm.org/ .
> Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>

-- 
Kind regards,
Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 8/9] zswap: add to mm/
  2013-01-28 15:27     ` Seth Jennings
@ 2013-01-29 10:21       ` Lord Glauber Costa of Sealand
  2013-02-07 16:13         ` Seth Jennings
  0 siblings, 1 reply; 43+ messages in thread
From: Lord Glauber Costa of Sealand @ 2013-01-29 10:21 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Rik van Riel, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Rzeszutek Wilk, Dan Magenheimer,
	Robert Jennings, Jenifer Hopper, Mel Gorman, Johannes Weiner,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/28/2013 07:27 PM, Seth Jennings wrote:
> Yes, I prototyped a shrinker interface for zswap, but, as we both
> figured, it shrinks the zswap compressed pool too aggressively to the
> point of being useless.
Can't you advertise a smaller number of objects that you actively have?

Since the shrinker would never try to shrink more objects than you
advertised, you could control pressure this way.


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-28  2:59         ` Minchan Kim
@ 2013-01-30 16:11           ` Konrad Rzeszutek Wilk
  2013-01-31  5:21             ` Minchan Kim
  0 siblings, 1 reply; 43+ messages in thread
From: Konrad Rzeszutek Wilk @ 2013-01-30 16:11 UTC (permalink / raw)
  To: Minchan Kim
  Cc: Dan Magenheimer, Seth Jennings, Greg Kroah-Hartman,
	Andrew Morton, Nitin Gupta, Robert Jennings, Jenifer Hopper,
	Mel Gorman, Johannes Weiner, Rik van Riel, Larry Woodman,
	linux-mm, linux-kernel, devel

On Mon, Jan 28, 2013 at 11:59:17AM +0900, Minchan Kim wrote:
> On Fri, Jan 25, 2013 at 07:56:29AM -0800, Dan Magenheimer wrote:
> > > From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> > > Subject: Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
> > > 
> > > On 01/24/2013 07:33 PM, Minchan Kim wrote:
> > > > Hi Seth, frontswap guys
> > > >
> > > > On Tue, Jan 8, 2013 at 5:24 AM, Seth Jennings
> > > > <sjenning@linux.vnet.ibm.com> wrote:
> > > >> zs_create_pool() currently takes a gfp flags argument
> > > >> that is used when growing the memory pool.  However
> > > >> it is not used in allocating the metadata for the pool
> > > >> itself.  That is currently hardcoded to GFP_KERNEL.
> > > >>
> > > >> zswap calls zs_create_pool() at swapon time which is done
> > > >> in atomic context, resulting in a "might sleep" warning.
> > > >
> > > > I didn't review this all series, really sorry but totday I saw Nitin
> > > > added Acked-by so I'm afraid Greg might get it under my radar. I'm not
> > > > strong against but I would like know why we should call frontswap_init
> > > > under swap_lock? Is there special reason?
> > > 
> > > The call stack is:
> > > 
> > > SYSCALL_DEFINE2(swapon.. <-- swapon_mutex taken here
> > > enable_swap_info() <-- swap_lock taken here
> > > frontswap_init()
> > > __frontswap_init()
> > > zswap_frontswap_init()
> > > zs_create_pool()
> > > 
> > > It isn't entirely clear to me why frontswap_init() is called under
> > > lock.  Then again, I'm not entirely sure what the swap_lock protects.
> > >  There are no comments near the swap_lock definition to tell me.
> > > 
> > > I would guess that the intent is to block any writes to the swap
> > > device until frontswap_init() has completed.
> > > 
> > > Dan care to weigh in?
> > 
> > I think frontswap's first appearance needs to be atomic, i.e.
> > the transition from (a) frontswap is not present and will fail
> > all calls, to (b) frontswap is fully functional... that transition
> > must be atomic.  And, once Konrad's module patches are in, the

To be fair it can be "delayed". Say the swap disk is in heavy usage and
the backend is registered. The time between the backend going online and
the frontswap_store functions calling in the backend can be delayed (so
we can use a racy unsigned long to check when the backend is on).

Obviously the opposite is not acceptable (so unsigned long says
backend is enabled, but in reality the backend has not yet been
initialized).

> > opposite transition must be atomic also.  But there are most
> > likely other ways to do those transitions atomically that
> > don't need to hold swap_lock.

Right. The opposite transition would be when a backend is unloaded.
Which is something we don't do yet. For that to work we would need
to make the "gatekeeper" (this unsigned long I've been referring to)
be atomic. Or at least in some fashion - either via spinlocks or perhaps
using static_key to patch the branching of the code. Naturally to
unload a module extra things such as flushing all the pages the backend
has to the disk is required.
> 
> It could be raced once swap_info is registered.
> But what's the problem if we call frontswap_init before calling
> _enable_swap_info out of lock?

So, we have two locks - the mutex and the spin_lock. I think we are
fine without the spinlock (swap_lock). 

> Swap subsystem never do I/O before it register new swap_info_struct.
> 
> And IMHO, if frontswap is to be atomic, it would be better to have
> own scheme without dependency of swap_lock if it's possible.

I think that can be independent of that lock. We are still under
the mutex (swapon_mutex) which protects us against two threads doing
swapon/swapoff and messing things up.
> > 
> > Honestly, I never really focused on the initialization code
> > so I am very open to improvements as long as they work for
> > all the various frontswap backends.
> 
> How about this?
> 
> From 157a3edf49feb93be0595574beb153b322ddf7d2 Mon Sep 17 00:00:00 2001
> From: Minchan Kim <minchan@kernel.org>
> Date: Mon, 28 Jan 2013 11:34:00 +0900
> Subject: [PATCH] frontswap: Get rid of swap_lock dependency
> 
> Frontswap initialization routine depends on swap_lock, which want
> to be atomic about frontswap's first appearance.
> IOW, frontswap is not present and will fail all calls OR frontswap is
> fully functional but if new swap_info_struct isn't registered
> by enable_swap_info, swap subsystem doesn't start I/O so there is no race
> between init procedure and page I/O working on frontswap.
> 
> So let's remove unncessary swap_lock dependency.

This looks good. I hadn't yet had a chance to test it out though.

> 
> Cc: Dan Magenheimer <dan.magenheimer@oracle.com>
> Cc: Konrad Rzeszutek Wilk <konrad@darnok.org>
> Signed-off-by: Minchan Kim <minchan@kernel.org>
> ---
>  include/linux/frontswap.h |    6 +++---
>  mm/frontswap.c            |    7 ++++---
>  mm/swapfile.c             |   11 +++++------
>  3 files changed, 12 insertions(+), 12 deletions(-)
> 
> diff --git a/include/linux/frontswap.h b/include/linux/frontswap.h
> index 3044254..b7e238e 100644
> --- a/include/linux/frontswap.h
> +++ b/include/linux/frontswap.h
> @@ -22,7 +22,7 @@ extern void frontswap_writethrough(bool);
>  #define FRONTSWAP_HAS_EXCLUSIVE_GETS
>  extern void frontswap_tmem_exclusive_gets(bool);
>  
> -extern void __frontswap_init(unsigned type);
> +extern void __frontswap_init(unsigned type, unsigned long *map);
>  extern int __frontswap_store(struct page *page);
>  extern int __frontswap_load(struct page *page);
>  extern void __frontswap_invalidate_page(unsigned, pgoff_t);
> @@ -120,10 +120,10 @@ static inline void frontswap_invalidate_area(unsigned type)
>  		__frontswap_invalidate_area(type);
>  }
>  
> -static inline void frontswap_init(unsigned type)
> +static inline void frontswap_init(unsigned type, unsigned long *map)
>  {
>  	if (frontswap_enabled)
> -		__frontswap_init(type);
> +		__frontswap_init(type, map);
>  }
>  
>  #endif /* _LINUX_FRONTSWAP_H */
> diff --git a/mm/frontswap.c b/mm/frontswap.c
> index 2890e67..bad21b0 100644
> --- a/mm/frontswap.c
> +++ b/mm/frontswap.c
> @@ -115,13 +115,14 @@ EXPORT_SYMBOL(frontswap_tmem_exclusive_gets);
>  /*
>   * Called when a swap device is swapon'd.
>   */
> -void __frontswap_init(unsigned type)
> +void __frontswap_init(unsigned type, unsigned long *map)
>  {
>  	struct swap_info_struct *sis = swap_info[type];
>  
>  	BUG_ON(sis == NULL);
> -	if (sis->frontswap_map == NULL)
> -		return;
> +	BUG_ON(sis->frontswap_map);
> +
> +	frontswap_map_set(sis, map);
>  	frontswap_ops.init(type);
>  }
>  EXPORT_SYMBOL(__frontswap_init);
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index dfaff5f..652e4fc 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -1497,8 +1497,7 @@ static int setup_swap_extents(struct swap_info_struct *sis, sector_t *span)
>  }
>  
>  static void _enable_swap_info(struct swap_info_struct *p, int prio,
> -				unsigned char *swap_map,
> -				unsigned long *frontswap_map)
> +				unsigned char *swap_map)
>  {
>  	int i, prev;
>  
> @@ -1507,7 +1506,6 @@ static void _enable_swap_info(struct swap_info_struct *p, int prio,
>  	else
>  		p->prio = --least_priority;
>  	p->swap_map = swap_map;
> -	frontswap_map_set(p, frontswap_map);
>  	p->flags |= SWP_WRITEOK;
>  	atomic_long_add(p->pages, &nr_swap_pages);
>  	total_swap_pages += p->pages;
> @@ -1530,10 +1528,10 @@ static void enable_swap_info(struct swap_info_struct *p, int prio,
>  				unsigned char *swap_map,
>  				unsigned long *frontswap_map)
>  {
> +	frontswap_init(p->type, frontswap_map);
>  	spin_lock(&swap_lock);
>  	spin_lock(&p->lock);
> -	_enable_swap_info(p, prio, swap_map, frontswap_map);
> -	frontswap_init(p->type);
> +	_enable_swap_info(p, prio, swap_map);
>  	spin_unlock(&p->lock);
>  	spin_unlock(&swap_lock);
>  }
> @@ -1542,7 +1540,7 @@ static void reinsert_swap_info(struct swap_info_struct *p)
>  {
>  	spin_lock(&swap_lock);
>  	spin_lock(&p->lock);
> -	_enable_swap_info(p, p->prio, p->swap_map, frontswap_map_get(p));
> +	_enable_swap_info(p, p->prio, p->swap_map);
>  	spin_unlock(&p->lock);
>  	spin_unlock(&swap_lock);
>  }
> @@ -1651,6 +1649,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
>  	p->swap_map = NULL;
>  	p->flags = 0;
>  	frontswap_invalidate_area(type);
> +	frontswap_map_set(p, NULL);
>  	spin_unlock(&p->lock);
>  	spin_unlock(&swap_lock);
>  	mutex_unlock(&swapon_mutex);
> -- 
> 1.7.9.5
> 
> 
> > 
> > Dan
> > 
> > --
> > To unsubscribe, send a message with 'unsubscribe linux-mm' in
> > the body to majordomo@kvack.org.  For more info on Linux MM,
> > see: http://www.linux-mm.org/ .
> > Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
> 
> -- 
> Kind regards,
> Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
  2013-01-30 16:11           ` Konrad Rzeszutek Wilk
@ 2013-01-31  5:21             ` Minchan Kim
  0 siblings, 0 replies; 43+ messages in thread
From: Minchan Kim @ 2013-01-31  5:21 UTC (permalink / raw)
  To: Konrad Rzeszutek Wilk
  Cc: Dan Magenheimer, Seth Jennings, Greg Kroah-Hartman,
	Andrew Morton, Nitin Gupta, Robert Jennings, Jenifer Hopper,
	Mel Gorman, Johannes Weiner, Rik van Riel, Larry Woodman,
	linux-mm, linux-kernel, devel

On Wed, Jan 30, 2013 at 11:11:47AM -0500, Konrad Rzeszutek Wilk wrote:
> On Mon, Jan 28, 2013 at 11:59:17AM +0900, Minchan Kim wrote:
> > On Fri, Jan 25, 2013 at 07:56:29AM -0800, Dan Magenheimer wrote:
> > > > From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> > > > Subject: Re: [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool
> > > > 
> > > > On 01/24/2013 07:33 PM, Minchan Kim wrote:
> > > > > Hi Seth, frontswap guys
> > > > >
> > > > > On Tue, Jan 8, 2013 at 5:24 AM, Seth Jennings
> > > > > <sjenning@linux.vnet.ibm.com> wrote:
> > > > >> zs_create_pool() currently takes a gfp flags argument
> > > > >> that is used when growing the memory pool.  However
> > > > >> it is not used in allocating the metadata for the pool
> > > > >> itself.  That is currently hardcoded to GFP_KERNEL.
> > > > >>
> > > > >> zswap calls zs_create_pool() at swapon time which is done
> > > > >> in atomic context, resulting in a "might sleep" warning.
> > > > >
> > > > > I didn't review this all series, really sorry but totday I saw Nitin
> > > > > added Acked-by so I'm afraid Greg might get it under my radar. I'm not
> > > > > strong against but I would like know why we should call frontswap_init
> > > > > under swap_lock? Is there special reason?
> > > > 
> > > > The call stack is:
> > > > 
> > > > SYSCALL_DEFINE2(swapon.. <-- swapon_mutex taken here
> > > > enable_swap_info() <-- swap_lock taken here
> > > > frontswap_init()
> > > > __frontswap_init()
> > > > zswap_frontswap_init()
> > > > zs_create_pool()
> > > > 
> > > > It isn't entirely clear to me why frontswap_init() is called under
> > > > lock.  Then again, I'm not entirely sure what the swap_lock protects.
> > > >  There are no comments near the swap_lock definition to tell me.
> > > > 
> > > > I would guess that the intent is to block any writes to the swap
> > > > device until frontswap_init() has completed.
> > > > 
> > > > Dan care to weigh in?
> > > 
> > > I think frontswap's first appearance needs to be atomic, i.e.
> > > the transition from (a) frontswap is not present and will fail
> > > all calls, to (b) frontswap is fully functional... that transition
> > > must be atomic.  And, once Konrad's module patches are in, the
> 
> To be fair it can be "delayed". Say the swap disk is in heavy usage and
> the backend is registered. The time between the backend going online and
> the frontswap_store functions calling in the backend can be delayed (so
> we can use a racy unsigned long to check when the backend is on).
> 
> Obviously the opposite is not acceptable (so unsigned long says
> backend is enabled, but in reality the backend has not yet been
> initialized).
> 
> > > opposite transition must be atomic also.  But there are most
> > > likely other ways to do those transitions atomically that
> > > don't need to hold swap_lock.
> 
> Right. The opposite transition would be when a backend is unloaded.
> Which is something we don't do yet. For that to work we would need
> to make the "gatekeeper" (this unsigned long I've been referring to)
> be atomic. Or at least in some fashion - either via spinlocks or perhaps
> using static_key to patch the branching of the code. Naturally to
> unload a module extra things such as flushing all the pages the backend
> has to the disk is required.
> > 
> > It could be raced once swap_info is registered.
> > But what's the problem if we call frontswap_init before calling
> > _enable_swap_info out of lock?
> 
> So, we have two locks - the mutex and the spin_lock. I think we are
> fine without the spinlock (swap_lock). 
> 
> > Swap subsystem never do I/O before it register new swap_info_struct.
> > 
> > And IMHO, if frontswap is to be atomic, it would be better to have
> > own scheme without dependency of swap_lock if it's possible.
> 
> I think that can be independent of that lock. We are still under
> the mutex (swapon_mutex) which protects us against two threads doing
> swapon/swapoff and messing things up.
> > > 
> > > Honestly, I never really focused on the initialization code
> > > so I am very open to improvements as long as they work for
> > > all the various frontswap backends.
> > 
> > How about this?
> > 
> > From 157a3edf49feb93be0595574beb153b322ddf7d2 Mon Sep 17 00:00:00 2001
> > From: Minchan Kim <minchan@kernel.org>
> > Date: Mon, 28 Jan 2013 11:34:00 +0900
> > Subject: [PATCH] frontswap: Get rid of swap_lock dependency
> > 
> > Frontswap initialization routine depends on swap_lock, which want
> > to be atomic about frontswap's first appearance.
> > IOW, frontswap is not present and will fail all calls OR frontswap is
> > fully functional but if new swap_info_struct isn't registered
> > by enable_swap_info, swap subsystem doesn't start I/O so there is no race
> > between init procedure and page I/O working on frontswap.
> > 
> > So let's remove unncessary swap_lock dependency.
> 
> This looks good. I hadn't yet had a chance to test it out though.

I hope you pick up if it pass your test.
Thanks, Konrad!

-- 
Kind regards,
Minchan Kim

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCHv2 8/9] zswap: add to mm/
  2013-01-29 10:21       ` Lord Glauber Costa of Sealand
@ 2013-02-07 16:13         ` Seth Jennings
  2013-02-11 19:13           ` Dan Magenheimer
  0 siblings, 1 reply; 43+ messages in thread
From: Seth Jennings @ 2013-02-07 16:13 UTC (permalink / raw)
  To: Lord Glauber Costa of Sealand
  Cc: Rik van Riel, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Rzeszutek Wilk, Dan Magenheimer,
	Robert Jennings, Jenifer Hopper, Mel Gorman, Johannes Weiner,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/29/2013 04:21 AM, Lord Glauber Costa of Sealand wrote:
> On 01/28/2013 07:27 PM, Seth Jennings wrote:
>> Yes, I prototyped a shrinker interface for zswap, but, as we both
>> figured, it shrinks the zswap compressed pool too aggressively to the
>> point of being useless.
> Can't you advertise a smaller number of objects that you actively have?

Thanks for looking at the code!

An interesting idea.  I'm just not sure how you would manage the
underlying policy of how aggressively does zswap allow itself to be
shrunk?  The fact that zswap _only_ operates under memory pressure
makes that policy difficult, because it is under continuous shrinking
pressure, unlike other shrinkable caches in the kernel that spend most
of their time operating in unconstrained or lightly/intermittently
strained conditions.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 43+ messages in thread

* RE: [PATCHv2 8/9] zswap: add to mm/
  2013-02-07 16:13         ` Seth Jennings
@ 2013-02-11 19:13           ` Dan Magenheimer
  0 siblings, 0 replies; 43+ messages in thread
From: Dan Magenheimer @ 2013-02-11 19:13 UTC (permalink / raw)
  To: Seth Jennings, Lord Glauber Costa of Sealand
  Cc: Rik van Riel, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Wilk, Robert Jennings, Jenifer Hopper,
	Mel Gorman, Johannes Weiner, Larry Woodman, linux-mm,
	linux-kernel, devel

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: Re: [PATCHv2 8/9] zswap: add to mm/
> 
> On 01/29/2013 04:21 AM, Lord Glauber Costa of Sealand wrote:
> > On 01/28/2013 07:27 PM, Seth Jennings wrote:
> >> Yes, I prototyped a shrinker interface for zswap, but, as we both
> >> figured, it shrinks the zswap compressed pool too aggressively to the
> >> point of being useless.
> > Can't you advertise a smaller number of objects that you actively have?
> 
> Thanks for looking at the code!
> 
> An interesting idea.  I'm just not sure how you would manage the
> underlying policy of how aggressively does zswap allow itself to be
> shrunk?  The fact that zswap _only_ operates under memory pressure
> makes that policy difficult, because it is under continuous shrinking
> pressure, unlike other shrinkable caches in the kernel that spend most
> of their time operating in unconstrained or lightly/intermittently
> strained conditions.

Hi Seth --

Zswap (as well as zcache) doesn't "_only_ operate under memory
pressure".  It _grows_ only under memory pressure but can get
smaller via frontswap_loads and frontswap_invalidates
at other times.  I agree that writeback (from zswap to the
real swap disk, what zswap calls "flush") need only occur
when under memory pressure, but that's when a shrinker is called.

FYI, the way that zcache does this (for swap pages) is the
zcache shrinker drives the number of wholepages used to store
zpages down to match the number of wholepages used for anonymous
pages.  In zswap terms, that means you would call zswap_flush_entry
in a zswap shrinker thread continually until

 zswap_pool_pages <= global_page_state(NR_LRU_BASE + LRU_ACTIVE_ANON) +
                     global_page_state(NR_LRU_BASE + LRU_INACTIVE_ANON)

The zcache shrinker (currently) ignores nr_to_scan entirely;
the fact that the zcache shrinker is called is the signal for
zswap/zcache to start flush/writeback (moving compressed pages out to
swap disk).  This isn't a great match for the system shrinker
API but it seems to avoid the "aggressively to the point of
being useless" problem so is at least a step in the right direction.

Dan

^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~2013-02-11 19:16 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-07 20:24 [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings
2013-01-07 20:24 ` [PATCHv2 1/9] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
2013-01-25  0:08   ` Nitin Gupta
2013-01-25  1:33   ` Minchan Kim
2013-01-25 15:07     ` Seth Jennings
2013-01-25 15:56       ` Dan Magenheimer
2013-01-28  2:59         ` Minchan Kim
2013-01-30 16:11           ` Konrad Rzeszutek Wilk
2013-01-31  5:21             ` Minchan Kim
2013-01-25 21:26   ` Rik van Riel
2013-01-07 20:24 ` [PATCHv2 2/9] staging: zsmalloc: remove unsed pool name Seth Jennings
2013-01-25  0:09   ` Nitin Gupta
2013-01-25 21:50   ` Rik van Riel
2013-01-07 20:24 ` [PATCHv2 3/9] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
2013-01-25  0:11   ` Nitin Gupta
2013-01-25 21:55   ` Rik van Riel
2013-01-07 20:24 ` [PATCHv2 4/9] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
2013-01-25  0:17   ` Nitin Gupta
2013-01-25 16:38     ` Seth Jennings
2013-01-07 20:24 ` [PATCHv2 5/9] debugfs: add get/set for atomic types Seth Jennings
2013-01-07 20:32   ` Greg Kroah-Hartman
2013-01-07 20:41     ` Seth Jennings
2013-01-25 16:45       ` Seth Jennings
2013-01-25 21:35         ` Greg Kroah-Hartman
2013-01-07 20:24 ` [PATCHv2 6/9] zsmalloc: promote to lib/ Seth Jennings
2013-01-28  4:01   ` Minchan Kim
2013-01-28  4:32     ` Minchan Kim
2013-01-28 17:41       ` Seth Jennings
2013-01-07 20:24 ` [PATCHv2 7/9] mm: break up swap_writepage() for frontswap backends Seth Jennings
2013-01-28  4:22   ` Minchan Kim
2013-01-28 17:26     ` Seth Jennings
2013-01-28 23:46       ` Minchan Kim
2013-01-07 20:24 ` [PATCHv2 8/9] zswap: add to mm/ Seth Jennings
2013-01-08 17:15   ` Dave Hansen
2013-01-08 17:54     ` Dan Magenheimer
2013-01-25 22:44   ` Rik van Riel
2013-01-25 23:15     ` Dan Magenheimer
2013-01-28 15:27     ` Seth Jennings
2013-01-29 10:21       ` Lord Glauber Costa of Sealand
2013-02-07 16:13         ` Seth Jennings
2013-02-11 19:13           ` Dan Magenheimer
2013-01-07 20:24 ` [PATCHv2 9/9] zswap: add documentation Seth Jennings
2013-01-22 18:10 ` [PATCHv2 0/9] zswap: compressed swap caching Seth Jennings

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).