Skip to content

Commit

Permalink
Btrfs: Change btree locking to use explicit blocking points
Browse files Browse the repository at this point in the history
Most of the btrfs metadata operations can be protected by a spinlock,
but some operations still need to schedule.

So far, btrfs has been using a mutex along with a trylock loop,
most of the time it is able to avoid going for the full mutex, so
the trylock loop is a big performance gain.

This commit is step one for getting rid of the blocking locks entirely.
btrfs_tree_lock takes a spinlock, and the code explicitly switches
to a blocking lock when it starts an operation that can schedule.

We'll be able get rid of the blocking locks in smaller pieces over time.
Tracing allows us to find the most common cause of blocking, so we
can start with the hot spots first.

The basic idea is:

btrfs_tree_lock() returns with the spin lock held

btrfs_set_lock_blocking() sets the EXTENT_BUFFER_BLOCKING bit in
the extent buffer flags, and then drops the spin lock.  The buffer is
still considered locked by all of the btrfs code.

If btrfs_tree_lock gets the spinlock but finds the blocking bit set, it drops
the spin lock and waits on a wait queue for the blocking bit to go away.

Much of the code that needs to set the blocking bit finishes without actually
blocking a good percentage of the time.  So, an adaptive spin is still
used against the blocking bit to avoid very high context switch rates.

btrfs_clear_lock_blocking() clears the blocking bit and returns
with the spinlock held again.

btrfs_tree_unlock() can be called on either blocking or spinning locks,
it does the right thing based on the blocking bit.

ctree.c has a helper function to set/clear all the locked buffers in a
path as blocking.

Signed-off-by: Chris Mason <[email protected]>
  • Loading branch information
chrismason-xx committed Feb 4, 2009
1 parent c487685 commit b4ce94d
Show file tree
Hide file tree
Showing 11 changed files with 470 additions and 39 deletions.
234 changes: 226 additions & 8 deletions fs/btrfs/ctree.c

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions fs/btrfs/ctree.h
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,10 @@ void btrfs_release_path(struct btrfs_root *root, struct btrfs_path *p);
struct btrfs_path *btrfs_alloc_path(void);
void btrfs_free_path(struct btrfs_path *p);
void btrfs_init_path(struct btrfs_path *p);
void btrfs_set_path_blocking(struct btrfs_path *p);
void btrfs_clear_path_blocking(struct btrfs_path *p);
void btrfs_unlock_up_safe(struct btrfs_path *p, int level);

int btrfs_del_items(struct btrfs_trans_handle *trans, struct btrfs_root *root,
struct btrfs_path *path, int slot, int nr);
int btrfs_del_leaf(struct btrfs_trans_handle *trans,
Expand Down
10 changes: 8 additions & 2 deletions fs/btrfs/disk-io.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ struct extent_buffer *read_tree_block(struct btrfs_root *root, u64 bytenr,
ret = btree_read_extent_buffer_pages(root, buf, 0, parent_transid);

if (ret == 0)
buf->flags |= EXTENT_UPTODATE;
set_bit(EXTENT_BUFFER_UPTODATE, &buf->bflags);
else
WARN_ON(1);
return buf;
Expand All @@ -813,6 +813,10 @@ int clean_tree_block(struct btrfs_trans_handle *trans, struct btrfs_root *root,
if (btrfs_header_generation(buf) ==
root->fs_info->running_transaction->transid) {
WARN_ON(!btrfs_tree_locked(buf));

/* ugh, clear_extent_buffer_dirty can be expensive */
btrfs_set_lock_blocking(buf);

clear_extent_buffer_dirty(&BTRFS_I(btree_inode)->io_tree,
buf);
}
Expand Down Expand Up @@ -2311,6 +2315,8 @@ void btrfs_mark_buffer_dirty(struct extent_buffer *buf)
u64 transid = btrfs_header_generation(buf);
struct inode *btree_inode = root->fs_info->btree_inode;

btrfs_set_lock_blocking(buf);

WARN_ON(!btrfs_tree_locked(buf));
if (transid != root->fs_info->generation) {
printk(KERN_CRIT "btrfs transid mismatch buffer %llu, "
Expand Down Expand Up @@ -2353,7 +2359,7 @@ int btrfs_read_buffer(struct extent_buffer *buf, u64 parent_transid)
int ret;
ret = btree_read_extent_buffer_pages(root, buf, 0, parent_transid);
if (ret == 0)
buf->flags |= EXTENT_UPTODATE;
set_bit(EXTENT_BUFFER_UPTODATE, &buf->bflags);
return ret;
}

Expand Down
5 changes: 5 additions & 0 deletions fs/btrfs/extent-tree.c
Original file line number Diff line number Diff line change
Expand Up @@ -3407,7 +3407,10 @@ struct extent_buffer *btrfs_init_new_buffer(struct btrfs_trans_handle *trans,
btrfs_set_header_generation(buf, trans->transid);
btrfs_tree_lock(buf);
clean_tree_block(trans, root, buf);

btrfs_set_lock_blocking(buf);
btrfs_set_buffer_uptodate(buf);

if (root->root_key.objectid == BTRFS_TREE_LOG_OBJECTID) {
set_extent_dirty(&root->dirty_log_pages, buf->start,
buf->start + buf->len - 1, GFP_NOFS);
Expand All @@ -3416,6 +3419,7 @@ struct extent_buffer *btrfs_init_new_buffer(struct btrfs_trans_handle *trans,
buf->start + buf->len - 1, GFP_NOFS);
}
trans->blocks_used++;
/* this returns a buffer locked for blocking */
return buf;
}

Expand Down Expand Up @@ -3752,6 +3756,7 @@ static noinline int walk_down_subtree(struct btrfs_trans_handle *trans,

next = read_tree_block(root, bytenr, blocksize, ptr_gen);
btrfs_tree_lock(next);
btrfs_set_lock_blocking(next);

ret = btrfs_lookup_extent_ref(trans, root, bytenr, blocksize,
&refs);
Expand Down
18 changes: 9 additions & 9 deletions fs/btrfs/extent_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -2990,7 +2990,9 @@ static struct extent_buffer *__alloc_extent_buffer(struct extent_io_tree *tree,
eb = kmem_cache_zalloc(extent_buffer_cache, mask);
eb->start = start;
eb->len = len;
mutex_init(&eb->mutex);
spin_lock_init(&eb->lock);
init_waitqueue_head(&eb->lock_wq);

#if LEAK_DEBUG
spin_lock_irqsave(&leak_lock, flags);
list_add(&eb->leak_list, &buffers);
Expand Down Expand Up @@ -3071,8 +3073,7 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
unlock_page(p);
}
if (uptodate)
eb->flags |= EXTENT_UPTODATE;
eb->flags |= EXTENT_BUFFER_FILLED;
set_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags);

spin_lock(&tree->buffer_lock);
exists = buffer_tree_insert(tree, start, &eb->rb_node);
Expand Down Expand Up @@ -3226,7 +3227,7 @@ int clear_extent_buffer_uptodate(struct extent_io_tree *tree,
unsigned long num_pages;

num_pages = num_extent_pages(eb->start, eb->len);
eb->flags &= ~EXTENT_UPTODATE;
clear_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags);

clear_extent_uptodate(tree, eb->start, eb->start + eb->len - 1,
GFP_NOFS);
Expand Down Expand Up @@ -3297,7 +3298,7 @@ int extent_buffer_uptodate(struct extent_io_tree *tree,
struct page *page;
int pg_uptodate = 1;

if (eb->flags & EXTENT_UPTODATE)
if (test_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags))
return 1;

ret = test_range_bit(tree, eb->start, eb->start + eb->len - 1,
Expand Down Expand Up @@ -3333,7 +3334,7 @@ int read_extent_buffer_pages(struct extent_io_tree *tree,
struct bio *bio = NULL;
unsigned long bio_flags = 0;

if (eb->flags & EXTENT_UPTODATE)
if (test_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags))
return 0;

if (test_range_bit(tree, eb->start, eb->start + eb->len - 1,
Expand Down Expand Up @@ -3364,7 +3365,7 @@ int read_extent_buffer_pages(struct extent_io_tree *tree,
}
if (all_uptodate) {
if (start_i == 0)
eb->flags |= EXTENT_UPTODATE;
set_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags);
goto unlock_exit;
}

Expand Down Expand Up @@ -3400,7 +3401,7 @@ int read_extent_buffer_pages(struct extent_io_tree *tree,
}

if (!ret)
eb->flags |= EXTENT_UPTODATE;
set_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags);
return ret;

unlock_exit:
Expand Down Expand Up @@ -3497,7 +3498,6 @@ int map_extent_buffer(struct extent_buffer *eb, unsigned long start,
unmap_extent_buffer(eb, eb->map_token, km);
eb->map_token = NULL;
save = 1;
WARN_ON(!mutex_is_locked(&eb->mutex));
}
err = map_private_extent_buffer(eb, start, min_len, token, map,
map_start, map_len, km);
Expand Down
16 changes: 14 additions & 2 deletions fs/btrfs/extent_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
/* flags for bio submission */
#define EXTENT_BIO_COMPRESSED 1

/* these are bit numbers for test/set bit */
#define EXTENT_BUFFER_UPTODATE 0
#define EXTENT_BUFFER_BLOCKING 1

/*
* page->private values. Every page that is controlled by the extent
* map has page->private set to one.
Expand Down Expand Up @@ -95,11 +99,19 @@ struct extent_buffer {
unsigned long map_start;
unsigned long map_len;
struct page *first_page;
unsigned long bflags;
atomic_t refs;
int flags;
struct list_head leak_list;
struct rb_node rb_node;
struct mutex mutex;

/* the spinlock is used to protect most operations */
spinlock_t lock;

/*
* when we keep the lock held while blocking, waiters go onto
* the wq
*/
wait_queue_head_t lock_wq;
};

struct extent_map_tree;
Expand Down
3 changes: 3 additions & 0 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "tree-log.h"
#include "ref-cache.h"
#include "compression.h"
#include "locking.h"

struct btrfs_iget_args {
u64 ino;
Expand Down Expand Up @@ -2021,6 +2022,7 @@ void btrfs_read_locked_inode(struct inode *inode)
BTRFS_I(inode)->flags = btrfs_inode_flags(leaf, inode_item);

alloc_group_block = btrfs_inode_block_group(leaf, inode_item);

BTRFS_I(inode)->block_group = btrfs_find_block_group(root, 0,
alloc_group_block, 0);
btrfs_free_path(path);
Expand Down Expand Up @@ -2117,6 +2119,7 @@ noinline int btrfs_update_inode(struct btrfs_trans_handle *trans,
goto failed;
}

btrfs_unlock_up_safe(path, 1);
leaf = path->nodes[0];
inode_item = btrfs_item_ptr(leaf, path->slots[0],
struct btrfs_inode_item);
Expand Down
Loading

0 comments on commit b4ce94d

Please sign in to comment.