Skip to content

Commit

Permalink
UBIFS: add bulk-read facility
Browse files Browse the repository at this point in the history
Some flash media are capable of reading sequentially at faster rates.
UBIFS bulk-read facility is designed to take advantage of that, by
reading in one go consecutive data nodes that are also located
consecutively in the same LEB.

Read speed on Arm platform with OneNAND goes from 17 MiB/s to
19 MiB/s.

Signed-off-by: Adrian Hunter <ext-adrian.hunter@nokia.com>
  • Loading branch information
Adrian Hunter authored and Artem Bityutskiy committed Sep 30, 2008
1 parent a70948b commit 4793e7c
Show file tree
Hide file tree
Showing 6 changed files with 629 additions and 3 deletions.
3 changes: 3 additions & 0 deletions Documentation/filesystems/ubifs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ norm_unmount (*) commit on unmount; the journal is committed
fast_unmount do not commit on unmount; this option makes
unmount faster, but the next mount slower
because of the need to replay the journal.
bulk_read read more in one go to take advantage of flash
media that read faster sequentially
no_bulk_read (*) do not bulk-read


Quick usage instructions
Expand Down
248 changes: 248 additions & 0 deletions fs/ubifs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,256 @@ static int ubifs_write_end(struct file *file, struct address_space *mapping,
return copied;
}

/**
* populate_page - copy data nodes into a page for bulk-read.
* @c: UBIFS file-system description object
* @page: page
* @bu: bulk-read information
* @n: next zbranch slot
*
* This function returns %0 on success and a negative error code on failure.
*/
static int populate_page(struct ubifs_info *c, struct page *page,
struct bu_info *bu, int *n)
{
int i = 0, nn = *n, offs = bu->zbranch[0].offs, hole = 1, read = 0;
struct inode *inode = page->mapping->host;
loff_t i_size = i_size_read(inode);
unsigned int page_block;
void *addr, *zaddr;
pgoff_t end_index;

dbg_gen("ino %lu, pg %lu, i_size %lld, flags %#lx",
inode->i_ino, page->index, i_size, page->flags);

addr = zaddr = kmap(page);

end_index = (i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
if (!i_size || page->index > end_index) {
memset(addr, 0, PAGE_CACHE_SIZE);
goto out_hole;
}

page_block = page->index << UBIFS_BLOCKS_PER_PAGE_SHIFT;
while (1) {
int err, len, out_len, dlen;

if (nn >= bu->cnt ||
key_block(c, &bu->zbranch[nn].key) != page_block)
memset(addr, 0, UBIFS_BLOCK_SIZE);
else {
struct ubifs_data_node *dn;

dn = bu->buf + (bu->zbranch[nn].offs - offs);

ubifs_assert(dn->ch.sqnum >
ubifs_inode(inode)->creat_sqnum);

len = le32_to_cpu(dn->size);
if (len <= 0 || len > UBIFS_BLOCK_SIZE)
goto out_err;

dlen = le32_to_cpu(dn->ch.len) - UBIFS_DATA_NODE_SZ;
out_len = UBIFS_BLOCK_SIZE;
err = ubifs_decompress(&dn->data, dlen, addr, &out_len,
le16_to_cpu(dn->compr_type));
if (err || len != out_len)
goto out_err;

if (len < UBIFS_BLOCK_SIZE)
memset(addr + len, 0, UBIFS_BLOCK_SIZE - len);

nn += 1;
hole = 0;
read = (i << UBIFS_BLOCK_SHIFT) + len;
}
if (++i >= UBIFS_BLOCKS_PER_PAGE)
break;
addr += UBIFS_BLOCK_SIZE;
page_block += 1;
}

if (end_index == page->index) {
int len = i_size & (PAGE_CACHE_SIZE - 1);

if (len < read)
memset(zaddr + len, 0, read - len);
}

out_hole:
if (hole) {
SetPageChecked(page);
dbg_gen("hole");
}

SetPageUptodate(page);
ClearPageError(page);
flush_dcache_page(page);
kunmap(page);
*n = nn;
return 0;

out_err:
ClearPageUptodate(page);
SetPageError(page);
flush_dcache_page(page);
kunmap(page);
ubifs_err("bad data node (block %u, inode %lu)",
page_block, inode->i_ino);
return -EINVAL;
}

/**
* ubifs_do_bulk_read - do bulk-read.
* @c: UBIFS file-system description object
* @page1: first page
*
* This function returns %1 if the bulk-read is done, otherwise %0 is returned.
*/
static int ubifs_do_bulk_read(struct ubifs_info *c, struct page *page1)
{
pgoff_t offset = page1->index, end_index;
struct address_space *mapping = page1->mapping;
struct inode *inode = mapping->host;
struct ubifs_inode *ui = ubifs_inode(inode);
struct bu_info *bu;
int err, page_idx, page_cnt, ret = 0, n = 0;
loff_t isize;

bu = kmalloc(sizeof(struct bu_info), GFP_NOFS);
if (!bu)
return 0;

bu->buf_len = c->bulk_read_buf_size;
bu->buf = kmalloc(bu->buf_len, GFP_NOFS);
if (!bu->buf)
goto out_free;

data_key_init(c, &bu->key, inode->i_ino,
offset << UBIFS_BLOCKS_PER_PAGE_SHIFT);

err = ubifs_tnc_get_bu_keys(c, bu);
if (err)
goto out_warn;

if (bu->eof) {
/* Turn off bulk-read at the end of the file */
ui->read_in_a_row = 1;
ui->bulk_read = 0;
}

page_cnt = bu->blk_cnt >> UBIFS_BLOCKS_PER_PAGE_SHIFT;
if (!page_cnt) {
/*
* This happens when there are multiple blocks per page and the
* blocks for the first page we are looking for, are not
* together. If all the pages were like this, bulk-read would
* reduce performance, so we turn it off for a while.
*/
ui->read_in_a_row = 0;
ui->bulk_read = 0;
goto out_free;
}

if (bu->cnt) {
err = ubifs_tnc_bulk_read(c, bu);
if (err)
goto out_warn;
}

err = populate_page(c, page1, bu, &n);
if (err)
goto out_warn;

unlock_page(page1);
ret = 1;

isize = i_size_read(inode);
if (isize == 0)
goto out_free;
end_index = ((isize - 1) >> PAGE_CACHE_SHIFT);

for (page_idx = 1; page_idx < page_cnt; page_idx++) {
pgoff_t page_offset = offset + page_idx;
struct page *page;

if (page_offset > end_index)
break;
page = find_or_create_page(mapping, page_offset,
GFP_NOFS | __GFP_COLD);
if (!page)
break;
if (!PageUptodate(page))
err = populate_page(c, page, bu, &n);
unlock_page(page);
page_cache_release(page);
if (err)
break;
}

ui->last_page_read = offset + page_idx - 1;

out_free:
kfree(bu->buf);
kfree(bu);
return ret;

out_warn:
ubifs_warn("ignoring error %d and skipping bulk-read", err);
goto out_free;
}

/**
* ubifs_bulk_read - determine whether to bulk-read and, if so, do it.
* @page: page from which to start bulk-read.
*
* Some flash media are capable of reading sequentially at faster rates. UBIFS
* bulk-read facility is designed to take advantage of that, by reading in one
* go consecutive data nodes that are also located consecutively in the same
* LEB. This function returns %1 if a bulk-read is done and %0 otherwise.
*/
static int ubifs_bulk_read(struct page *page)
{
struct inode *inode = page->mapping->host;
struct ubifs_info *c = inode->i_sb->s_fs_info;
struct ubifs_inode *ui = ubifs_inode(inode);
pgoff_t index = page->index, last_page_read = ui->last_page_read;
int ret = 0;

ui->last_page_read = index;

if (!c->bulk_read)
return 0;
/*
* Bulk-read is protected by ui_mutex, but it is an optimization, so
* don't bother if we cannot lock the mutex.
*/
if (!mutex_trylock(&ui->ui_mutex))
return 0;
if (index != last_page_read + 1) {
/* Turn off bulk-read if we stop reading sequentially */
ui->read_in_a_row = 1;
if (ui->bulk_read)
ui->bulk_read = 0;
goto out_unlock;
}
if (!ui->bulk_read) {
ui->read_in_a_row += 1;
if (ui->read_in_a_row < 3)
goto out_unlock;
/* Three reads in a row, so switch on bulk-read */
ui->bulk_read = 1;
}
ret = ubifs_do_bulk_read(c, page);
out_unlock:
mutex_unlock(&ui->ui_mutex);
return ret;
}

static int ubifs_readpage(struct file *file, struct page *page)
{
if (ubifs_bulk_read(page))
return 0;
do_readpage(page);
unlock_page(page);
return 0;
Expand Down
22 changes: 21 additions & 1 deletion fs/ubifs/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ static inline void key_copy(const struct ubifs_info *c,
* @key2: the second key to compare
*
* This function compares 2 keys and returns %-1 if @key1 is less than
* @key2, 0 if the keys are equivalent and %1 if @key1 is greater than @key2.
* @key2, %0 if the keys are equivalent and %1 if @key1 is greater than @key2.
*/
static inline int keys_cmp(const struct ubifs_info *c,
const union ubifs_key *key1,
Expand All @@ -502,6 +502,26 @@ static inline int keys_cmp(const struct ubifs_info *c,
return 0;
}

/**
* keys_eq - determine if keys are equivalent.
* @c: UBIFS file-system description object
* @key1: the first key to compare
* @key2: the second key to compare
*
* This function compares 2 keys and returns %1 if @key1 is equal to @key2 and
* %0 if not.
*/
static inline int keys_eq(const struct ubifs_info *c,
const union ubifs_key *key1,
const union ubifs_key *key2)
{
if (key1->u32[0] != key2->u32[0])
return 0;
if (key1->u32[1] != key2->u32[1])
return 0;
return 1;
}

/**
* is_hash_key - is a key vulnerable to hash collisions.
* @c: UBIFS file-system description object
Expand Down
31 changes: 31 additions & 0 deletions fs/ubifs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,11 @@ static int ubifs_show_options(struct seq_file *s, struct vfsmount *mnt)
else if (c->mount_opts.unmount_mode == 1)
seq_printf(s, ",norm_unmount");

if (c->mount_opts.bulk_read == 2)
seq_printf(s, ",bulk_read");
else if (c->mount_opts.bulk_read == 1)
seq_printf(s, ",no_bulk_read");

return 0;
}

Expand Down Expand Up @@ -538,6 +543,18 @@ static int init_constants_early(struct ubifs_info *c)
* calculations when reporting free space.
*/
c->leb_overhead = c->leb_size % UBIFS_MAX_DATA_NODE_SZ;
/* Buffer size for bulk-reads */
c->bulk_read_buf_size = UBIFS_MAX_BULK_READ * UBIFS_MAX_DATA_NODE_SZ;
if (c->bulk_read_buf_size > c->leb_size)
c->bulk_read_buf_size = c->leb_size;
if (c->bulk_read_buf_size > 128 * 1024) {
/* Check if we can kmalloc more than 128KiB */
void *try = kmalloc(c->bulk_read_buf_size, GFP_KERNEL);

kfree(try);
if (!try)
c->bulk_read_buf_size = 128 * 1024;
}
return 0;
}

Expand Down Expand Up @@ -840,17 +857,23 @@ static int check_volume_empty(struct ubifs_info *c)
*
* Opt_fast_unmount: do not run a journal commit before un-mounting
* Opt_norm_unmount: run a journal commit before un-mounting
* Opt_bulk_read: enable bulk-reads
* Opt_no_bulk_read: disable bulk-reads
* Opt_err: just end of array marker
*/
enum {
Opt_fast_unmount,
Opt_norm_unmount,
Opt_bulk_read,
Opt_no_bulk_read,
Opt_err,
};

static match_table_t tokens = {
{Opt_fast_unmount, "fast_unmount"},
{Opt_norm_unmount, "norm_unmount"},
{Opt_bulk_read, "bulk_read"},
{Opt_no_bulk_read, "no_bulk_read"},
{Opt_err, NULL},
};

Expand Down Expand Up @@ -888,6 +911,14 @@ static int ubifs_parse_options(struct ubifs_info *c, char *options,
c->mount_opts.unmount_mode = 1;
c->fast_unmount = 0;
break;
case Opt_bulk_read:
c->mount_opts.bulk_read = 2;
c->bulk_read = 1;
break;
case Opt_no_bulk_read:
c->mount_opts.bulk_read = 1;
c->bulk_read = 0;
break;
default:
ubifs_err("unrecognized mount option \"%s\" "
"or missing value", p);
Expand Down
Loading

0 comments on commit 4793e7c

Please sign in to comment.