Skip to content

Commit

Permalink
Merge tag 'squashfs-updates' of git://git.kernel.org/pub/scm/linux/ke…
Browse files Browse the repository at this point in the history
…rnel/git/pkl/squashfs-next

Pull squashfs updates from Phillip Lougher:
 "These patches optionally improve the multi-threading peformance of
  Squashfs by adding parallel decompression, and direct decompression
  into the page cache, eliminating an intermediate buffer (removing
  memcpy overhead and lock contention)"

* tag 'squashfs-updates' of git://git.kernel.org/pub/scm/linux/kernel/git/pkl/squashfs-next:
  Squashfs: Check stream is not NULL in decompressor_multi.c
  Squashfs: Directly decompress into the page cache for file data
  Squashfs: Restructure squashfs_readpage()
  Squashfs: Generalise paging handling in the decompressors
  Squashfs: add multi-threaded decompression using percpu variable
  squashfs: Enhance parallel I/O
  Squashfs: Refactor decompressor interface and code
  • Loading branch information
Linus Torvalds committed Nov 20, 2013
2 parents 8b2e9b7 + ed4f381 commit af2e2f3
Showing 20 changed files with 1,145 additions and 243 deletions.
72 changes: 72 additions & 0 deletions fs/squashfs/Kconfig
Original file line number Diff line number Diff line change
@@ -25,6 +25,78 @@ config SQUASHFS

If unsure, say N.

choice
prompt "File decompression options"
depends on SQUASHFS
help
Squashfs now supports two options for decompressing file
data. Traditionally Squashfs has decompressed into an
intermediate buffer and then memcopied it into the page cache.
Squashfs now supports the ability to decompress directly into
the page cache.

If unsure, select "Decompress file data into an intermediate buffer"

config SQUASHFS_FILE_CACHE
bool "Decompress file data into an intermediate buffer"
help
Decompress file data into an intermediate buffer and then
memcopy it into the page cache.

config SQUASHFS_FILE_DIRECT
bool "Decompress files directly into the page cache"
help
Directly decompress file data into the page cache.
Doing so can significantly improve performance because
it eliminates a memcpy and it also removes the lock contention
on the single buffer.

endchoice

choice
prompt "Decompressor parallelisation options"
depends on SQUASHFS
help
Squashfs now supports three parallelisation options for
decompression. Each one exhibits various trade-offs between
decompression performance and CPU and memory usage.

If in doubt, select "Single threaded compression"

config SQUASHFS_DECOMP_SINGLE
bool "Single threaded compression"
help
Traditionally Squashfs has used single-threaded decompression.
Only one block (data or metadata) can be decompressed at any
one time. This limits CPU and memory usage to a minimum.

config SQUASHFS_DECOMP_MULTI
bool "Use multiple decompressors for parallel I/O"
help
By default Squashfs uses a single decompressor but it gives
poor performance on parallel I/O workloads when using multiple CPU
machines due to waiting on decompressor availability.

If you have a parallel I/O workload and your system has enough memory,
using this option may improve overall I/O performance.

This decompressor implementation uses up to two parallel
decompressors per core. It dynamically allocates decompressors
on a demand basis.

config SQUASHFS_DECOMP_MULTI_PERCPU
bool "Use percpu multiple decompressors for parallel I/O"
help
By default Squashfs uses a single decompressor but it gives
poor performance on parallel I/O workloads when using multiple CPU
machines due to waiting on decompressor availability.

This decompressor implementation uses a maximum of one
decompressor per core. It uses percpu variables to ensure
decompression is load-balanced across the cores.

endchoice

config SQUASHFS_XATTR
bool "Squashfs XATTR support"
depends on SQUASHFS
5 changes: 5 additions & 0 deletions fs/squashfs/Makefile
Original file line number Diff line number Diff line change
@@ -5,6 +5,11 @@
obj-$(CONFIG_SQUASHFS) += squashfs.o
squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o
squashfs-y += namei.o super.o symlink.o decompressor.o
squashfs-$(CONFIG_SQUASHFS_FILE_CACHE) += file_cache.o
squashfs-$(CONFIG_SQUASHFS_FILE_DIRECT) += file_direct.o page_actor.o
squashfs-$(CONFIG_SQUASHFS_DECOMP_SINGLE) += decompressor_single.o
squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI) += decompressor_multi.o
squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU) += decompressor_multi_percpu.o
squashfs-$(CONFIG_SQUASHFS_XATTR) += xattr.o xattr_id.o
squashfs-$(CONFIG_SQUASHFS_LZO) += lzo_wrapper.o
squashfs-$(CONFIG_SQUASHFS_XZ) += xz_wrapper.o
36 changes: 21 additions & 15 deletions fs/squashfs/block.c
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@
#include "squashfs_fs_sb.h"
#include "squashfs.h"
#include "decompressor.h"
#include "page_actor.h"

/*
* Read the metadata block length, this is stored in the first two
@@ -86,16 +87,16 @@ static struct buffer_head *get_block_length(struct super_block *sb,
* generated a larger block - this does occasionally happen with compression
* algorithms).
*/
int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
int length, u64 *next_index, int srclength, int pages)
int squashfs_read_data(struct super_block *sb, u64 index, int length,
u64 *next_index, struct squashfs_page_actor *output)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
struct buffer_head **bh;
int offset = index & ((1 << msblk->devblksize_log2) - 1);
u64 cur_index = index >> msblk->devblksize_log2;
int bytes, compressed, b = 0, k = 0, page = 0, avail;
int bytes, compressed, b = 0, k = 0, avail, i;

bh = kcalloc(((srclength + msblk->devblksize - 1)
bh = kcalloc(((output->length + msblk->devblksize - 1)
>> msblk->devblksize_log2) + 1, sizeof(*bh), GFP_KERNEL);
if (bh == NULL)
return -ENOMEM;
@@ -111,9 +112,9 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
*next_index = index + length;

TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n",
index, compressed ? "" : "un", length, srclength);
index, compressed ? "" : "un", length, output->length);

if (length < 0 || length > srclength ||
if (length < 0 || length > output->length ||
(index + length) > msblk->bytes_used)
goto read_failure;

@@ -145,7 +146,7 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
TRACE("Block @ 0x%llx, %scompressed size %d\n", index,
compressed ? "" : "un", length);

if (length < 0 || length > srclength ||
if (length < 0 || length > output->length ||
(index + length) > msblk->bytes_used)
goto block_release;

@@ -158,39 +159,44 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
ll_rw_block(READ, b - 1, bh + 1);
}

for (i = 0; i < b; i++) {
wait_on_buffer(bh[i]);
if (!buffer_uptodate(bh[i]))
goto block_release;
}

if (compressed) {
length = squashfs_decompress(msblk, buffer, bh, b, offset,
length, srclength, pages);
length = squashfs_decompress(msblk, bh, b, offset, length,
output);
if (length < 0)
goto read_failure;
} else {
/*
* Block is uncompressed.
*/
int in, pg_offset = 0;
void *data = squashfs_first_page(output);

for (bytes = length; k < b; k++) {
in = min(bytes, msblk->devblksize - offset);
bytes -= in;
wait_on_buffer(bh[k]);
if (!buffer_uptodate(bh[k]))
goto block_release;
while (in) {
if (pg_offset == PAGE_CACHE_SIZE) {
page++;
data = squashfs_next_page(output);
pg_offset = 0;
}
avail = min_t(int, in, PAGE_CACHE_SIZE -
pg_offset);
memcpy(buffer[page] + pg_offset,
bh[k]->b_data + offset, avail);
memcpy(data + pg_offset, bh[k]->b_data + offset,
avail);
in -= avail;
pg_offset += avail;
offset += avail;
}
offset = 0;
put_bh(bh[k]);
}
squashfs_finish_page(output);
}

kfree(bh);
28 changes: 23 additions & 5 deletions fs/squashfs/cache.c
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs.h"
#include "page_actor.h"

/*
* Look-up block in cache, and increment usage count. If not in cache, read
@@ -119,9 +120,8 @@ struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb,
entry->error = 0;
spin_unlock(&cache->lock);

entry->length = squashfs_read_data(sb, entry->data,
block, length, &entry->next_index,
cache->block_size, cache->pages);
entry->length = squashfs_read_data(sb, block, length,
&entry->next_index, entry->actor);

spin_lock(&cache->lock);

@@ -220,6 +220,7 @@ void squashfs_cache_delete(struct squashfs_cache *cache)
kfree(cache->entry[i].data[j]);
kfree(cache->entry[i].data);
}
kfree(cache->entry[i].actor);
}

kfree(cache->entry);
@@ -280,6 +281,13 @@ struct squashfs_cache *squashfs_cache_init(char *name, int entries,
goto cleanup;
}
}

entry->actor = squashfs_page_actor_init(entry->data,
cache->pages, 0);
if (entry->actor == NULL) {
ERROR("Failed to allocate %s cache entry\n", name);
goto cleanup;
}
}

return cache;
@@ -410,6 +418,7 @@ void *squashfs_read_table(struct super_block *sb, u64 block, int length)
int pages = (length + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
int i, res;
void *table, *buffer, **data;
struct squashfs_page_actor *actor;

table = buffer = kmalloc(length, GFP_KERNEL);
if (table == NULL)
@@ -421,19 +430,28 @@ void *squashfs_read_table(struct super_block *sb, u64 block, int length)
goto failed;
}

actor = squashfs_page_actor_init(data, pages, length);
if (actor == NULL) {
res = -ENOMEM;
goto failed2;
}

for (i = 0; i < pages; i++, buffer += PAGE_CACHE_SIZE)
data[i] = buffer;

res = squashfs_read_data(sb, data, block, length |
SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, length, pages);
res = squashfs_read_data(sb, block, length |
SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, actor);

kfree(data);
kfree(actor);

if (res < 0)
goto failed;

return table;

failed2:
kfree(data);
failed:
kfree(table);
return ERR_PTR(res);
59 changes: 42 additions & 17 deletions fs/squashfs/decompressor.c
Original file line number Diff line number Diff line change
@@ -30,36 +30,37 @@
#include "squashfs_fs_sb.h"
#include "decompressor.h"
#include "squashfs.h"
#include "page_actor.h"

/*
* This file (and decompressor.h) implements a decompressor framework for
* Squashfs, allowing multiple decompressors to be easily supported
*/

static const struct squashfs_decompressor squashfs_lzma_unsupported_comp_ops = {
NULL, NULL, NULL, LZMA_COMPRESSION, "lzma", 0
NULL, NULL, NULL, NULL, LZMA_COMPRESSION, "lzma", 0
};

#ifndef CONFIG_SQUASHFS_LZO
static const struct squashfs_decompressor squashfs_lzo_comp_ops = {
NULL, NULL, NULL, LZO_COMPRESSION, "lzo", 0
NULL, NULL, NULL, NULL, LZO_COMPRESSION, "lzo", 0
};
#endif

#ifndef CONFIG_SQUASHFS_XZ
static const struct squashfs_decompressor squashfs_xz_comp_ops = {
NULL, NULL, NULL, XZ_COMPRESSION, "xz", 0
NULL, NULL, NULL, NULL, XZ_COMPRESSION, "xz", 0
};
#endif

#ifndef CONFIG_SQUASHFS_ZLIB
static const struct squashfs_decompressor squashfs_zlib_comp_ops = {
NULL, NULL, NULL, ZLIB_COMPRESSION, "zlib", 0
NULL, NULL, NULL, NULL, ZLIB_COMPRESSION, "zlib", 0
};
#endif

static const struct squashfs_decompressor squashfs_unknown_comp_ops = {
NULL, NULL, NULL, 0, "unknown", 0
NULL, NULL, NULL, NULL, 0, "unknown", 0
};

static const struct squashfs_decompressor *decompressor[] = {
@@ -83,34 +84,58 @@ const struct squashfs_decompressor *squashfs_lookup_decompressor(int id)
}


void *squashfs_decompressor_init(struct super_block *sb, unsigned short flags)
static void *get_comp_opts(struct super_block *sb, unsigned short flags)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
void *strm, *buffer = NULL;
void *buffer = NULL, *comp_opts;
struct squashfs_page_actor *actor = NULL;
int length = 0;

/*
* Read decompressor specific options from file system if present
*/
if (SQUASHFS_COMP_OPTS(flags)) {
buffer = kmalloc(PAGE_CACHE_SIZE, GFP_KERNEL);
if (buffer == NULL)
return ERR_PTR(-ENOMEM);
if (buffer == NULL) {
comp_opts = ERR_PTR(-ENOMEM);
goto out;
}

actor = squashfs_page_actor_init(&buffer, 1, 0);
if (actor == NULL) {
comp_opts = ERR_PTR(-ENOMEM);
goto out;
}

length = squashfs_read_data(sb, &buffer,
sizeof(struct squashfs_super_block), 0, NULL,
PAGE_CACHE_SIZE, 1);
length = squashfs_read_data(sb,
sizeof(struct squashfs_super_block), 0, NULL, actor);

if (length < 0) {
strm = ERR_PTR(length);
goto finished;
comp_opts = ERR_PTR(length);
goto out;
}
}

strm = msblk->decompressor->init(msblk, buffer, length);
comp_opts = squashfs_comp_opts(msblk, buffer, length);

finished:
out:
kfree(actor);
kfree(buffer);
return comp_opts;
}


void *squashfs_decompressor_setup(struct super_block *sb, unsigned short flags)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
void *stream, *comp_opts = get_comp_opts(sb, flags);

if (IS_ERR(comp_opts))
return comp_opts;

stream = squashfs_decompressor_create(msblk, comp_opts);
if (IS_ERR(stream))
kfree(comp_opts);

return strm;
return stream;
}
Loading

0 comments on commit af2e2f3

Please sign in to comment.