Signed-off-by: Kent Overstreet <koverstreet@xxxxxxxxxx> --- drivers/block/bcache/writeback.c | 518 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 518 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/writeback.c diff --git a/drivers/block/bcache/writeback.c b/drivers/block/bcache/writeback.c new file mode 100644 index 0000000..cfcfe52 --- /dev/null +++ b/drivers/block/bcache/writeback.c @@ -0,0 +1,518 @@ +#include "bcache.h" +#include "btree.h" +#include "debug.h" + +static struct workqueue_struct *dirty_wq; + +static void read_dirty(struct cached_dev *); + +/* Background writeback */ + +static void dirty_init(struct dirty *w) +{ + struct bio *bio = &w->io->bio; + + bio_init(bio); + bio_get(bio); + if (!w->io->d->writeback_percent) + bio_set_prio(bio, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)); + + bio->bi_size = KEY_SIZE(&w->key) << 9; + bio->bi_max_vecs = DIV_ROUND_UP(KEY_SIZE(&w->key), PAGE_SECTORS); + bio->bi_private = w; + bio_map(bio, NULL); +} + +static int dirty_cmp(struct dirty *r, struct dirty *l) +{ + /* Overlapping keys must compare equal */ + if (KEY_START(&r->key) >= l->key.key) + return 1; + if (KEY_START(&l->key) >= r->key.key) + return -1; + return 0; +} + +static int btree_refill_dirty_leaf(struct btree *b, struct btree_op *op, + struct cached_dev *dc) +{ + struct dirty *w; + struct btree_iter iter; + btree_iter_init(b, &iter, &KEY(op->d->id, dc->last_found, 0)); + + /* To protect rb tree access vs. read_dirty() */ + spin_lock(&dc->dirty_lock); + + while (!array_freelist_empty(&dc->dirty_freelist)) { + struct bkey *k = btree_iter_next(&iter); + if (!k || KEY_DEV(k) != op->d->id) + break; + + if (ptr_bad(b, k)) + continue; + + if (KEY_DIRTY(k)) { + w = array_alloc(&dc->dirty_freelist); + + dc->last_found = k->key; + pr_debug("%s", pkey(k)); + w->io = NULL; + bkey_copy(&w->key, k); + SET_KEY_DIRTY(&w->key, false); + + if (RB_INSERT(&dc->dirty, w, node, dirty_cmp)) + array_free(&dc->dirty_freelist, w); + } + } + + spin_unlock(&dc->dirty_lock); + + return 0; +} + +static int btree_refill_dirty(struct btree *b, struct btree_op *op, + struct cached_dev *dc) +{ + int r; + struct btree_iter iter; + btree_iter_init(b, &iter, &KEY(op->d->id, dc->last_found, 0)); + + if (!b->level) + return btree_refill_dirty_leaf(b, op, dc); + + while (!array_freelist_empty(&dc->dirty_freelist)) { + struct bkey *k = btree_iter_next(&iter); + if (!k) + break; + + if (ptr_bad(b, k)) + continue; + + r = btree(refill_dirty, k, b, op, dc); + if (r) { + char buf[BDEVNAME_SIZE]; + bdevname(dc->bdev, buf); + + printk(KERN_WARNING "Error trying to read the btree " + "for background writeback on %s: " + "dirty data may have been lost!\n", buf); + } + + if (KEY_DEV(k) != op->d->id) + break; + + cond_resched(); + } + + return 0; +} + +static void refill_dirty(struct work_struct *work) +{ + struct cached_dev *dc = container_of(to_delayed_work(work), + struct cached_dev, refill_dirty); + uint64_t start; + + struct btree_op op; + btree_op_init_stack(&op); + op.d = &dc->disk; + + if (!atomic_read(&dc->disk.detaching) && + !dc->writeback_running) + return; + + down_write(&dc->writeback_lock); + start = dc->last_found; + + if (!atomic_read(&dc->has_dirty)) { + SET_BDEV_STATE(&dc->sb, BDEV_STATE_CLEAN); + write_bdev_super(dc, NULL); + up_write(&dc->writeback_lock); + return; + } + + btree_root(refill_dirty, dc->disk.c, &op, dc); + closure_sync(&op.cl); + + pr_debug("found %s keys on %i from %llu to %llu, %i%% used", + RB_EMPTY_ROOT(&dc->dirty) ? "no" : + array_freelist_empty(&dc->dirty_freelist) ? "some" : "a few", + dc->disk.id, start, (uint64_t) dc->last_found, + dc->disk.c->gc_stats.in_use); + + /* Got to the end of the btree */ + if (!array_freelist_empty(&dc->dirty_freelist)) + dc->last_found = 0; + + /* Searched the entire btree - delay for awhile */ + if (!array_freelist_empty(&dc->dirty_freelist) && !start) + queue_delayed_work(dirty_wq, &dc->refill_dirty, + dc->writeback_delay * HZ); + + spin_lock(&dc->dirty_lock); + + if (!RB_EMPTY_ROOT(&dc->dirty)) { + struct dirty *w; + w = RB_FIRST(&dc->dirty, struct dirty, node); + dc->writeback_start = KEY_START(&w->key); + + w = RB_LAST(&dc->dirty, struct dirty, node); + dc->writeback_end = w->key.key; + } else { + dc->writeback_start = 0; + dc->writeback_end = 0; + + if (!start) { + atomic_set(&dc->has_dirty, 0); + cached_dev_put(dc); + } + } + + up_write(&dc->writeback_lock); + + dc->next_writeback_io = local_clock(); + read_dirty(dc); +} + +bool bcache_in_writeback(struct cached_dev *dc, sector_t offset, unsigned len) +{ + struct dirty *w, s; + s.key = KEY(dc->disk.id, offset + len, len); + + if (offset >= dc->writeback_end || + offset + len <= dc->writeback_start) + return false; + + spin_lock(&dc->dirty_lock); + w = RB_SEARCH(&dc->dirty, s, node, dirty_cmp); + if (w && !w->io) { + rb_erase(&w->node, &dc->dirty); + array_free(&dc->dirty_freelist, w); + w = NULL; + } + + spin_unlock(&dc->dirty_lock); + return w != NULL; +} + +void bcache_writeback_queue(struct cached_dev *d) +{ + queue_delayed_work(dirty_wq, &d->refill_dirty, 0); +} + +void bcache_writeback_add(struct cached_dev *d, unsigned sectors) +{ + atomic_long_add(sectors, &d->disk.sectors_dirty); + + if (!atomic_read(&d->has_dirty) && + !atomic_xchg(&d->has_dirty, 1)) { + if (BDEV_STATE(&d->sb) != BDEV_STATE_DIRTY) { + SET_BDEV_STATE(&d->sb, BDEV_STATE_DIRTY); + /* XXX: should do this synchronously */ + write_bdev_super(d, NULL); + } + + atomic_inc(&d->count); + queue_delayed_work(dirty_wq, &d->refill_dirty, + d->writeback_delay * HZ); + + if (d->writeback_percent) + schedule_delayed_work(&d->writeback_rate_update, + d->writeback_rate_update_seconds * HZ); + } +} + +static void __update_writeback_rate(struct cached_dev *dc) +{ + struct cache_set *c = dc->disk.c; + uint64_t cache_sectors = c->nbuckets * c->sb.bucket_size; + uint64_t cache_dirty_target = + div_u64(cache_sectors * dc->writeback_percent, 100); + + int64_t target = div64_u64(cache_dirty_target * bdev_sectors(dc->bdev), + c->cached_dev_sectors); + + /* PD controller */ + + int change = 0; + int64_t error; + int64_t dirty = atomic_long_read(&dc->disk.sectors_dirty); + int64_t derivative = dirty - dc->disk.sectors_dirty_last; + + dc->disk.sectors_dirty_last = dirty; + + derivative *= dc->writeback_rate_d_term; + derivative = clamp(derivative, -dirty, dirty); + + derivative = ewma_add(dc->disk.sectors_dirty_derivative, derivative, + dc->writeback_rate_d_smooth, 0); + + /* Avoid divide by zero */ + if (!target) + goto out; + + error = div64_s64((dirty + derivative - target) << 8, target); + + change = div_s64((dc->writeback_rate * error) >> 8, + dc->writeback_rate_p_term_inverse); + + /* Don't increase writeback rate if the device isn't keeping up */ + if (change > 0 && + time_after64(local_clock(), + dc->next_writeback_io + 10 * NSEC_PER_MSEC)) + change = 0; + + dc->writeback_rate = clamp_t(int64_t, dc->writeback_rate + change, + 1, NSEC_PER_MSEC); +out: + dc->writeback_rate_derivative = derivative; + dc->writeback_rate_change = change; + dc->writeback_rate_target = target; + + schedule_delayed_work(&dc->writeback_rate_update, + dc->writeback_rate_update_seconds * HZ); +} + +static void update_writeback_rate(struct work_struct *work) +{ + struct cached_dev *dc = container_of(to_delayed_work(work), + struct cached_dev, + writeback_rate_update); + + down_read(&dc->writeback_lock); + + if (atomic_read(&dc->has_dirty) && + dc->writeback_percent) + __update_writeback_rate(dc); + + up_read(&dc->writeback_lock); +} + +static unsigned writeback_delay(struct cached_dev *dc, unsigned sectors) +{ + uint64_t now = local_clock(); + + if (atomic_read(&dc->disk.detaching) || + !dc->writeback_percent) + return 0; + + /* writeback_rate = sectors per 10 ms */ + dc->next_writeback_io += div_u64(sectors * 10000000ULL, + dc->writeback_rate); + + return time_after64(dc->next_writeback_io, now) + ? div_u64(dc->next_writeback_io - now, NSEC_PER_SEC / HZ) + : 0; +} + +/* Background writeback - IO loop */ + +static void write_dirty_finish(struct closure *cl) +{ + struct dirty_io *io = container_of(cl, struct dirty_io, cl); + struct dirty *w = io->bio.bi_private; + struct cached_dev *dc = io->d; + struct bio_vec *bv = bio_iovec_idx(&io->bio, io->bio.bi_vcnt); + + while (bv-- != w->io->bio.bi_io_vec) + __free_page(bv->bv_page); + + closure_debug_destroy(cl); + kfree(io); + + /* This is kind of a dumb way of signalling errors. */ + if (!KEY_DIRTY(&w->key)) { + struct btree_op op; + btree_op_init_stack(&op); + + op.type = BTREE_REPLACE; + bkey_copy(&op.replace, &w->key); + SET_KEY_DIRTY(&op.replace, true); + + keylist_add(&op.keys, &w->key); + + for (unsigned i = 0; i < KEY_PTRS(&w->key); i++) + atomic_inc(&PTR_BUCKET(dc->disk.c, &w->key, i)->pin); + + pr_debug("clearing %s", pkey(&w->key)); + bcache_btree_insert(&op, dc->disk.c); + closure_sync(&op.cl); + + atomic_long_inc(op.insert_collision + ? &dc->disk.c->writeback_keys_failed + : &dc->disk.c->writeback_keys_done); + } + + spin_lock(&dc->dirty_lock); + rb_erase(&w->node, &dc->dirty); + array_free(&dc->dirty_freelist, w); + atomic_dec_bug(&dc->in_flight); + + read_dirty(dc); +} + +static void dirty_endio(struct bio *bio, int error) +{ + struct dirty *w = bio->bi_private; + + if (error) + SET_KEY_DIRTY(&w->key, true); + + bio_put(bio); + closure_put(&w->io->cl); +} + +static void write_dirty(struct closure *cl) +{ + struct dirty_io *io = container_of(cl, struct dirty_io, cl); + struct dirty *w = io->bio.bi_private; + + dirty_init(w); + io->bio.bi_rw = WRITE|REQ_UNPLUG; + io->bio.bi_sector = KEY_START(&w->key); + io->bio.bi_bdev = io->d->bdev; + io->bio.bi_end_io = dirty_endio; + + trace_bcache_write_dirty(&w->io->bio); + closure_bio_submit(&w->io->bio, cl, io->d->disk.bio_split); + + continue_at(&io->cl, write_dirty_finish, dirty_wq); +} + +static void read_dirty_endio(struct bio *bio, int error) +{ + struct dirty *w = bio->bi_private; + + count_io_errors(PTR_CACHE(w->io->d->disk.c, &w->key, 0), + error, "reading dirty data from cache"); + + dirty_endio(bio, error); +} + +static void read_dirty(struct cached_dev *dc) +{ + unsigned delay = writeback_delay(dc, 0); + struct dirty *w; + struct dirty_io *io; + + /* XXX: if we error, background writeback could stall indefinitely */ + + while (1) { + w = RB_FIRST(&dc->dirty, struct dirty, node); + + while (w && w->io) + w = RB_NEXT(w, node); + + if (!w) + break; + + BUG_ON(ptr_stale(dc->disk.c, &w->key, 0)); + + if (delay > 0 && + (KEY_START(&w->key) != dc->last_read || + jiffies_to_msecs(delay) > 50)) { + queue_delayed_work(dirty_wq, &dc->read_dirty, delay); + break; + } + + dc->last_read = w->key.key; + w->io = ERR_PTR(-EINTR); + spin_unlock(&dc->dirty_lock); + + io = kzalloc(sizeof(struct dirty_io) + sizeof(struct bio_vec) + * DIV_ROUND_UP(KEY_SIZE(&w->key), PAGE_SECTORS), + GFP_KERNEL); + if (!io) + goto err; + + w->io = io; + w->io->d = dc; + + dirty_init(w); + w->io->bio.bi_sector = PTR_OFFSET(&w->key, 0); + w->io->bio.bi_bdev = PTR_CACHE(dc->disk.c, + &w->key, 0)->bdev; + w->io->bio.bi_rw = READ|REQ_UNPLUG; + w->io->bio.bi_end_io = read_dirty_endio; + + if (bio_alloc_pages(&w->io->bio, GFP_KERNEL)) + goto err; + + pr_debug("%s", pkey(&w->key)); + + closure_init(&w->io->cl, NULL); + set_closure_fn(&w->io->cl, write_dirty, dirty_wq); + closure_set_stopped(&w->io->cl); + + trace_bcache_read_dirty(&w->io->bio); + closure_bio_submit_put(&w->io->bio, &w->io->cl, + dc->disk.bio_split); + + delay = writeback_delay(dc, KEY_SIZE(&w->key)); + + if (atomic_inc_return(&dc->in_flight) >= 128) + return; + + spin_lock(&dc->dirty_lock); + } + + if (0) { +err: spin_lock(&dc->dirty_lock); + if (!IS_ERR_OR_NULL(w->io)) + kfree(w->io); + rb_erase(&w->node, &dc->dirty); + array_free(&dc->dirty_freelist, w); + } + + if (RB_EMPTY_ROOT(&dc->dirty)) + queue_delayed_work(dirty_wq, &dc->refill_dirty, 0); + + spin_unlock(&dc->dirty_lock); +} + +static void read_dirty_work(struct work_struct *work) +{ + struct cached_dev *dc = container_of(to_delayed_work(work), + struct cached_dev, read_dirty); + + spin_lock(&dc->dirty_lock); + read_dirty(dc); +} + +void bcache_writeback_init_cached_dev(struct cached_dev *d) +{ + INIT_DELAYED_WORK(&d->refill_dirty, refill_dirty); + INIT_DELAYED_WORK(&d->read_dirty, read_dirty_work); + init_rwsem(&d->writeback_lock); + array_allocator_init(&d->dirty_freelist); + + d->dirty = RB_ROOT; + d->writeback_metadata = true; + d->writeback_running = true; + d->writeback_delay = 30; + d->writeback_rate = 1024; + + d->writeback_rate_update_seconds = 30; + d->writeback_rate_d_term = 16; + d->writeback_rate_p_term_inverse = 64; + d->writeback_rate_d_smooth = 8; + + INIT_DELAYED_WORK(&d->writeback_rate_update, update_writeback_rate); + schedule_delayed_work(&d->writeback_rate_update, + d->writeback_rate_update_seconds * HZ); +} + +void bcache_writeback_exit(void) +{ + if (dirty_wq) + destroy_workqueue(dirty_wq); +} + +int __init bcache_writeback_init(void) +{ + dirty_wq = create_singlethread_workqueue("bcache_writeback"); + if (!dirty_wq) + return -ENOMEM; + + return 0; +} -- 1.7.9.rc2 -- To unsubscribe from this list: send the line "unsubscribe linux-bcache" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html