linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/6] compcache: compressed RAM block device
@ 2008-03-24 15:02 Nitin Gupta
  2008-03-24 16:23 ` Will Newton
  0 siblings, 1 reply; 4+ messages in thread
From: Nitin Gupta @ 2008-03-24 15:02 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-mm

This creates RAM based block device (called ramzswap0) which is used as swap disk.

On write (swap-out):
 - compress page (using LZO)
 - Allocate required amount of memory (using TLSF)
 - Store reference to its location in simple array.

On read (swap-in):
 - Get compressed page location from array
 - Decompress this page.

It also makes required Makefile changes.

Signed-off-by: Nitin Gupta <nitingupta910 at gmail dot com>
---
 drivers/block/Kconfig     |   10 +
 drivers/block/Makefile    |    1 +
 drivers/block/compcache.c |  440 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 451 insertions(+), 0 deletions(-)

diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 0d1d213..0cba42f 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -347,6 +347,16 @@ config BLK_DEV_RAM_SIZE
 	  The default value is 4096 kilobytes. Only change this if you know
 	  what you are doing.
 
+config BLK_DEV_COMPCACHE
+	tristate "Compressed RAM based swap device"
+	select TLSF
+	select LZO_COMPRESS
+	select LZO_DECOMPRESS
+	help
+	  This creates RAM based block device which acts as swap disk. Pages
+	  swapped to this disk are compressed and stored in memory itself.
+	  Project Home: http://code.google.com/p/compcache/
+
 config BLK_DEV_XIP
 	bool "Support XIP filesystems on RAM block device"
 	depends on BLK_DEV_RAM
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 5e58430..b6d3dd2 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_PS3_DISK)		+= ps3disk.o
 obj-$(CONFIG_ATARI_FLOPPY)	+= ataflop.o
 obj-$(CONFIG_AMIGA_Z2RAM)	+= z2ram.o
 obj-$(CONFIG_BLK_DEV_RAM)	+= brd.o
+obj-$(CONFIG_BLK_DEV_COMPCACHE)	+= compcache.o
 obj-$(CONFIG_BLK_DEV_LOOP)	+= loop.o
 obj-$(CONFIG_BLK_DEV_XD)	+= xd.o
 obj-$(CONFIG_BLK_CPQ_DA)	+= cpqarray.o
diff --git a/drivers/block/compcache.c b/drivers/block/compcache.c
new file mode 100644
index 0000000..4ffcd63
--- /dev/null
+++ b/drivers/block/compcache.c
@@ -0,0 +1,440 @@
+/*
+ * Compressed RAM based swap device
+ *
+ * (C) Nitin Gupta
+ *
+ * This RAM based block device acts as swap disk.
+ * Pages swapped to this device are compressed and
+ * stored in memory.
+ *
+ * Project home: http://code.google.com/p/compcache
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/device.h>
+#include <linux/genhd.h>
+#include <linux/highmem.h>
+#include <linux/lzo.h>
+#include <linux/mutex.h>
+#include <linux/proc_fs.h>
+#include <linux/swap.h>
+#include <linux/tlsf.h>
+#include <linux/vmalloc.h>
+#include <asm/pgtable.h>
+#include <asm/string.h>
+
+#include "compcache.h"
+
+static struct block_device_operations compcache_devops = {
+	.owner = THIS_MODULE,
+};
+
+static struct compcache compcache;
+static unsigned long compcache_size_kbytes;
+#if STATS
+static struct compcache_stats stats;
+#endif
+
+#ifdef CONFIG_COMPCACHE_PROC
+static struct proc_dir_entry *proc;
+
+static int proc_compcache_read(char *page, char **start, off_t off,
+				int count, int *eof, void *data)
+{
+	int len;
+#if STATS
+	size_t succ_writes;
+	unsigned int good_compress_perc = 0, no_compress_perc = 0;
+#endif
+
+	if (off > 0) {
+		*eof = 1;
+		return 0;
+	}
+
+	len = sprintf(page,
+		"DiskSize:	%8u kB\n",
+		compcache.size >> (10 - SECTOR_SHIFT));
+#if STATS
+	succ_writes = stats.num_writes - stats.failed_writes;
+	if (succ_writes) {
+		good_compress_perc = stats.good_compress * 100 / succ_writes;
+		no_compress_perc = stats.pages_expand * 100 / succ_writes;
+	}
+
+	len += sprintf(page + len,
+		"NumReads:	%8u\n"
+		"NumWrites:	%8u\n"
+		"FailedReads:	%8u\n"
+		"FailedWrites:	%8u\n"
+		"InvalidIO:	%8u\n"
+		"GoodCompress:	%8u %%\n"
+		"NoCompress:	%8u %%\n"
+		"CurrentPages:	%8zu\n"
+		"CurrentMem:	%8zu kB\n"
+		"PeakMem:	%8zu kB\n",
+		stats.num_reads,
+		stats.num_writes,
+		stats.failed_reads,
+		stats.failed_writes,
+		stats.invalid_io,
+		good_compress_perc,
+		no_compress_perc,
+		stats.curr_pages,
+		K(stats.curr_mem),
+		K(stats.peak_mem));
+#endif
+	return len;
+}
+#endif	/* CONFIG_COMPCACHE_PROC */
+
+/* Check if request is within bounds and page aligned */
+static inline int valid_swap_request(struct bio *bio)
+{
+	if (unlikely((bio->bi_sector >= compcache.size) ||
+			(bio->bi_sector & (SECTORS_PER_PAGE - 1)) ||
+			(bio->bi_vcnt != 1) ||
+			(bio->bi_size != PAGE_SIZE) ||
+			(bio->bi_io_vec[0].bv_offset != 0)))
+		return 0;
+	return 1;
+}
+
+static int compcache_make_request(struct request_queue *queue, struct bio *bio)
+{
+	int ret;
+	size_t clen, page_no;
+	void *user_mem;
+	struct page *page;
+
+	if (!valid_swap_request(bio)) {
+		stat_inc(&stats.invalid_io);
+		goto out_nomap;
+	}
+
+	CC_DEBUG2("bio sector: %lu (%s)\n", (unsigned long)bio->bi_sector,
+				bio_data_dir(bio) == READ ? "R" : "W");
+
+	page = bio->bi_io_vec[0].bv_page;
+	page_no = bio->bi_sector >> SECTORS_PER_PAGE_SHIFT;
+	user_mem = kmap(page);
+
+	if (bio_data_dir(bio) == READ) {
+		stat_inc(&stats.num_reads);
+		/*
+		 * This is attempt to read before any previous write
+		 * to this location. This happens due to readahead when
+		 * swap device is read from user-space (e.g. during swapon)
+		 */
+		if (unlikely(compcache.table[page_no].addr == NULL)) {
+			CC_DEBUG("Read before write on swap device: "
+				"sector=%lu, size=%zu, offset=%zu\n",
+				(unsigned long)(bio->bi_sector),
+				bio->bi_size,
+				bio->bi_io_vec[0].bv_offset);
+			memset(user_mem, 0, PAGE_SIZE);
+			kunmap(page);
+			set_bit(BIO_UPTODATE, &bio->bi_flags);
+			bio_endio(bio, 0);
+			return 0;
+		}
+
+		/* Page is stored uncompressed since its incompressible */
+		if (unlikely(compcache.table[page_no].len == PAGE_SIZE)) {
+			memcpy(user_mem, compcache.table[page_no].addr,
+							PAGE_SIZE);
+			kunmap(page);
+			set_bit(BIO_UPTODATE, &bio->bi_flags);
+			bio_endio(bio, 0);
+			return 0;
+		}
+
+		clen = PAGE_SIZE;
+		ret = lzo1x_decompress_safe(
+			compcache.table[page_no].addr,
+			compcache.table[page_no].len,
+			user_mem,
+			&clen);
+
+		/* should NEVER happen */
+		if (unlikely(ret != LZO_E_OK)) {
+			pr_err(C "Decompression failed! "
+				"err=%d, page=%zu, len=%u\n", ret, page_no,
+				compcache.table[page_no].len);
+			stat_inc(&stats.failed_reads);
+			goto out;
+		}
+
+		CC_DEBUG2("Page decompressed: page_no=%zu\n", page_no);
+		kunmap(page);
+		set_bit(BIO_UPTODATE, &bio->bi_flags);
+		bio_endio(bio, 0);
+		return 0;
+	} else {	/* Write */
+		unsigned char *src = compcache.compress_buffer;
+		stat_inc(&stats.num_writes);
+		/*
+		 * System swaps to same sector again when the stored page
+		 * is no longer referenced by any process. So, its now safe
+		 * to free the memory that was allocated for this page.
+		 */
+		if (compcache.table[page_no].addr) {
+			CC_DEBUG2("Freeing page: %zu\n", page_no);
+			tlsf_free(compcache.table[page_no].addr,
+				compcache.mem_pool);
+			stat_dec(&stats.curr_pages);
+			compcache.table[page_no].addr = NULL;
+			compcache.table[page_no].len = 0;
+		}
+
+		mutex_lock(&compcache.lock);
+		ret = lzo1x_1_compress(user_mem, PAGE_SIZE,
+			src, &clen, compcache.compress_workmem);
+		if (unlikely(ret != LZO_E_OK)) {
+			mutex_unlock(&compcache.lock);
+			pr_err(C "Compression failed! err=%d\n", ret);
+			compcache.table[page_no].addr = NULL;
+			compcache.table[page_no].len = 0;
+			stat_inc(&stats.failed_writes);
+			goto out;
+		}
+
+		/* Page is incompressible - store it as is */
+		if (clen >= PAGE_SIZE) {
+			CC_DEBUG("Page expand on compression: "
+				"page=%zu, size=%zu\n", page_no, clen);
+			clen = PAGE_SIZE;
+			src = user_mem;
+		} else {
+			CC_DEBUG2("Page compressed: page_no=%zu, len=%zu\n",
+				page_no, clen);
+		}
+		if ((compcache.table[page_no].addr = tlsf_malloc(clen,
+					compcache.mem_pool)) == NULL) {
+			mutex_unlock(&compcache.lock);
+			pr_err(C "Error allocating memory for compressed "
+				"page: %zu, size=%zu \n", page_no, clen);
+			compcache.table[page_no].len = 0;
+			stat_inc(&stats.failed_writes);
+			goto out;
+		}
+		
+		memcpy(compcache.table[page_no].addr, src, clen);
+
+		/* Update stats */
+		stat_inc(&stats.curr_pages);
+		stat_set(&stats.curr_mem, stats.curr_mem + clen);
+		stat_setmax(&stats.peak_mem, stats.curr_mem);
+		stat_inc_if_less(&stats.pages_expand, PAGE_SIZE - 1, clen);
+		stat_inc_if_less(&stats.good_compress, clen,
+						PAGE_SIZE / 2 + 1);
+		mutex_unlock(&compcache.lock);
+		
+		compcache.table[page_no].len = clen;
+
+		kunmap(page);
+		set_bit(BIO_UPTODATE, &bio->bi_flags);
+		bio_endio(bio, 0);
+		return 0;
+	}
+out:
+	kunmap(page);
+out_nomap:
+	bio_io_error(bio);
+	return 0;
+}
+
+static void setup_swap_header(union swap_header *s)
+{
+	s->info.version = 1;
+	s->info.last_page = compcache.size >> SECTORS_PER_PAGE_SHIFT;
+	s->info.nr_badpages = 0;
+	memcpy(s->magic.magic, "SWAPSPACE2", 10);
+}
+
+static void *get_mem(size_t size)
+{
+	return __vmalloc(size, GFP_NOIO, PAGE_KERNEL);
+}
+
+static void put_mem(void *ptr)
+{
+	vfree(ptr);
+}
+
+static int __init compcache_init(void)
+{
+	int ret;
+	size_t num_pages;
+	struct sysinfo i;
+
+	mutex_init(&compcache.lock);
+
+	if (compcache_size_kbytes == 0) {
+		pr_info(C "compcache size not provided."
+			" Using default: (%u%% of Total RAM).\n"
+			"Use compcache_size_kbytes module param to specify"
+			" custom size\n", DEFAULT_COMPCACHE_PERCENT);
+		si_meminfo(&i);
+		compcache_size_kbytes = ((DEFAULT_COMPCACHE_PERCENT *
+				i.totalram) / 100) << (PAGE_SHIFT - 10);
+	}
+	
+	CC_DEBUG2("compcache_size_kbytes=%lu\n", compcache_size_kbytes);
+	compcache.size = compcache_size_kbytes << 10;
+	compcache.size = (compcache.size + PAGE_SIZE - 1) & PAGE_MASK;
+	pr_info(C "Compressed swap size set to: %zu KB\n", compcache.size >> 10);
+	compcache.size >>= SECTOR_SHIFT;
+
+	compcache.compress_workmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
+	if (compcache.compress_workmem == NULL) {
+		pr_err(C "Error allocating compressor working memory\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	compcache.compress_buffer = kmalloc(2 * PAGE_SIZE, GFP_KERNEL);
+	if (compcache.compress_buffer == NULL) {
+		pr_err(C "Error allocating compressor buffer space\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	num_pages = compcache.size >> SECTORS_PER_PAGE_SHIFT;
+        compcache.table = vmalloc(num_pages * sizeof(*compcache.table));
+        if (compcache.table == NULL) {
+                pr_err(C "Error allocating compcache address table\n");
+                ret = -ENOMEM;
+                goto fail;
+        }
+        memset(compcache.table, 0, num_pages * sizeof(*compcache.table));
+
+	compcache.table[0].addr = (void *)get_zeroed_page(GFP_KERNEL);
+	if (compcache.table[0].addr == NULL) {
+		pr_err(C "Error allocating swap header page\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+	compcache.table[0].len = PAGE_SIZE;
+	setup_swap_header((union swap_header *)(compcache.table[0].addr));
+
+	compcache.disk = alloc_disk(1);
+	if (compcache.disk == NULL) {
+		pr_err(C "Error allocating disk structure\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	compcache.disk->first_minor = 0;
+	compcache.disk->fops = &compcache_devops;
+	/*
+	 * It is named like this to prevent distro installers
+	 * from offering compcache as installation target. They
+	 * seem to ignore all devices beginning with 'ram'
+	 */
+	sprintf(compcache.disk->disk_name, "%s", "ramzswap0");
+
+	compcache.disk->major = register_blkdev(0, compcache.disk->disk_name);
+	if (compcache.disk->major < 0) {
+		pr_err(C "Cannot register block device\n");
+		ret = -EFAULT;
+		goto fail;
+	}
+
+	compcache.disk->queue = blk_alloc_queue(GFP_KERNEL);
+	if (compcache.disk->queue == NULL) {
+		pr_err(C "Cannot register disk queue\n");
+		ret = -EFAULT;
+		goto fail;
+	}
+
+	set_capacity(compcache.disk, compcache.size);
+	blk_queue_make_request(compcache.disk->queue, compcache_make_request);
+	blk_queue_hardsect_size(compcache.disk->queue, PAGE_SIZE);
+	add_disk(compcache.disk);
+
+	compcache.mem_pool = tlsf_create_memory_pool("compcache",
+				get_mem, put_mem,
+				INIT_SIZE, 0, GROW_SIZE);
+	if (compcache.mem_pool == NULL) {
+		pr_err(C "Error creating memory pool\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+#ifdef CONFIG_COMPCACHE_PROC
+	proc = create_proc_entry("compcache", S_IRUGO, NULL);
+	if (proc)
+		proc->read_proc = &proc_compcache_read;
+	else {
+		ret = -ENOMEM;
+		pr_warning(C "Error creating proc entry\n");
+		goto fail;
+	}
+#endif
+
+	CC_DEBUG(C "Initialization done!\n");
+	return 0;
+
+fail:
+	if (compcache.disk != NULL) {
+		if (compcache.disk->major > 0)
+			unregister_blkdev(compcache.disk->major,
+					compcache.disk->disk_name);
+		del_gendisk(compcache.disk);
+	}
+
+	if (compcache.table[0].addr)
+		free_page((unsigned long)compcache.table[0].addr);
+	if (compcache.compress_workmem)
+		kfree(compcache.compress_workmem);
+	if (compcache.compress_buffer)
+		kfree(compcache.compress_buffer);
+	if (compcache.table)
+		vfree(compcache.table);
+	if (compcache.mem_pool)
+		tlsf_destroy_memory_pool(compcache.mem_pool);
+#ifdef CONFIG_COMPCACHE_PROC
+	if (proc)
+		remove_proc_entry("compcache", &proc_root);
+#endif
+	pr_err(C "Initialization failed: err=%d\n", ret);
+	return ret;
+}
+
+static void __exit compcache_exit(void)
+{
+	size_t i, num_pages;
+	num_pages = compcache.size >> SECTORS_PER_PAGE_SHIFT;
+
+	unregister_blkdev(compcache.disk->major, compcache.disk->disk_name);
+	del_gendisk(compcache.disk);
+	free_page((unsigned long)compcache.table[0].addr);
+	kfree(compcache.compress_workmem);
+	kfree(compcache.compress_buffer);
+
+	/* Free all pages that are still in compcache */
+	for (i = 1; i < num_pages; i++)
+		if (compcache.table[i].addr)
+			tlsf_free(compcache.table[i].addr, compcache.mem_pool);
+	vfree(compcache.table);
+	tlsf_destroy_memory_pool(compcache.mem_pool);
+
+#ifdef CONFIG_COMPCACHE_PROC
+	remove_proc_entry("compcache", &proc_root);
+#endif
+	CC_DEBUG("cleanup done!\n");
+}
+
+module_param(compcache_size_kbytes, ulong, 0);
+MODULE_PARM_DESC(compcache_size_kbytes, "compcache device size (in KB)");
+
+module_init(compcache_init);
+module_exit(compcache_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nitin Gupta <nitingupta910 at gmail dot com>");
+MODULE_DESCRIPTION("Compressed RAM Based Swap Device");

--
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] 4+ messages in thread

* Re: [PATCH 1/6] compcache: compressed RAM block device
  2008-03-24 15:02 [PATCH 1/6] compcache: compressed RAM block device Nitin Gupta
@ 2008-03-24 16:23 ` Will Newton
  2008-03-24 18:35   ` Nitin Gupta
  2008-03-26 22:19   ` Pavel Machek
  0 siblings, 2 replies; 4+ messages in thread
From: Will Newton @ 2008-03-24 16:23 UTC (permalink / raw)
  To: nitingupta910; +Cc: linux-kernel, linux-mm

On Mon, Mar 24, 2008 at 3:02 PM, Nitin Gupta <nitingupta910@gmail.com> wrote:
> This creates RAM based block device (called ramzswap0) which is used as swap disk.
>
>  On write (swap-out):
>   - compress page (using LZO)
>   - Allocate required amount of memory (using TLSF)
>   - Store reference to its location in simple array.
>
>  On read (swap-in):
>   - Get compressed page location from array
>   - Decompress this page.
>
>  It also makes required Makefile changes.
>
>  Signed-off-by: Nitin Gupta <nitingupta910 at gmail dot com>
>  ---
>   drivers/block/Kconfig     |   10 +
>   drivers/block/Makefile    |    1 +
>   drivers/block/compcache.c |  440 +++++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 451 insertions(+), 0 deletions(-)
>
>  diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
>  index 0d1d213..0cba42f 100644
>  --- a/drivers/block/Kconfig
>  +++ b/drivers/block/Kconfig
>  @@ -347,6 +347,16 @@ config BLK_DEV_RAM_SIZE
>           The default value is 4096 kilobytes. Only change this if you know
>           what you are doing.
>
>  +config BLK_DEV_COMPCACHE
>  +       tristate "Compressed RAM based swap device"
>  +       select TLSF
>  +       select LZO_COMPRESS
>  +       select LZO_DECOMPRESS
>  +       help
>  +         This creates RAM based block device which acts as swap disk. Pages
>  +         swapped to this disk are compressed and stored in memory itself.
>  +         Project Home: http://code.google.com/p/compcache/
>  +
>   config BLK_DEV_XIP
>         bool "Support XIP filesystems on RAM block device"
>         depends on BLK_DEV_RAM
>  diff --git a/drivers/block/Makefile b/drivers/block/Makefile
>  index 5e58430..b6d3dd2 100644
>  --- a/drivers/block/Makefile
>  +++ b/drivers/block/Makefile
>  @@ -12,6 +12,7 @@ obj-$(CONFIG_PS3_DISK)                += ps3disk.o
>   obj-$(CONFIG_ATARI_FLOPPY)     += ataflop.o
>   obj-$(CONFIG_AMIGA_Z2RAM)      += z2ram.o
>   obj-$(CONFIG_BLK_DEV_RAM)      += brd.o
>  +obj-$(CONFIG_BLK_DEV_COMPCACHE)        += compcache.o

Minor - this isn't in alphabetical order.

>   obj-$(CONFIG_BLK_DEV_LOOP)     += loop.o
>   obj-$(CONFIG_BLK_DEV_XD)       += xd.o
>   obj-$(CONFIG_BLK_CPQ_DA)       += cpqarray.o
>  diff --git a/drivers/block/compcache.c b/drivers/block/compcache.c
>  new file mode 100644
>  index 0000000..4ffcd63
>  --- /dev/null
>  +++ b/drivers/block/compcache.c
>  @@ -0,0 +1,440 @@
>  +/*
>  + * Compressed RAM based swap device
>  + *
>  + * (C) Nitin Gupta

Should add a copyright year.

>  + *
>  + * This RAM based block device acts as swap disk.
>  + * Pages swapped to this device are compressed and
>  + * stored in memory.
>  + *
>  + * Project home: http://code.google.com/p/compcache
>  + */
>  +
>  +#include <linux/module.h>
>  +#include <linux/kernel.h>
>  +#include <linux/blkdev.h>
>  +#include <linux/buffer_head.h>
>  +#include <linux/device.h>
>  +#include <linux/genhd.h>
>  +#include <linux/highmem.h>
>  +#include <linux/lzo.h>
>  +#include <linux/mutex.h>
>  +#include <linux/proc_fs.h>
>  +#include <linux/swap.h>
>  +#include <linux/tlsf.h>
>  +#include <linux/vmalloc.h>
>  +#include <asm/pgtable.h>
>  +#include <asm/string.h>

Should this be <linux/string.h>?

>  +
>  +#include "compcache.h"
>  +
>  +static struct block_device_operations compcache_devops = {
>  +       .owner = THIS_MODULE,
>  +};
>  +
>  +static struct compcache compcache;
>  +static unsigned long compcache_size_kbytes;
>  +#if STATS
>  +static struct compcache_stats stats;
>  +#endif
>  +
>  +#ifdef CONFIG_COMPCACHE_PROC
>  +static struct proc_dir_entry *proc;
>  +
>  +static int proc_compcache_read(char *page, char **start, off_t off,
>  +                               int count, int *eof, void *data)
>  +{
>  +       int len;
>  +#if STATS
>  +       size_t succ_writes;
>  +       unsigned int good_compress_perc = 0, no_compress_perc = 0;
>  +#endif
>  +
>  +       if (off > 0) {
>  +               *eof = 1;
>  +               return 0;
>  +       }
>  +
>  +       len = sprintf(page,
>  +               "DiskSize:      %8u kB\n",
>  +               compcache.size >> (10 - SECTOR_SHIFT));
>  +#if STATS
>  +       succ_writes = stats.num_writes - stats.failed_writes;
>  +       if (succ_writes) {
>  +               good_compress_perc = stats.good_compress * 100 / succ_writes;
>  +               no_compress_perc = stats.pages_expand * 100 / succ_writes;
>  +       }
>  +
>  +       len += sprintf(page + len,
>  +               "NumReads:      %8u\n"
>  +               "NumWrites:     %8u\n"
>  +               "FailedReads:   %8u\n"
>  +               "FailedWrites:  %8u\n"
>  +               "InvalidIO:     %8u\n"
>  +               "GoodCompress:  %8u %%\n"
>  +               "NoCompress:    %8u %%\n"
>  +               "CurrentPages:  %8zu\n"
>  +               "CurrentMem:    %8zu kB\n"
>  +               "PeakMem:       %8zu kB\n",
>  +               stats.num_reads,
>  +               stats.num_writes,
>  +               stats.failed_reads,
>  +               stats.failed_writes,
>  +               stats.invalid_io,
>  +               good_compress_perc,
>  +               no_compress_perc,
>  +               stats.curr_pages,
>  +               K(stats.curr_mem),
>  +               K(stats.peak_mem));
>  +#endif
>  +       return len;
>  +}
>  +#endif /* CONFIG_COMPCACHE_PROC */
>  +
>  +/* Check if request is within bounds and page aligned */
>  +static inline int valid_swap_request(struct bio *bio)
>  +{
>  +       if (unlikely((bio->bi_sector >= compcache.size) ||
>  +                       (bio->bi_sector & (SECTORS_PER_PAGE - 1)) ||
>  +                       (bio->bi_vcnt != 1) ||
>  +                       (bio->bi_size != PAGE_SIZE) ||
>  +                       (bio->bi_io_vec[0].bv_offset != 0)))
>  +               return 0;
>  +       return 1;
>  +}

Probably unnecessary to mark this explicitly inline.

>  +
>  +static int compcache_make_request(struct request_queue *queue, struct bio *bio)
>  +{
>  +       int ret;
>  +       size_t clen, page_no;
>  +       void *user_mem;
>  +       struct page *page;
>  +
>  +       if (!valid_swap_request(bio)) {
>  +               stat_inc(&stats.invalid_io);
>  +               goto out_nomap;
>  +       }
>  +
>  +       CC_DEBUG2("bio sector: %lu (%s)\n", (unsigned long)bio->bi_sector,
>  +                               bio_data_dir(bio) == READ ? "R" : "W");
>  +
>  +       page = bio->bi_io_vec[0].bv_page;
>  +       page_no = bio->bi_sector >> SECTORS_PER_PAGE_SHIFT;
>  +       user_mem = kmap(page);
>  +
>  +       if (bio_data_dir(bio) == READ) {
>  +               stat_inc(&stats.num_reads);
>  +               /*
>  +                * This is attempt to read before any previous write
>  +                * to this location. This happens due to readahead when
>  +                * swap device is read from user-space (e.g. during swapon)
>  +                */
>  +               if (unlikely(compcache.table[page_no].addr == NULL)) {
>  +                       CC_DEBUG("Read before write on swap device: "
>  +                               "sector=%lu, size=%zu, offset=%zu\n",
>  +                               (unsigned long)(bio->bi_sector),
>  +                               bio->bi_size,
>  +                               bio->bi_io_vec[0].bv_offset);
>  +                       memset(user_mem, 0, PAGE_SIZE);
>  +                       kunmap(page);
>  +                       set_bit(BIO_UPTODATE, &bio->bi_flags);
>  +                       bio_endio(bio, 0);
>  +                       return 0;
>  +               }
>  +
>  +               /* Page is stored uncompressed since its incompressible */
>  +               if (unlikely(compcache.table[page_no].len == PAGE_SIZE)) {
>  +                       memcpy(user_mem, compcache.table[page_no].addr,
>  +                                                       PAGE_SIZE);
>  +                       kunmap(page);
>  +                       set_bit(BIO_UPTODATE, &bio->bi_flags);
>  +                       bio_endio(bio, 0);
>  +                       return 0;
>  +               }
>  +
>  +               clen = PAGE_SIZE;
>  +               ret = lzo1x_decompress_safe(
>  +                       compcache.table[page_no].addr,
>  +                       compcache.table[page_no].len,
>  +                       user_mem,
>  +                       &clen);
>  +
>  +               /* should NEVER happen */
>  +               if (unlikely(ret != LZO_E_OK)) {
>  +                       pr_err(C "Decompression failed! "
>  +                               "err=%d, page=%zu, len=%u\n", ret, page_no,
>  +                               compcache.table[page_no].len);
>  +                       stat_inc(&stats.failed_reads);
>  +                       goto out;
>  +               }
>  +
>  +               CC_DEBUG2("Page decompressed: page_no=%zu\n", page_no);
>  +               kunmap(page);
>  +               set_bit(BIO_UPTODATE, &bio->bi_flags);
>  +               bio_endio(bio, 0);
>  +               return 0;
>  +       } else {        /* Write */
>  +               unsigned char *src = compcache.compress_buffer;
>  +               stat_inc(&stats.num_writes);
>  +               /*
>  +                * System swaps to same sector again when the stored page
>  +                * is no longer referenced by any process. So, its now safe
>  +                * to free the memory that was allocated for this page.
>  +                */
>  +               if (compcache.table[page_no].addr) {
>  +                       CC_DEBUG2("Freeing page: %zu\n", page_no);
>  +                       tlsf_free(compcache.table[page_no].addr,
>  +                               compcache.mem_pool);
>  +                       stat_dec(&stats.curr_pages);
>  +                       compcache.table[page_no].addr = NULL;
>  +                       compcache.table[page_no].len = 0;
>  +               }
>  +
>  +               mutex_lock(&compcache.lock);
>  +               ret = lzo1x_1_compress(user_mem, PAGE_SIZE,
>  +                       src, &clen, compcache.compress_workmem);
>  +               if (unlikely(ret != LZO_E_OK)) {
>  +                       mutex_unlock(&compcache.lock);
>  +                       pr_err(C "Compression failed! err=%d\n", ret);
>  +                       compcache.table[page_no].addr = NULL;
>  +                       compcache.table[page_no].len = 0;
>  +                       stat_inc(&stats.failed_writes);
>  +                       goto out;
>  +               }
>  +
>  +               /* Page is incompressible - store it as is */
>  +               if (clen >= PAGE_SIZE) {
>  +                       CC_DEBUG("Page expand on compression: "
>  +                               "page=%zu, size=%zu\n", page_no, clen);
>  +                       clen = PAGE_SIZE;
>  +                       src = user_mem;
>  +               } else {
>  +                       CC_DEBUG2("Page compressed: page_no=%zu, len=%zu\n",
>  +                               page_no, clen);
>  +               }
>  +               if ((compcache.table[page_no].addr = tlsf_malloc(clen,
>  +                                       compcache.mem_pool)) == NULL) {
>  +                       mutex_unlock(&compcache.lock);
>  +                       pr_err(C "Error allocating memory for compressed "
>  +                               "page: %zu, size=%zu \n", page_no, clen);
>  +                       compcache.table[page_no].len = 0;
>  +                       stat_inc(&stats.failed_writes);
>  +                       goto out;
>  +               }
>  +
>  +               memcpy(compcache.table[page_no].addr, src, clen);
>  +
>  +               /* Update stats */
>  +               stat_inc(&stats.curr_pages);
>  +               stat_set(&stats.curr_mem, stats.curr_mem + clen);
>  +               stat_setmax(&stats.peak_mem, stats.curr_mem);
>  +               stat_inc_if_less(&stats.pages_expand, PAGE_SIZE - 1, clen);
>  +               stat_inc_if_less(&stats.good_compress, clen,
>  +                                               PAGE_SIZE / 2 + 1);
>  +               mutex_unlock(&compcache.lock);
>  +
>  +               compcache.table[page_no].len = clen;
>  +
>  +               kunmap(page);
>  +               set_bit(BIO_UPTODATE, &bio->bi_flags);
>  +               bio_endio(bio, 0);
>  +               return 0;
>  +       }
>  +out:
>  +       kunmap(page);
>  +out_nomap:
>  +       bio_io_error(bio);
>  +       return 0;
>  +}
>  +
>  +static void setup_swap_header(union swap_header *s)
>  +{
>  +       s->info.version = 1;
>  +       s->info.last_page = compcache.size >> SECTORS_PER_PAGE_SHIFT;
>  +       s->info.nr_badpages = 0;
>  +       memcpy(s->magic.magic, "SWAPSPACE2", 10);
>  +}
>  +
>  +static void *get_mem(size_t size)
>  +{
>  +       return __vmalloc(size, GFP_NOIO, PAGE_KERNEL);
>  +}
>  +
>  +static void put_mem(void *ptr)
>  +{
>  +       vfree(ptr);
>  +}
>  +
>  +static int __init compcache_init(void)
>  +{
>  +       int ret;
>  +       size_t num_pages;
>  +       struct sysinfo i;
>  +
>  +       mutex_init(&compcache.lock);
>  +
>  +       if (compcache_size_kbytes == 0) {
>  +               pr_info(C "compcache size not provided."
>  +                       " Using default: (%u%% of Total RAM).\n"
>  +                       "Use compcache_size_kbytes module param to specify"
>  +                       " custom size\n", DEFAULT_COMPCACHE_PERCENT);
>  +               si_meminfo(&i);
>  +               compcache_size_kbytes = ((DEFAULT_COMPCACHE_PERCENT *
>  +                               i.totalram) / 100) << (PAGE_SHIFT - 10);
>  +       }
>  +
>  +       CC_DEBUG2("compcache_size_kbytes=%lu\n", compcache_size_kbytes);
>  +       compcache.size = compcache_size_kbytes << 10;
>  +       compcache.size = (compcache.size + PAGE_SIZE - 1) & PAGE_MASK;
>  +       pr_info(C "Compressed swap size set to: %zu KB\n", compcache.size >> 10);
>  +       compcache.size >>= SECTOR_SHIFT;
>  +
>  +       compcache.compress_workmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
>  +       if (compcache.compress_workmem == NULL) {
>  +               pr_err(C "Error allocating compressor working memory\n");
>  +               ret = -ENOMEM;
>  +               goto fail;
>  +       }
>  +
>  +       compcache.compress_buffer = kmalloc(2 * PAGE_SIZE, GFP_KERNEL);
>  +       if (compcache.compress_buffer == NULL) {
>  +               pr_err(C "Error allocating compressor buffer space\n");
>  +               ret = -ENOMEM;
>  +               goto fail;
>  +       }
>  +
>  +       num_pages = compcache.size >> SECTORS_PER_PAGE_SHIFT;
>  +        compcache.table = vmalloc(num_pages * sizeof(*compcache.table));
>  +        if (compcache.table == NULL) {
>  +                pr_err(C "Error allocating compcache address table\n");
>  +                ret = -ENOMEM;
>  +                goto fail;
>  +        }
>  +        memset(compcache.table, 0, num_pages * sizeof(*compcache.table));
>  +
>  +       compcache.table[0].addr = (void *)get_zeroed_page(GFP_KERNEL);
>  +       if (compcache.table[0].addr == NULL) {
>  +               pr_err(C "Error allocating swap header page\n");
>  +               ret = -ENOMEM;
>  +               goto fail;
>  +       }
>  +       compcache.table[0].len = PAGE_SIZE;
>  +       setup_swap_header((union swap_header *)(compcache.table[0].addr));
>  +
>  +       compcache.disk = alloc_disk(1);
>  +       if (compcache.disk == NULL) {
>  +               pr_err(C "Error allocating disk structure\n");
>  +               ret = -ENOMEM;
>  +               goto fail;
>  +       }
>  +
>  +       compcache.disk->first_minor = 0;
>  +       compcache.disk->fops = &compcache_devops;
>  +       /*
>  +        * It is named like this to prevent distro installers
>  +        * from offering compcache as installation target. They
>  +        * seem to ignore all devices beginning with 'ram'
>  +        */
>  +       sprintf(compcache.disk->disk_name, "%s", "ramzswap0");

I'm not sure the name makes it 100% obvious what the device is for.
You could use strcpy here also.

>  +
>  +       compcache.disk->major = register_blkdev(0, compcache.disk->disk_name);
>  +       if (compcache.disk->major < 0) {
>  +               pr_err(C "Cannot register block device\n");
>  +               ret = -EFAULT;
>  +               goto fail;
>  +       }
>  +
>  +       compcache.disk->queue = blk_alloc_queue(GFP_KERNEL);
>  +       if (compcache.disk->queue == NULL) {
>  +               pr_err(C "Cannot register disk queue\n");
>  +               ret = -EFAULT;
>  +               goto fail;
>  +       }
>  +
>  +       set_capacity(compcache.disk, compcache.size);
>  +       blk_queue_make_request(compcache.disk->queue, compcache_make_request);
>  +       blk_queue_hardsect_size(compcache.disk->queue, PAGE_SIZE);
>  +       add_disk(compcache.disk);
>  +
>  +       compcache.mem_pool = tlsf_create_memory_pool("compcache",
>  +                               get_mem, put_mem,
>  +                               INIT_SIZE, 0, GROW_SIZE);
>  +       if (compcache.mem_pool == NULL) {
>  +               pr_err(C "Error creating memory pool\n");
>  +               ret = -ENOMEM;
>  +               goto fail;
>  +       }
>  +
>  +#ifdef CONFIG_COMPCACHE_PROC
>  +       proc = create_proc_entry("compcache", S_IRUGO, NULL);
>  +       if (proc)
>  +               proc->read_proc = &proc_compcache_read;
>  +       else {
>  +               ret = -ENOMEM;
>  +               pr_warning(C "Error creating proc entry\n");
>  +               goto fail;
>  +       }
>  +#endif
>  +
>  +       CC_DEBUG(C "Initialization done!\n");
>  +       return 0;
>  +
>  +fail:
>  +       if (compcache.disk != NULL) {
>  +               if (compcache.disk->major > 0)
>  +                       unregister_blkdev(compcache.disk->major,
>  +                                       compcache.disk->disk_name);
>  +               del_gendisk(compcache.disk);
>  +       }
>  +
>  +       if (compcache.table[0].addr)
>  +               free_page((unsigned long)compcache.table[0].addr);
>  +       if (compcache.compress_workmem)
>  +               kfree(compcache.compress_workmem);
>  +       if (compcache.compress_buffer)
>  +               kfree(compcache.compress_buffer);
>  +       if (compcache.table)
>  +               vfree(compcache.table);

kfree() and vfree() may safely be called on NULL pointers.

>  +       if (compcache.mem_pool)
>  +               tlsf_destroy_memory_pool(compcache.mem_pool);
>  +#ifdef CONFIG_COMPCACHE_PROC
>  +       if (proc)
>  +               remove_proc_entry("compcache", &proc_root);
>  +#endif
>  +       pr_err(C "Initialization failed: err=%d\n", ret);
>  +       return ret;
>  +}
>  +
>  +static void __exit compcache_exit(void)
>  +{
>  +       size_t i, num_pages;
>  +       num_pages = compcache.size >> SECTORS_PER_PAGE_SHIFT;
>  +
>  +       unregister_blkdev(compcache.disk->major, compcache.disk->disk_name);
>  +       del_gendisk(compcache.disk);
>  +       free_page((unsigned long)compcache.table[0].addr);
>  +       kfree(compcache.compress_workmem);
>  +       kfree(compcache.compress_buffer);
>  +
>  +       /* Free all pages that are still in compcache */
>  +       for (i = 1; i < num_pages; i++)
>  +               if (compcache.table[i].addr)
>  +                       tlsf_free(compcache.table[i].addr, compcache.mem_pool);
>  +       vfree(compcache.table);
>  +       tlsf_destroy_memory_pool(compcache.mem_pool);
>  +
>  +#ifdef CONFIG_COMPCACHE_PROC
>  +       remove_proc_entry("compcache", &proc_root);
>  +#endif
>  +       CC_DEBUG("cleanup done!\n");
>  +}
>  +
>  +module_param(compcache_size_kbytes, ulong, 0);
>  +MODULE_PARM_DESC(compcache_size_kbytes, "compcache device size (in KB)");
>  +
>  +module_init(compcache_init);
>  +module_exit(compcache_exit);
>  +
>  +MODULE_LICENSE("GPL");
>  +MODULE_AUTHOR("Nitin Gupta <nitingupta910 at gmail dot com>");
>  +MODULE_DESCRIPTION("Compressed RAM Based Swap Device");
>  --
>  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/
>

--
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] 4+ messages in thread

* Re: [PATCH 1/6] compcache: compressed RAM block device
  2008-03-24 16:23 ` Will Newton
@ 2008-03-24 18:35   ` Nitin Gupta
  2008-03-26 22:19   ` Pavel Machek
  1 sibling, 0 replies; 4+ messages in thread
From: Nitin Gupta @ 2008-03-24 18:35 UTC (permalink / raw)
  To: Will Newton; +Cc: linux-kernel, linux-mm

On Mon, Mar 24, 2008 at 9:53 PM, Will Newton <will.newton@gmail.com> wrote:
>
> On Mon, Mar 24, 2008 at 3:02 PM, Nitin Gupta <nitingupta910@gmail.com> wrote:


>  >  diff --git a/drivers/block/Makefile b/drivers/block/Makefile
>  >  index 5e58430..b6d3dd2 100644
>  >  --- a/drivers/block/Makefile
>  >  +++ b/drivers/block/Makefile
>  >  @@ -12,6 +12,7 @@ obj-$(CONFIG_PS3_DISK)                += ps3disk.o
>  >   obj-$(CONFIG_ATARI_FLOPPY)     += ataflop.o
>  >   obj-$(CONFIG_AMIGA_Z2RAM)      += z2ram.o
>  >   obj-$(CONFIG_BLK_DEV_RAM)      += brd.o
>  >  +obj-$(CONFIG_BLK_DEV_COMPCACHE)        += compcache.o
>
>  Minor - this isn't in alphabetical order.

Intent here is to keep related things together. So, I have placed it
with generic ramdisk. This also seems to be convention used in this
file.

>  >  diff --git a/drivers/block/compcache.c b/drivers/block/compcache.c
>  >  new file mode 100644
>  >  index 0000000..4ffcd63
>  >  --- /dev/null
>  >  +++ b/drivers/block/compcache.c
>  >  @@ -0,0 +1,440 @@
>  >  +/*
>  >  + * Compressed RAM based swap device
>  >  + *
>  >  + * (C) Nitin Gupta
>
>  Should add a copyright year.
>

ok.

>  >  +#include <asm/string.h>
>
>  Should this be <linux/string.h>?
>

Yes. I will change this.


>  >  +/* Check if request is within bounds and page aligned */
>  >  +static inline int valid_swap_request(struct bio *bio)
>  >  +{
>  >  +       if (unlikely((bio->bi_sector >= compcache.size) ||
>  >  +                       (bio->bi_sector & (SECTORS_PER_PAGE - 1)) ||
>  >  +                       (bio->bi_vcnt != 1) ||
>  >  +                       (bio->bi_size != PAGE_SIZE) ||
>  >  +                       (bio->bi_io_vec[0].bv_offset != 0)))
>  >  +               return 0;
>  >  +       return 1;
>  >  +}
>
>  Probably unnecessary to mark this explicitly inline.
>
>

Probably yes. I am not sure.


>  >  +       /*
>  >  +        * It is named like this to prevent distro installers
>  >  +        * from offering compcache as installation target. They
>  >  +        * seem to ignore all devices beginning with 'ram'
>  >  +        */
>  >  +       sprintf(compcache.disk->disk_name, "%s", "ramzswap0");
>
>  I'm not sure the name makes it 100% obvious what the device is for.
>  You could use strcpy here also.
>

"z" == compress
and hence the name ramzswap :)


>  >  +       if (compcache.table[0].addr)
>  >  +               free_page((unsigned long)compcache.table[0].addr);
>  >  +       if (compcache.compress_workmem)
>  >  +               kfree(compcache.compress_workmem);
>  >  +       if (compcache.compress_buffer)
>  >  +               kfree(compcache.compress_buffer);
>  >  +       if (compcache.table)
>  >  +               vfree(compcache.table);
>
>  kfree() and vfree() may safely be called on NULL pointers.
>

I will remove these unnecessary checks then.


Thanks,
Nitin

--
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] 4+ messages in thread

* Re: [PATCH 1/6] compcache: compressed RAM block device
  2008-03-24 16:23 ` Will Newton
  2008-03-24 18:35   ` Nitin Gupta
@ 2008-03-26 22:19   ` Pavel Machek
  1 sibling, 0 replies; 4+ messages in thread
From: Pavel Machek @ 2008-03-26 22:19 UTC (permalink / raw)
  To: Will Newton; +Cc: nitingupta910, linux-kernel, linux-mm

Hi!

> >  --- /dev/null
> >  +++ b/drivers/block/compcache.c
> >  @@ -0,0 +1,440 @@
> >  +/*
> >  + * Compressed RAM based swap device
> >  + *
> >  + * (C) Nitin Gupta
> 
> Should add a copyright year.

...and GPL.

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

--
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] 4+ messages in thread

end of thread, other threads:[~2008-03-26 22:19 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-03-24 15:02 [PATCH 1/6] compcache: compressed RAM block device Nitin Gupta
2008-03-24 16:23 ` Will Newton
2008-03-24 18:35   ` Nitin Gupta
2008-03-26 22:19   ` Pavel Machek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox