Skip to content

Commit e994f5b

Browse files
vwaxakpm00
authored andcommitted
squashfs: cache partial compressed blocks
Before commit 93e72b3 ("squashfs: migrate from ll_rw_block usage to BIO"), compressed blocks read by squashfs were cached in the page cache, but that is not the case after that commit. That has lead to squashfs having to re-read a lot of sectors from disk/flash. For example, the first sectors of every metadata block need to be read twice from the disk. Once partially to read the length, and a second time to read the block itself. Also, in linear reads of large files, the last sectors of one data block are re-read from disk when reading the next data block, since the compressed blocks are of variable sizes and not aligned to device blocks. This extra I/O results in a degrade in read performance of, for example, ~16% in one scenario on my ARM platform using squashfs with dm-verity and NAND. Since the decompressed data is cached in the page cache or squashfs' internal metadata and fragment caches, caching _all_ compressed pages would lead to a lot of double caching and is undesirable. But make the code cache any disk blocks which were only partially requested, since these are the ones likely to include data which is needed by other file system blocks. This restores read performance in my test scenario. The compressed block caching is only applied when the disk block size is equal to the page size, to avoid having to deal with caching sub-page reads. [[email protected]: fs/squashfs/block.c needs linux/pagemap.h] [[email protected]: fix page update race] Link: https://lkml.kernel.org/r/[email protected] [[email protected]: fix page indices] Link: https://lkml.kernel.org/r/[email protected] [[email protected]: fix layout, per hch] Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Vincent Whitchurch <[email protected]> Reviewed-by: Christoph Hellwig <[email protected]> Reviewed-by: Phillip Lougher <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent 6b81459 commit e994f5b

File tree

3 files changed

+129
-6
lines changed

3 files changed

+129
-6
lines changed

fs/squashfs/block.c

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <linux/fs.h>
1818
#include <linux/vfs.h>
1919
#include <linux/slab.h>
20+
#include <linux/pagemap.h>
2021
#include <linux/string.h>
2122
#include <linux/bio.h>
2223

@@ -75,10 +76,101 @@ static int copy_bio_to_actor(struct bio *bio,
7576
return copied_bytes;
7677
}
7778

79+
static int squashfs_bio_read_cached(struct bio *fullbio,
80+
struct address_space *cache_mapping, u64 index, int length,
81+
u64 read_start, u64 read_end, int page_count)
82+
{
83+
struct page *head_to_cache = NULL, *tail_to_cache = NULL;
84+
struct block_device *bdev = fullbio->bi_bdev;
85+
int start_idx = 0, end_idx = 0;
86+
struct bvec_iter_all iter_all;
87+
struct bio *bio = NULL;
88+
struct bio_vec *bv;
89+
int idx = 0;
90+
int err = 0;
91+
92+
bio_for_each_segment_all(bv, fullbio, iter_all) {
93+
struct page *page = bv->bv_page;
94+
95+
if (page->mapping == cache_mapping) {
96+
idx++;
97+
continue;
98+
}
99+
100+
/*
101+
* We only use this when the device block size is the same as
102+
* the page size, so read_start and read_end cover full pages.
103+
*
104+
* Compare these to the original required index and length to
105+
* only cache pages which were requested partially, since these
106+
* are the ones which are likely to be needed when reading
107+
* adjacent blocks.
108+
*/
109+
if (idx == 0 && index != read_start)
110+
head_to_cache = page;
111+
else if (idx == page_count - 1 && index + length != read_end)
112+
tail_to_cache = page;
113+
114+
if (!bio || idx != end_idx) {
115+
struct bio *new = bio_alloc_clone(bdev, fullbio,
116+
GFP_NOIO, &fs_bio_set);
117+
118+
if (bio) {
119+
bio_trim(bio, start_idx * PAGE_SECTORS,
120+
(end_idx - start_idx) * PAGE_SECTORS);
121+
bio_chain(bio, new);
122+
submit_bio(bio);
123+
}
124+
125+
bio = new;
126+
start_idx = idx;
127+
}
128+
129+
idx++;
130+
end_idx = idx;
131+
}
132+
133+
if (bio) {
134+
bio_trim(bio, start_idx * PAGE_SECTORS,
135+
(end_idx - start_idx) * PAGE_SECTORS);
136+
err = submit_bio_wait(bio);
137+
bio_put(bio);
138+
}
139+
140+
if (err)
141+
return err;
142+
143+
if (head_to_cache) {
144+
int ret = add_to_page_cache_lru(head_to_cache, cache_mapping,
145+
read_start >> PAGE_SHIFT,
146+
GFP_NOIO);
147+
148+
if (!ret) {
149+
SetPageUptodate(head_to_cache);
150+
unlock_page(head_to_cache);
151+
}
152+
153+
}
154+
155+
if (tail_to_cache) {
156+
int ret = add_to_page_cache_lru(tail_to_cache, cache_mapping,
157+
(read_end >> PAGE_SHIFT) - 1,
158+
GFP_NOIO);
159+
160+
if (!ret) {
161+
SetPageUptodate(tail_to_cache);
162+
unlock_page(tail_to_cache);
163+
}
164+
}
165+
166+
return 0;
167+
}
168+
78169
static int squashfs_bio_read(struct super_block *sb, u64 index, int length,
79170
struct bio **biop, int *block_offset)
80171
{
81172
struct squashfs_sb_info *msblk = sb->s_fs_info;
173+
struct address_space *cache_mapping = msblk->cache_mapping;
82174
const u64 read_start = round_down(index, msblk->devblksize);
83175
const sector_t block = read_start >> msblk->devblksize_log2;
84176
const u64 read_end = round_up(index + length, msblk->devblksize);
@@ -98,21 +190,34 @@ static int squashfs_bio_read(struct super_block *sb, u64 index, int length,
98190
for (i = 0; i < page_count; ++i) {
99191
unsigned int len =
100192
min_t(unsigned int, PAGE_SIZE - offset, total_len);
101-
struct page *page = alloc_page(GFP_NOIO);
193+
struct page *page = NULL;
194+
195+
if (cache_mapping)
196+
page = find_get_page(cache_mapping,
197+
(read_start >> PAGE_SHIFT) + i);
198+
if (!page)
199+
page = alloc_page(GFP_NOIO);
102200

103201
if (!page) {
104202
error = -ENOMEM;
105203
goto out_free_bio;
106204
}
107-
if (!bio_add_page(bio, page, len, offset)) {
108-
error = -EIO;
109-
goto out_free_bio;
110-
}
205+
206+
/*
207+
* Use the __ version to avoid merging since we need each page
208+
* to be separate when we check for and avoid cached pages.
209+
*/
210+
__bio_add_page(bio, page, len, offset);
111211
offset = 0;
112212
total_len -= len;
113213
}
114214

115-
error = submit_bio_wait(bio);
215+
if (cache_mapping)
216+
error = squashfs_bio_read_cached(bio, cache_mapping, index,
217+
length, read_start, read_end,
218+
page_count);
219+
else
220+
error = submit_bio_wait(bio);
116221
if (error)
117222
goto out_free_bio;
118223

fs/squashfs/squashfs_fs_sb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct squashfs_sb_info {
4747
struct squashfs_cache *block_cache;
4848
struct squashfs_cache *fragment_cache;
4949
struct squashfs_cache *read_page;
50+
struct address_space *cache_mapping;
5051
int next_meta_index;
5152
__le64 *id_table;
5253
__le64 *fragment_index;

fs/squashfs/super.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,19 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
329329
goto failed_mount;
330330
}
331331

332+
if (msblk->devblksize == PAGE_SIZE) {
333+
struct inode *cache = new_inode(sb);
334+
335+
if (cache == NULL)
336+
goto failed_mount;
337+
338+
set_nlink(cache, 1);
339+
cache->i_size = OFFSET_MAX;
340+
mapping_set_gfp_mask(cache->i_mapping, GFP_NOFS);
341+
342+
msblk->cache_mapping = cache->i_mapping;
343+
}
344+
332345
msblk->stream = squashfs_decompressor_setup(sb, flags);
333346
if (IS_ERR(msblk->stream)) {
334347
err = PTR_ERR(msblk->stream);
@@ -454,6 +467,8 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
454467
squashfs_cache_delete(msblk->block_cache);
455468
squashfs_cache_delete(msblk->fragment_cache);
456469
squashfs_cache_delete(msblk->read_page);
470+
if (msblk->cache_mapping)
471+
iput(msblk->cache_mapping->host);
457472
msblk->thread_ops->destroy(msblk);
458473
kfree(msblk->inode_lookup_table);
459474
kfree(msblk->fragment_index);
@@ -572,6 +587,8 @@ static void squashfs_put_super(struct super_block *sb)
572587
squashfs_cache_delete(sbi->block_cache);
573588
squashfs_cache_delete(sbi->fragment_cache);
574589
squashfs_cache_delete(sbi->read_page);
590+
if (sbi->cache_mapping)
591+
iput(sbi->cache_mapping->host);
575592
sbi->thread_ops->destroy(sbi);
576593
kfree(sbi->id_table);
577594
kfree(sbi->fragment_index);

0 commit comments

Comments
 (0)