aboutsummaryrefslogtreecommitdiffstats
path: root/fs/squashfs
diff options
context:
space:
mode:
authorPhillip Lougher <phillip@squashfs.org.uk>2013-11-12 21:04:19 -0500
committerPhillip Lougher <phillip@squashfs.org.uk>2013-11-19 22:59:13 -0500
commit0d455c12c6428647547bacccaaced3cae0f35570 (patch)
treea0c47256d66de9d70942b0d7f9422c8d7d5c5a41 /fs/squashfs
parent5f55dbc0c5c466a9cdfa4da7ac1bfe351c7fc52a (diff)
Squashfs: Directly decompress into the page cache for file data
This introduces an implementation of squashfs_readpage_block() that directly decompresses into the page cache. This uses the previously added page handler abstraction to push down the necessary kmap_atomic/kunmap_atomic operations on the page cache buffers into the decompressors. This enables direct copying into the page cache without using the slow kmap/kunmap calls. The code detects when multiple threads are racing in squashfs_readpage() to decompress the same block, and avoids this regression by falling back to using an intermediate buffer. This patch enhances the performance of Squashfs significantly when multiple processes are accessing the filesystem simultaneously because it not only reduces memcopying, but it more importantly eliminates the lock contention on the intermediate buffer. Using single-thread decompression. dd if=file1 of=/dev/null bs=4096 & dd if=file2 of=/dev/null bs=4096 & dd if=file3 of=/dev/null bs=4096 & dd if=file4 of=/dev/null bs=4096 Before: 629145600 bytes (629 MB) copied, 45.8046 s, 13.7 MB/s After: 629145600 bytes (629 MB) copied, 9.29414 s, 67.7 MB/s Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk> Reviewed-by: Minchan Kim <minchan@kernel.org>
Diffstat (limited to 'fs/squashfs')
-rw-r--r--fs/squashfs/Kconfig28
-rw-r--r--fs/squashfs/Makefile4
-rw-r--r--fs/squashfs/file_direct.c173
-rw-r--r--fs/squashfs/page_actor.c100
-rw-r--r--fs/squashfs/page_actor.h32
5 files changed, 336 insertions, 1 deletions
diff --git a/fs/squashfs/Kconfig b/fs/squashfs/Kconfig
index 159bd6676dc2..b6fa8657dcbc 100644
--- a/fs/squashfs/Kconfig
+++ b/fs/squashfs/Kconfig
@@ -26,6 +26,34 @@ config SQUASHFS
26 If unsure, say N. 26 If unsure, say N.
27 27
28choice 28choice
29 prompt "File decompression options"
30 depends on SQUASHFS
31 help
32 Squashfs now supports two options for decompressing file
33 data. Traditionally Squashfs has decompressed into an
34 intermediate buffer and then memcopied it into the page cache.
35 Squashfs now supports the ability to decompress directly into
36 the page cache.
37
38 If unsure, select "Decompress file data into an intermediate buffer"
39
40config SQUASHFS_FILE_CACHE
41 bool "Decompress file data into an intermediate buffer"
42 help
43 Decompress file data into an intermediate buffer and then
44 memcopy it into the page cache.
45
46config SQUASHFS_FILE_DIRECT
47 bool "Decompress files directly into the page cache"
48 help
49 Directly decompress file data into the page cache.
50 Doing so can significantly improve performance because
51 it eliminates a memcpy and it also removes the lock contention
52 on the single buffer.
53
54endchoice
55
56choice
29 prompt "Decompressor parallelisation options" 57 prompt "Decompressor parallelisation options"
30 depends on SQUASHFS 58 depends on SQUASHFS
31 help 59 help
diff --git a/fs/squashfs/Makefile b/fs/squashfs/Makefile
index e01ba1126c89..4132520b4ff2 100644
--- a/fs/squashfs/Makefile
+++ b/fs/squashfs/Makefile
@@ -4,7 +4,9 @@
4 4
5obj-$(CONFIG_SQUASHFS) += squashfs.o 5obj-$(CONFIG_SQUASHFS) += squashfs.o
6squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o 6squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o
7squashfs-y += namei.o super.o symlink.o decompressor.o file_cache.o 7squashfs-y += namei.o super.o symlink.o decompressor.o
8squashfs-$(CONFIG_SQUASHFS_FILE_CACHE) += file_cache.o
9squashfs-$(CONFIG_SQUASHFS_FILE_DIRECT) += file_direct.o page_actor.o
8squashfs-$(CONFIG_SQUASHFS_DECOMP_SINGLE) += decompressor_single.o 10squashfs-$(CONFIG_SQUASHFS_DECOMP_SINGLE) += decompressor_single.o
9squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI) += decompressor_multi.o 11squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI) += decompressor_multi.o
10squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU) += decompressor_multi_percpu.o 12squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU) += decompressor_multi_percpu.o
diff --git a/fs/squashfs/file_direct.c b/fs/squashfs/file_direct.c
new file mode 100644
index 000000000000..2943b2bfae48
--- /dev/null
+++ b/fs/squashfs/file_direct.c
@@ -0,0 +1,173 @@
1/*
2 * Copyright (c) 2013
3 * Phillip Lougher <phillip@squashfs.org.uk>
4 *
5 * This work is licensed under the terms of the GNU GPL, version 2. See
6 * the COPYING file in the top-level directory.
7 */
8
9#include <linux/fs.h>
10#include <linux/vfs.h>
11#include <linux/kernel.h>
12#include <linux/slab.h>
13#include <linux/string.h>
14#include <linux/pagemap.h>
15#include <linux/mutex.h>
16
17#include "squashfs_fs.h"
18#include "squashfs_fs_sb.h"
19#include "squashfs_fs_i.h"
20#include "squashfs.h"
21#include "page_actor.h"
22
23static int squashfs_read_cache(struct page *target_page, u64 block, int bsize,
24 int pages, struct page **page);
25
26/* Read separately compressed datablock directly into page cache */
27int squashfs_readpage_block(struct page *target_page, u64 block, int bsize)
28
29{
30 struct inode *inode = target_page->mapping->host;
31 struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
32
33 int file_end = (i_size_read(inode) - 1) >> PAGE_CACHE_SHIFT;
34 int mask = (1 << (msblk->block_log - PAGE_CACHE_SHIFT)) - 1;
35 int start_index = target_page->index & ~mask;
36 int end_index = start_index | mask;
37 int i, n, pages, missing_pages, bytes, res = -ENOMEM;
38 struct page **page;
39 struct squashfs_page_actor *actor;
40 void *pageaddr;
41
42 if (end_index > file_end)
43 end_index = file_end;
44
45 pages = end_index - start_index + 1;
46
47 page = kmalloc(sizeof(void *) * pages, GFP_KERNEL);
48 if (page == NULL)
49 return res;
50
51 /*
52 * Create a "page actor" which will kmap and kunmap the
53 * page cache pages appropriately within the decompressor
54 */
55 actor = squashfs_page_actor_init_special(page, pages, 0);
56 if (actor == NULL)
57 goto out;
58
59 /* Try to grab all the pages covered by the Squashfs block */
60 for (missing_pages = 0, i = 0, n = start_index; i < pages; i++, n++) {
61 page[i] = (n == target_page->index) ? target_page :
62 grab_cache_page_nowait(target_page->mapping, n);
63
64 if (page[i] == NULL) {
65 missing_pages++;
66 continue;
67 }
68
69 if (PageUptodate(page[i])) {
70 unlock_page(page[i]);
71 page_cache_release(page[i]);
72 page[i] = NULL;
73 missing_pages++;
74 }
75 }
76
77 if (missing_pages) {
78 /*
79 * Couldn't get one or more pages, this page has either
80 * been VM reclaimed, but others are still in the page cache
81 * and uptodate, or we're racing with another thread in
82 * squashfs_readpage also trying to grab them. Fall back to
83 * using an intermediate buffer.
84 */
85 res = squashfs_read_cache(target_page, block, bsize, pages,
86 page);
87 goto out;
88 }
89
90 /* Decompress directly into the page cache buffers */
91 res = squashfs_read_data(inode->i_sb, block, bsize, NULL, actor);
92 if (res < 0)
93 goto mark_errored;
94
95 /* Last page may have trailing bytes not filled */
96 bytes = res % PAGE_CACHE_SIZE;
97 if (bytes) {
98 pageaddr = kmap_atomic(page[pages - 1]);
99 memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes);
100 kunmap_atomic(pageaddr);
101 }
102
103 /* Mark pages as uptodate, unlock and release */
104 for (i = 0; i < pages; i++) {
105 flush_dcache_page(page[i]);
106 SetPageUptodate(page[i]);
107 unlock_page(page[i]);
108 if (page[i] != target_page)
109 page_cache_release(page[i]);
110 }
111
112 kfree(actor);
113 kfree(page);
114
115 return 0;
116
117mark_errored:
118 /* Decompression failed, mark pages as errored. Target_page is
119 * dealt with by the caller
120 */
121 for (i = 0; i < pages; i++) {
122 if (page[i] == target_page)
123 continue;
124 flush_dcache_page(page[i]);
125 SetPageError(page[i]);
126 unlock_page(page[i]);
127 page_cache_release(page[i]);
128 }
129
130out:
131 kfree(actor);
132 kfree(page);
133 return res;
134}
135
136
137static int squashfs_read_cache(struct page *target_page, u64 block, int bsize,
138 int pages, struct page **page)
139{
140 struct inode *i = target_page->mapping->host;
141 struct squashfs_cache_entry *buffer = squashfs_get_datablock(i->i_sb,
142 block, bsize);
143 int bytes = buffer->length, res = buffer->error, n, offset = 0;
144 void *pageaddr;
145
146 if (res) {
147 ERROR("Unable to read page, block %llx, size %x\n", block,
148 bsize);
149 goto out;
150 }
151
152 for (n = 0; n < pages && bytes > 0; n++,
153 bytes -= PAGE_CACHE_SIZE, offset += PAGE_CACHE_SIZE) {
154 int avail = min_t(int, bytes, PAGE_CACHE_SIZE);
155
156 if (page[n] == NULL)
157 continue;
158
159 pageaddr = kmap_atomic(page[n]);
160 squashfs_copy_data(pageaddr, buffer, offset, avail);
161 memset(pageaddr + avail, 0, PAGE_CACHE_SIZE - avail);
162 kunmap_atomic(pageaddr);
163 flush_dcache_page(page[n]);
164 SetPageUptodate(page[n]);
165 unlock_page(page[n]);
166 if (page[n] != target_page)
167 page_cache_release(page[n]);
168 }
169
170out:
171 squashfs_cache_put(buffer);
172 return res;
173}
diff --git a/fs/squashfs/page_actor.c b/fs/squashfs/page_actor.c
new file mode 100644
index 000000000000..5a1c11f56441
--- /dev/null
+++ b/fs/squashfs/page_actor.c
@@ -0,0 +1,100 @@
1/*
2 * Copyright (c) 2013
3 * Phillip Lougher <phillip@squashfs.org.uk>
4 *
5 * This work is licensed under the terms of the GNU GPL, version 2. See
6 * the COPYING file in the top-level directory.
7 */
8
9#include <linux/kernel.h>
10#include <linux/slab.h>
11#include <linux/pagemap.h>
12#include "page_actor.h"
13
14/*
15 * This file contains implementations of page_actor for decompressing into
16 * an intermediate buffer, and for decompressing directly into the
17 * page cache.
18 *
19 * Calling code should avoid sleeping between calls to squashfs_first_page()
20 * and squashfs_finish_page().
21 */
22
23/* Implementation of page_actor for decompressing into intermediate buffer */
24static void *cache_first_page(struct squashfs_page_actor *actor)
25{
26 actor->next_page = 1;
27 return actor->buffer[0];
28}
29
30static void *cache_next_page(struct squashfs_page_actor *actor)
31{
32 if (actor->next_page == actor->pages)
33 return NULL;
34
35 return actor->buffer[actor->next_page++];
36}
37
38static void cache_finish_page(struct squashfs_page_actor *actor)
39{
40 /* empty */
41}
42
43struct squashfs_page_actor *squashfs_page_actor_init(void **buffer,
44 int pages, int length)
45{
46 struct squashfs_page_actor *actor = kmalloc(sizeof(*actor), GFP_KERNEL);
47
48 if (actor == NULL)
49 return NULL;
50
51 actor->length = length ? : pages * PAGE_CACHE_SIZE;
52 actor->buffer = buffer;
53 actor->pages = pages;
54 actor->next_page = 0;
55 actor->squashfs_first_page = cache_first_page;
56 actor->squashfs_next_page = cache_next_page;
57 actor->squashfs_finish_page = cache_finish_page;
58 return actor;
59}
60
61/* Implementation of page_actor for decompressing directly into page cache. */
62static void *direct_first_page(struct squashfs_page_actor *actor)
63{
64 actor->next_page = 1;
65 return actor->pageaddr = kmap_atomic(actor->page[0]);
66}
67
68static void *direct_next_page(struct squashfs_page_actor *actor)
69{
70 if (actor->pageaddr)
71 kunmap_atomic(actor->pageaddr);
72
73 return actor->pageaddr = actor->next_page == actor->pages ? NULL :
74 kmap_atomic(actor->page[actor->next_page++]);
75}
76
77static void direct_finish_page(struct squashfs_page_actor *actor)
78{
79 if (actor->pageaddr)
80 kunmap_atomic(actor->pageaddr);
81}
82
83struct squashfs_page_actor *squashfs_page_actor_init_special(struct page **page,
84 int pages, int length)
85{
86 struct squashfs_page_actor *actor = kmalloc(sizeof(*actor), GFP_KERNEL);
87
88 if (actor == NULL)
89 return NULL;
90
91 actor->length = length ? : pages * PAGE_CACHE_SIZE;
92 actor->page = page;
93 actor->pages = pages;
94 actor->next_page = 0;
95 actor->pageaddr = NULL;
96 actor->squashfs_first_page = direct_first_page;
97 actor->squashfs_next_page = direct_next_page;
98 actor->squashfs_finish_page = direct_finish_page;
99 return actor;
100}
diff --git a/fs/squashfs/page_actor.h b/fs/squashfs/page_actor.h
index 5b0ba5a7133a..26dd82008b82 100644
--- a/fs/squashfs/page_actor.h
+++ b/fs/squashfs/page_actor.h
@@ -8,6 +8,7 @@
8 * the COPYING file in the top-level directory. 8 * the COPYING file in the top-level directory.
9 */ 9 */
10 10
11#ifndef CONFIG_SQUASHFS_FILE_DIRECT
11struct squashfs_page_actor { 12struct squashfs_page_actor {
12 void **page; 13 void **page;
13 int pages; 14 int pages;
@@ -46,4 +47,35 @@ static inline void squashfs_finish_page(struct squashfs_page_actor *actor)
46{ 47{
47 /* empty */ 48 /* empty */
48} 49}
50#else
51struct squashfs_page_actor {
52 union {
53 void **buffer;
54 struct page **page;
55 };
56 void *pageaddr;
57 void *(*squashfs_first_page)(struct squashfs_page_actor *);
58 void *(*squashfs_next_page)(struct squashfs_page_actor *);
59 void (*squashfs_finish_page)(struct squashfs_page_actor *);
60 int pages;
61 int length;
62 int next_page;
63};
64
65extern struct squashfs_page_actor *squashfs_page_actor_init(void **, int, int);
66extern struct squashfs_page_actor *squashfs_page_actor_init_special(struct page
67 **, int, int);
68static inline void *squashfs_first_page(struct squashfs_page_actor *actor)
69{
70 return actor->squashfs_first_page(actor);
71}
72static inline void *squashfs_next_page(struct squashfs_page_actor *actor)
73{
74 return actor->squashfs_next_page(actor);
75}
76static inline void squashfs_finish_page(struct squashfs_page_actor *actor)
77{
78 actor->squashfs_finish_page(actor);
79}
80#endif
49#endif 81#endif