diff options
-rw-r--r-- | fs/squashfs/Kconfig | 28 | ||||
-rw-r--r-- | fs/squashfs/Makefile | 4 | ||||
-rw-r--r-- | fs/squashfs/file_direct.c | 173 | ||||
-rw-r--r-- | fs/squashfs/page_actor.c | 100 | ||||
-rw-r--r-- | fs/squashfs/page_actor.h | 32 |
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 | ||
28 | choice | 28 | choice |
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 | |||
40 | config 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 | |||
46 | config 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 | |||
54 | endchoice | ||
55 | |||
56 | choice | ||
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 | ||
5 | obj-$(CONFIG_SQUASHFS) += squashfs.o | 5 | obj-$(CONFIG_SQUASHFS) += squashfs.o |
6 | squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o | 6 | squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o |
7 | squashfs-y += namei.o super.o symlink.o decompressor.o file_cache.o | 7 | squashfs-y += namei.o super.o symlink.o decompressor.o |
8 | squashfs-$(CONFIG_SQUASHFS_FILE_CACHE) += file_cache.o | ||
9 | squashfs-$(CONFIG_SQUASHFS_FILE_DIRECT) += file_direct.o page_actor.o | ||
8 | squashfs-$(CONFIG_SQUASHFS_DECOMP_SINGLE) += decompressor_single.o | 10 | squashfs-$(CONFIG_SQUASHFS_DECOMP_SINGLE) += decompressor_single.o |
9 | squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI) += decompressor_multi.o | 11 | squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI) += decompressor_multi.o |
10 | squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU) += decompressor_multi_percpu.o | 12 | squashfs-$(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 | |||
23 | static 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 */ | ||
27 | int 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 | |||
117 | mark_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 | |||
130 | out: | ||
131 | kfree(actor); | ||
132 | kfree(page); | ||
133 | return res; | ||
134 | } | ||
135 | |||
136 | |||
137 | static 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 | |||
170 | out: | ||
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 */ | ||
24 | static void *cache_first_page(struct squashfs_page_actor *actor) | ||
25 | { | ||
26 | actor->next_page = 1; | ||
27 | return actor->buffer[0]; | ||
28 | } | ||
29 | |||
30 | static 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 | |||
38 | static void cache_finish_page(struct squashfs_page_actor *actor) | ||
39 | { | ||
40 | /* empty */ | ||
41 | } | ||
42 | |||
43 | struct 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. */ | ||
62 | static 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 | |||
68 | static 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 | |||
77 | static void direct_finish_page(struct squashfs_page_actor *actor) | ||
78 | { | ||
79 | if (actor->pageaddr) | ||
80 | kunmap_atomic(actor->pageaddr); | ||
81 | } | ||
82 | |||
83 | struct 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 | ||
11 | struct squashfs_page_actor { | 12 | struct 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 | ||
51 | struct 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 | |||
65 | extern struct squashfs_page_actor *squashfs_page_actor_init(void **, int, int); | ||
66 | extern struct squashfs_page_actor *squashfs_page_actor_init_special(struct page | ||
67 | **, int, int); | ||
68 | static inline void *squashfs_first_page(struct squashfs_page_actor *actor) | ||
69 | { | ||
70 | return actor->squashfs_first_page(actor); | ||
71 | } | ||
72 | static inline void *squashfs_next_page(struct squashfs_page_actor *actor) | ||
73 | { | ||
74 | return actor->squashfs_next_page(actor); | ||
75 | } | ||
76 | static inline void squashfs_finish_page(struct squashfs_page_actor *actor) | ||
77 | { | ||
78 | actor->squashfs_finish_page(actor); | ||
79 | } | ||
80 | #endif | ||
49 | #endif | 81 | #endif |