2019-05-21 01:08:00 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2009-01-05 16:46:27 +08:00
|
|
|
/*
|
|
|
|
* Squashfs - a compressed read only filesystem for Linux
|
|
|
|
*
|
|
|
|
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
|
2011-05-26 17:39:56 +08:00
|
|
|
* Phillip Lougher <phillip@squashfs.org.uk>
|
2009-01-05 16:46:27 +08:00
|
|
|
*
|
|
|
|
* block.c
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This file implements the low-level routines to read and decompress
|
|
|
|
* datablocks and metadata blocks.
|
|
|
|
*/
|
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
#include <linux/blkdev.h>
|
2009-01-05 16:46:27 +08:00
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/vfs.h>
|
|
|
|
#include <linux/slab.h>
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
#include <linux/pagemap.h>
|
2009-01-05 16:46:27 +08:00
|
|
|
#include <linux/string.h>
|
2016-11-01 21:40:13 +08:00
|
|
|
#include <linux/bio.h>
|
2009-01-05 16:46:27 +08:00
|
|
|
|
|
|
|
#include "squashfs_fs.h"
|
|
|
|
#include "squashfs_fs_sb.h"
|
|
|
|
#include "squashfs.h"
|
2009-10-06 11:04:15 +08:00
|
|
|
#include "decompressor.h"
|
2013-11-18 10:59:12 +08:00
|
|
|
#include "page_actor.h"
|
2009-01-05 16:46:27 +08:00
|
|
|
|
|
|
|
/*
|
2020-06-02 12:45:23 +08:00
|
|
|
* Returns the amount of bytes copied to the page actor.
|
2009-01-05 16:46:27 +08:00
|
|
|
*/
|
2020-06-02 12:45:23 +08:00
|
|
|
static int copy_bio_to_actor(struct bio *bio,
|
|
|
|
struct squashfs_page_actor *actor,
|
|
|
|
int offset, int req_length)
|
|
|
|
{
|
squashfs: extend "page actor" to handle missing pages
Patch series "Squashfs: handle missing pages decompressing into page
cache".
This patchset enables Squashfs to handle missing pages when directly
decompressing datablocks into the page cache.
Previously if the full set of pages needed was not available, Squashfs
would have to fall back to using an intermediate buffer (the older
method), which is slower, involving a memcopy, and it introduces
contention on a shared buffer.
The first patch extends the "page actor" code to handle missing pages.
The second patch updates Squashfs_readpage_block() to use the new
functionality, and removes the code that falls back to using an
intermediate buffer.
This patchset is independent of the readahead work, and it is standalone.
It can be merged on its own.
But the readahead patch for efficiency also needs this patch-set.
This patch (of 2):
This patch extends the "page actor" code to handle missing pages.
Previously if the full set of pages needed to decompress a Squashfs
datablock was unavailable, this would cause decompression to fail on the
missing pages.
In this case direct decompression into the page cache could not be
achieved and the code would fall back to using the older intermediate
buffer method.
With this patch, direct decompression into the page cache can be achieved
with missing pages.
For "multi-shot" decompressors (zlib, xz, zstd), the page actor will
allocate a temporary buffer which is passed to the decompressor, and then
freed by the page actor.
For "single shot" decompressors (lz4, lzo) which decompress into a
contiguous "bounce buffer", and which is then copied into the page cache,
it would be pointless to allocate a temporary buffer, memcpy into it, and
then free it. For these decompressors -ENOMEM is returned, which
signifies that the memcpy for that page should be skipped.
This also happens if the data block is uncompressed.
Link: https://lkml.kernel.org/r/20220611032133.5743-1-phillip@squashfs.org.uk
Link: https://lkml.kernel.org/r/20220611032133.5743-2-phillip@squashfs.org.uk
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Hsin-Yi Wang <hsinyi@chromium.org>
Cc: Xiongwei Song <Xiongwei.Song@windriver.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2022-06-11 11:21:32 +08:00
|
|
|
void *actor_addr;
|
2020-06-02 12:45:23 +08:00
|
|
|
struct bvec_iter_all iter_all = {};
|
|
|
|
struct bio_vec *bvec = bvec_init_iter_all(&iter_all);
|
|
|
|
int copied_bytes = 0;
|
|
|
|
int actor_offset = 0;
|
|
|
|
|
squashfs: extend "page actor" to handle missing pages
Patch series "Squashfs: handle missing pages decompressing into page
cache".
This patchset enables Squashfs to handle missing pages when directly
decompressing datablocks into the page cache.
Previously if the full set of pages needed was not available, Squashfs
would have to fall back to using an intermediate buffer (the older
method), which is slower, involving a memcopy, and it introduces
contention on a shared buffer.
The first patch extends the "page actor" code to handle missing pages.
The second patch updates Squashfs_readpage_block() to use the new
functionality, and removes the code that falls back to using an
intermediate buffer.
This patchset is independent of the readahead work, and it is standalone.
It can be merged on its own.
But the readahead patch for efficiency also needs this patch-set.
This patch (of 2):
This patch extends the "page actor" code to handle missing pages.
Previously if the full set of pages needed to decompress a Squashfs
datablock was unavailable, this would cause decompression to fail on the
missing pages.
In this case direct decompression into the page cache could not be
achieved and the code would fall back to using the older intermediate
buffer method.
With this patch, direct decompression into the page cache can be achieved
with missing pages.
For "multi-shot" decompressors (zlib, xz, zstd), the page actor will
allocate a temporary buffer which is passed to the decompressor, and then
freed by the page actor.
For "single shot" decompressors (lz4, lzo) which decompress into a
contiguous "bounce buffer", and which is then copied into the page cache,
it would be pointless to allocate a temporary buffer, memcpy into it, and
then free it. For these decompressors -ENOMEM is returned, which
signifies that the memcpy for that page should be skipped.
This also happens if the data block is uncompressed.
Link: https://lkml.kernel.org/r/20220611032133.5743-1-phillip@squashfs.org.uk
Link: https://lkml.kernel.org/r/20220611032133.5743-2-phillip@squashfs.org.uk
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Hsin-Yi Wang <hsinyi@chromium.org>
Cc: Xiongwei Song <Xiongwei.Song@windriver.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2022-06-11 11:21:32 +08:00
|
|
|
squashfs_actor_nobuff(actor);
|
|
|
|
actor_addr = squashfs_first_page(actor);
|
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all)))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while (copied_bytes < req_length) {
|
|
|
|
int bytes_to_copy = min_t(int, bvec->bv_len - offset,
|
|
|
|
PAGE_SIZE - actor_offset);
|
|
|
|
|
|
|
|
bytes_to_copy = min_t(int, bytes_to_copy,
|
|
|
|
req_length - copied_bytes);
|
squashfs: extend "page actor" to handle missing pages
Patch series "Squashfs: handle missing pages decompressing into page
cache".
This patchset enables Squashfs to handle missing pages when directly
decompressing datablocks into the page cache.
Previously if the full set of pages needed was not available, Squashfs
would have to fall back to using an intermediate buffer (the older
method), which is slower, involving a memcopy, and it introduces
contention on a shared buffer.
The first patch extends the "page actor" code to handle missing pages.
The second patch updates Squashfs_readpage_block() to use the new
functionality, and removes the code that falls back to using an
intermediate buffer.
This patchset is independent of the readahead work, and it is standalone.
It can be merged on its own.
But the readahead patch for efficiency also needs this patch-set.
This patch (of 2):
This patch extends the "page actor" code to handle missing pages.
Previously if the full set of pages needed to decompress a Squashfs
datablock was unavailable, this would cause decompression to fail on the
missing pages.
In this case direct decompression into the page cache could not be
achieved and the code would fall back to using the older intermediate
buffer method.
With this patch, direct decompression into the page cache can be achieved
with missing pages.
For "multi-shot" decompressors (zlib, xz, zstd), the page actor will
allocate a temporary buffer which is passed to the decompressor, and then
freed by the page actor.
For "single shot" decompressors (lz4, lzo) which decompress into a
contiguous "bounce buffer", and which is then copied into the page cache,
it would be pointless to allocate a temporary buffer, memcpy into it, and
then free it. For these decompressors -ENOMEM is returned, which
signifies that the memcpy for that page should be skipped.
This also happens if the data block is uncompressed.
Link: https://lkml.kernel.org/r/20220611032133.5743-1-phillip@squashfs.org.uk
Link: https://lkml.kernel.org/r/20220611032133.5743-2-phillip@squashfs.org.uk
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Hsin-Yi Wang <hsinyi@chromium.org>
Cc: Xiongwei Song <Xiongwei.Song@windriver.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2022-06-11 11:21:32 +08:00
|
|
|
if (!IS_ERR(actor_addr))
|
|
|
|
memcpy(actor_addr + actor_offset, bvec_virt(bvec) +
|
|
|
|
offset, bytes_to_copy);
|
2020-06-02 12:45:23 +08:00
|
|
|
|
|
|
|
actor_offset += bytes_to_copy;
|
|
|
|
copied_bytes += bytes_to_copy;
|
|
|
|
offset += bytes_to_copy;
|
|
|
|
|
|
|
|
if (actor_offset >= PAGE_SIZE) {
|
|
|
|
actor_addr = squashfs_next_page(actor);
|
|
|
|
if (!actor_addr)
|
|
|
|
break;
|
|
|
|
actor_offset = 0;
|
|
|
|
}
|
|
|
|
if (offset >= bvec->bv_len) {
|
|
|
|
if (!bio_next_segment(bio, &iter_all))
|
|
|
|
break;
|
|
|
|
offset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
squashfs_finish_page(actor);
|
|
|
|
return copied_bytes;
|
|
|
|
}
|
|
|
|
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
static int squashfs_bio_read_cached(struct bio *fullbio,
|
|
|
|
struct address_space *cache_mapping, u64 index, int length,
|
|
|
|
u64 read_start, u64 read_end, int page_count)
|
|
|
|
{
|
|
|
|
struct page *head_to_cache = NULL, *tail_to_cache = NULL;
|
|
|
|
struct block_device *bdev = fullbio->bi_bdev;
|
|
|
|
int start_idx = 0, end_idx = 0;
|
|
|
|
struct bvec_iter_all iter_all;
|
|
|
|
struct bio *bio = NULL;
|
|
|
|
struct bio_vec *bv;
|
|
|
|
int idx = 0;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
bio_for_each_segment_all(bv, fullbio, iter_all) {
|
|
|
|
struct page *page = bv->bv_page;
|
|
|
|
|
|
|
|
if (page->mapping == cache_mapping) {
|
|
|
|
idx++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only use this when the device block size is the same as
|
|
|
|
* the page size, so read_start and read_end cover full pages.
|
|
|
|
*
|
|
|
|
* Compare these to the original required index and length to
|
|
|
|
* only cache pages which were requested partially, since these
|
|
|
|
* are the ones which are likely to be needed when reading
|
|
|
|
* adjacent blocks.
|
|
|
|
*/
|
|
|
|
if (idx == 0 && index != read_start)
|
|
|
|
head_to_cache = page;
|
|
|
|
else if (idx == page_count - 1 && index + length != read_end)
|
|
|
|
tail_to_cache = page;
|
|
|
|
|
|
|
|
if (!bio || idx != end_idx) {
|
|
|
|
struct bio *new = bio_alloc_clone(bdev, fullbio,
|
|
|
|
GFP_NOIO, &fs_bio_set);
|
|
|
|
|
|
|
|
if (bio) {
|
|
|
|
bio_trim(bio, start_idx * PAGE_SECTORS,
|
|
|
|
(end_idx - start_idx) * PAGE_SECTORS);
|
|
|
|
bio_chain(bio, new);
|
|
|
|
submit_bio(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
bio = new;
|
|
|
|
start_idx = idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
end_idx = idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bio) {
|
|
|
|
bio_trim(bio, start_idx * PAGE_SECTORS,
|
|
|
|
(end_idx - start_idx) * PAGE_SECTORS);
|
|
|
|
err = submit_bio_wait(bio);
|
|
|
|
bio_put(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (head_to_cache) {
|
|
|
|
int ret = add_to_page_cache_lru(head_to_cache, cache_mapping,
|
|
|
|
read_start >> PAGE_SHIFT,
|
|
|
|
GFP_NOIO);
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
SetPageUptodate(head_to_cache);
|
|
|
|
unlock_page(head_to_cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tail_to_cache) {
|
|
|
|
int ret = add_to_page_cache_lru(tail_to_cache, cache_mapping,
|
|
|
|
(read_end >> PAGE_SHIFT) - 1,
|
|
|
|
GFP_NOIO);
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
SetPageUptodate(tail_to_cache);
|
|
|
|
unlock_page(tail_to_cache);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-06-29 22:17:57 +08:00
|
|
|
static struct page *squashfs_get_cache_page(struct address_space *mapping,
|
|
|
|
pgoff_t index)
|
|
|
|
{
|
|
|
|
struct page *page;
|
|
|
|
|
|
|
|
if (!mapping)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
page = find_get_page(mapping, index);
|
|
|
|
if (!page)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!PageUptodate(page)) {
|
|
|
|
put_page(page);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return page;
|
|
|
|
}
|
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
static int squashfs_bio_read(struct super_block *sb, u64 index, int length,
|
|
|
|
struct bio **biop, int *block_offset)
|
2009-01-05 16:46:27 +08:00
|
|
|
{
|
|
|
|
struct squashfs_sb_info *msblk = sb->s_fs_info;
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
struct address_space *cache_mapping = msblk->cache_mapping;
|
2020-06-02 12:45:23 +08:00
|
|
|
const u64 read_start = round_down(index, msblk->devblksize);
|
|
|
|
const sector_t block = read_start >> msblk->devblksize_log2;
|
|
|
|
const u64 read_end = round_up(index + length, msblk->devblksize);
|
|
|
|
const sector_t block_end = read_end >> msblk->devblksize_log2;
|
|
|
|
int offset = read_start - round_down(index, PAGE_SIZE);
|
|
|
|
int total_len = (block_end - block) << msblk->devblksize_log2;
|
|
|
|
const int page_count = DIV_ROUND_UP(total_len + offset, PAGE_SIZE);
|
|
|
|
int error, i;
|
|
|
|
struct bio *bio;
|
|
|
|
|
2022-04-06 14:12:27 +08:00
|
|
|
bio = bio_kmalloc(page_count, GFP_NOIO);
|
2020-06-02 12:45:23 +08:00
|
|
|
if (!bio)
|
|
|
|
return -ENOMEM;
|
2022-04-06 14:12:27 +08:00
|
|
|
bio_init(bio, sb->s_bdev, bio->bi_inline_vecs, page_count, REQ_OP_READ);
|
2020-06-02 12:45:23 +08:00
|
|
|
bio->bi_iter.bi_sector = block * (msblk->devblksize >> SECTOR_SHIFT);
|
|
|
|
|
|
|
|
for (i = 0; i < page_count; ++i) {
|
|
|
|
unsigned int len =
|
|
|
|
min_t(unsigned int, PAGE_SIZE - offset, total_len);
|
2023-06-29 22:17:57 +08:00
|
|
|
pgoff_t index = (read_start >> PAGE_SHIFT) + i;
|
|
|
|
struct page *page;
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
|
2023-06-29 22:17:57 +08:00
|
|
|
page = squashfs_get_cache_page(cache_mapping, index);
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
if (!page)
|
|
|
|
page = alloc_page(GFP_NOIO);
|
2020-06-02 12:45:23 +08:00
|
|
|
|
|
|
|
if (!page) {
|
|
|
|
error = -ENOMEM;
|
|
|
|
goto out_free_bio;
|
|
|
|
}
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Use the __ version to avoid merging since we need each page
|
|
|
|
* to be separate when we check for and avoid cached pages.
|
|
|
|
*/
|
|
|
|
__bio_add_page(bio, page, len, offset);
|
2020-06-02 12:45:23 +08:00
|
|
|
offset = 0;
|
|
|
|
total_len -= len;
|
2009-01-05 16:46:27 +08:00
|
|
|
}
|
|
|
|
|
squashfs: cache partial compressed blocks
Before commit 93e72b3c612adcaca1 ("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.
[akpm@linux-foundation.org: fs/squashfs/block.c needs linux/pagemap.h]
[vincent.whitchurch@axis.com: fix page update race]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-1-d54a7fa23e7b@axis.com
[vincent.whitchurch@axis.com: fix page indices]
Link: https://lkml.kernel.org/r/20230526-squashfs-cache-fixup-v1-2-d54a7fa23e7b@axis.com
[akpm@linux-foundation.org: fix layout, per hch]
Link: https://lkml.kernel.org/r/20230510-squashfs-cache-v4-1-3bd394e1ee71@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Phillip Lougher <phillip@squashfs.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-05-17 22:18:19 +08:00
|
|
|
if (cache_mapping)
|
|
|
|
error = squashfs_bio_read_cached(bio, cache_mapping, index,
|
|
|
|
length, read_start, read_end,
|
|
|
|
page_count);
|
|
|
|
else
|
|
|
|
error = submit_bio_wait(bio);
|
2020-06-02 12:45:23 +08:00
|
|
|
if (error)
|
|
|
|
goto out_free_bio;
|
2009-01-05 16:46:27 +08:00
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
*biop = bio;
|
|
|
|
*block_offset = index & ((1 << msblk->devblksize_log2) - 1);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_free_bio:
|
|
|
|
bio_free_pages(bio);
|
2022-04-06 14:12:27 +08:00
|
|
|
bio_uninit(bio);
|
|
|
|
kfree(bio);
|
2020-06-02 12:45:23 +08:00
|
|
|
return error;
|
|
|
|
}
|
2009-01-05 16:46:27 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Read and decompress a metadata block or datablock. Length is non-zero
|
|
|
|
* if a datablock is being read (the size is stored elsewhere in the
|
|
|
|
* filesystem), otherwise the length is obtained from the first two bytes of
|
|
|
|
* the metadata block. A bit in the length field indicates if the block
|
|
|
|
* is stored uncompressed in the filesystem (usually because compression
|
2012-03-06 09:18:49 +08:00
|
|
|
* generated a larger block - this does occasionally happen with compression
|
|
|
|
* algorithms).
|
2009-01-05 16:46:27 +08:00
|
|
|
*/
|
2013-11-18 10:59:12 +08:00
|
|
|
int squashfs_read_data(struct super_block *sb, u64 index, int length,
|
2020-06-02 12:45:23 +08:00
|
|
|
u64 *next_index, struct squashfs_page_actor *output)
|
2009-01-05 16:46:27 +08:00
|
|
|
{
|
|
|
|
struct squashfs_sb_info *msblk = sb->s_fs_info;
|
2020-06-02 12:45:23 +08:00
|
|
|
struct bio *bio = NULL;
|
|
|
|
int compressed;
|
|
|
|
int res;
|
|
|
|
int offset;
|
2009-01-05 16:46:27 +08:00
|
|
|
|
|
|
|
if (length) {
|
|
|
|
/*
|
|
|
|
* Datablock.
|
|
|
|
*/
|
|
|
|
compressed = SQUASHFS_COMPRESSED_BLOCK(length);
|
|
|
|
length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length);
|
|
|
|
TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n",
|
2013-11-18 10:59:12 +08:00
|
|
|
index, compressed ? "" : "un", length, output->length);
|
2009-01-05 16:46:27 +08:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Metadata block.
|
|
|
|
*/
|
2020-06-02 12:45:23 +08:00
|
|
|
const u8 *data;
|
|
|
|
struct bvec_iter_all iter_all = {};
|
|
|
|
struct bio_vec *bvec = bvec_init_iter_all(&iter_all);
|
2009-01-05 16:46:27 +08:00
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
if (index + 2 > msblk->bytes_used) {
|
|
|
|
res = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
res = squashfs_bio_read(sb, index, 2, &bio, &offset);
|
|
|
|
if (res)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) {
|
|
|
|
res = -EIO;
|
|
|
|
goto out_free_bio;
|
|
|
|
}
|
|
|
|
/* Extract the length of the metadata block */
|
2021-08-04 17:56:25 +08:00
|
|
|
data = bvec_virt(bvec);
|
2020-06-02 12:45:23 +08:00
|
|
|
length = data[offset];
|
2020-07-24 12:15:40 +08:00
|
|
|
if (offset < bvec->bv_len - 1) {
|
2020-06-02 12:45:23 +08:00
|
|
|
length |= data[offset + 1] << 8;
|
|
|
|
} else {
|
|
|
|
if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) {
|
|
|
|
res = -EIO;
|
|
|
|
goto out_free_bio;
|
|
|
|
}
|
2021-08-04 17:56:25 +08:00
|
|
|
data = bvec_virt(bvec);
|
2020-06-02 12:45:23 +08:00
|
|
|
length |= data[0] << 8;
|
|
|
|
}
|
|
|
|
bio_free_pages(bio);
|
2022-04-06 14:12:27 +08:00
|
|
|
bio_uninit(bio);
|
|
|
|
kfree(bio);
|
2009-01-05 16:46:27 +08:00
|
|
|
|
|
|
|
compressed = SQUASHFS_COMPRESSED(length);
|
|
|
|
length = SQUASHFS_COMPRESSED_SIZE(length);
|
2020-06-02 12:45:23 +08:00
|
|
|
index += 2;
|
2009-01-05 16:46:27 +08:00
|
|
|
|
2021-02-10 05:41:50 +08:00
|
|
|
TRACE("Block @ 0x%llx, %scompressed size %d\n", index - 2,
|
2020-06-02 12:45:23 +08:00
|
|
|
compressed ? "" : "un", length);
|
2009-01-05 16:46:27 +08:00
|
|
|
}
|
2023-11-16 11:13:52 +08:00
|
|
|
if (length <= 0 || length > output->length ||
|
2021-02-10 05:41:50 +08:00
|
|
|
(index + length) > msblk->bytes_used) {
|
|
|
|
res = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
if (next_index)
|
|
|
|
*next_index = index + length;
|
2009-01-05 16:46:27 +08:00
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
res = squashfs_bio_read(sb, index, length, &bio, &offset);
|
|
|
|
if (res)
|
|
|
|
goto out;
|
2013-11-13 10:56:26 +08:00
|
|
|
|
2009-01-05 16:46:27 +08:00
|
|
|
if (compressed) {
|
2020-06-02 12:45:23 +08:00
|
|
|
if (!msblk->stream) {
|
|
|
|
res = -EIO;
|
|
|
|
goto out_free_bio;
|
2009-01-05 16:46:27 +08:00
|
|
|
}
|
2022-10-19 11:09:29 +08:00
|
|
|
res = msblk->thread_ops->decompress(msblk, bio, offset, length, output);
|
2020-06-02 12:45:23 +08:00
|
|
|
} else {
|
|
|
|
res = copy_bio_to_actor(bio, output, offset, length);
|
2009-01-05 16:46:27 +08:00
|
|
|
}
|
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
out_free_bio:
|
|
|
|
bio_free_pages(bio);
|
2022-04-06 14:12:27 +08:00
|
|
|
bio_uninit(bio);
|
|
|
|
kfree(bio);
|
2020-06-02 12:45:23 +08:00
|
|
|
out:
|
2021-06-29 10:33:55 +08:00
|
|
|
if (res < 0) {
|
2020-06-02 12:45:23 +08:00
|
|
|
ERROR("Failed to read block 0x%llx: %d\n", index, res);
|
2021-06-29 10:33:55 +08:00
|
|
|
if (msblk->panic_on_errors)
|
|
|
|
panic("squashfs read failed");
|
|
|
|
}
|
2009-01-05 16:46:27 +08:00
|
|
|
|
2020-06-02 12:45:23 +08:00
|
|
|
return res;
|
2009-01-05 16:46:27 +08:00
|
|
|
}
|