[Bcache v13 00/16]
Subsystems (17)
v13 cover [Bcache v13 00/16]
bcache: a cache for arbitrary block devices using an SSD.
Short overview:
Bcache does both writethrough and writeback caching. It presents itself as a new block device, a bit like say md. You can cache an arbitrary number of block devices with a single cache device, and attach and detach things at runtime - it's quite flexible.
It's very fast. It uses a b+ tree for the index, along with a journal to coalesce index updates, and a bunch of other cool tricks like auxiliary binary search trees with software floating point keys for searching within btree nodes.
Bcache is solid, production ready code. There are still bugs being found that affect specific configurations, but there haven't been any major issues found in awhile - it's well past time I started working on getting it into mainline.
It's a lot of code - I tried to split it out so that it'd make some sort of sense for reviewing. Let me know if there's anything else I can do to make review easier.
TODO/known issues:
__up_write() needs to be exported for bcache to compile as a module - it's used for bypassing lockdep when traversing the btree during garbage collection. If someone else knows a better solution, please let me know.
The userspace interface is going to change before it goes in. The general consensus at LSF was that we don't want yet another interface for probing/managing block devices, and dm exists so we may as well use that. I don't think anyone's started on that yet, though.
Documentation needs to be updated. That's being actively worked on, though.
Kent Overstreet (16):
Only clone bio vecs that are in use
Bio pool freeing
Revert "rw_semaphore: remove up/down_read_non_owner" Fix ratelimit macro to compile in c99 mode Export get_random_int()
Export blk_fill_rwbs()
Closures
bcache: Documentation, and changes to generic code
Bcache: generic utility code
bcache: Superblock/initialization/sysfs code
bcache: Core btree code
bcache: Bset code (lookups within a btree node)
bcache: Journalling
bcache: Request, io and allocation code
bcache: Writeback
bcache: Debug and tracing code
Documentation/ABI/testing/sysfs-block-bcache | 156 ++ Documentation/bcache.txt | 255 +++ block/blk-core.c | 2 +- drivers/block/Kconfig | 2 + drivers/block/Makefile | 1 + drivers/block/bcache/Kconfig | 42 + drivers/block/bcache/Makefile | 8 + drivers/block/bcache/alloc.c | 591 +++++++ drivers/block/bcache/bcache.h | 839 ++++++++++ drivers/block/bcache/bset.c | 1149 +++++++++++++ drivers/block/bcache/bset.h | 218 +++ drivers/block/bcache/btree.c | 2249 ++++++++++++++++++++++++++ drivers/block/bcache/btree.h | 272 ++++ drivers/block/bcache/debug.c | 574 +++++++ drivers/block/bcache/debug.h | 53 + drivers/block/bcache/io.c | 198 +++ drivers/block/bcache/journal.c | 722 +++++++++ drivers/block/bcache/journal.h | 113 ++ drivers/block/bcache/request.c | 1470 +++++++++++++++++ drivers/block/bcache/request.h | 58 + drivers/block/bcache/stats.c | 243 +++ drivers/block/bcache/stats.h | 58 + drivers/block/bcache/super.c | 2000 +++++++++++++++++++++++ drivers/block/bcache/sysfs.c | 802 +++++++++ drivers/block/bcache/sysfs.h | 99 ++ drivers/block/bcache/trace.c | 26 + drivers/block/bcache/util.c | 572 +++++++ drivers/block/bcache/util.h | 657 ++++++++ drivers/block/bcache/writeback.c | 518 ++++++ drivers/block/rbd.c | 2 +- drivers/char/random.c | 1 + drivers/md/dm.c | 27 +- drivers/md/md.c | 3 +- fs/bio.c | 55 +- include/linux/bio.h | 7 +- include/linux/blk_types.h | 2 + include/linux/cgroup_subsys.h | 6 + include/linux/closure.h | 614 +++++++ include/linux/ratelimit.h | 2 +- include/linux/rwsem.h | 10 + include/linux/sched.h | 4 + include/trace/events/bcache.h | 257 +++ kernel/fork.c | 4 + kernel/rwsem.c | 16 + kernel/trace/blktrace.c | 1 + lib/Kconfig | 3 + lib/Kconfig.debug | 9 + lib/Makefile | 2 + lib/closure.c | 363 +++++ 49 files changed, 15288 insertions(+), 47 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-block-bcache create mode 100644 Documentation/bcache.txt create mode 100644 drivers/block/bcache/Kconfig create mode 100644 drivers/block/bcache/Makefile create mode 100644 drivers/block/bcache/alloc.c create mode 100644 drivers/block/bcache/bcache.h create mode 100644 drivers/block/bcache/bset.c create mode 100644 drivers/block/bcache/bset.h create mode 100644 drivers/block/bcache/btree.c create mode 100644 drivers/block/bcache/btree.h create mode 100644 drivers/block/bcache/debug.c create mode 100644 drivers/block/bcache/debug.h create mode 100644 drivers/block/bcache/io.c create mode 100644 drivers/block/bcache/journal.c create mode 100644 drivers/block/bcache/journal.h create mode 100644 drivers/block/bcache/request.c create mode 100644 drivers/block/bcache/request.h create mode 100644 drivers/block/bcache/stats.c create mode 100644 drivers/block/bcache/stats.h create mode 100644 drivers/block/bcache/super.c create mode 100644 drivers/block/bcache/sysfs.c create mode 100644 drivers/block/bcache/sysfs.h create mode 100644 drivers/block/bcache/trace.c create mode 100644 drivers/block/bcache/util.c create mode 100644 drivers/block/bcache/util.h create mode 100644 drivers/block/bcache/writeback.c create mode 100644 include/linux/closure.h create mode 100644 include/trace/events/bcache.h create mode 100644 lib/closure.c
Signature · 1 line
1.7.9.rc2
PATCH v13 1/16 Only clone bio vecs that are in use
Bcache creates large bios internally, and then splits them according to the requirements of the underlying device. If the underlying device then needs to clone the bio, the clone will fail if the original bio had more than 256 segments - even if bi_vcnt - bi_idx was smaller.
--- block/blk-core.c | 2 +- drivers/block/rbd.c | 2 +- drivers/md/dm.c | 27 ++++++++++----------------- drivers/md/md.c | 3 ++- fs/bio.c | 46 +++++++++++++++++++++++----------------------- include/linux/bio.h | 7 ++++++- 6 files changed, 43 insertions(+), 44 deletions(-)
block/blk-core.c +1 -1
@@ -2659,7 +2659,7 @@ int blk_rq_prep_clone(struct request *rq, struct request *rq_src,blk_rq_init (NULL , rq);__rq_for_each_bio (bio_src, rq_src) { - bio =bio_alloc_bioset (gfp_mask, bio_src->bi_max_vecs, bs); + bio =bio_alloc_bioset (gfp_mask,bio_segments (bio_src), bs);if (!bio) goto free_and_out;
drivers/block/rbd.c +1 -1
@@ -699,7 +699,7 @@ static struct bio *bio_chain_clone(struct bio **old, struct bio **next, }while (old_chain && (total < len)) { - tmp =bio_kmalloc (gfpmask, old_chain->bi_max_vecs); + tmp =bio_kmalloc (gfpmask,bio_segments (old_chain));if (!tmp) goto err_out;
drivers/md/dm.c +10 -17
@@ -1078,28 +1078,22 @@ static struct bio *split_bvec(struct bio *bio, sector_t sector,* Creates a bio that consists of range of complete bvecs. */ static struct bio *clone_bio (struct bio *bio,sector_t sector, -unsigned short idx,unsigned short bv_count, +unsigned short bv_count,unsigned int len,struct bio_set *bs) {struct bio *clone; - clone =bio_alloc_bioset (GFP_NOIO , bio->bi_max_vecs, bs); -__bio_clone (clone, bio); - clone->bi_destructor = dm_bio_destructor; + clone =bio_clone_bioset (bio,GFP_NOIO , bs); clone->bi_sector = sector; - clone->bi_idx = idx; - clone->bi_vcnt = idx + bv_count; + clone->bi_vcnt = bv_count; clone->bi_size =to_bytes (len); clone->bi_flags &= ~(1 <<BIO_SEG_VALID ); - -if (bio_integrity (bio)) { -bio_integrity_clone (clone, bio,GFP_NOIO , bs); - +#if 0 +if (bio_integrity (bio))if (idx != bio->bi_idx || clone->bi_size < bio->bi_size)bio_integrity_trim (clone,bio_sector_offset (bio, idx,0 ), len); - } - +#endif return clone; }
@@ -1128,9 +1122,8 @@ static void __issue_target_request(struct clone_info *ci, struct dm_target *ti,* ci->bio->bi_max_vecs is BIO_INLINE_VECS anyway, for both flush * and discard, so no need for concern about wasted bvec allocations. */ - clone =bio_alloc_bioset (GFP_NOIO , ci->bio->bi_max_vecs, ci->md->bs); -__bio_clone (clone, ci->bio); - clone->bi_destructor = dm_bio_destructor; + clone =bio_clone_bioset (ci->bio,GFP_NOIO , ci->md->bs); +if (len) { clone->bi_sector = ci->sector; clone->bi_size =to_bytes (len);
@@ -1169,7 +1162,7 @@ static void __clone_and_map_simple(struct clone_info *ci, struct dm_target *ti)struct dm_target_io *tio; tio =alloc_tio (ci, ti); - clone =clone_bio (bio, ci->sector, ci->idx, + clone =clone_bio (bio, ci->sector, bio->bi_vcnt - ci->idx, ci->sector_count, ci->md->bs);__map_bio (ti, clone, tio);
@@ -1248,7 +1241,7 @@ static int __clone_and_map(struct clone_info *ci) } tio =alloc_tio (ci, ti); - clone =clone_bio (bio, ci->sector, ci->idx, i - ci->idx, len, + clone =clone_bio (bio, ci->sector, i - ci->idx, len, ci->md->bs);__map_bio (ti, clone, tio);
drivers/md/md.c +2 -1
@@ -194,7 +194,8 @@ struct bio *bio_clone_mddev(struct bio *bio, gfp_t gfp_mask,if (!mddev || !mddev->bio_set)return bio_clone (bio, gfp_mask); - b =bio_alloc_bioset (gfp_mask, bio->bi_max_vecs, + b =bio_alloc_bioset (gfp_mask, +bio_segments (bio), mddev->bio_set);if (!b)return NULL ;
fs/bio.c +23 -23
106 lines not on this page · raw message
include/linux/bio.h +6 -1
@@ -222,7 +222,7 @@ struct request_queue;extern int bio_phys_segments (struct request_queue *,struct bio *);extern void __bio_clone (struct bio *,struct bio *); -extern struct bio *bio_clone (struct bio *,gfp_t ); +extern struct bio *bio_clone_bioset (struct bio *,gfp_t ,struct bio_set *bs);extern void bio_init (struct bio *);
@@ -297,6 +297,11 @@ struct biovec_slab {extern struct bio_set *fs_bio_set; +static inline struct bio *bio_clone (struct bio *bio,gfp_t gfp_mask) +{ +return bio_clone_bioset (bio, gfp_mask, fs_bio_set); +} +/* * a small number of entries is fine, not going to be performance critical. * basically we just need to survive
Signature · 1 line
1.7.9.rc2
Re: [dm-devel] [Bcache v13 01/16] Only clone bio vecs that are in use
On Wed, May 09, 2012 at 11:08:13PM -0400, Kent Overstreet wrote:
[..]
drivers/md/dm.c static struct bio *split_bvec(struct bio *bio, sector_t sector, · line 1096
Quoted 12 lines
drivers/md/dm.c +3
-6- -if (bio_integrity (bio)) { -bio_integrity_clone (clone, bio,GFP_NOIO , bs); - +#if 0 +if (bio_integrity (bio))if (idx != bio->bi_idx || clone->bi_size < bio->bi_size)bio_integrity_trim (clone,bio_sector_offset (bio, idx,0 ), len); - } - +#endif
Dead/debug code under "#if 0" ?
Thanks
Vivek
Re: [dm-devel] [Bcache v13 01/16] Only clone bio vecs that are in use
Oh, I never got around to figuring out what needed to be done with the bio integrity - presumably it'll have to be handled differently somehow (I'm assuming an index in the integrity bvec is intended to match up with an index in the regular bvec), but I don't have any way to test the integrity stuff.
2012/5/10 Vivek Goyal <[email protected]>:
Quoted 20 lines · Kent Overstreet
[..]
Review
Quoted 12 lines
drivers/md/dm.c +2
-4- - if (bio_integrity(bio)) { - bio_integrity_clone(clone, bio, GFP_NOIO, bs); - +#if 0 + if (bio_integrity(bio))if (idx !=
bio->bi_idx||clone->bi_size<bio->bi_size)bio_integrity_trim(clone, bio_sector_offset(bio, idx, 0), len);
- }(quoted diff) +1
-1- +#endifDead/debug code under "#if 0" ?
Thanks
Vivek
Re: [dm-devel] [Bcache v13 01/16] Only clone bio vecs that are in use
[top posting fixed]
Quoted 19 lines · Kent Overstreet
2012/5/10 Vivek Goyal <[email protected]>:
Quoted 18 lines · Kent Overstreet
[..]
Review
Quoted 12 lines
drivers/md/dm.c +2
-4- - if (bio_integrity(bio)) { - bio_integrity_clone(clone, bio, GFP_NOIO, bs); - +#if 0 + if (bio_integrity(bio))if (idx !=
bio->bi_idx||clone->bi_size<bio->bi_size)bio_integrity_trim(clone, bio_sector_offset(bio, idx, 0), len);
- }(quoted diff) +1
-1- +#endifDead/debug code under "#if 0" ?
Quoted 5 lines
Oh, I never got around to figuring out what needed to be done with the bio integrity - presumably it'll have to be handled differently somehow (I'm assuming an index in the integrity bvec is intended to match up with an index in the regular bvec), but I don't have any way to test the integrity stuff.
The scsi debug module supports dif/dix, so you can use that for testing.
Cheers,
Jeff
Re: [dm-devel] [Bcache v13 01/16] Only clone bio vecs that are in use
Quoted 24 lines · Jeff Moyer
[top posting fixed]
Quoted 19 lines · Kent Overstreet
2012/5/10 Vivek Goyal <[email protected]>:
Quoted 18 lines · Kent Overstreet
[..]
Review
Quoted 12 lines
drivers/md/dm.c +2
-4- - if (bio_integrity(bio)) { - bio_integrity_clone(clone, bio, GFP_NOIO, bs); - +#if 0 + if (bio_integrity(bio))if (idx !=
bio->bi_idx||clone->bi_size<bio->bi_size)bio_integrity_trim(clone, bio_sector_offset(bio, idx, 0), len);
- }(quoted diff) +1
-1- +#endifDead/debug code under "#if 0" ?
Quoted 7 lines
Quoted 5 lines
Oh, I never got around to figuring out what needed to be done with the bio integrity - presumably it'll have to be handled differently somehow (I'm assuming an index in the integrity bvec is intended to match up with an index in the regular bvec), but I don't have any way to test the integrity stuff.
The scsi debug module supports dif/dix, so you can use that for testing.
Thanks, I'll try that.
Tejun Heo
maintainerRe: [Bcache v13 01/16] Only clone bio vecs that are in use
In reply to Kent OverstreetHello, Kent.
Quoted 4 lines · Kent Overstreet
Bcache creates large bios internally, and then splits them according to the requirements of the underlying device. If the underlying device then needs to clone the bio, the clone will fail if the original bio had more than 256 segments - even if
bi_vcnt-bi_idxwas smaller.
I think this is a useful change outside the context of bcache. How about generalizing the description a bit and send it to Jens w/ block:
tag?
Quoted 24 lines
drivers/md/dm.c +3
-10@@ -1078,28 +1078,22 @@ static struct bio *split_bvec(struct bio *bio, sector_t sector,* Creates a bio that consists of range of complete bvecs. */ static struct bio *clone_bio (struct bio *bio,sector_t sector, -unsigned short idx,unsigned short bv_count, +unsigned short bv_count,unsigned int len,struct bio_set *bs) {struct bio *clone; - clone =bio_alloc_bioset (GFP_NOIO , bio->bi_max_vecs, bs); -__bio_clone (clone, bio); - clone->bi_destructor = dm_bio_destructor; + clone =bio_clone_bioset (bio,GFP_NOIO , bs); clone->bi_sector = sector; - clone->bi_idx = idx; - clone->bi_vcnt = idx + bv_count; + clone->bi_vcnt = bv_count; clone->bi_size =to_bytes (len); clone->bi_flags &= ~(1 <<BIO_SEG_VALID ); - -if (bio_integrity (bio)) { -bio_integrity_clone (clone, bio,GFP_NOIO , bs); -
drivers/md/dm.c static struct bio *split_bvec(struct bio *bio, sector_t sector, · line 1098
Quoted 10 lines
drivers/md/dm.c +3
-2+#if 0 +if (bio_integrity (bio))if (idx != bio->bi_idx || clone->bi_size < bio->bi_size)bio_integrity_trim (clone,bio_sector_offset (bio, idx,0 ), len); - } - +#endif return clone; }
Hmmm... I wish these changes are done in separate steps. ie.
1. Add void *bi_destructor_data to bio so that BIO_HAS_POOL trickery, which BTW is broken - this patch doesn't build, isn't necessary and destructors can be consolidated - bio_alloc_bioset() can set bi_destructor() and bi_destructor_data automatically so that only the ones which want to override the default allocation need to bother.
2. Introduce bio_clone_bioset() without the allocation change and convert md (and whoever applicable).
3. Change the behavior such that only bio_segments() is copied.
drivers/md/md.c struct bio *bio_clone_mddev(struct bio *bio, gfp_t gfp_mask, · line 199
Quoted 12 lines
drivers/md/md.c +2
-1@@ -194,7 +194,8 @@ struct bio *bio_clone_mddev(struct bio *bio, gfp_t gfp_mask,if (!mddev || !mddev->bio_set)return bio_clone (bio, gfp_mask); - b =bio_alloc_bioset (gfp_mask, bio->bi_max_vecs, + b =bio_alloc_bioset (gfp_mask, +bio_segments (bio), mddev->bio_set);
Line broken unnecessarily?
drivers/md/md.c static struct biovec_slab bvec_slabs[BIOVEC_NR_POOLS] __read_mostly = { · line 56
Quoted 5 lines
drivers/md/md.c +1
-0@@ -53,6 +53,7 @@ static struct biovec_slab bvec_slabs[BIOVEC_NR_POOLS] __read_mostly = {* IO code that does not need private memory pools. */ struct bio_set *fs_bio_set; +EXPORT_SYMBOL (fs_bio_set);
Is making bio_clone() an inline function really worth it?
drivers/md/md.c struct bio *bio_alloc(gfp_t gfp_mask, unsigned int nr_iovecs) · line 343
Quoted 10 lines
drivers/md/md.c +4
-2@@ -341,8 +337,10 @@ struct bio *bio_alloc(gfp_t gfp_mask, unsigned int nr_iovecs) {struct bio *bio =bio_alloc_bioset (gfp_mask, nr_iovecs, fs_bio_set); -if (bio) - bio->bi_destructor = bio_fs_destructor; +if (bio) { + bio->bi_flags |=1 <<BIO_HAS_POOL ; + bio->bi_destructor = (void *) fs_bio_set; + }
Wrote above but it looks like the patch is missing some part of BIO_HAS_POOL handling. Doesn't build. Also, it's a pretty ugly hack.
drivers/md/md.c struct bio *bio_alloc(gfp_t gfp_mask, unsigned int nr_iovecs)
Quoted 12 lines
drivers/md/md.c +4
-4/** -* __bio_clone - clone a bio -* @bio: destination bio -* @bio_src: bio to clone +* __bio_clone - clone a bio +* @bio: destination bio +* @bio_src: bio to clone * * Clone a &bio. Caller will own the returned bio, but not * the actual data it points to. Reference count of returned -* bio will be one. +* bio will be one.
Whilespace cleanup? I'm okay with doing these as part of larger changes but would appreciate if these were noted in the commit message in the form of "while at it, blah blah...".
drivers/md/md.c
Quoted 8 lines
*/
void__bio_clone(struct bio *bio, struct bio *bio_src){drivers/md/md.c +3
-2-memcpy (bio->bi_io_vec, bio_src->bi_io_vec, - bio_src->bi_max_vecs *sizeof (struct bio_vec )); +memcpy (bio->bi_io_vec, +bio_iovec (bio_src), +bio_segments (bio_src) *sizeof (struct bio_vec ));
Unnecessary line break?
drivers/md/md.c
Quoted 7 lines
drivers/md/md.c +3
-3-struct bio *bio_clone (struct bio *bio,gfp_t gfp_mask) +struct bio *bio_clone_bioset (struct bio *bio,gfp_t gfp_mask, +struct bio_set *bs) { -struct bio *b =bio_alloc_bioset (gfp_mask, bio->bi_max_vecs, fs_bio_set); - +struct bio *b =bio_alloc_bioset (gfp_mask,bio_segments (bio), bs);
Blank line between var decls and body please.
drivers/md/md.c
Quoted 6 lines
drivers/md/md.c +1
-1if (!b)return NULL ; - b->bi_destructor = bio_fs_destructor;__bio_clone (b, bio); + b->bi_flags |=1 <<BIO_HAS_POOL ;
No indenting of assignments please. There are some exceptions when there are a lot of assignments and not aligning makes it very difficult to read but I don't think that applies here.
drivers/md/md.c
Quoted 7 lines
drivers/md/md.c +2
-1+ b->bi_destructor = (void *) bs;if (bio_integrity (bio)) {int ret; - ret =bio_integrity_clone (b, bio, gfp_mask, fs_bio_set); + ret =bio_integrity_clone (b, bio, gfp_mask, bs);
You'll probably need to update bio_integrity about the same way. It does about the same thing as bio alloc path.
Thanks.
Signature · 1 line
tejun
PATCH v13 2/16 Bio pool freeing
In reply to Kent OverstreetWhen you allocate a bio from a bio pool, to free it you have to know where it came from; this adds a flag which, if set, means bi_destructor is the pointer to the pool and bio_put() can do the right thing.
This is used in bcache, so we can cleanly use per device bio pools.
--- fs/bio.c | 9 +++++++-- include/linux/blk_types.h | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-)
fs/bio.c +7 -2
@@ -235,7 +235,7 @@ void bio_free(struct bio *bio, struct bio_set *bs) {void *p; -if (bio_has_allocated_vec (bio)) +if (bio_flagged (bio,BIO_HAS_VEC ))bvec_free_bs (bs, bio->bi_io_vec,BIO_POOL_IDX (bio));if (bio_integrity (bio))
@@ -301,6 +301,7 @@ struct bio *bio_alloc_bioset(gfp_t gfp_mask, int nr_iovecs, struct bio_set *bs) goto err_free; nr_iovecs =bvec_nr_vecs (idx); + bio->bi_flags |=1 <<BIO_HAS_VEC ; } out_set: bio->bi_flags |= idx <<BIO_POOL_OFFSET ;
@@ -417,7 +418,11 @@ void bio_put(struct bio *bio)*/ if (atomic_dec_and_test (&bio->bi_cnt)) { bio->bi_next =NULL ; - bio->bi_destructor (bio); + +if (bio_flagged (bio,BIO_HAS_POOL )) +bio_free (bio, (void *) bio->bi_destructor); +else + bio->bi_destructor (bio); } }EXPORT_SYMBOL (bio_put);
include/linux/blk_types.h +2 -0
@@ -95,6 +95,8 @@ struct bio {#define BIO_FS_INTEGRITY 9/* fs owns integrity data, not block layer */ #define BIO_QUIET 10/* Make BIO Quiet */ #define BIO_MAPPED_INTEGRITY 11/* integrity metadata has been remapped */ +#define BIO_HAS_POOL 12/* bi_destructor points to bio_pool */ +#define BIO_HAS_VEC 13/* bio_free() should free bvec */ #define bio_flagged (bio, flag) ((bio)->bi_flags & (1 << (flag)))/*
Signature · 1 line
1.7.9.rc2
Re: [dm-devel] [Bcache v13 02/16] Bio pool freeing
Quoted 5 lines · Kent Overstreet
When you allocate a bio from a bio pool, to free it you have to know where it came from; this adds a flag which, if set, means
bi_destructoris the pointer to the pool andbio_put()can do the right thing.This is used in bcache, so we can cleanly use per device bio pools.
Ok, that will explain BIO_HAS_POOL flag. Why to replace bio_has_allocated_vec() with BIO_HAS_VEC flag?
Thanks
Vivek
Quoted 61 lines
Signed-off-by Kent Overstreet <[email protected]>--- fs/bio.c | 9 +++++++-- include/linux/blk_types.h | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-)fs/bio.c +7
-2@@ -235,7 +235,7 @@ void bio_free(struct bio *bio, struct bio_set *bs) {void *p; -if (bio_has_allocated_vec (bio)) +if (bio_flagged (bio,BIO_HAS_VEC ))bvec_free_bs (bs, bio->bi_io_vec,BIO_POOL_IDX (bio));if (bio_integrity (bio))@@ -301,6 +301,7 @@ struct bio *bio_alloc_bioset(gfp_t gfp_mask, int nr_iovecs, struct bio_set *bs) goto err_free; nr_iovecs =bvec_nr_vecs (idx); + bio->bi_flags |=1 <<BIO_HAS_VEC ; } out_set: bio->bi_flags |= idx <<BIO_POOL_OFFSET ;@@ -417,7 +418,11 @@ void bio_put(struct bio *bio)*/ if (atomic_dec_and_test (&bio->bi_cnt)) { bio->bi_next =NULL ; - bio->bi_destructor (bio); + +if (bio_flagged (bio,BIO_HAS_POOL )) +bio_free (bio, (void *) bio->bi_destructor); +else + bio->bi_destructor (bio); } }EXPORT_SYMBOL (bio_put);include/linux/blk_types.h +2
-0@@ -95,6 +95,8 @@ struct bio {#define BIO_FS_INTEGRITY 9/* fs owns integrity data, not block layer */ #define BIO_QUIET 10/* Make BIO Quiet */ #define BIO_MAPPED_INTEGRITY 11/* integrity metadata has been remapped */ +#define BIO_HAS_POOL 12/* bi_destructor points to bio_pool */ +#define BIO_HAS_VEC 13/* bio_free() should free bvec */ #define bio_flagged (bio, flag) ((bio)->bi_flags & (1 << (flag)))/* Signature · 6 lines
1.7.9.rc2 -- dm-devel mailing list [email protected] https://www.redhat.com/mailman/listinfo/dm-devel
Re: [dm-devel] [Bcache v13 02/16] Bio pool freeing
Quoted 9 lines · Vivek Goyal
Quoted 5 lines · Kent Overstreet
When you allocate a bio from a bio pool, to free it you have to know where it came from; this adds a flag which, if set, means
bi_destructoris the pointer to the pool andbio_put()can do the right thing.This is used in bcache, so we can cleanly use per device bio pools.
Ok, that will explain
BIO_HAS_POOLflag. Why to replacebio_has_allocated_vec()withBIO_HAS_VECflag?
Using bio_has_allocated_vec() would mean the bvec would always be freed if it wasn't a pointer to the inline vecs - my bio splitting code will use the bvec from the original bio for the split if it's splitting on a bvec boundary, in which case that's not what we want.
It means "bio owns its bvec".
Quoted 65 lines
Thanks
VivekQuoted 61 lines
Signed-off-by Kent Overstreet <[email protected]>---
fs/bio.c | 9 +++++++--
include/linux/blk_types.h | 2 ++2 files changed, 9 insertions(+), 2 deletions(-)fs/bio.c +2
-1@@ -235,7 +235,7 @@ void bio_free(struct bio *bio, struct bio_set *bs) { void *p; - if (bio_has_allocated_vec(bio)) + if (bio_flagged(bio, BIO_HAS_VEC)) bvec_free_bs(bs, bio->bi_io_vec, BIO_POOL_IDX(bio)); if (bio_integrity(bio))@@ -301,6 +301,7 @@ struct bio *bio_alloc_bioset(gfp_t gfp_mask, int nr_iovecs, struct bio_set *bs) goto err_free; nr_iovecs = bvec_nr_vecs(idx); + bio->bi_flags |= 1 << BIO_HAS_VEC; }out_set:
bio->bi_flags|= idx <<BIO_POOL_OFFSET;fs/bio.c +5
-1@@ -417,7 +418,11 @@ void bio_put(struct bio *bio)*/ if (atomic_dec_and_test(&bio->bi_cnt)) { bio->bi_next = NULL; - bio->bi_destructor(bio); + + if (bio_flagged(bio, BIO_HAS_POOL)) + bio_free(bio, (void *) bio->bi_destructor); + else + bio->bi_destructor(bio); } } EXPORT_SYMBOL(bio_put);include/linux/blk_types.h +2
-1@@ -95,6 +95,8 @@ struct bio { #define BIO_FS_INTEGRITY 9 /* fs owns integrity data, not block layer */ #define BIO_QUIET 10 /* Make BIO Quiet */ #define BIO_MAPPED_INTEGRITY 11/* integrity metadata has been remapped */ +#define BIO_HAS_POOL 12/* bi_destructor points to bio_pool */ +#define BIO_HAS_VEC 13/* bio_free() should free bvec */ #define bio_flagged(bio, flag) ((bio)->bi_flags & (1 << (flag))) /* -- 1.7.9.rc2
--
dm-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/dm-devel
Re: [dm-devel] [Bcache v13 02/16] Bio pool freeing
Quoted 11 lines · Kent Overstreet
Quoted 9 lines · Vivek Goyal
Quoted 5 lines · Kent Overstreet
When you allocate a bio from a bio pool, to free it you have to know where it came from; this adds a flag which, if set, means
bi_destructoris the pointer to the pool andbio_put()can do the right thing.This is used in bcache, so we can cleanly use per device bio pools.
Ok, that will explain
BIO_HAS_POOLflag. Why to replacebio_has_allocated_vec()withBIO_HAS_VECflag?
Quoted 6 lines
Using
bio_has_allocated_vec()would mean the bvec would always be freed if it wasn't a pointer to the inline vecs - my bio splitting code will use the bvec from the original bio for the split if it's splitting on a bvec boundary, in which case that's not what we want.It means "bio owns its bvec".
ok, probably we could explain it more in changelog or push this in same patch where you introduce bio splitting so that bio shares bvec with other bio. That way it is easier to understand.
Thanks
Vivek
Re: [dm-devel] [Bcache v13 02/16] Bio pool freeing
Quoted 3 lines · Vivek Goyal
ok, probably we could explain it more in changelog or push this in same patch where you introduce bio splitting so that bio shares bvec with other bio. That way it is easier to understand.
Yeah, agreed.
Re: [Bcache v13 02/16] Bio pool freeing
In reply to Kent OverstreetQuoted 9 lines · Kent Overstreet
When you allocate a bio from a bio pool, to free it you have to know where it came from; this adds a flag which, if set, means
bi_destructoris the pointer to the pool andbio_put()can do the right thing.This is used in bcache, so we can cleanly use per device bio pools.
Signed-off-by Kent Overstreet <[email protected]>---
fs/bio.c void bio_put(struct bio *bio) · line 425
Quoted 9 lines
fs/bio.c +5
-1@@ -417,7 +418,11 @@ void bio_put(struct bio *bio)*/ if (atomic_dec_and_test (&bio->bi_cnt)) { bio->bi_next =NULL ; - bio->bi_destructor (bio); + +if (bio_flagged (bio,BIO_HAS_POOL )) +bio_free (bio, (void *) bio->bi_destructor); +else + bio->bi_destructor (bio);
Ummm... this is way too cryptic. Please just add another field to bio.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 02/16] Bio pool freeing
In reply to Kent OverstreetQuoted 17 lines · Kent Overstreet
When you allocate a bio from a bio pool, to free it you have to know where it came from; this adds a flag which, if set, means
bi_destructoris the pointer to the pool andbio_put()can do the right thing.This is used in bcache, so we can cleanly use per device bio pools.
Signed-off-by Kent Overstreet <[email protected]>--- fs/bio.c | 9 +++++++-- include/linux/blk_types.h | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-)fs/bio.c +0
-0
fs/bio.c void bio_put(struct bio *bio) · line 425
Quoted 9 lines
fs/bio.c +5
-1@@ -417,7 +418,11 @@ void bio_put(struct bio *bio)*/ if (atomic_dec_and_test (&bio->bi_cnt)) { bio->bi_next =NULL ; - bio->bi_destructor (bio); + +if (bio_flagged (bio,BIO_HAS_POOL )) +bio_free (bio, (void *) bio->bi_destructor); +else + bio->bi_destructor (bio);
Ummm... this is way too cryptic. Please just add another field to bio.
Thanks.
Signature · 1 line
tejun
PATCH v13 3/16 Revert "rw_semaphore: remove up/down_read_non_owner"
In reply to Kent OverstreetThis reverts commit 11b80f459adaf91a712f95e7734a17655a36bf30.
--- include/linux/rwsem.h | 10 ++++++++++ kernel/rwsem.c | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 0 deletions(-)
include/linux/rwsem.h +10 -0
@@ -126,9 +126,19 @@ extern void downgrade_write(struct rw_semaphore *sem);*/ extern void down_read_nested (struct rw_semaphore *sem,int subclass);extern void down_write_nested (struct rw_semaphore *sem,int subclass); +/* +* Take/release a lock when not the owner will release it. +* +* [ This API should be avoided as much as possible - the +* proper abstraction for this case is completions. ] +*/ +extern void down_read_non_owner (struct rw_semaphore *sem); +extern void up_read_non_owner (struct rw_semaphore *sem);#else # define down_read_nested (sem, subclass) down_read(sem)# define down_write_nested (sem, subclass) down_write(sem) +# define down_read_non_owner (sem) down_read(sem) +# define up_read_non_owner (sem) up_read(sem)#endif #endif /* _LINUX_RWSEM_H */
kernel/rwsem.c +16 -0
@@ -117,6 +117,15 @@ void down_read_nested(struct rw_semaphore *sem, int subclass)EXPORT_SYMBOL (down_read_nested); +void down_read_non_owner (struct rw_semaphore *sem) +{ +might_sleep (); + +__down_read (sem); +} + +EXPORT_SYMBOL (down_read_non_owner); +void down_write_nested (struct rw_semaphore *sem,int subclass) {might_sleep ();
@@ -127,6 +136,13 @@ void down_write_nested(struct rw_semaphore *sem, int subclass)EXPORT_SYMBOL (down_write_nested); +void up_read_non_owner (struct rw_semaphore *sem) +{ +__up_read (sem); +} + +EXPORT_SYMBOL (up_read_non_owner); +#endif
Signature · 1 line
1.7.9.rc2
Re: [Bcache v13 03/16] Revert "rw_semaphore: remove up/down_read_non_owner"
Quoted 3 lines · Kent Overstreet
This reverts commit
11b80f459adaf91a712f95e7734a17655a36bf30.Signed-off-by Kent Overstreet <[email protected]>
Please cc Christoph and Al and justify why it needs to be reverted.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 03/16] Revert "rw_semaphore: remove up/down_read_non_owner"
In reply to Kent OverstreetQuoted 3 lines · Kent Overstreet
This reverts commit
11b80f459adaf91a712f95e7734a17655a36bf30.Signed-off-by Kent Overstreet <[email protected]>
Please cc Christoph and Al and justify why it needs to be reverted.
Thanks.
--
tejun
PATCH v13 4/16 Fix ratelimit macro to compile in c99 mode
In reply to Kent Overstreet--- include/linux/ratelimit.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
include/linux/ratelimit.h +1 -1
@@ -20,7 +20,7 @@ struct ratelimit_state {#define DEFINE_RATELIMIT_STATE (name, interval_init, burst_init) \ \ struct ratelimit_state name = { \ - .lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock), \ + .lock = __RAW_SPIN_LOCK_INITIALIZER(name.lock),\ .interval = interval_init, \ .burst = burst_init, \ }
Signature · 1 line
1.7.9.rc2
Re: [Bcache v13 04/16] Fix ratelimit macro to compile in c99 mode
Quoted 11 lines · Kent Overstreet
Signed-off-by Kent Overstreet <[email protected]>--- include/linux/ratelimit.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)include/linux/ratelimit.h +0
-0
include/linux/ratelimit.h struct ratelimit_state { · line 23
Quoted 5 lines
include/linux/ratelimit.h +1
-1@@ -20,7 +20,7 @@ struct ratelimit_state {#define DEFINE_RATELIMIT_STATE (name, interval_init, burst_init) \ \ struct ratelimit_state name = { \ - .lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock), \ + .lock = __RAW_SPIN_LOCK_INITIALIZER(name.lock),\
NACK, kernel is not built in c99 mode and using anonymous struct in struct initializers is an accepted convention.
Thanks.
Signature · 1 line
tejun
PATCH v13 5/16 Export get_random_int()
In reply to Kent OverstreetThis is needed for bcache - it needs a fast source of random numbers for the throttling code in order to fuzz IO sizes, but the random numbers don't need to be any good (and it immediately converts it to a binomially distributed random number with popcount anyways).
--- drivers/char/random.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
drivers/char/random.c +1 -0
@@ -1357,6 +1357,7 @@ unsigned int get_random_int(void)return ret; } +EXPORT_SYMBOL (get_random_int);/* * randomize_range() returns a start address such that
Signature · 1 line
1.7.9.rc2
Tejun Heo
maintainerRe: [Bcache v13 05/16] Export get_random_int()
Quoted 6 lines · Kent Overstreet
This is needed for bcache - it needs a fast source of random numbers for the throttling code in order to fuzz IO sizes, but the random numbers don't need to be any good (and it immediately converts it to a binomially distributed random number with popcount anyways).
Signed-off-by Kent Overstreet <[email protected]>
Please cc the maintainer (for other patches too).
Thanks.
Signature · 1 line
tejun
PATCH v13 6/16 Export blk_fill_rwbs()
In reply to Kent OverstreetUsed by bcache's tracepoints.
--- kernel/trace/blktrace.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
kernel/trace/blktrace.c +1 -0
@@ -1820,6 +1820,7 @@ void blk_fill_rwbs(char *rwbs, u32 rw, int bytes) rwbs[i] = '\0'; } +EXPORT_SYMBOL_GPL (blk_fill_rwbs);#endif /* CONFIG_EVENT_TRACING */
Signature · 1 line
1.7.9.rc2
PATCH v13 7/16 Closures
In reply to Kent OverstreetClosures are asynchronous refcounty things based on workqueues, used extensively in bcache.
--- include/linux/closure.h | 614 +++++++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig | 3 + lib/Kconfig.debug | 9 + lib/Makefile | 2 + lib/closure.c | 363 ++++++++++++++++++++++++++++ 5 files changed, 991 insertions(+), 0 deletions(-) create mode 100644 include/linux/closure.h create mode 100644 lib/closure.c
include/linux/closure.h new file +614 -0
@@ -0,0 +1,614 @@ +#ifndef _LINUX_CLOSURE_H +#define _LINUX_CLOSURE_H + +#include <linux/llist.h> +#include <linux/sched.h> +#include <linux/workqueue.h> + +/* +* Closure is perhaps the most overused and abused term in computer science, but +* since I've been unable to come up with anything better you're stuck with it +* again. +* +* What are closures? +* +* They embed a refcount. The basic idea is they count "things that are in +* progress" - in flight bios, some other thread that's doing something else - +* anything you might want to wait on. +* +* The refcount may be manipulated with closure_get() and closure_put(). +* closure_put() is where many of the interesting things happen, when it causes +* the refcount to go to 0. +* +* Closures can be used to wait on things both synchronously and asynchronously, +* and synchronous and asynchronous use can be mixed without restriction. To +* wait synchronously, use closure_sync() - you will sleep until your closure's +* refcount hits 1. +* +* To wait asynchronously, use +* continue_at(cl, next_function, workqueue); +* +* passing it, as you might expect, the function to run when nothing is pending +* and the workqueue to run that function out of. +* +* continue_at() also, critically, is a macro that returns the calling function. +* There's good reason for this. +* +* To use safely closures asynchronously, they must always have a refcount while +* they are running owned by the thread that is running them. Otherwise, suppose +* you submit some bios and wish to have a function run when they all complete: +* +* foo_endio(struct bio *bio, int error) +* { +* closure_put(cl); +* } +* +* closure_init(cl); +* +* do_stuff(); +* closure_get(cl); +* bio1->bi_endio = foo_endio; +* bio_submit(bio1); +* +* do_more_stuff(); +* closure_get(cl); +* bio2->bi_endio = foo_endio; +* bio_submit(bio2); +* +* continue_at(cl, complete_some_read, system_wq); +* +* If closure's refcount started at 0, complete_some_read() could run before the +* second bio was submitted - which is almost always not what you want! More +* importantly, it wouldn't be possible to say whether the original thread or +* complete_some_read()'s thread owned the closure - and whatever state it was +* associated with! +* +* So, closure_init() initializes a closure's refcount to 1 - and when a +* closure_fn is run, the refcount will be reset to 1 first. +* +* Then, the rule is - if you got the refcount with closure_get(), release it +* with closure_put() (i.e, in a bio->bi_endio function). If you have a refcount +* on a closure because you called closure_init() or you were run out of a +* closure - _always_ use continue_at(). Doing so consistently will help +* eliminate an entire class of particularly pernicious races. +* +* For a closure to wait on an arbitrary event, we need to introduce waitlists: +* +* closure_list_t list; +* closure_wait_event(list, cl, condition); +* closure_wake_up(wait_list); +* +* These work analagously to wait_event() and wake_up() - except that instead of +* operating on the current thread (for wait_event()) and lists of threads, they +* operate on an explicit closure and lists of closures. +* +* Because it's a closure we can now wait either synchronously or +* asynchronously. closure_wait_event() returns the current value of the +* condition, and if it returned false continue_at() or closure_sync() can be +* used to wait for it to become true. +* +* It's useful for waiting on things when you can't sleep in the context in +* which you must check the condition (perhaps a spinlock held, or you might be +* beneath generic_make_request() - in which case you can't sleep on IO). +* +* closure_wait_event() will wait either synchronously or asynchronously, +* depending on whether the closure is in blocking mode or not. You can pick a +* mode explicitly with closure_wait_event_sync() and +* closure_wait_event_async(), which do just what you might expect. +* +* Lastly, you might have a wait list dedicated to a specific event, and have no +* need for specifying the condition - you just want to wait until someone runs +* closure_wake_up() on the appropriate wait list. In that case, just use +* closure_wait(). It will return either true or false, depending on whether the +* closure was already on a wait list or not - a closure can only be on one wait +* list at a time. +* +* Parents: +* +* closure_init() takes two arguments - it takes the closure to initialize, and +* a (possibly null) parent. +* +* If parent is non null, the new closure will have a refcount for its lifetime; +* a closure is considered to be "finished" when its refcount hits 0 and the +* function to run is null. Hence +* +* continue_at(cl, NULL, NULL); +* +* returns up the (spaghetti) stack of closures, precisely like normal return +* returns up the C stack. continue_at() with non null fn is better thought of +* as doing a tail call. +* +* All this implies that a closure should typically be embedded in a particular +* struct (which its refcount will normally control the lifetime of), and that +* struct can very much be thought of as a stack frame. +* +* Locking: +* +* Closures are based on work items but they can be thought of as more like +* threads - in that like threads and unlike work items they have a well +* defined lifetime; they are created (with closure_init()) and eventually +* complete after a continue_at(cl, NULL, NULL). +* +* Suppose you've got some larger structure with a closure embedded in it that's +* used for periodically doing garbage collection. You only want one garbage +* collection happening at a time, so the natural thing to do is protect it with +* a lock. However, it's difficult to use a lock protecting a closure correctly +* because the unlock should come after the last continue_to() (additionally, if +* you're using the closure asynchronously a mutex won't work since a mutex has +* to be unlocked by the same process that locked it). +* +* So to make it less error prone and more efficient, we also have the ability +* to use closures as locks: +* +* closure_init_unlocked(); +* closure_trylock(); +* +* That's all we need for trylock() - the last closure_put() implicitly unlocks +* it for you. But for closure_lock(), we also need a wait list: +* +* struct closure_with_waitlist frobnicator_cl; +* +* closure_init_unlocked(&frobnicator_cl); +* closure_lock(&frobnicator_cl); +* +* A closure_with_waitlist embeds a closure and a wait list - much like struct +* delayed_work embeds a work item and a timer_list. The important thing is, use +* it exactly like you would a regular closure and closure_put() will magically +* handle everything for you. +* +* We've got closures that embed timers, too. They're called, appropriately +* enough: +* struct closure_with_timer; +* +* This gives you access to closure_sleep(). It takes a refcount for a specified +* number of jiffies - you could then call closure_sync() (for a slightly +* convoluted version of msleep()) or continue_at() - which gives you the same +* effect as using a delayed work item, except you can reuse the work_struct +* already embedded in struct closure. +* +* Lastly, there's struct closure_with_waitlist_and_timer. It does what you +* probably expect, if you happen to need the features of both. (You don't +* really want to know how all this is implemented, but if I've done my job +* right you shouldn't have to care). +*/ + +struct closure ; +typedef void (closure_fn ) (struct closure *); + +typedef struct llist_head closure_list_t ; + +struct closure { +union { +struct { +struct workqueue_struct *wq; +struct task_struct *task; +struct llist_node list; +closure_fn *fn; + }; +struct work_struct work; + }; + +struct closure *parent; + +#define CLOSURE_REMAINING_MASK (~(~0 << 20)) +#define CLOSURE_GUARD_MASK \ + ((1 << 20)|(1 << 22)|(1 << 24)|(1 << 26)|(1 << 28)|(1 << 30)) + +/* +* CLOSURE_RUNNING: Set when a closure is running (i.e. by +* closure_init() and when closure_put() runs then next function), and +* must be cleared before remaining hits 0. Primarily to help guard +* against incorrect usage and accidently transferring references. +* continue_at() and closure_return() clear it for you, if you're doing +* something unusual you can use closure_set_dead() which also helps +* annotate where references are being transferred. +* +* CLOSURE_BLOCKING: Causes closure_wait_event() to block, instead of +* waiting asynchronously +* +* CLOSURE_STACK: Sanity check - remaining should never hit 0 on a +* closure with this flag set +* +* CLOSURE_WAITING: Set iff the closure is on a waitlist. Must be set by +* the thread that owns the closure, and cleared by the thread that's +* waking up the closure. +* +* CLOSURE_SLEEPING: Must be set before a thread uses a closure to sleep +* - indicates that cl->task is valid and closure_put() may wake it up. +* Only set or cleared by the thread that owns the closure. +* +* CLOSURE_TIMER: Analagous to CLOSURE_WAITING, indicates that a closure +* has an outstanding timer. Must be set by the thread that owns the +* closure, and cleared by the timer function when the timer goes off. +*/ + +#define CLOSURE_RUNNING (1 << 21) +#define CLOSURE_BLOCKING (1 << 23) +#define CLOSURE_STACK (1 << 25) +#define CLOSURE_WAITING (1 << 27) +#define CLOSURE_SLEEPING (1 << 29) +#define CLOSURE_TIMER (1 << 31) +atomic_t remaining; + +#define CLOSURE_REMAINING_INITIALIZER (1|CLOSURE_RUNNING) + +#define TYPE_closure 0U +#define TYPE_closure_with_waitlist 1U +#define TYPE_closure_with_timer 2U +#define TYPE_closure_with_waitlist_and_timer 3U +#define MAX_CLOSURE_TYPE 3U +unsigned type; + +#ifdef CONFIG_DEBUG_CLOSURES +#define CLOSURE_MAGIC_DEAD 0xc1054e0dead +#define CLOSURE_MAGIC_ALIVE 0xc1054e0a11e + +unsigned long magic; +struct list_head all; +unsigned long ip; +unsigned long waiting_on; +#endif +}; + +struct closure_with_waitlist { +struct closure cl; +closure_list_t wait; +}; + +struct closure_with_timer { +struct closure cl; +struct timer_list timer; +}; + +struct closure_with_waitlist_and_timer { +struct closure cl; +closure_list_t wait; +struct timer_list timer; +}; + +extern unsigned invalid_closure_type (void ); + +#define __CL_TYPE (cl, _t) \ + __builtin_types_compatible_p(typeof(cl), struct _t) \ + ? TYPE_ ## _t : \ + +#define __closure_type (cl) \ +( \ + __CL_TYPE(cl, closure) \ + __CL_TYPE(cl, closure_with_waitlist) \ + __CL_TYPE(cl, closure_with_timer) \ + __CL_TYPE(cl, closure_with_waitlist_and_timer) \ + invalid_closure_type() \ +) + +void closure_sub (struct closure *cl,int v); +void closure_put (struct closure *cl); +void closure_queue (struct closure *cl); +void __closure_wake_up (closure_list_t *list); +bool closure_wait (closure_list_t *list,struct closure *cl); +void closure_sync (struct closure *cl); + +bool closure_trylock (struct closure *cl,struct closure *parent); +void __closure_lock (struct closure *cl,struct closure *parent, +closure_list_t *wait_list); + +void do_closure_timer_init (struct closure *cl); +bool __closure_sleep (struct closure *cl,unsigned long delay, +struct timer_list *timer); +void __closure_flush (struct closure *cl,struct timer_list *timer); +void __closure_flush_sync (struct closure *cl,struct timer_list *timer); + +#ifdef CONFIG_DEBUG_CLOSURES + +void closure_debug_create (struct closure *cl); +void closure_debug_destroy (struct closure *cl); + +#else + +static inline void closure_debug_create (struct closure *cl) {} +static inline void closure_debug_destroy (struct closure *cl) {} + +#endif + +static inline void closure_set_ip (struct closure *cl) +{ +#ifdef CONFIG_DEBUG_CLOSURES + cl->ip = _THIS_IP_; +#endif +} + +static inline void closure_set_ret_ip (struct closure *cl) +{ +#ifdef CONFIG_DEBUG_CLOSURES + cl->ip = _RET_IP_; +#endif +} + +static inline void closure_get (struct closure *cl) +{ +#ifdef CONFIG_DEBUG_CLOSURES +BUG_ON ((atomic_inc_return (&cl->remaining) & +CLOSURE_REMAINING_MASK ) <=1 ); +#else +atomic_inc (&cl->remaining); +#endif +} + +static inline void closure_set_stopped (struct closure *cl) +{ +atomic_sub (CLOSURE_RUNNING , &cl->remaining); +} + +static inline bool closure_is_stopped (struct closure *cl) +{ +return !(atomic_read (&cl->remaining) &CLOSURE_RUNNING ); +} + +static inline bool closure_is_unlocked (struct closure *cl) +{ +return atomic_read (&cl->remaining) ==-1 ; +} + +static inline void do_closure_init (struct closure *cl,struct closure *parent, +bool running) +{ +switch (cl->type) { +case TYPE_closure_with_timer: +case TYPE_closure_with_waitlist_and_timer: +do_closure_timer_init (cl); + } + +#if defined(CONFIG_LOCKDEP ) || defined(CONFIG_DEBUG_OBJECTS_WORK ) +INIT_WORK (&cl->work,NULL ); +#endif + cl->parent = parent; +if (parent) +closure_get (parent); + +if (running) { +closure_debug_create (cl); +atomic_set (&cl->remaining,CLOSURE_REMAINING_INITIALIZER ); + }else +atomic_set (&cl->remaining,-1 ); +} + +/* +* Hack to get at the embedded closure if there is one, by doing an unsafe cast: +* the result of __closure_type() is thrown away, it's used merely for type +* checking. +*/ +#define __to_internal_closure (cl) \ +({ \ + BUILD_BUG_ON(__closure_type(*cl) > MAX_CLOSURE_TYPE); \ + (struct closure *) cl; \ +}) + +#define closure_init_type (cl, parent, running, memset) \ +do { \ + struct closure *_cl = __to_internal_closure(cl); \ + _cl->type = __closure_type(*(cl)); \ + closure_set_ip(_cl); \ + do_closure_init(_cl, parent, running); \ +} while (0) + +/** +* __closure_init() - Initialize a closure, skipping the memset() +* +* May be used instead of closure_init() when memory has already been zeroed. +*/ +#define __closure_init (cl, parent) \ + closure_init_type(cl, parent, true, false) + +/** +* closure_init() - Initialize a closure, setting the refcount to 1 +* @cl: closure to initialize +* @parent: parent of the new closure. cl will take a refcount on it for its +* lifetime; may be NULL. +*/ +#define closure_init (cl, parent) \ + closure_init_type(cl, parent, true, true) + +static inline void closure_init_stack (struct closure *cl) +{ +memset (cl,0 ,sizeof (struct closure )); +atomic_set (&cl->remaining,CLOSURE_REMAINING_INITIALIZER | +CLOSURE_BLOCKING |CLOSURE_STACK ); +} + +/** +* closure_init_unlocked() - Initialize a closure but leave it unlocked. +* @cl: closure to initialize +* +* For when the closure will be used as a lock. The closure may not be used +* until after a closure_lock() or closure_trylock(). +*/ +#define closure_init_unlocked (cl) \ + closure_init_type(cl, NULL, false, true) + +/** +* closure_lock() - lock and initialize a closure. +* @cl: the closure to lock +* @parent: the new parent for this closure +* +* The closure must be of one of the types that has a waitlist (otherwise we +* wouldn't be able to sleep on contention). +* +* @parent has exactly the same meaning as in closure_init(); if non null, the +* closure will take a reference on @parent which will be released when it is +* unlocked. +*/ +#define closure_lock (cl, parent) \ + __closure_lock(__to_internal_closure(cl), parent, &(cl)->wait) + +/** +* closure_sleep() - asynchronous sleep +* @cl: the closure that will sleep +* @delay: the delay in jiffies +* +* Takes a refcount on @cl which will be released after @delay jiffies; this may +* be used to have a function run after a delay with continue_at(), or +* closure_sync() may be used for a convoluted version of msleep(). +*/ +#define closure_sleep (cl, delay) \ + __closure_sleep(__to_internal_closure(cl), delay, &(cl)->timer) + +#define closure_flush (cl) \ + __closure_flush(__to_internal_closure(cl), &(cl)->timer) + +#define closure_flush_sync (cl) \ + __closure_flush_sync(__to_internal_closure(cl), &(cl)->timer) + +static inline void __closure_end_sleep (struct closure *cl) +{ +__set_current_state (TASK_RUNNING ); + +if (atomic_read (&cl->remaining) &CLOSURE_SLEEPING ) +atomic_sub (CLOSURE_SLEEPING , &cl->remaining); +} + +static inline void __closure_start_sleep (struct closure *cl) +{ +closure_set_ip (cl); + cl->task = current; +set_current_state (TASK_UNINTERRUPTIBLE ); + +if (!(atomic_read (&cl->remaining) &CLOSURE_SLEEPING )) +atomic_add (CLOSURE_SLEEPING , &cl->remaining); +} + +/** +* closure_blocking() - returns true if the closure is in blocking mode. +* +* If a closure is in blocking mode, closure_wait_event() will sleep until the +* condition is true instead of waiting asynchronously. +*/ +static inline bool closure_blocking (struct closure *cl) +{ +return atomic_read (&cl->remaining) &CLOSURE_BLOCKING ; +} + +/** +* set_closure_blocking() - put a closure in blocking mode. +* +* If a closure is in blocking mode, closure_wait_event() will sleep until the +* condition is true instead of waiting asynchronously. +* +* Not thread safe - can only be called by the thread running the closure. +*/ +static inline void set_closure_blocking (struct closure *cl) +{ +if (!closure_blocking (cl)) +atomic_add (CLOSURE_BLOCKING , &cl->remaining); +} + +/* +* Not thread safe - can only be called by the thread running the closure. +*/ +static inline void clear_closure_blocking (struct closure *cl) +{ +if (closure_blocking (cl)) +atomic_sub (CLOSURE_BLOCKING , &cl->remaining); +} + +/** +* closure_wake_up() - wake up all closures on a wait list. +*/ +static inline void closure_wake_up (closure_list_t *list) +{ +smp_mb (); +__closure_wake_up (list); +} + +/* +* Wait on an event, synchronously or asynchronously - analagous to wait_event() +* but for closures. +* +* The loop is oddly structured so as to avoid a race; we must check the +* condition again after we've added ourself to the waitlist. We know if we were +* already on the waitlist because closure_wait() returns false; thus, we only +* schedule or break if closure_wait() returns false. If it returns true, we +* just loop again - rechecking the condition. +* +* The __closure_wake_up() is necessary because we may race with the event +* becoming true; i.e. we see event false -> wait -> recheck condition, but the +* thread that made the event true may have called closure_wake_up() before we +* added ourself to the wait list. +* +* We have to call closure_sync() at the end instead of just +* __closure_end_sleep() because a different thread might've called +* closure_wake_up() before us and gotten preempted before they dropped the +* refcount on our closure. If this was a stack allocated closure, that would be +* bad. +*/ +#define __closure_wait_event (list, cl, condition, _block) \ +({ \ + __label__ out; \ + bool block = _block; \ + typeof(condition) ret; \ + \ + while (!(ret = (condition))) { \ + if (block) \ + __closure_start_sleep(cl); \ + if (!closure_wait(list, cl)) { \ + if (!block) \ + goto out; \ + schedule(); \ + } \ + } \ + __closure_wake_up(list); \ + if (block) \ + closure_sync(cl); \ +out: \ + ret; \ +}) + +/** +* closure_wait_event() - wait on a condition, synchronously or asynchronously. +* @list: the wait list to wait on +* @cl: the closure that is doing the waiting +* @condition: a C expression for the event to wait for +* +* If the closure is in blocking mode, sleeps until the @condition evaluates to +* true - exactly like wait_event(). +* +* If the closure is not in blocking mode, waits asynchronously; if the +* condition is currently false the @cl is put onto @list and returns. @list +* owns a refcount on @cl; closure_sync() or continue_at() may be used later to +* wait for another thread to wake up @list, which drops the refcount on @cl. +* +* Returns the value of @condition; @cl will be on @list iff @condition was +* false. +* +* closure_wake_up(@list) must be called after changing any variable that could +* cause @condition to become true. +*/ +#define closure_wait_event (list, cl, condition) \ + __closure_wait_event(list, cl, condition, closure_blocking(cl)) + +#define closure_wait_event_async (list, cl, condition) \ + __closure_wait_event(list, cl, condition, false) + +#define closure_wait_event_sync (list, cl, condition) \ + __closure_wait_event(list, cl, condition, true) + +static inline void set_closure_fn (struct closure *cl,closure_fn *fn, +struct workqueue_struct *wq) +{ + cl->fn = fn; + cl->wq = wq; +/* between atomic_dec() in closure_put() */ +smp_mb__before_atomic_dec (); +} + +#define continue_at (_cl, _fn, _wq, ...) \ +do { \ + BUG_ON(!(_cl) || object_is_on_stack(_cl)); \ + closure_set_ip(_cl); \ + set_closure_fn(_cl, _fn, _wq); \ + closure_sub(_cl, CLOSURE_RUNNING + 1); \ + return __VA_ARGS__; \ +} while (0) + +#define closure_return (_cl) continue_at((_cl), NULL, NULL) + +#endif /* _LINUX_CLOSURE_H */
lib/Kconfig +3 -0
@@ -316,4 +316,7 @@ config SIGNATURE Digital signature verification. Currently only RSA is supported. Implementation is done using GnuPG MPI library +config CLOSURES + bool + endmenu
lib/Kconfig.debug +9 -0
@@ -374,6 +374,15 @@ config DEBUG_OBJECTS_ENABLE_DEFAULT help Debug objects boot parameter default value +config DEBUG_CLOSURES + bool "Debug closures" + depends on CLOSURES + select DEBUG_FS + ---help--- + Keeps all active closures in a linked list and provides a debugfs + interface to list them, which makes it possible to see asynchronous + operations that get stuck. + config DEBUG_SLAB bool "Debug slab memory allocations" depends on DEBUG_KERNEL && SLAB && !KMEMCHECK
lib/Makefile +2 -0
@@ -123,6 +123,8 @@ obj-$(CONFIG_SIGNATURE) += digsig.o obj-$(CONFIG_CLZ_TAB) += clz_tab.o +obj-$(CONFIG_CLOSURES) += closure.o + hostprogs-y := gen_crc32table clean-files := crc32table.h
lib/closure.c new file +363 -0
363 lines not on this page · raw message
Signature · 1 line
1.7.9.rc2
Tejun Heo
maintainerRe: [Bcache v13 07/16] Closures
Hello, Kent.
Quoted 4 lines · Kent Overstreet
Closures are asynchronous refcounty things based on workqueues, used extensively in bcache.
Signed-off-by Kent Overstreet <[email protected]>
Overall, I can't find myself liking this closure thing much. It seems to be trying to roll too much into itself. To me, it almost feels like some combination of semaphore and async execution with hierarchy and some other stuff thrown in for good measure. Kernel moved away from semaphores for pretty good reasons.
I haven't seen the actual usages yet so my opinion might do a back-flip but for now I would much prefer something simpler - e.g. work item triggering mechanism which might include hierarchy support, some wrappers around wait_queue_head_t and so on.
Another concern that the terms and concepts introduced are foreign. When something gets executed asynchronously, we make that explicit. Such explicit bouncing tends to cause some tension especially in IO code paths which often end up doing stuff which might require sleeping without process context but it usually is manageable with sensible split between what gets done with and without process context. Maybe having a mechanism to blur the distinction helps such situations enough to justify introduction of new constructs but I feel somewhat skeptical at this point.
So, those are my high level concerns. I generally feel reserved about it. Let's see whether that changes with the actual review of the usages.
include/linux/closure.h line 178
Quoted 4 lines
include/linux/closure.h new file +4
-0+struct closure ; +typedef void (closure_fn ) (struct closure *); + +typedef struct llist_head closure_list_t ;
Please just use llist_head directly.
Quoted 29 lines
include/linux/closure.h new file +29
-0+#define CLOSURE_REMAINING_MASK (~(~0 << 20)) +#define CLOSURE_GUARD_MASK \ + ((1 << 20)|(1 << 22)|(1 << 24)|(1 << 26)|(1 << 28)|(1 << 30)) + +/* +* CLOSURE_RUNNING: Set when a closure is running (i.e. by +* closure_init() and when closure_put() runs then next function), and +* must be cleared before remaining hits 0. Primarily to help guard +* against incorrect usage and accidently transferring references. +* continue_at() and closure_return() clear it for you, if you're doing +* something unusual you can use closure_set_dead() which also helps +* annotate where references are being transferred. +* +* CLOSURE_BLOCKING: Causes closure_wait_event() to block, instead of +* waiting asynchronously +* +* CLOSURE_STACK: Sanity check - remaining should never hit 0 on a +* closure with this flag set +* +* CLOSURE_WAITING: Set iff the closure is on a waitlist. Must be set by +* the thread that owns the closure, and cleared by the thread that's +* waking up the closure. +* +* CLOSURE_SLEEPING: Must be set before a thread uses a closure to sleep +* - indicates that cl->task is valid and closure_put() may wake it up. +* Only set or cleared by the thread that owns the closure. +* +* CLOSURE_TIMER: Analagous to CLOSURE_WAITING, indicates that a closure +* has an outstanding timer. Must be set by the thread that owns the
include/linux/closure.h line 231
Quoted 10 lines
include/linux/closure.h new file +10
-0+* closure, and cleared by the timer function when the timer goes off. +*/ + +#define CLOSURE_RUNNING (1 << 21) +#define CLOSURE_BLOCKING (1 << 23) +#define CLOSURE_STACK (1 << 25) +#define CLOSURE_WAITING (1 << 27) +#define CLOSURE_SLEEPING (1 << 29) +#define CLOSURE_TIMER (1 << 31) +atomic_t remaining;
So, these bits are to be set alonside refcnt in lower 20 bits and there are guard bits between flag bits to detect cases where flag modification over/underflow caused by atomic_add/sub(), right? Hmmmm... I *hope* it were something dumber, especially given that the flags seem to be mostly for debugging and it would be nice which ones are for debugging and which ones are actually necessary for operation with better overall explanation.
Also, I personally don't like embedding constant definitions inside struct definition and wish these were defined outside as enums but that's just my preference.
include/linux/closure.h line 251
Quoted 10 lines
include/linux/closure.h new file +10
-0+#ifdef CONFIG_DEBUG_CLOSURES +#define CLOSURE_MAGIC_DEAD 0xc1054e0dead +#define CLOSURE_MAGIC_ALIVE 0xc1054e0a11e + +unsigned long magic; +struct list_head all; +unsigned long ip; +unsigned long waiting_on; +#endif +};
Maybe using debugobj is better for debugging object lifetime issues?
include/linux/closure.h line 282
Quoted 12 lines
include/linux/closure.h new file +12
-0+#define __CL_TYPE (cl, _t) \ + __builtin_types_compatible_p(typeof(cl), struct _t) \ + ? TYPE_ ## _t : \ + +#define __closure_type (cl) \ +( \ + __CL_TYPE(cl, closure) \ + __CL_TYPE(cl, closure_with_waitlist) \ + __CL_TYPE(cl, closure_with_timer) \ + __CL_TYPE(cl, closure_with_waitlist_and_timer) \ + invalid_closure_type() \ +)
Again, I with this were dumber and more conventional. Not everything has to be smart and it's not like this can cover all cases anyway. What about INITIALIZERs? I'd suggest just following what everyone else does.
include/linux/closure.h line 392
Quoted 7 lines
include/linux/closure.h new file +7
-0+#define closure_init_type (cl, parent, running, memset) \ +do { \ + struct closure *_cl = __to_internal_closure(cl); \ + _cl->type = __closure_type(*(cl)); \ + closure_set_ip(_cl); \ + do_closure_init(_cl, parent, running); \ +} while (0)
Why is @memset unused? And is it actually worth having a separate initializer to avoid memset?
Quoted 2 lines
+/**
+ *closure_sleep()- asynchronous sleep
Heh, I would much prefer delayed queue (or something along that line). Sleep has a lot of connotation attached to it and asynchronous sleep is way too confusing.
include/linux/closure.h line 459
Quoted 5 lines
include/linux/closure.h new file +5
-0+#define closure_flush (cl) \ + __closure_flush(__to_internal_closure(cl), &(cl)->timer) + +#define closure_flush_sync (cl) \ + __closure_flush_sync(__to_internal_closure(cl), &(cl)->timer)
Is this distinction really necessary?
include/linux/closure.h line 518
Quoted 6 lines
include/linux/closure.h new file +6
-0+/** +* closure_wake_up() - wake up all closures on a wait list. +*/ +static inline void closure_wake_up (closure_list_t *list) +{ +smp_mb ();
Why is this mb() instead of wmb()? Which barrier is it paired with? In general, please always annotate barrier pairs when using barriers. Nothing causes more head scratches than memory barriers without proper explanation.
Quoted 18 lines
include/linux/closure.h new file +18
-0+__closure_wake_up (list); +} + +/* +* Wait on an event, synchronously or asynchronously - analagous to wait_event() +* but for closures. +* +* The loop is oddly structured so as to avoid a race; we must check the +* condition again after we've added ourself to the waitlist. We know if we were +* already on the waitlist because closure_wait() returns false; thus, we only +* schedule or break if closure_wait() returns false. If it returns true, we +* just loop again - rechecking the condition. +* +* The __closure_wake_up() is necessary because we may race with the event +* becoming true; i.e. we see event false -> wait -> recheck condition, but the +* thread that made the event true may have called closure_wake_up() before we +* added ourself to the wait list. +*
include/linux/closure.h line 546
Quoted 10 lines
include/linux/closure.h new file +10
-0+* We have to call closure_sync() at the end instead of just +* __closure_end_sleep() because a different thread might've called +* closure_wake_up() before us and gotten preempted before they dropped the +* refcount on our closure. If this was a stack allocated closure, that would be +* bad. +*/ +#define __closure_wait_event (list, cl, condition, _block) \ +({ \ + __label__ out; \ + bool block = _block; \
What if @condition contains @block? Local vars in macros better have long and ugly prefixes.
Quoted 5 lines
include/linux/closure.h new file +5
-0+typeof (condition) ret; \ + \ +while (!(ret = (condition))) { \ +if (block) \ +__closure_start_sleep (cl); \
include/linux/closure.h line 561
Quoted 10 lines
include/linux/closure.h new file +10
-0+if (!closure_wait (list, cl)) { \ +if (!block) \ + goto out; \ +schedule (); \ + } \ + } \ +__closure_wake_up (list); \ +if (block) \ +closure_sync (cl); \ +out: \
I suppose __label__ out decl makes this local somehow. I'd *much* prefer if something this unusual wasn't used tho.
include/linux/closure.h line 600
Quoted 7 lines
include/linux/closure.h new file +7
-0+static inline void set_closure_fn (struct closure *cl,closure_fn *fn, +struct workqueue_struct *wq) +{ + cl->fn = fn; + cl->wq = wq; +/* between atomic_dec() in closure_put() */ +smp_mb__before_atomic_dec ();
Please be more explicit. I can't follow.
include/linux/closure.h line 612
Quoted 10 lines
include/linux/closure.h new file +10
-0+#define continue_at (_cl, _fn, _wq, ...) \ +do { \ + BUG_ON(!(_cl) || object_is_on_stack(_cl)); \ + closure_set_ip(_cl); \ + set_closure_fn(_cl, _fn, _wq); \ + closure_sub(_cl, CLOSURE_RUNNING + 1); \ + return __VA_ARGS__; \ +} while (0) + +#define closure_return (_cl) continue_at((_cl), NULL, NULL)
Umm... so, anything which wraps something which are baked into people's reflexes is a bad idea. It might seem to increase writability or whatnot somewhat and could feel fun but the accumulated overhead of all other people going WTF when trying to read the code far outweights any benefit which can be gained that way. So, please don't bury return in a macro. It's just a bad idea.
lib/closure.c line 20
Quoted 9 lines
lib/closure.c new file +9
-0+void closure_queue (struct closure *cl) +{ +struct workqueue_struct *wq = cl->wq; +if (wq) { + cl->work.data = (atomic_long_t )WORK_DATA_INIT (); +INIT_LIST_HEAD (&cl->work.entry); +BUG_ON (!queue_work (wq, &cl->work)); + }else + cl->fn (cl);
Umm... so the code is taking advantage of how fields of work_struct is laid and using internal initializer to partially initialize work item? Am I reading it correctly? If so, please don't do this. It's way too fragile.
Quoted 11 lines
lib/closure.c new file +11
-0+#define CL_FIELD (type, field) \ + case TYPE_ ## type: \ + return &container_of(cl, struct type, cl)->field + +static closure_list_t *closure_waitlist (struct closure *cl) +{ +switch (cl->type) { +CL_FIELD (closure_with_waitlist, wait); +CL_FIELD (closure_with_waitlist_and_timer, wait); + } +return NULL ;
lib/closure.c line 46
Quoted 10 lines
lib/closure.c new file +10
-0+} + +static struct timer_list *closure_timer (struct closure *cl) +{ +switch (cl->type) { +CL_FIELD (closure_with_timer, timer); +CL_FIELD (closure_with_waitlist_and_timer, timer); + } +return NULL ; +}
C code trumps macros. There are only four of them. Let's just open code.
lib/closure.c line 58
Quoted 11 lines
lib/closure.c new file +11
-0+static void closure_put_after_sub (struct closure *cl,int r) +{ +BUG_ON (r &CLOSURE_GUARD_MASK ); +/* CLOSURE_BLOCK is the only flag that's allowed when r hits 0 */ +BUG_ON ((r &CLOSURE_REMAINING_MASK ) ==0 && + (r & ~CLOSURE_BLOCKING )); + +/* Must deliver precisely one wakeup */ +if ((r &CLOSURE_REMAINING_MASK ) ==1 && + (r &CLOSURE_SLEEPING )) { +smp_mb__after_atomic_dec ();
Why? wake_up_process() includes smp_wmb() and atomic_sub_return() has full smp_mb().
lib/closure.c line 63
Quoted 5 lines
lib/closure.c new file +5
-0+wake_up_process (cl->task); + } + +if ((r &CLOSURE_REMAINING_MASK ) ==0 ) { +smp_mb__after_atomic_dec ();
Ditto.
lib/closure.c line 76
Quoted 12 lines
lib/closure.c new file +12
-0+if (cl->fn) { +/* CLOSURE_BLOCKING might be set - clear it */ +atomic_set (&cl->remaining, +CLOSURE_REMAINING_INITIALIZER ); +closure_queue (cl); + }else { +struct closure *parent = cl->parent; +closure_list_t *wait =closure_waitlist (cl); + +closure_debug_destroy (cl); + +smp_wmb ();
Why? What is it paired with? The only thing between the previous smp_mb() and here is closure_debug_destroy().
Quoted 11 lines
lib/closure.c new file +11
-0+/* mb between last use of closure and unlocking it */ +atomic_set (&cl->remaining,-1 ); + +if (wait) +closure_wake_up (wait); + +if (parent) +closure_put (parent); + } + } +}
...
Quoted 5 lines
+/*
+ * Broken up becauseclosure_put()has to do thexchg()and grab the wait list
+ * before unlocking the closure, but the wakeup has to come after unlocking the
+ * closure.
+ */
Doesn't seem to be used by closure_put(). Stale comment?
Quoted 9 lines
lib/closure.c new file +9
-0+static void closure_wake_up_after_xchg (struct llist_node *list) +{ +struct closure *cl; +struct llist_node *reverse =NULL ; + +while (list) { +struct llist_node *t = list; + list =llist_next (list); +
lib/closure.c line 132
Quoted 10 lines
lib/closure.c new file +10
-0+ t->next = reverse; + reverse = t; + } + +while (reverse) { + cl = container_of(reverse, struct closure, list); + reverse =llist_next (reverse); + +set_waiting (cl,0 ); +closure_sub (cl,CLOSURE_WAITING +1 );
Why is it done in reverse? To keep FIFO order? If so, what difference does that make and why no explanation?
Quoted 2 lines
+/**
+ *closure_sync()- sleep until a closure a closure has nothing left to wait on
^^^^^^^^^^^^^^^^^^^
Quoted 20 lines
+ *
+ * Sleeps until the refcount hits 1 - the thread that's running the closure owns
+ * the last refcount.
+ */lib/closure.c new file +16
-0+void closure_sync (struct closure *cl) +{ +while (1 ) { +__closure_start_sleep (cl); +closure_set_ret_ip (cl); + +if ((atomic_read (&cl->remaining) & +CLOSURE_REMAINING_MASK ) ==1 ) +break ; + +schedule (); + } + +__closure_end_sleep (cl); +} +EXPORT_SYMBOL_GPL (closure_sync);
...
lib/closure.c line 262
Quoted 13 lines
lib/closure.c new file +13
-0+void __closure_flush (struct closure *cl,struct timer_list *timer) +{ +if (del_timer (timer)) +closure_sub (cl,CLOSURE_TIMER +1 ); +} +EXPORT_SYMBOL_GPL (__closure_flush); + +void __closure_flush_sync (struct closure *cl,struct timer_list *timer) +{ +if (del_timer_sync (timer)) +closure_sub (cl,CLOSURE_TIMER +1 ); +} +EXPORT_SYMBOL_GPL (__closure_flush_sync);
I'm kinda lost why this distinction is necessary. Can you please explain a bit?
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 07/16] Closures
Quoted 29 lines · Tejun Heo
Hello, Kent.
Quoted 4 lines · Kent Overstreet
Closures are asynchronous refcounty things based on workqueues, used extensively in bcache.
Signed-off-by Kent Overstreet <[email protected]>Overall, I can't find myself liking this closure thing much. It seems to be trying to roll too much into itself. To me, it almost feels like some combination of semaphore and async execution with hierarchy and some other stuff thrown in for good measure. Kernel moved away from semaphores for pretty good reasons.
I haven't seen the actual usages yet so my opinion might do a back-flip but for now I would much prefer something simpler - e.g. work item triggering mechanism which might include hierarchy support, some wrappers around
wait_queue_head_tand so on.Another concern that the terms and concepts introduced are foreign. When something gets executed asynchronously, we make that explicit. Such explicit bouncing tends to cause some tension especially in IO code paths which often end up doing stuff which might require sleeping without process context but it usually is manageable with sensible split between what gets done with and without process context. Maybe having a mechanism to blur the distinction helps such situations enough to justify introduction of new constructs but I feel somewhat skeptical at this point.
Quoted 3 lines
So, those are my high level concerns. I generally feel reserved about it. Let's see whether that changes with the actual review of the usages.
Well, I can definitely understand your reservations about the code; it's strange and foreign and complicated. But having beaten my head against the various problems and difficulties inherent in asynchronous programming for years - well, having found a way to make it sane to me it'd be madness to go back.
And to me it's a small and elegant solution to a lot of nasty problems, but I just don't know how to distill all that down and make it easy to understand. Which is unfortunate.
That said, while IMO the basic concepts and the way it all fits together is _elegant_ (seriously, watching code simplify as I've converted things in bcache I hadn't considered originally to use bcache - it's been a thing of beauty) - the whole thing is still evolving and rough in areas, and I'm still looking for ways to simplify it.
The locking stuff still feels tacked on to me and somewhat inelegant - insufficiently general, perhaps - but there's very strong motivations for it. Until I or someone else comes up with something better, I'm stuck with it but you can ignore it for the most part.
The closure_with_waitlist and closure_with_timer stuff I'm much happier with, as they don't affect the base concepts and they just build off of it relatively cleanly (at least conceptually), much like delayed work items.
Quoted 6 lines
include/linux/closure.h line 178
Quoted 4 lines
include/linux/closure.h new file +4
-0+struct closure ; +typedef void (closure_fn ) (struct closure *); + +typedef struct llist_head closure_list_t ;Please just use
llist_headdirectly.
Don't want to do that because it really is a distinct type; however, it shouldn't be a typedef since that doesn't actually get us typechecking. I'll just make a struct closure_waitlist.
Quoted 40 lines
Quoted 29 lines
include/linux/closure.h new file +29
-0+#define CLOSURE_REMAINING_MASK (~(~0 << 20)) +#define CLOSURE_GUARD_MASK \ + ((1 << 20)|(1 << 22)|(1 << 24)|(1 << 26)|(1 << 28)|(1 << 30)) + +/* +* CLOSURE_RUNNING: Set when a closure is running (i.e. by +* closure_init() and when closure_put() runs then next function), and +* must be cleared before remaining hits 0. Primarily to help guard +* against incorrect usage and accidently transferring references. +* continue_at() and closure_return() clear it for you, if you're doing +* something unusual you can use closure_set_dead() which also helps +* annotate where references are being transferred. +* +* CLOSURE_BLOCKING: Causes closure_wait_event() to block, instead of +* waiting asynchronously +* +* CLOSURE_STACK: Sanity check - remaining should never hit 0 on a +* closure with this flag set +* +* CLOSURE_WAITING: Set iff the closure is on a waitlist. Must be set by +* the thread that owns the closure, and cleared by the thread that's +* waking up the closure. +* +* CLOSURE_SLEEPING: Must be set before a thread uses a closure to sleep +* - indicates that cl->task is valid and closure_put() may wake it up. +* Only set or cleared by the thread that owns the closure. +* +* CLOSURE_TIMER: Analagous to CLOSURE_WAITING, indicates that a closure +* has an outstanding timer. Must be set by the thread that owns the include/linux/closure.h line 231
Quoted 10 lines
include/linux/closure.h new file +10
-0+* closure, and cleared by the timer function when the timer goes off. +*/ + +#define CLOSURE_RUNNING (1 << 21) +#define CLOSURE_BLOCKING (1 << 23) +#define CLOSURE_STACK (1 << 25) +#define CLOSURE_WAITING (1 << 27) +#define CLOSURE_SLEEPING (1 << 29) +#define CLOSURE_TIMER (1 << 31) +atomic_t remaining;
Quoted 7 lines
So, these bits are to be set alonside refcnt in lower 20 bits and there are guard bits between flag bits to detect cases where flag modification over/underflow caused by atomic_add/sub(), right? Hmmmm... I *hope* it were something dumber, especially given that the flags seem to be mostly for debugging and it would be nice which ones are for debugging and which ones are actually necessary for operation with better overall explanation.
So, CLOSURE_RUNNING and CLOSURE_STACK are debugging aids - the rest are not. I'll rearrange them to better distinguish that.
Quoted 3 lines
Also, I personally don't like embedding constant definitions inside struct definition and wish these were defined outside as enums but that's just my preference.
I do tend to prefer defining constants next to the member they're for when they're for only one thing, but it's not something I feel that strongly about. Suppose it's big enough with the comments to justify pulling out of struct closure into an enum.
Quoted 4 lines
Quoted 4 lines
include/linux/closure.h new file +4
-0+#ifdef CONFIG_DEBUG_CLOSURES +#define CLOSURE_MAGIC_DEAD 0xc1054e0dead +#define CLOSURE_MAGIC_ALIVE 0xc1054e0a11e +
Quoted 8 lines
include/linux/closure.h line 251
Quoted 6 lines
include/linux/closure.h new file +6
-0+unsigned long magic; +struct list_head all; +unsigned long ip; +unsigned long waiting_on; +#endif +};Maybe using debugobj is better for debugging object lifetime issues?
Quite possibly. I looked at it briefly and I was having trouble figuring out how to use it, but I can take another look.
Quoted 9 lines
Quoted 9 lines
include/linux/closure.h new file +9
-0+#define __CL_TYPE (cl, _t) \ + __builtin_types_compatible_p(typeof(cl), struct _t) \ + ? TYPE_ ## _t : \ + +#define __closure_type (cl) \ +( \ + __CL_TYPE(cl, closure) \ + __CL_TYPE(cl, closure_with_waitlist) \ + __CL_TYPE(cl, closure_with_timer) \
Quoted 8 lines
include/linux/closure.h line 282
Quoted 3 lines
include/linux/closure.h new file +3
-0+ __CL_TYPE(cl, closure_with_waitlist_and_timer) \ +invalid_closure_type () \ +)Again, I with this were dumber and more conventional. Not everything has to be smart and it's not like this can cover all cases anyway. What about INITIALIZERs? I'd suggest just following what everyone else does.
Heh, that thing was kind of perverse. But we've got this 1:1 mapping from types to enums, so it makes sense to consolidate that and not open code it for each every type, IMO. I felt dirty after I wrote that, though.
Quoted 10 lines
include/linux/closure.h line 392
Quoted 7 lines
include/linux/closure.h new file +7
-0+#define closure_init_type (cl, parent, running, memset) \ +do { \ + struct closure *_cl = __to_internal_closure(cl); \ + _cl->type = __closure_type(*(cl)); \ + closure_set_ip(_cl); \ + do_closure_init(_cl, parent, running); \ +} while (0)Why is @memset unused? And is it actually worth having a separate initializer to avoid memset?
I can't for the life of me remember what the memset parameter is doing there. But at one point when I was working on bcache performance the memset showed up enough in the profile to be worth it, yeah.
Quoted 6 lines
Quoted 2 lines
+/**
+ *closure_sleep()- asynchronous sleepHeh, I would much prefer delayed queue (or something along that line). Sleep has a lot of connotation attached to it and asynchronous sleep is way too confusing.
closure_delay(), maybe? Agreed about sleep.
Quoted 7 lines
include/linux/closure.h line 459
Quoted 5 lines
include/linux/closure.h new file +5
-0+#define closure_flush (cl) \ + __closure_flush(__to_internal_closure(cl), &(cl)->timer) + +#define closure_flush_sync (cl) \ + __closure_flush_sync(__to_internal_closure(cl), &(cl)->timer)Is this distinction really necessary?
Not sure. Haven't used that code much yet, it's definitely more experimental than the rest.
Quoted 12 lines
include/linux/closure.h line 518
Quoted 6 lines
include/linux/closure.h new file +6
-0+/** +* closure_wake_up() - wake up all closures on a wait list. +*/ +static inline void closure_wake_up (closure_list_t *list) +{ +smp_mb ();Why is this
mb()instead ofwmb()? Which barrier is it paired with? In general, please always annotate barrier pairs when using barriers. Nothing causes more head scratches than memory barriers without proper explanation.
Quoted 1 line
include/linux/closure.h line 519
Quoted 1 line
include/linux/closure.h new file +1
-0+__closure_wake_up (list);
That one is for when you just made a condition true that another thread is checking - the barrier is between whatever made the condition true and the wakeup.
Uh, there should be a corresponding barrier in __closure_wait_event(). Now that I think about it though, probably neither are needed; closure_wait() and __closure_wake_up() are lockless, but if memory serves cmpxchg()/xchg() imply full barriers, and it's just atomic_xchg()/atomic_cmxchg() that don't - does that sound right to you?
Quoted 22 lines
Quoted 22 lines
include/linux/closure.h new file +22
-0+} + +/* +* Wait on an event, synchronously or asynchronously - analagous to wait_event() +* but for closures. +* +* The loop is oddly structured so as to avoid a race; we must check the +* condition again after we've added ourself to the waitlist. We know if we were +* already on the waitlist because closure_wait() returns false; thus, we only +* schedule or break if closure_wait() returns false. If it returns true, we +* just loop again - rechecking the condition. +* +* The __closure_wake_up() is necessary because we may race with the event +* becoming true; i.e. we see event false -> wait -> recheck condition, but the +* thread that made the event true may have called closure_wake_up() before we +* added ourself to the wait list. +* +* We have to call closure_sync() at the end instead of just +* __closure_end_sleep() because a different thread might've called +* closure_wake_up() before us and gotten preempted before they dropped the +* refcount on our closure. If this was a stack allocated closure, that would be +* bad.
Quoted 8 lines
include/linux/closure.h line 546
Quoted 5 lines
+ */
include/linux/closure.h new file +4
-0+#define __closure_wait_event (list, cl, condition, _block) \ +({ \ + __label__ out; \ + bool block = _block; \What if @condition contains @block? Local vars in macros better have long and ugly prefixes.
Yeah, good point.
Quoted 11 lines
Quoted 11 lines
include/linux/closure.h new file +10
-0+typeof (condition) ret; \ + \ +while (!(ret = (condition))) { \ +if (block) \ +__closure_start_sleep (cl); \ +if (!closure_wait (list, cl)) { \ +if (!block) \ + goto out; \ +schedule (); \ + } \
Quoted 8 lines
include/linux/closure.h line 561
Quoted 5 lines
include/linux/closure.h new file +5
-0+ } \ +__closure_wake_up (list); \ +if (block) \ +closure_sync (cl); \ +out: \I suppose
__label__out decl makes this local somehow. I'd *much* prefer if something this unusual wasn't used tho.
It does. I just rewrote it to not use the goto, but... eh, I think I like the old version better.
Quoted 9 lines
include/linux/closure.h line 600
Quoted 7 lines
include/linux/closure.h new file +7
-0+static inline void set_closure_fn (struct closure *cl,closure_fn *fn, +struct workqueue_struct *wq) +{ + cl->fn = fn; + cl->wq = wq; +/* between atomic_dec() in closure_put() */ +smp_mb__before_atomic_dec ();Please be more explicit. I can't follow.
Thread 1 does set_closure_fn() then closure_put(), but remaining doesn't go to 0 - thread 2 does closure_put() and remaining is now 0. Don't want thread 2 to see the old fn/wq.
Quoted 9 lines
Quoted 9 lines
include/linux/closure.h new file +9
-0+#define continue_at (_cl, _fn, _wq, ...) \ +do { \ + BUG_ON(!(_cl) || object_is_on_stack(_cl)); \ + closure_set_ip(_cl); \ + set_closure_fn(_cl, _fn, _wq); \ + closure_sub(_cl, CLOSURE_RUNNING + 1); \ + return __VA_ARGS__; \ +} while (0) +
Quoted 8 lines
Quoted 1 line
+#define
closure_return(_cl)continue_at((_cl), NULL, NULL)Umm... so, anything which wraps something which are baked into people's reflexes is a bad idea. It might seem to increase writability or whatnot somewhat and could feel fun but the accumulated overhead of all other people going WTF when trying to read the code far outweights any benefit which can be gained that way. So, please don't bury return in a macro. It's just a bad idea.
It's not about writeability or terseness, it's there so you really have to go out of your way to use your closure after you've dropped your refcount on it, and making sure you think and are conscious of it if you're doing anything unusual.
Having been bit painfully by bugs like that - and honestly I of all people should know better - there's just no way I'm just taking it out. Yes, confusing the reader is bad, but - it's really the lesser evil here.
That said, your idea about having it return an opaque struct that the caller then has to return - I liked that idea. I don't know what to do about the first user of a closure (i.e. the function that allocated it), but it can be made to work cleanly I'd be fine with that.
Quoted 6 lines
Quoted 6 lines
lib/closure.c new file +6
-0+void closure_queue (struct closure *cl) +{ +struct workqueue_struct *wq = cl->wq; +if (wq) { + cl->work.data = (atomic_long_t )WORK_DATA_INIT (); +INIT_LIST_HEAD (&cl->work.entry);
Quoted 8 lines
lib/closure.c line 20
Quoted 3 lines
lib/closure.c new file +3
-0+BUG_ON (!queue_work (wq, &cl->work)); + }else + cl->fn (cl);Umm... so the code is taking advantage of how fields of
work_structis laid and using internal initializer to partially initialize work item? Am I reading it correctly? If so, please don't do this. It's way too fragile.
It's not really partially initializing it, it is fully initializing it (except for work->func which is the same as cl->fn). I used to have INIT_WORK() there, don't remember why I switched (debug code?). Can probably switch back.
The only part of work_struct's layout that I'm really depending on is func. It is kind of a terrible hack but it makes struct closure 8 bytes smaller (48 instead of 56), and for generic infrastructure it is not IMO completely crazy.
Should at least have a BUILD_BUG_ON() somewhere, though...
Quoted 16 lines
Quoted 16 lines
lib/closure.c new file +16
-0+#define CL_FIELD (type, field) \ + case TYPE_ ## type: \ + return &container_of(cl, struct type, cl)->field + +static closure_list_t *closure_waitlist (struct closure *cl) +{ +switch (cl->type) { +CL_FIELD (closure_with_waitlist, wait); +CL_FIELD (closure_with_waitlist_and_timer, wait); + } +return NULL ; +} + +static struct timer_list *closure_timer (struct closure *cl) +{ +switch (cl->type) {
Quoted 8 lines
lib/closure.c line 46
Quoted 5 lines
lib/closure.c new file +5
-0+CL_FIELD (closure_with_timer, timer); +CL_FIELD (closure_with_waitlist_and_timer, timer); + } +return NULL ; +}C code trumps macros. There are only four of them. Let's just open code.
Eh, this one I don't feel that strongly about.
Quoted 6 lines
Quoted 6 lines
lib/closure.c new file +6
-0+static void closure_put_after_sub (struct closure *cl,int r) +{ +BUG_ON (r &CLOSURE_GUARD_MASK ); +/* CLOSURE_BLOCK is the only flag that's allowed when r hits 0 */ +BUG_ON ((r &CLOSURE_REMAINING_MASK ) ==0 && + (r & ~CLOSURE_BLOCKING ));
Quoted 8 lines
lib/closure.c line 58
Quoted 5 lines
lib/closure.c new file +5
-0+ +/* Must deliver precisely one wakeup */ +if ((r &CLOSURE_REMAINING_MASK ) ==1 && + (r &CLOSURE_SLEEPING )) { +smp_mb__after_atomic_dec ();Why?
wake_up_process()includessmp_wmb()andatomic_sub_return()has fullsmp_mb().
Missed that about atomic_sub_return().
Quoted 8 lines
lib/closure.c line 63
Quoted 5 lines
lib/closure.c new file +5
-0+wake_up_process (cl->task); + } + +if ((r &CLOSURE_REMAINING_MASK ) ==0 ) { +smp_mb__after_atomic_dec ();Ditto.
Quoted 15 lines
lib/closure.c line 76
Quoted 12 lines
lib/closure.c new file +12
-0+if (cl->fn) { +/* CLOSURE_BLOCKING might be set - clear it */ +atomic_set (&cl->remaining, +CLOSURE_REMAINING_INITIALIZER ); +closure_queue (cl); + }else { +struct closure *parent = cl->parent; +closure_list_t *wait =closure_waitlist (cl); + +closure_debug_destroy (cl); + +smp_wmb ();Why? What is it paired with? The only thing between the previous
smp_mb()and here isclosure_debug_destroy().
It used to be that it grabbed the waitlist before unlocking, but that was incorrect and buggy. I can't think of any reason for it now.
Quoted 13 lines
Quoted 12 lines
lib/closure.c new file +11
-0+/* mb between last use of closure and unlocking it */ +atomic_set (&cl->remaining,-1 ); + +if (wait) +closure_wake_up (wait); + +if (parent) +closure_put (parent); + } + } +}...
Quoted 7 lines
Quoted 5 lines
+/*
+ * Broken up becauseclosure_put()has to do thexchg()and grab the wait list
+ * before unlocking the closure, but the wakeup has to come after unlocking the
+ * closure.
+ */Doesn't seem to be used by
closure_put(). Stale comment?
Yes.
Quoted 14 lines
Quoted 14 lines
lib/closure.c new file +14
-0+static void closure_wake_up_after_xchg (struct llist_node *list) +{ +struct closure *cl; +struct llist_node *reverse =NULL ; + +while (list) { +struct llist_node *t = list; + list =llist_next (list); + + t->next = reverse; + reverse = t; + } + +while (reverse) {
Quoted 8 lines
lib/closure.c line 132
Quoted 5 lines
lib/closure.c new file +5
-0+ cl = container_of(reverse, struct closure, list); + reverse =llist_next (reverse); + +set_waiting (cl,0 ); +closure_sub (cl,CLOSURE_WAITING +1 );Why is it done in reverse? To keep FIFO order? If so, what difference does that make and why no explanation?
Exactly. I'm sure it's the kind of thing that'd make a difference only under extremely pathalogical conditions, but... it's easy to do it right. Added a comment, though.
Quoted 25 lines
Quoted 2 lines
+/**
+ *closure_sync()- sleep until a closure a closure has nothing left to wait on^^^^^^^^^^^^^^^^^^^Quoted 20 lines
+ *
+ * Sleeps until the refcount hits 1 - the thread that's running the closure owns
+ * the last refcount.
+ */lib/closure.c new file +16
-0+void closure_sync (struct closure *cl) +{ +while (1 ) { +__closure_start_sleep (cl); +closure_set_ret_ip (cl); + +if ((atomic_read (&cl->remaining) & +CLOSURE_REMAINING_MASK ) ==1 ) +break ; + +schedule (); + } + +__closure_end_sleep (cl); +} +EXPORT_SYMBOL_GPL (closure_sync);...
Quoted 16 lines
lib/closure.c line 262
Quoted 13 lines
lib/closure.c new file +13
-0+void __closure_flush (struct closure *cl,struct timer_list *timer) +{ +if (del_timer (timer)) +closure_sub (cl,CLOSURE_TIMER +1 ); +} +EXPORT_SYMBOL_GPL (__closure_flush); + +void __closure_flush_sync (struct closure *cl,struct timer_list *timer) +{ +if (del_timer_sync (timer)) +closure_sub (cl,CLOSURE_TIMER +1 ); +} +EXPORT_SYMBOL_GPL (__closure_flush_sync);I'm kinda lost why this distinction is necessary. Can you please explain a bit?
I had a reason for it, but I can't remember what it was. I'll have to look at the code that uses it - quite possibly it's not needed at all. Like I said above, this is newer/more experimental.
Re: [Bcache v13 07/16] Closures
Quoted 5 lines
Well, I can definitely understand your reservations about the code; it's strange and foreign and complicated. But having beaten my head against the various problems and difficulties inherent in asynchronous programming for years - well, having found a way to make it sane to me it'd be madness to go back.
I think its more of a different paradigm than complicated. The big question I'd have to ask is how does it fit with hard real time. That is a big issue given the gradual merging and getting of "hard" real time into the kernel proper.
In some ways it's not that strange either. A look at what happens on some of our kref handling in drivers is very similar but hardcoded.
Is the handling of priority inversion within closures a well understood and solved problem ?
Alan
Re: [Bcache v13 07/16] Closures
Quoted 7 lines · Alan Cox
Quoted 5 lines
Well, I can definitely understand your reservations about the code; it's strange and foreign and complicated. But having beaten my head against the various problems and difficulties inherent in asynchronous programming for years - well, having found a way to make it sane to me it'd be madness to go back.
I think its more of a different paradigm than complicated.
Yeah, that's probably apt :)
Quoted 10 lines
The big
question I'd have to ask is how does it fit with hard real time. That is a big issue given the gradual merging and getting of "hard" real time into the kernel proper.In some ways it's not that strange either. A look at what happens on some of our kref handling in drivers is very similar but hardcoded.
Is the handling of priority inversion within closures a well understood and solved problem ?
Haven't really thought about it (aside from deadlocks which are a slightly different issue). AFAICT there shouldn't be much to worry about, at least with the way I'm using closures today; scheduling is all handled by the existing workqueue code and you have to explicitly say which workqueue a function is going to run out of.
There are the wait lists I've implemented but that's mostly just for when some thread/closure is waiting on an arbitrary event to become true
- analagous to wait_event(), in which case you want everything on the wait list to to run at once which is all it implements.
If the closure wait lists ever did end up being used in a situation where priorities or priority inversion was more of a concern it wouldn't be a huge deal to swap out the implementation for something more sophisticated, or just use the existing wait_queue_t.
PATCH v13 8/16 bcache: Documentation, and changes to generic code
In reply to Kent Overstreet--- Documentation/ABI/testing/sysfs-block-bcache | 156 ++++++++++++++++ Documentation/bcache.txt | 255 +++++++++++++++++++++++++ drivers/block/Kconfig | 2 + drivers/block/Makefile | 1 + drivers/block/bcache/Kconfig | 42 +++++ drivers/block/bcache/Makefile | 8 + include/linux/cgroup_subsys.h | 6 + include/linux/sched.h | 4 + include/trace/events/bcache.h | 257 ++++++++++++++++++++++++++ kernel/fork.c | 4 + 10 files changed, 735 insertions(+), 0 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-block-bcache create mode 100644 Documentation/bcache.txt create mode 100644 drivers/block/bcache/Kconfig create mode 100644 drivers/block/bcache/Makefile create mode 100644 include/trace/events/bcache.h
Documentation/ABI/testing/sysfs-block-bcache new file +156 -0
@@ -0,0 +1,156 @@ +What: /sys/block/<disk>/bcache/unregister +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + A write to this file causes the backing device or cache to be + unregistered. If a backing device had dirty data in the cache, + writeback mode is automatically disabled and all dirty data is + flushed before the device is unregistered. Caches unregister + all associated backing devices before unregistering themselves. + +What: /sys/block/<disk>/bcache/clear_stats +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + Writing to this file resets all the statistics for the device. + +What: /sys/block/<disk>/bcache/cache +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a backing device that has cache, a symlink to + the bcache/ dir of that cache. + +What: /sys/block/<disk>/bcache/cache_hits +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: integer number of full cache hits, + counted per bio. A partial cache hit counts as a miss. + +What: /sys/block/<disk>/bcache/cache_misses +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: integer number of cache misses. + +What: /sys/block/<disk>/bcache/cache_hit_ratio +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: cache hits as a percentage. + +What: /sys/block/<disk>/bcache/sequential_cutoff +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: Threshold past which sequential IO will + skip the cache. Read and written as bytes in human readable + units (i.e. echo 10M > sequntial_cutoff). + +What: /sys/block/<disk>/bcache/bypassed +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + Sum of all reads and writes that have bypassed the cache (due + to the sequential cutoff). Expressed as bytes in human + readable units. + +What: /sys/block/<disk>/bcache/writeback +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: When on, writeback caching is enabled and + writes will be buffered in the cache. When off, caching is in + writethrough mode; reads and writes will be added to the + cache but no write buffering will take place. + +What: /sys/block/<disk>/bcache/writeback_running +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: when off, dirty data will not be written + from the cache to the backing device. The cache will still be + used to buffer writes until it is mostly full, at which point + writes transparently revert to writethrough mode. Intended only + for benchmarking/testing. + +What: /sys/block/<disk>/bcache/writeback_delay +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: In writeback mode, when dirty data is + written to the cache and the cache held no dirty data for that + backing device, writeback from cache to backing device starts + after this delay, expressed as an integer number of seconds. + +What: /sys/block/<disk>/bcache/writeback_percent +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For backing devices: If nonzero, writeback from cache to + backing device only takes place when more than this percentage + of the cache is used, allowing more write coalescing to take + place and reducing total number of writes sent to the backing + device. Integer between 0 and 40. + +What: /sys/block/<disk>/bcache/synchronous +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, a boolean that allows synchronous mode to be + switched on and off. In synchronous mode all writes are ordered + such that the cache can reliably recover from unclean shutdown; + if disabled bcache will not generally wait for writes to + complete but if the cache is not shut down cleanly all data + will be discarded from the cache. Should not be turned off with + writeback caching enabled. + +What: /sys/block/<disk>/bcache/discard +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, a boolean allowing discard/TRIM to be turned off + or back on if the device supports it. + +What: /sys/block/<disk>/bcache/bucket_size +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, bucket size in human readable units, as set at + cache creation time; should match the erase block size of the + SSD for optimal performance. + +What: /sys/block/<disk>/bcache/nbuckets +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, the number of usable buckets. + +What: /sys/block/<disk>/bcache/tree_depth +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, height of the btree excluding leaf nodes (i.e. a + one node tree will have a depth of 0). + +What: /sys/block/<disk>/bcache/btree_cache_size +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + Number of btree buckets/nodes that are currently cached in + memory; cache dynamically grows and shrinks in response to + memory pressure from the rest of the system. + +What: /sys/block/<disk>/bcache/written +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, total amount of data in human readable units + written to the cache, excluding all metadata. + +What: /sys/block/<disk>/bcache/btree_written +Date: November 2010 +Contact: Kent Overstreet <[email protected]> +Description: + For a cache, sum of all btree writes in human readable units.
Documentation/bcache.txt new file +255 -0
255 lines not on this page · raw message
drivers/block/Kconfig +2 -0
@@ -526,6 +526,8 @@ config VIRTIO_BLK This is the virtual block driver for virtio. It can be used with lguest or QEMU based VMMs (like KVM or Xen). Say Y or M. +source "drivers/block/bcache/Kconfig" + config BLK_DEV_HD bool "Very old hard disk (MFM/RLL/IDE) driver" depends on HAVE_IDE
drivers/block/Makefile +1 -0
@@ -41,5 +41,6 @@ obj-$(CONFIG_XEN_BLKDEV_BACKEND) += xen-blkback/ obj-$(CONFIG_BLK_DEV_DRBD) += drbd/ obj-$(CONFIG_BLK_DEV_RBD) += rbd.o obj-$(CONFIG_BLK_DEV_PCIESSD_MTIP32XX) += mtip32xx/ +obj-$(CONFIG_BCACHE) += bcache/ swim_mod-y := swim.o swim_asm.o
drivers/block/bcache/Kconfig new file +42 -0
42 lines not on this page · raw message
drivers/block/bcache/Makefile new file +8 -0
@@ -0,0 +1,8 @@ + +ccflags-y += -std=gnu99 +obj-$(CONFIG_BCACHE) += bcache.o + +bcache-y := alloc.o btree.o bset.o io.o journal.o\ + writeback.o request.o super.o debug.o util.o trace.o stats.o + +CFLAGS_request.o += -Iblock
include/linux/cgroup_subsys.h +6 -0
@@ -72,3 +72,9 @@ SUBSYS(net_prio)#endif /* */ + +#ifdef CONFIG_CGROUP_BCACHE +SUBSYS(bcache) +#endif + +/* */
include/linux/sched.h +4 -0
@@ -1590,6 +1590,10 @@ struct task_struct {#ifdef CONFIG_HAVE_HW_BREAKPOINT atomic_t ptrace_bp_refcnt;#endif +#if defined(CONFIG_BCACHE ) || defined(CONFIG_BCACHE_MODULE ) +unsigned int sequential_io; +unsigned int sequential_io_avg; +#endif };/* Future-safe accessor for struct task_struct's cpus_allowed. */
include/trace/events/bcache.h new file +257 -0
257 lines not on this page · raw message
kernel/fork.c +4 -0
@@ -1255,6 +1255,10 @@ static struct task_struct *copy_process(unsigned long clone_flags, p->memcg_batch.do_batch =0 ; p->memcg_batch.memcg =NULL ;#endif +#ifdef CONFIG_BCACHE + p->sequential_io =0 ; + p->sequential_io_avg =0 ; +#endif /* Perform scheduler related setup. Assign this task to a CPU. */ sched_fork (p);
Signature · 1 line
1.7.9.rc2
PATCH v13 9/16 Bcache: generic utility code
In reply to Kent OverstreetMuch of this code should be moved out of drivers/block/bcache, but it was originally written for bcache.
--- drivers/block/bcache/util.c | 572 +++++++++++++++++++++++++++++++++++++ drivers/block/bcache/util.h | 657 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1229 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/util.c create mode 100644 drivers/block/bcache/util.h
drivers/block/bcache/util.c new file +572 -0
@@ -0,0 +1,572 @@ +#include <linux/bio.h> +#include <linux/blkdev.h> +#include <linux/ctype.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/types.h> + +#include <linux/dynamic_fault.h> + +#include "util.h" + +#define simple_strtoint (c, end, base) simple_strtol(c, end, base) +#define simple_strtouint (c, end, base) simple_strtoul(c, end, base) + +#define STRTO_H (name, type) \ +int name ## _h(const char *cp, type *res) \ +{ \ + int u = 0; \ + char *e; \ + type i = simple_ ## name(cp, &e, 10); \ + \ + switch (tolower(*e)) { \ + default: \ + return -EINVAL; \ + case 'y': \ + case 'z': \ + u++; \ + case 'e': \ + u++; \ + case 'p': \ + u++; \ + case 't': \ + u++; \ + case 'g': \ + u++; \ + case 'm': \ + u++; \ + case 'k': \ + u++; \ + if (e++ == cp) \ + return -EINVAL; \ + case '\n': \ + case '\0': \ + if (*e == '\n') \ + e++; \ + } \ + \ + if (*e) \ + return -EINVAL; \ + \ + while (u--) { \ + if ((type) ~0 > 0 && \ + (type) ~0 / 1024 <= i) \ + return -EINVAL; \ + if ((i > 0 && ANYSINT_MAX(type) / 1024 < i) || \ + (i < 0 && -ANYSINT_MAX(type) / 1024 > i)) \ + return -EINVAL; \ + i *= 1024; \ + } \ + \ + *res = i; \ + return 0; \ +} \ +EXPORT_SYMBOL_GPL(name ## _h); + +STRTO_H(strtoint, int) +STRTO_H(strtouint, unsigned int) +STRTO_H(strtoll, long long) +STRTO_H(strtoull, unsigned long long) + +ssize_t hprint (char *buf,int64_t v) +{ +static const char units[] ="?kMGTPEZY" ; +char dec[3 ] ="" ; +int u, t =0 ; + +for (u =0 ; v >=1024 || v <=-1024 ; u++) { + t = v & ~(~0 <<10 ); + v >>=10 ; + } + +if (!u) +return sprintf (buf,"%llu" , v); + +if (v <100 && v >-100 ) +sprintf (dec,".%i" , t /100 ); + +return sprintf (buf,"%lli%s%c" , v, dec, units[u]); +} +EXPORT_SYMBOL_GPL (hprint); + +ssize_t sprint_string_list (char *buf,const char *const list[], +size_t selected) +{ +char *out = buf; + +for (size_t i =0 ; list[i]; i++) + out +=sprintf (out, i == selected ?"[%s] " :"%s " , list[i]); + + out[-1 ] ='\n' ; +return out - buf; +} +EXPORT_SYMBOL_GPL (sprint_string_list); + +ssize_t read_string_list (const char *buf,const char *const list[]) +{ +size_t i; +char *s, *d =kstrndup (buf,PAGE_SIZE -1 ,GFP_KERNEL ); +if (!d) +return -ENOMEM ; + + s =strim (d); + +for (i =0 ; list[i]; i++) +if (!strcmp (list[i], s)) +break ; + +kfree (d); + +if (!list[i]) +return -EINVAL ; + +return i; +} +EXPORT_SYMBOL_GPL (read_string_list); + +bool is_zero (const char *p,size_t n) +{ +for (size_t i =0 ; i < n; i++) +if (p[i]) +return false; +return true; +} +EXPORT_SYMBOL_GPL (is_zero); + +int parse_uuid (const char *s,char *uuid) +{ +size_t i, j, x; +memset (uuid,0 ,16 ); + +for (i =0 , j =0 ; + i <strspn (s,"-0123456789:ABCDEFabcdef" ) && j <32 ; + i++) { + x = s[i] |32 ; + +switch (x) { + case '0'...'9': + x -='0' ; +break ; + case 'a'...'f': + x -='a' -10 ; +break ; +default : +continue ; + } + +if (!(j &1 )) + x <<=4 ; + uuid[j++ >>1 ] |= x; + } +return i; +} +EXPORT_SYMBOL_GPL (parse_uuid); + +void time_stats_update (struct time_stats *stats,uint64_t start_time) +{ +uint64_t now =local_clock (); +uint64_t duration =time_after64 (now, start_time) + ? now - start_time :0 ; +uint64_t last =time_after64 (now, stats->last) + ? now - stats->last :0 ; + + stats->max_duration =max (stats->max_duration, duration); + +if (stats->last) { +ewma_add (stats->average_duration, duration,8 ,8 ); + +if (stats->average_frequency) +ewma_add (stats->average_frequency, last,8 ,8 ); +else + stats->average_frequency = last <<8 ; + }else + stats->average_duration = duration <<8 ; + + stats->last = now ?:1 ; +} +EXPORT_SYMBOL_GPL (time_stats_update); + +#ifdef CONFIG_BCACHE_LATENCY_DEBUG +unsigned latency_warn_ms; +#endif + +#ifdef CONFIG_BCACHE_EDEBUG + +static void check_bio (struct bio *bio) +{ +unsigned i, size =0 ; +struct bio_vec *bv; +struct request_queue *q =bdev_get_queue (bio->bi_bdev); +BUG_ON (!bio->bi_vcnt); +BUG_ON (!bio->bi_size); + +bio_for_each_segment (bv, bio, i) + size += bv->bv_len; + +BUG_ON (size != bio->bi_size); +BUG_ON (size >queue_max_sectors (q) <<9 ); + +blk_recount_segments (q, bio); +BUG_ON (bio->bi_phys_segments >queue_max_segments (q)); +} + +#else /* EDEBUG */ + +#define check_bio (bio) do {} while (0) + +#endif + +void bio_reset (struct bio *bio) +{ +struct bio_vec *bv = bio->bi_io_vec; +unsigned max_vecs = bio->bi_max_vecs; +bio_destructor_t *destructor = bio->bi_destructor; + +bio_init (bio); +atomic_set (&bio->bi_cnt,2 ); + bio->bi_max_vecs = max_vecs; + bio->bi_io_vec = bv; + bio->bi_destructor = destructor; +} +EXPORT_SYMBOL_GPL (bio_reset); + +void bio_map (struct bio *bio,void *base) +{ +size_t size = bio->bi_size; +struct bio_vec *bv = bio->bi_inline_vecs; + +BUG_ON (!bio->bi_size); + bio->bi_vcnt =0 ; + bio->bi_io_vec = bv; + + bv->bv_offset = base ? ((unsigned long ) base) %PAGE_SIZE :0 ; + goto start; + +for (; size; bio->bi_vcnt++, bv++) { + bv->bv_offset =0 ; +start: bv->bv_len =min_t (size_t,PAGE_SIZE - bv->bv_offset, + size); +if (base) { + bv->bv_page =is_vmalloc_addr (base) + ?vmalloc_to_page (base) + :virt_to_page (base); + + base += bv->bv_len; + } + + size -= bv->bv_len; + } +} +EXPORT_SYMBOL_GPL (bio_map); + +#undef bio_alloc_pages +int bio_alloc_pages (struct bio *bio,gfp_t gfp) +{ +int i; +struct bio_vec *bv; +bio_for_each_segment (bv, bio, i) { + bv->bv_page =alloc_page (gfp); +if (!bv->bv_page) { +while (bv-- != bio->bi_io_vec + bio->bi_idx) +__free_page (bv->bv_page); +return -ENOMEM ; + } + } +return 0 ; +} +EXPORT_SYMBOL_GPL (bio_alloc_pages); + +struct bio *bio_split_front (struct bio *bio,int sectors,bio_alloc_fn *_alloc, +gfp_t gfp,struct bio_set *bs) +{ +unsigned idx, vcnt =0 , nbytes = sectors <<9 ; +struct bio_vec *bv; +struct bio *ret =NULL ; + +struct bio *alloc (int n) + { +if (bs) +return bio_alloc_bioset (gfp, n, bs); +else if (_alloc) +return _alloc (gfp, n); +else +return bio_kmalloc (gfp, n); + } + +if (current->bio_list) + bs =NULL ; + +BUG_ON (sectors <=0 ); + +if (nbytes >= bio->bi_size) +return bio; + +bio_for_each_segment (bv, bio, idx) { + vcnt = idx - bio->bi_idx; + +if (!nbytes) { + ret =alloc (0 ); +if (!ret) +return NULL ; + + ret->bi_io_vec =bio_iovec (bio); + ret->bi_flags |=1 <<BIO_CLONED ; +break ; + }else if (nbytes < bv->bv_len) { + ret =alloc (++vcnt); +if (!ret) +return NULL ; + +memcpy (ret->bi_io_vec,bio_iovec (bio), +sizeof (struct bio_vec ) * vcnt); + + ret->bi_io_vec[vcnt -1 ].bv_len = nbytes; + bv->bv_offset += nbytes; + bv->bv_len -= nbytes; +break ; + } + + nbytes -= bv->bv_len; + } + + ret->bi_bdev = bio->bi_bdev; + ret->bi_sector = bio->bi_sector; + ret->bi_size = sectors <<9 ; + ret->bi_rw = bio->bi_rw; + ret->bi_vcnt = vcnt; + ret->bi_max_vecs = vcnt; + ret->bi_end_io = bio->bi_end_io; + ret->bi_private = bio->bi_private; + +if (ret && ret != bio && bs) { + ret->bi_flags |=1 <<BIO_HAS_POOL ; + ret->bi_destructor = (void *) bs; + } + + bio->bi_sector += sectors; + bio->bi_size -= sectors <<9 ; + bio->bi_idx = idx; + +return ret; +} +EXPORT_SYMBOL_GPL (bio_split_front); + +unsigned __bio_max_sectors (struct bio *bio,struct block_device *bdev, +sector_t sector) +{ +unsigned ret =bio_sectors (bio); +struct request_queue *q =bdev_get_queue (bdev); +struct bio_vec *end =bio_iovec (bio) + +min_t (int,bio_segments (bio),queue_max_segments (q)); + +struct bvec_merge_data bvm = { + .bi_bdev = bdev, + .bi_sector = sector, + .bi_size =0 , + .bi_rw = bio->bi_rw, + }; + +if (bio_segments (bio) >queue_max_segments (q) || + q->merge_bvec_fn) { + ret =0 ; + +for (struct bio_vec *bv =bio_iovec (bio); bv < end; bv++) { +if (q->merge_bvec_fn && + q->merge_bvec_fn (q, &bvm, bv) < (int ) bv->bv_len) +break ; + + ret += bv->bv_len >>9 ; + bvm.bi_size += bv->bv_len; + } + } + + ret =min (ret,queue_max_sectors (q)); + +WARN_ON (!ret); + ret =max_t (int, ret,bio_iovec (bio)->bv_len >>9 ); + +return ret; +} +EXPORT_SYMBOL_GPL (__bio_max_sectors); + +int bio_submit_split (struct bio *bio,atomic_t *i,struct bio_set *bs) +{ +struct bio *n; + +do { + n =bio_split_front (bio,bio_max_sectors (bio), +NULL ,GFP_NOIO , bs); +if (!n) +return -ENOMEM ; +else if (n != bio) +atomic_inc (i); + +check_bio (n); +generic_make_request (n); + }while (n != bio); + +return 0 ; +} +EXPORT_SYMBOL_GPL (bio_submit_split); + +/* +* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group (Any +* use permitted, subject to terms of PostgreSQL license; see.) + +* If we have a 64-bit integer type, then a 64-bit CRC looks just like the +* usual sort of implementation. (See Ross Williams' excellent introduction +* A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS, available from +* ftp://ftp.rocksoft.com/papers/crc_v3.txt or several other net sites.) +* If we have no working 64-bit type, then fake it with two 32-bit registers. +* +* The present implementation is a normal (not "reflected", in Williams' +* terms) 64-bit CRC, using initial all-ones register contents and a final +* bit inversion. The chosen polynomial is borrowed from the DLT1 spec +* (ECMA-182, available from http://www.ecma.ch/ecma1/STAND/ECMA-182.HTM): +* +* x^64 + x^62 + x^57 + x^55 + x^54 + x^53 + x^52 + x^47 + x^46 + x^45 + +* x^40 + x^39 + x^38 + x^37 + x^35 + x^33 + x^32 + x^31 + x^29 + x^27 + +* x^24 + x^23 + x^22 + x^21 + x^19 + x^17 + x^13 + x^12 + x^10 + x^9 + +* x^7 + x^4 + x + 1 +*/ + +static const uint64_t crc_table[256 ] = { +0x0000000000000000 ,0x42F0E1EBA9EA3693 ,0x85E1C3D753D46D26 , +0xC711223CFA3E5BB5 ,0x493366450E42ECDF ,0x0BC387AEA7A8DA4C , +0xCCD2A5925D9681F9 ,0x8E224479F47CB76A ,0x9266CC8A1C85D9BE , +0xD0962D61B56FEF2D ,0x17870F5D4F51B498 ,0x5577EEB6E6BB820B , +0xDB55AACF12C73561 ,0x99A54B24BB2D03F2 ,0x5EB4691841135847 , +0x1C4488F3E8F96ED4 ,0x663D78FF90E185EF ,0x24CD9914390BB37C , +0xE3DCBB28C335E8C9 ,0xA12C5AC36ADFDE5A ,0x2F0E1EBA9EA36930 , +0x6DFEFF5137495FA3 ,0xAAEFDD6DCD770416 ,0xE81F3C86649D3285 , +0xF45BB4758C645C51 ,0xB6AB559E258E6AC2 ,0x71BA77A2DFB03177 , +0x334A9649765A07E4 ,0xBD68D2308226B08E ,0xFF9833DB2BCC861D , +0x388911E7D1F2DDA8 ,0x7A79F00C7818EB3B ,0xCC7AF1FF21C30BDE , +0x8E8A101488293D4D ,0x499B3228721766F8 ,0x0B6BD3C3DBFD506B , +0x854997BA2F81E701 ,0xC7B97651866BD192 ,0x00A8546D7C558A27 , +0x4258B586D5BFBCB4 ,0x5E1C3D753D46D260 ,0x1CECDC9E94ACE4F3 , +0xDBFDFEA26E92BF46 ,0x990D1F49C77889D5 ,0x172F5B3033043EBF , +0x55DFBADB9AEE082C ,0x92CE98E760D05399 ,0xD03E790CC93A650A , +0xAA478900B1228E31 ,0xE8B768EB18C8B8A2 ,0x2FA64AD7E2F6E317 , +0x6D56AB3C4B1CD584 ,0xE374EF45BF6062EE ,0xA1840EAE168A547D , +0x66952C92ECB40FC8 ,0x2465CD79455E395B ,0x3821458AADA7578F , +0x7AD1A461044D611C ,0xBDC0865DFE733AA9 ,0xFF3067B657990C3A , +0x711223CFA3E5BB50 ,0x33E2C2240A0F8DC3 ,0xF4F3E018F031D676 , +0xB60301F359DBE0E5 ,0xDA050215EA6C212F ,0x98F5E3FE438617BC , +0x5FE4C1C2B9B84C09 ,0x1D14202910527A9A ,0x93366450E42ECDF0 , +0xD1C685BB4DC4FB63 ,0x16D7A787B7FAA0D6 ,0x5427466C1E109645 , +0x4863CE9FF6E9F891 ,0x0A932F745F03CE02 ,0xCD820D48A53D95B7 , +0x8F72ECA30CD7A324 ,0x0150A8DAF8AB144E ,0x43A04931514122DD , +0x84B16B0DAB7F7968 ,0xC6418AE602954FFB ,0xBC387AEA7A8DA4C0 , +0xFEC89B01D3679253 ,0x39D9B93D2959C9E6 ,0x7B2958D680B3FF75 , +0xF50B1CAF74CF481F ,0xB7FBFD44DD257E8C ,0x70EADF78271B2539 , +0x321A3E938EF113AA ,0x2E5EB66066087D7E ,0x6CAE578BCFE24BED , +0xABBF75B735DC1058 ,0xE94F945C9C3626CB ,0x676DD025684A91A1 , +0x259D31CEC1A0A732 ,0xE28C13F23B9EFC87 ,0xA07CF2199274CA14 , +0x167FF3EACBAF2AF1 ,0x548F120162451C62 ,0x939E303D987B47D7 , +0xD16ED1D631917144 ,0x5F4C95AFC5EDC62E ,0x1DBC74446C07F0BD , +0xDAAD56789639AB08 ,0x985DB7933FD39D9B ,0x84193F60D72AF34F , +0xC6E9DE8B7EC0C5DC ,0x01F8FCB784FE9E69 ,0x43081D5C2D14A8FA , +0xCD2A5925D9681F90 ,0x8FDAB8CE70822903 ,0x48CB9AF28ABC72B6 , +0x0A3B7B1923564425 ,0x70428B155B4EAF1E ,0x32B26AFEF2A4998D , +0xF5A348C2089AC238 ,0xB753A929A170F4AB ,0x3971ED50550C43C1 , +0x7B810CBBFCE67552 ,0xBC902E8706D82EE7 ,0xFE60CF6CAF321874 , +0xE224479F47CB76A0 ,0xA0D4A674EE214033 ,0x67C58448141F1B86 , +0x253565A3BDF52D15 ,0xAB1721DA49899A7F ,0xE9E7C031E063ACEC , +0x2EF6E20D1A5DF759 ,0x6C0603E6B3B7C1CA ,0xF6FAE5C07D3274CD , +0xB40A042BD4D8425E ,0x731B26172EE619EB ,0x31EBC7FC870C2F78 , +0xBFC9838573709812 ,0xFD39626EDA9AAE81 ,0x3A28405220A4F534 , +0x78D8A1B9894EC3A7 ,0x649C294A61B7AD73 ,0x266CC8A1C85D9BE0 , +0xE17DEA9D3263C055 ,0xA38D0B769B89F6C6 ,0x2DAF4F0F6FF541AC , +0x6F5FAEE4C61F773F ,0xA84E8CD83C212C8A ,0xEABE6D3395CB1A19 , +0x90C79D3FEDD3F122 ,0xD2377CD44439C7B1 ,0x15265EE8BE079C04 , +0x57D6BF0317EDAA97 ,0xD9F4FB7AE3911DFD ,0x9B041A914A7B2B6E , +0x5C1538ADB04570DB ,0x1EE5D94619AF4648 ,0x02A151B5F156289C , +0x4051B05E58BC1E0F ,0x87409262A28245BA ,0xC5B073890B687329 , +0x4B9237F0FF14C443 ,0x0962D61B56FEF2D0 ,0xCE73F427ACC0A965 , +0x8C8315CC052A9FF6 ,0x3A80143F5CF17F13 ,0x7870F5D4F51B4980 , +0xBF61D7E80F251235 ,0xFD913603A6CF24A6 ,0x73B3727A52B393CC , +0x31439391FB59A55F ,0xF652B1AD0167FEEA ,0xB4A25046A88DC879 , +0xA8E6D8B54074A6AD ,0xEA16395EE99E903E ,0x2D071B6213A0CB8B , +0x6FF7FA89BA4AFD18 ,0xE1D5BEF04E364A72 ,0xA3255F1BE7DC7CE1 , +0x64347D271DE22754 ,0x26C49CCCB40811C7 ,0x5CBD6CC0CC10FAFC , +0x1E4D8D2B65FACC6F ,0xD95CAF179FC497DA ,0x9BAC4EFC362EA149 , +0x158E0A85C2521623 ,0x577EEB6E6BB820B0 ,0x906FC95291867B05 , +0xD29F28B9386C4D96 ,0xCEDBA04AD0952342 ,0x8C2B41A1797F15D1 , +0x4B3A639D83414E64 ,0x09CA82762AAB78F7 ,0x87E8C60FDED7CF9D , +0xC51827E4773DF90E ,0x020905D88D03A2BB ,0x40F9E43324E99428 , +0x2CFFE7D5975E55E2 ,0x6E0F063E3EB46371 ,0xA91E2402C48A38C4 , +0xEBEEC5E96D600E57 ,0x65CC8190991CB93D ,0x273C607B30F68FAE , +0xE02D4247CAC8D41B ,0xA2DDA3AC6322E288 ,0xBE992B5F8BDB8C5C , +0xFC69CAB42231BACF ,0x3B78E888D80FE17A ,0x7988096371E5D7E9 , +0xF7AA4D1A85996083 ,0xB55AACF12C735610 ,0x724B8ECDD64D0DA5 , +0x30BB6F267FA73B36 ,0x4AC29F2A07BFD00D ,0x08327EC1AE55E69E , +0xCF235CFD546BBD2B ,0x8DD3BD16FD818BB8 ,0x03F1F96F09FD3CD2 , +0x41011884A0170A41 ,0x86103AB85A2951F4 ,0xC4E0DB53F3C36767 , +0xD8A453A01B3A09B3 ,0x9A54B24BB2D03F20 ,0x5D45907748EE6495 , +0x1FB5719CE1045206 ,0x919735E51578E56C ,0xD367D40EBC92D3FF , +0x1476F63246AC884A ,0x568617D9EF46BED9 ,0xE085162AB69D5E3C , +0xA275F7C11F7768AF ,0x6564D5FDE549331A ,0x279434164CA30589 , +0xA9B6706FB8DFB2E3 ,0xEB46918411358470 ,0x2C57B3B8EB0BDFC5 , +0x6EA7525342E1E956 ,0x72E3DAA0AA188782 ,0x30133B4B03F2B111 , +0xF7021977F9CCEAA4 ,0xB5F2F89C5026DC37 ,0x3BD0BCE5A45A6B5D , +0x79205D0E0DB05DCE ,0xBE317F32F78E067B ,0xFCC19ED95E6430E8 , +0x86B86ED5267CDBD3 ,0xC4488F3E8F96ED40 ,0x0359AD0275A8B6F5 , +0x41A94CE9DC428066 ,0xCF8B0890283E370C ,0x8D7BE97B81D4019F , +0x4A6ACB477BEA5A2A ,0x089A2AACD2006CB9 ,0x14DEA25F3AF9026D , +0x562E43B4931334FE ,0x913F6188692D6F4B ,0xD3CF8063C0C759D8 , +0x5DEDC41A34BBEEB2 ,0x1F1D25F19D51D821 ,0xD80C07CD676F8394 , +0x9AFCE626CE85B507 +}; + +uint64_t crc64_update (uint64_t crc,const void *_data,size_t len) +{ +const unsigned char *data = _data; + +while (len--) { +int i = ((int ) (crc >>56 ) ^ *data++) &0xFF ; + crc = crc_table[i] ^ (crc <<8 ); + } + +return crc; +} +EXPORT_SYMBOL (crc64_update); + +uint64_t crc64 (const void *data,size_t len) +{ +uint64_t crc =0xffffffffffffffff ; + + crc =crc64_update (crc, data, len); + +return crc ^0xffffffffffffffff ; +} +EXPORT_SYMBOL (crc64); + +unsigned popcount_64 (uint64_t x) +{ +static const uint64_t m1 =0x5555555555555555LLU ; +static const uint64_t m2 =0x3333333333333333LLU ; +static const uint64_t m4 =0x0f0f0f0f0f0f0f0fLLU ; +static const uint64_t h01 =0x0101010101010101LLU ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>56 ; +} +EXPORT_SYMBOL (popcount_64); + +unsigned popcount_32 (uint32_t x) +{ +static const uint32_t m1 =0x55555555 ; +static const uint32_t m2 =0x33333333 ; +static const uint32_t m4 =0x0f0f0f0f ; +static const uint32_t h01 =0x01010101 ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>24 ; +} +EXPORT_SYMBOL (popcount_32);
drivers/block/bcache/util.h new file +657 -0
657 lines not on this page · raw message
Signature · 1 line
1.7.9.rc2
Re: [Bcache v13 09/16] Bcache: generic utility code
Quoted 5 lines · Kent Overstreet
Much of this code should be moved out of
drivers/block/bcache, but it was originally written for bcache.Signed-off-by Kent Overstreet <[email protected]>---
[..]
Quoted 1 line
+#include <
linux/dynamic_fault.h>
[..]
drivers/block/bcache/util.h line 602
Quoted 3 lines
drivers/block/bcache/util.h new file +3
-0+#define bio_alloc_pages (...) \ + (dynamic_fault() ? -ENOMEM : bio_alloc_pages(__VA_ARGS__)) +
...these missed the removal of the dynamic_fault bits.
Re: [Bcache v13 09/16] Bcache: generic utility code
Whoops, grepped around but missed that. Thanks.
Quoted 15 lines · Dan Williams
Quoted 5 lines · Kent Overstreet
Much of this code should be moved out of
drivers/block/bcache, but it was originally written for bcache.Signed-off-by Kent Overstreet <[email protected]>---
[..]
Quoted 1 line
+#include <
linux/dynamic_fault.h>[..]
drivers/block/bcache/util.h line 602
Quoted 3 lines
drivers/block/bcache/util.h new file +3
-0+#define bio_alloc_pages (...) \ + (dynamic_fault() ? -ENOMEM : bio_alloc_pages(__VA_ARGS__)) +...these missed the removal of the
dynamic_faultbits.
Re: [Bcache v13 09/16] Bcache: generic utility code
In reply to Kent OverstreetHello, Kent.
I've been reading the code and am still very far from understanding the whole thing but here are general impressions upto this point.
* I started reading from get_bucket(), mostly because I wasn't sure where to start. It would be very helpful to the reviewers and future participants if there's a design doc and more comments (much more). The code is on the big side and the problem is exacerbated by use of somewhat unconventional constructs and conventions and lack of documentation and comments.
* It's unfortunate but true that different parts of kernel are held to different standards in terms of conformance to overall style and structure. Code for obscure device, for example, is allowed to be way more weird than common core code. That said, one thing which is generally frowned upon is bringing in lots of foreign constructs because that tends to make the code much more difficult to follow for other people than plain silly code. Even for drivers, code with a lot of wrappers and foreign constructs (synchronization, data structure...) tend to get nacked and are recommended to convert to existing infrastructure even if that ends up, say, more verbose code.
It is also true that new constructs and mechanisms are constantly being added to kernel and the ones used by bcache could be useful enough to have; however, with my currently limited understanding, the oddities seem too much.
* Too many smart macros. Macros suck. Smart ones double suck.
* For file internal ones, it's okay to be somewhat relaxed with namespace separation but bcache code seems to be going too far. It also becomes a lot worse with macros as warnings and errors from compiler get much more difficult to decipher. e.g. things like next() and end() macros are calling for trouble.
* Somewhat related to the above, I'm not a fan of super-verbose symbols but I *hope* that the variable names were just a tiny bit more descriptive. At least, the ones used as arguments.
* The biggest thing that I dislike about closure is that it's not clear what it does when I see one. Is it a refcount, synchronization construct or asynchronous execution mechanism? To me, it seems like a construct with too many purposes and too much abstraction, which tends to make it difficult to understand, easy to misuse and difficult to debug. IMHO, constructs like this could seem very attractive to small group of people with certain concepts firmly on their minds; however, one man's paradise is another man's hell and we're often better off without either. In many ways, closure feels like kobject and friends.
I'd like to recommend using something more concerete even if that means more verbosity. ie. if there are lots of bio sequencing going on, implement and use bio sequencer. That's way more concerete and concerete stuff tends to be much easier to wrap one's head around mostly because it's much easier to agree upon for different people and they don't have to keep second-guessing what the original author would have on his/her mind while implementing it.
The problem that closure poses is that stuff like this adds a lot to on-going maintenance overhead. bcache could be very successful and something we continue to use for decades but it might as well be mostly meaningless in five years due to developments in storage technology. It'll still have to be maintained and you might not be around for the task and we don't want people to face code body which depends on alien abstract synchronization and async constructs while updating, say, block or workqueue API. Code may be doing complex things but they still better use the constructs and follow the conventions that the rest of the kernel is using as much as possible.
Again, my understanding of the code base is quite limited at this point so I might change my mind but these were the impressions I got upto this point. I'll keep on reading. Specific comments follow.
Quoted 6 lines · Kent Overstreet
drivers/block/bcache/util.c new file +6
-0+#define simple_strtoint (c, end, base) simple_strtol(c, end, base) +#define simple_strtouint (c, end, base) simple_strtoul(c, end, base) + +#define STRTO_H (name, type) \ +int name ## _h(const char *cp, type *res) \ +{ \
...
drivers/block/bcache/util.c line 70
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+} \ +EXPORT_SYMBOL_GPL(name ## _h); + +STRTO_H(strtoint, int) +STRTO_H(strtouint, unsigned int) +STRTO_H(strtoll, long long) +STRTO_H(strtoull, unsigned long long)
Function defining macros == bad. Can't it be implemented with a common backend function with wrappers implementing limits? Why not use memparse()?
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ssize_t hprint (char *buf,int64_t v) +{ +static const char units[] ="?kMGTPEZY" ; +char dec[3 ] ="" ; +int u, t =0 ; + +for (u =0 ; v >=1024 || v <=-1024 ; u++) { + t = v & ~(~0 <<10 ); + v >>=10 ; + }
drivers/block/bcache/util.c line 91
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ +if (!u) +return sprintf (buf,"%llu" , v); + +if (v <100 && v >-100 ) +sprintf (dec,".%i" , t /100 ); + +return sprintf (buf,"%lli%s%c" , v, dec, units[u]); +} +EXPORT_SYMBOL_GPL (hprint);
Not your fault but maybe we want integer vsnprintf modifier for this. Also, sprintf() sucks.
drivers/block/bcache/util.c line 104
Quoted 12 lines
drivers/block/bcache/util.c new file +12
-0+ssize_t sprint_string_list (char *buf,const char *const list[], +size_t selected) +{ +char *out = buf; + +for (size_t i =0 ; list[i]; i++) + out +=sprintf (out, i == selected ?"[%s] " :"%s " , list[i]); + + out[-1 ] ='\n' ; +return out - buf; +} +EXPORT_SYMBOL_GPL (sprint_string_list);
sprintf() sucks.
drivers/block/bcache/util.c line 130
Quoted 3 lines
drivers/block/bcache/util.c new file +3
-0+bool is_zero (const char *p,size_t n) +{ +for (size_t i =0 ; i < n; i++)
This doesn't build. While loop var decl in for loop is nice, the kernel isn't built in c99 mode. If you want to enable c99 mode kernel-wide, you're welcome to try but you can't flip that per-component.
Quoted 24 lines
drivers/block/bcache/util.c new file +23
-0+if (p[i]) +return false; +return true; +} +EXPORT_SYMBOL_GPL (is_zero); +int parse_uuid (const char *s,char *uuid) +{ +size_t i, j, x; +memset (uuid,0 ,16 ); + +for (i =0 , j =0 ; + i <strspn (s,"-0123456789:ABCDEFabcdef" ) && j <32 ; + i++) { + x = s[i] |32 ; + +switch (x) { + case '0'...'9': + x -='0' ; +break ; + case 'a'...'f': + x -='a' -10 ; +break ; +default :
drivers/block/bcache/util.c line 164
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+continue ; + } + +if (!(j &1 )) + x <<=4 ; + uuid[j++ >>1 ] |= x; + } +return i; +} +EXPORT_SYMBOL_GPL (parse_uuid);
Hmmm... can't we just enforce fixed format used by vsnprintf()?
Quoted 13 lines
drivers/block/bcache/util.c new file +13
-0+void time_stats_update (struct time_stats *stats,uint64_t start_time) +{ +uint64_t now =local_clock (); +uint64_t duration =time_after64 (now, start_time) + ? now - start_time :0 ; +uint64_t last =time_after64 (now, stats->last) + ? now - stats->last :0 ; + + stats->max_duration =max (stats->max_duration, duration); + +if (stats->last) { +ewma_add (stats->average_duration, duration,8 ,8 ); +
drivers/block/bcache/util.c line 188
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+if (stats->average_frequency) +ewma_add (stats->average_frequency, last,8 ,8 ); +else + stats->average_frequency = last <<8 ; + }else + stats->average_duration = duration <<8 ; + + stats->last = now ?:1 ; +} +EXPORT_SYMBOL_GPL (time_stats_update);
if {
} else {
}
Even if one of the branches is single line. Also, I'm not sure this (and a few others) is general enough to be common utility.
drivers/block/bcache/util.c line 200
Quoted 11 lines
drivers/block/bcache/util.c new file +11
-0+#ifdef CONFIG_BCACHE_LATENCY_DEBUG +unsigned latency_warn_ms; +#endif + +#ifdef CONFIG_BCACHE_EDEBUG + +static void check_bio (struct bio *bio) +{ +unsigned i, size =0 ; +struct bio_vec *bv; +struct request_queue *q =bdev_get_queue (bio->bi_bdev);
New line missing.
Quoted 12 lines
drivers/block/bcache/util.c new file +12
-0+BUG_ON (!bio->bi_vcnt); +BUG_ON (!bio->bi_size); + +bio_for_each_segment (bv, bio, i) + size += bv->bv_len; + +BUG_ON (size != bio->bi_size); +BUG_ON (size >queue_max_sectors (q) <<9 ); + +blk_recount_segments (q, bio); +BUG_ON (bio->bi_phys_segments >queue_max_segments (q)); +}
...
drivers/block/bcache/util.c line 241
Quoted 8 lines
drivers/block/bcache/util.c new file +8
-0+void bio_map (struct bio *bio,void *base) +{ +size_t size = bio->bi_size; +struct bio_vec *bv = bio->bi_inline_vecs; + +BUG_ON (!bio->bi_size); + bio->bi_vcnt =0 ; + bio->bi_io_vec = bv;
I'd prefer these assignments didn't have indentations.
drivers/block/bcache/util.c line 249
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+ bv->bv_offset = base ? ((unsigned long ) base) %PAGE_SIZE :0 ; + goto start; + +for (; size; bio->bi_vcnt++, bv++) { + bv->bv_offset =0 ; +start: bv->bv_len =min_t (size_t,PAGE_SIZE - bv->bv_offset, + size);
Please don't do this.
Quoted 4 lines
drivers/block/bcache/util.c new file +4
-0+if (base) { + bv->bv_page =is_vmalloc_addr (base) + ?vmalloc_to_page (base) + :virt_to_page (base);
drivers/block/bcache/util.c line 263
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ + base += bv->bv_len; + } + + size -= bv->bv_len; + } +} +EXPORT_SYMBOL_GPL (bio_map); + +#undef bio_alloc_pages
Why is this undef necessary? If it's necessary, why isn't there any explanation?
drivers/block/bcache/util.c line 267
Quoted 4 lines
drivers/block/bcache/util.c new file +4
-0+int bio_alloc_pages (struct bio *bio,gfp_t gfp) +{ +int i; +struct bio_vec *bv;
New line missing.
Quoted 18 lines
drivers/block/bcache/util.c new file +18
-0+bio_for_each_segment (bv, bio, i) { + bv->bv_page =alloc_page (gfp); +if (!bv->bv_page) { +while (bv-- != bio->bi_io_vec + bio->bi_idx) +__free_page (bv->bv_page); +return -ENOMEM ; + } + } +return 0 ; +} +EXPORT_SYMBOL_GPL (bio_alloc_pages); + +struct bio *bio_split_front (struct bio *bio,int sectors,bio_alloc_fn *_alloc, +gfp_t gfp,struct bio_set *bs) +{ +unsigned idx, vcnt =0 , nbytes = sectors <<9 ; +struct bio_vec *bv; +struct bio *ret =NULL ;
drivers/block/bcache/util.c line 295
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ +struct bio *alloc (int n) + { +if (bs) +return bio_alloc_bioset (gfp, n, bs); +else if (_alloc) +return _alloc (gfp, n); +else +return bio_kmalloc (gfp, n); + }
These are now separate patch series, right? But, please don't use nested functions. Apart from being very unconventional (does gnu99 even allow this?), the implicit outer scope access is dangerous when mixed with context bouncing which is rather common in kernel. We (well, at least I) actually want cross stack frame accesses to be explicit.
Quoted 17 lines
drivers/block/bcache/util.c new file +17
-0+unsigned popcount_64 (uint64_t x) +{ +static const uint64_t m1 =0x5555555555555555LLU ; +static const uint64_t m2 =0x3333333333333333LLU ; +static const uint64_t m4 =0x0f0f0f0f0f0f0f0fLLU ; +static const uint64_t h01 =0x0101010101010101LLU ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>56 ; +} +EXPORT_SYMBOL (popcount_64); + +unsigned popcount_32 (uint32_t x) +{ +static const uint32_t m1 =0x55555555 ;
drivers/block/bcache/util.c line 572
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+static const uint32_t m2 =0x33333333 ; +static const uint32_t m4 =0x0f0f0f0f ; +static const uint32_t h01 =0x01010101 ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>24 ; +} +EXPORT_SYMBOL (popcount_32);
How are these different from bitmap_weight()?
drivers/block/bcache/util.h line 16
Quoted 4 lines
drivers/block/bcache/util.h new file +4
-0+#ifndef USHRT_MAX +#define USHRT_MAX ((u16)(~0U)) +#define SHRT_MAX ((s16)(USHRT_MAX>>1)) +#endif
...
These compat macros can be removed for upstream submission, right?
drivers/block/bcache/util.h line 69
Quoted 9 lines
drivers/block/bcache/util.h new file +9
-0+#define BITMASK (name, type, field, offset, size) \ +static inline uint64_t name(const type *k) \ +{ return (k->field >> offset) & ~(((uint64_t) ~0) << size); } \ + \ +static inline void SET_##name(type *k, uint64_t v) \ +{ \ + k->field &= ~(~((uint64_t) ~0 << size) << offset); \ + k->field |= v << offset; \ +}
More function defining macros.
drivers/block/bcache/util.h line 77
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_HEAP (type, name) \ + struct { \ + size_t size, used; \ + type *data; \ + } name + +#define init_heap (heap, _size, gfp) \
Ummm.... so, essentially templates done in macros. Please don't do that. Definitely NACK on this.
drivers/block/bcache/util.h line 164
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_FIFO (type, name) \ + struct { \ + size_t front, back, size, mask; \ + type *data; \ + } name + +#define fifo_for_each (c, fifo) \
Ditto. Templates are already yucky enough even with compiler support. IMHO, it's a horrible idea to try that with preprocessor in C.
drivers/block/bcache/util.h line 301
Quoted 5 lines
drivers/block/bcache/util.h new file +5
-0+#define DECLARE_ARRAY_ALLOCATOR (type, name, size) \ + struct { \ + type *freelist; \ + type data[size]; \ + } name
Ditto.
drivers/block/bcache/util.h line 372
Quoted 13 lines
drivers/block/bcache/util.h new file +13
-0+#define strtoi_h (cp, res) \ + (__builtin_types_compatible_p(typeof(*res), int) \ + ? strtoint_h(cp, (void *) res) \ + :__builtin_types_compatible_p(typeof(*res), long) \ + ? strtol_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), long long) \ + ? strtoll_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned int) \ + ? strtouint_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned long) \ + ? strtoul_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned long long)\ + ? strtoull_h(cp, (void *) res) : -EINVAL)
Is this *really* necessary?
Quoted 24 lines
drivers/block/bcache/util.h new file +24
-0+#define strtoul_safe (cp, var) \ +({ \ + unsigned long _v; \ + int _r = strict_strtoul(cp, 10, &_v); \ + if (!_r) \ + var = _v; \ + _r; \ +}) + +#define strtoul_safe_clamp (cp, var, min, max) \ +({ \ + unsigned long _v; \ + int _r = strict_strtoul(cp, 10, &_v); \ + if (!_r) \ + var = clamp_t(typeof(var), _v, min, max); \ + _r; \ +}) + +#define snprint (buf, size, var) \ + snprintf(buf, size, \ + __builtin_types_compatible_p(typeof(var), int) \ + ? "%i\n" : \ + __builtin_types_compatible_p(typeof(var), unsigned) \ + ? "%u\n" : \
drivers/block/bcache/util.h line 407
Quoted 10 lines
drivers/block/bcache/util.h new file +10
-0+__builtin_types_compatible_p (typeof (var), long) \ + ?"%li\n" : \ + __builtin_types_compatible_p(typeof(var), unsigned long)\ + ?"%lu\n" : \ +__builtin_types_compatible_p (typeof (var), int64_t) \ + ?"%lli\n" : \ +__builtin_types_compatible_p (typeof (var), uint64_t) \ + ?"%llu\n" : \ + __builtin_types_compatible_p(typeof(var), const char *) \ + ?"%s\n" :"%i\n" , var)
Ditto.
I'm gonna stop here. It should be pretty clear what I'm bitching about by now. :) Please make it C and, better, kernel C.
Thanks.
--
tejun
Re: [Bcache v13 09/16] Bcache: generic utility code
Quoted 11 lines · Tejun Heo
Hello, Kent.
I've been reading the code and am still very far from understanding the whole thing but here are general impressions upto this point.
* I started reading from
get_bucket(), mostly because I wasn't sure where to start. It would be very helpful to the reviewers and future participants if there's a design doc and more comments (much more). The code is on the big side and the problem is exacerbated by use of somewhat unconventional constructs and conventions and lack of documentation and comments.
Yeah, I'm gonna try and spend more time on documentation. That's gonna take a bit, though.
Quoted 17 lines
* It's unfortunate but true that different parts of kernel are held to different standards in terms of conformance to overall style and structure. Code for obscure device, for example, is allowed to be way more weird than common core code. That said, one thing which is generally frowned upon is bringing in lots of foreign constructs because that tends to make the code much more difficult to follow for other people than plain silly code. Even for drivers, code with a lot of wrappers and foreign constructs (synchronization, data structure...) tend to get nacked and are recommended to convert to existing infrastructure even if that ends up, say, more verbose code.
It is also true that new constructs and mechanisms are constantly being added to kernel and the ones used by bcache could be useful enough to have; however, with my currently limited understanding, the oddities seem too much.
Quoted 7 lines
* Too many smart macros. Macros suck. Smart ones double suck.
* For file internal ones, it's okay to be somewhat relaxed with namespace separation but bcache code seems to be going too far. It also becomes a lot worse with macros as warnings and errors from compiler get much more difficult to decipher. e.g. things like
next()andend()macros are calling for trouble.
Yes, that's been on the todo list forever - legacy of it starting out as a single file. I'll try and work some more on namespacing tonight.
Quoted 3 lines
* Somewhat related to the above, I'm not a fan of super-verbose symbols but I *hope* that the variable names were just a tiny bit more descriptive. At least, the ones used as arguments.
If you want to point out particularly egregious examples in your code reviews I'll tackle those first. I suffer from being too close to the code, so I really have a hard time guessing what's going to trip other people up.
Quoted 10 lines
* The biggest thing that I dislike about closure is that it's not clear what it does when I see one. Is it a refcount, synchronization construct or asynchronous execution mechanism? To me, it seems like a construct with too many purposes and too much abstraction, which tends to make it difficult to understand, easy to misuse and difficult to debug. IMHO, constructs like this could seem very attractive to small group of people with certain concepts firmly on their minds; however, one man's paradise is another man's hell and we're often better off without either. In many ways, closure feels like kobject and friends.
Heh, it's worse than kobjects in that respect. But I really like the fact that it's more abstract than kobjects, in that it's not at all tied to implementation details of sysfs or anything else.
Quoted 7 lines
I'd like to recommend using something more concerete even if that means more verbosity. ie. if there are lots of bio sequencing going on, implement and use bio sequencer. That's way more concerete and concerete stuff tends to be much easier to wrap one's head around mostly because it's much easier to agree upon for different people and they don't have to keep second-guessing what the original author would have on his/her mind while implementing it.
There's got to be some kind of middle ground. Having a unifying abstraction for refcounty things really is incredibly powerful - the unified parent mechanism makes a lot of problems go away, I don't think I can adequately explain how sucessful that was, you had to experience it.
But nevertheless I sympathize with your basic complaint - it ought to be possible to restructure something so the code is easier to follow and it's easier to tell what a given closure is for.
Maybe if I go through and document all the instances where they're declared it might help.
Quoted 8 lines · Kent Overstreet
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+#define simple_strtoint (c, end, base) simple_strtol(c, end, base) +#define simple_strtouint (c, end, base) simple_strtoul(c, end, base) + +#define STRTO_H (name, type) \ +int name ## _h(const char *cp, type *res) \ +{ \...
Quoted 11 lines
drivers/block/bcache/util.c line 70
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+} \ +EXPORT_SYMBOL_GPL(name ## _h); + +STRTO_H(strtoint, int) +STRTO_H(strtouint, unsigned int) +STRTO_H(strtoll, long long) +STRTO_H(strtoull, unsigned long long)Function defining macros == bad. Can't it be implemented with a common backend function with wrappers implementing limits? Why not use
memparse()?
Had not come across memparse(). I don't think it's a sufficient replacement for my code because of the per type limits, but we should definitely get rid of the duplication one way or the other.
As you can probably tell I'm strongly biased against duplication when it's a question of macros vs. duplication - but if we can make the commend backend code + wrapper approach work that's even better (smaller code size ftw). It'll be tricky to get u64 vs. int64 right, but I'll take a stab at it.
Quoted 15 lines
Quoted 15 lines
drivers/block/bcache/util.c new file +14
-0+ssize_t hprint (char *buf,int64_t v) +{ +static const char units[] ="?kMGTPEZY" ; +char dec[3 ] ="" ; +int u, t =0 ; + +for (u =0 ; v >=1024 || v <=-1024 ; u++) { + t = v & ~(~0 <<10 ); + v >>=10 ; + } + +if (!u) +return sprintf (buf,"%llu" , v); +
Quoted 8 lines
drivers/block/bcache/util.c line 91
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+if (v <100 && v >-100 ) +sprintf (dec,".%i" , t /100 ); + +return sprintf (buf,"%lli%s%c" , v, dec, units[u]); +} +EXPORT_SYMBOL_GPL (hprint);Not your fault but maybe we want integer vsnprintf modifier for this.
Yes.
Quoted 1 line
Also,
sprintf()sucks.
Yeah, but considering the output is very bounded... you are technically correct, but I have a hard time caring. Making it a vsnprintf modifier would make that issue go away though. I'll see if I can figure out how to do it.
Quoted 6 lines
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+ssize_t sprint_string_list (char *buf,const char *const list[], +size_t selected) +{ +char *out = buf; + +for (size_t i =0 ; list[i]; i++)
Quoted 8 lines
drivers/block/bcache/util.c line 104
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+ out +=sprintf (out, i == selected ?"[%s] " :"%s " , list[i]); + + out[-1 ] ='\n' ; +return out - buf; +} +EXPORT_SYMBOL_GPL (sprint_string_list);
sprintf()sucks.
Output's again bounded so it's not _buggy_ as is, but this one I agree more - I'll switch it.
Quoted 8 lines
drivers/block/bcache/util.c line 130
Quoted 3 lines
drivers/block/bcache/util.c new file +3
-0+bool is_zero (const char *p,size_t n) +{ +for (size_t i =0 ; i < n; i++)This doesn't build. While loop var decl in for loop is nice, the kernel isn't built in c99 mode. If you want to enable c99 mode kernel-wide, you're welcome to try but you can't flip that per-component.
If patches to enable c99 mode would be accepted I'd be happy to work on them (provided I can find time, but I doubt it'd be much work).
That said, I'm just as happy to switch to something that builds in c89 mode here.
Quoted 22 lines
Quoted 22 lines
drivers/block/bcache/util.c new file +22
-0+int parse_uuid (const char *s,char *uuid) +{ +size_t i, j, x; +memset (uuid,0 ,16 ); + +for (i =0 , j =0 ; + i <strspn (s,"-0123456789:ABCDEFabcdef" ) && j <32 ; + i++) { + x = s[i] |32 ; + +switch (x) { + case '0'...'9': + x -='0' ; +break ; + case 'a'...'f': + x -='a' -10 ; +break ; +default : +continue ; + } + +if (!(j &1 ))
Quoted 8 lines
drivers/block/bcache/util.c line 164
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+ x <<=4 ; + uuid[j++ >>1 ] |= x; + } +return i; +} +EXPORT_SYMBOL_GPL (parse_uuid);Hmmm... can't we just enforce fixed format used by
vsnprintf()?
I don't follow - vsnprintf() is output, this is input...
Quoted 29 lines
Quoted 14 lines
drivers/block/bcache/util.c new file +13
-0+void time_stats_update (struct time_stats *stats,uint64_t start_time) +{ +uint64_t now =local_clock (); +uint64_t duration =time_after64 (now, start_time) + ? now - start_time :0 ; +uint64_t last =time_after64 (now, stats->last) + ? now - stats->last :0 ; + + stats->max_duration =max (stats->max_duration, duration); + +if (stats->last) { +ewma_add (stats->average_duration, duration,8 ,8 ); +drivers/block/bcache/util.c line 188
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+if (stats->average_frequency) +ewma_add (stats->average_frequency, last,8 ,8 ); +else + stats->average_frequency = last <<8 ; + }else + stats->average_duration = duration <<8 ; + + stats->last = now ?:1 ; +} +EXPORT_SYMBOL_GPL (time_stats_update);if {
} else {
}
Quoted 2 lines
Even if one of the branches is single line. Also, I'm not sure this (and a few others) is general enough to be common utility.
Yeah, not everything in util.[ch] has to become generic code.
Quoted 30 lines
drivers/block/bcache/util.c line 200
Quoted 12 lines
drivers/block/bcache/util.c new file +11
-0+#ifdef CONFIG_BCACHE_LATENCY_DEBUG +unsigned latency_warn_ms; +#endif + +#ifdef CONFIG_BCACHE_EDEBUG + +static void check_bio (struct bio *bio) +{ +unsigned i, size =0 ; +struct bio_vec *bv; +struct request_queue *q =bdev_get_queue (bio->bi_bdev);New line missing.
Quoted 12 lines
drivers/block/bcache/util.c new file +12
-0+BUG_ON (!bio->bi_vcnt); +BUG_ON (!bio->bi_size); + +bio_for_each_segment (bv, bio, i) + size += bv->bv_len; + +BUG_ON (size != bio->bi_size); +BUG_ON (size >queue_max_sectors (q) <<9 ); + +blk_recount_segments (q, bio); +BUG_ON (bio->bi_phys_segments >queue_max_segments (q)); +}...
Quoted 10 lines
drivers/block/bcache/util.c line 241
Quoted 8 lines
drivers/block/bcache/util.c new file +8
-0+void bio_map (struct bio *bio,void *base) +{ +size_t size = bio->bi_size; +struct bio_vec *bv = bio->bi_inline_vecs; + +BUG_ON (!bio->bi_size); + bio->bi_vcnt =0 ; + bio->bi_io_vec = bv;I'd prefer these assignments didn't have indentations.
Suppose there's only two of them.
Quoted 9 lines
drivers/block/bcache/util.c line 249
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+ bv->bv_offset = base ? ((unsigned long ) base) %PAGE_SIZE :0 ; + goto start; + +for (; size; bio->bi_vcnt++, bv++) { + bv->bv_offset =0 ; +start: bv->bv_len =min_t (size_t,PAGE_SIZE - bv->bv_offset, + size);Please don't do this.
I don't really get your objection to jumping into the middle of loops... sure it shouldn't be the first way you try to write something, but I don't see anything inherently wrong with it. IMO it _can_ make the code easier to follow.
Quoted 4 lines
Quoted 1 line
+#undef
bio_alloc_pagesWhy is this undef necessary? If it's necessary, why isn't there any explanation?
From the dynamic fault code, should've been removed.
Quoted 35 lines
drivers/block/bcache/util.c line 267
Quoted 5 lines
drivers/block/bcache/util.c new file +4
-0+int bio_alloc_pages (struct bio *bio,gfp_t gfp) +{ +int i; +struct bio_vec *bv;New line missing.
Quoted 27 lines
drivers/block/bcache/util.c new file +27
-0+bio_for_each_segment (bv, bio, i) { + bv->bv_page =alloc_page (gfp); +if (!bv->bv_page) { +while (bv-- != bio->bi_io_vec + bio->bi_idx) +__free_page (bv->bv_page); +return -ENOMEM ; + } + } +return 0 ; +} +EXPORT_SYMBOL_GPL (bio_alloc_pages); + +struct bio *bio_split_front (struct bio *bio,int sectors,bio_alloc_fn *_alloc, +gfp_t gfp,struct bio_set *bs) +{ +unsigned idx, vcnt =0 , nbytes = sectors <<9 ; +struct bio_vec *bv; +struct bio *ret =NULL ; + +struct bio *alloc (int n) + { +if (bs) +return bio_alloc_bioset (gfp, n, bs); +else if (_alloc) +return _alloc (gfp, n); +else +return bio_kmalloc (gfp, n);
Quoted 8 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ }These are now separate patch series, right? But, please don't use nested functions. Apart from being very unconventional (does gnu99 even allow this?), the implicit outer scope access is dangerous when mixed with context bouncing which is rather common in kernel. We (well, at least I) actually want cross stack frame accesses to be explicit.
Yes. I took out the nested function in the newer version (I never liked the way allocation worked in the old code and I finally figured out a sane way of doing it).
What do you mean by context bouncing? IME the problem with nested functions in the kernel is the trampolines gcc can silently generate (which is a serious problem).
Quoted 21 lines
Quoted 21 lines
drivers/block/bcache/util.c new file +21
-0+unsigned popcount_64 (uint64_t x) +{ +static const uint64_t m1 =0x5555555555555555LLU ; +static const uint64_t m2 =0x3333333333333333LLU ; +static const uint64_t m4 =0x0f0f0f0f0f0f0f0fLLU ; +static const uint64_t h01 =0x0101010101010101LLU ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>56 ; +} +EXPORT_SYMBOL (popcount_64); + +unsigned popcount_32 (uint32_t x) +{ +static const uint32_t m1 =0x55555555 ; +static const uint32_t m2 =0x33333333 ; +static const uint32_t m4 =0x0f0f0f0f ; +static const uint32_t h01 =0x01010101 ; +
Quoted 8 lines
drivers/block/bcache/util.c line 572
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+ x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>24 ; +} +EXPORT_SYMBOL (popcount_32);How are these different from
bitmap_weight()?
No important difference, I just never saw bitmap_weight() before - I'll switch to that. (These are faster, but bigger and in the kernel I highly doubt we've a workload where the performance of popcount justifies the extra memory).
Quoted 8 lines
drivers/block/bcache/util.h line 16
Quoted 5 lines
drivers/block/bcache/util.h new file +4
-0+#ifndef USHRT_MAX +#define USHRT_MAX ((u16)(~0U)) +#define SHRT_MAX ((s16)(USHRT_MAX>>1)) +#endif ...
These compat macros can be removed for upstream submission, right?
Yeah.
Quoted 11 lines
drivers/block/bcache/util.h line 69
Quoted 9 lines
drivers/block/bcache/util.h new file +9
-0+#define BITMASK (name, type, field, offset, size) \ +static inline uint64_t name(const type *k) \ +{ return (k->field >> offset) & ~(((uint64_t) ~0) << size); } \ + \ +static inline void SET_##name(type *k, uint64_t v) \ +{ \ + k->field &= ~(~((uint64_t) ~0 << size) << offset); \ + k->field |= v << offset; \ +}More function defining macros.
This one I'm not getting rid of unless you know a better way of doing it than I do - I use it all over the place and well, duplication.
Quoted 10 lines
drivers/block/bcache/util.h line 77
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_HEAP (type, name) \ + struct { \ + size_t size, used; \ + type *data; \ + } name + +#define init_heap (heap, _size, gfp) \Ummm.... so, essentially templates done in macros. Please don't do that. Definitely NACK on this.
I have a passionate... dislike of templates, but do you have any alternatives? I don't know any other way of implementing this that doesn't give up typechecking, and especially for the fifo code that typechecking is just too important to give up.
Quoted 20 lines
drivers/block/bcache/util.h line 164
Quoted 8 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_FIFO (type, name) \ + struct { \ + size_t front, back, size, mask; \ + type *data; \ + } name + +#define fifo_for_each (c, fifo) \Ditto. Templates are already yucky enough even with compiler support. IMHO, it's a horrible idea to try that with preprocessor in C.
drivers/block/bcache/util.h line 301
Quoted 5 lines
drivers/block/bcache/util.h new file +5
-0+#define DECLARE_ARRAY_ALLOCATOR (type, name, size) \ + struct { \ + type *freelist; \ + type data[size]; \ + } nameDitto.
Quoted 15 lines
drivers/block/bcache/util.h line 372
Quoted 13 lines
drivers/block/bcache/util.h new file +13
-0+#define strtoi_h (cp, res) \ + (__builtin_types_compatible_p(typeof(*res), int) \ + ? strtoint_h(cp, (void *) res) \ + :__builtin_types_compatible_p(typeof(*res), long) \ + ? strtol_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), long long) \ + ? strtoll_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned int) \ + ? strtouint_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned long) \ + ? strtoul_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned long long)\ + ? strtoull_h(cp, (void *) res) : -EINVAL)Is this *really* necessary?
This one might be excessive - but if we can implement memparse() + variable limit correctly that would certainly get rid of this.
Quoted 29 lines
Quoted 29 lines
drivers/block/bcache/util.h new file +28
-0+#define strtoul_safe (cp, var) \ +({ \ + unsigned long _v; \ + int _r = strict_strtoul(cp, 10, &_v); \ + if (!_r) \ + var = _v; \ + _r; \ +}) + +#define strtoul_safe_clamp (cp, var, min, max) \ +({ \ + unsigned long _v; \ + int _r = strict_strtoul(cp, 10, &_v); \ + if (!_r) \ + var = clamp_t(typeof(var), _v, min, max); \ + _r; \ +}) + +#define snprint (buf, size, var) \ + snprintf(buf, size, \ + __builtin_types_compatible_p(typeof(var), int) \ + ? "%i\n" : \ + __builtin_types_compatible_p(typeof(var), unsigned) \ + ? "%u\n" : \ + __builtin_types_compatible_p(typeof(var), long) \ + ? "%li\n" : \ + __builtin_types_compatible_p(typeof(var), unsigned long)\ + ? "%lu\n" : \
Quoted 8 lines
drivers/block/bcache/util.h line 407
Quoted 6 lines
drivers/block/bcache/util.h new file +6
-0+__builtin_types_compatible_p (typeof (var), int64_t) \ + ?"%lli\n" : \ +__builtin_types_compatible_p (typeof (var), uint64_t) \ + ?"%llu\n" : \ + __builtin_types_compatible_p(typeof(var), const char *) \ + ?"%s\n" :"%i\n" , var)Ditto.
I suppose I could take this out.
Re: [Bcache v13 09/16] Bcache: generic utility code
Quoted 1 line · Kent Overstreet
On Tue, May 22, 2012 at 02:17:06PM -0700, Tejun Heo wrote:
[]
Quoted 23 lines
Quoted 14 lines
Quoted 14 lines
drivers/block/bcache/util.c new file +14
-0+ssize_t hprint (char *buf,int64_t v) +{ +static const char units[] ="?kMGTPEZY" ; +char dec[3 ] ="" ; +int u, t =0 ; + +for (u =0 ; v >=1024 || v <=-1024 ; u++) { + t = v & ~(~0 <<10 ); + v >>=10 ; + } + +if (!u) +return sprintf (buf,"%llu" , v); +Quoted 8 lines
drivers/block/bcache/util.c line 91
Quoted 6 lines
drivers/block/bcache/util.c new file +6
-0+if (v <100 && v >-100 ) +sprintf (dec,".%i" , t /100 ); + +return sprintf (buf,"%lli%s%c" , v, dec, units[u]); +} +EXPORT_SYMBOL_GPL (hprint);Not your fault but maybe we want integer vsnprintf modifier for this.
Quoted 1 line
Yes.
or maybe yet another lib/vsprintf pointer extension like %pD with some descriptors after the %pD for type, width and precision.
PATCH 1/1 Add human-readable units modifier to vsnprintf()
Quoted 26 lines · Joe Perches
Quoted 1 line · Kent Overstreet
On Tue, May 22, 2012 at 02:17:06PM -0700, Tejun Heo wrote:
[]
Quoted 23 lines
Quoted 22 lines
Quoted 10 lines
(quoted diff) +10
-0+ssize_t hprint(char *buf, int64_t v) +{ + static const char units[] = "?kMGTPEZY"; + char dec[3] = ""; + int u, t = 0; + + for (u = 0; v >= 1024 || v <= -1024; u++) { + t = v & ~(~0 << 10); + v >>= 10; + }Review
Quoted 10 lines
(quoted diff) +10
-0+ + if (!u) + return sprintf(buf, "%llu", v); + + if (v < 100 && v > -100) + sprintf(dec, ".%i", t / 100); + + return sprintf(buf, "%lli%s%c", v, dec, units[u]); +} +EXPORT_SYMBOL_GPL(hprint);Not your fault but maybe we want integer vsnprintf modifier for this.
Quoted 5 lines
Quoted 1 line
Yes.
or maybe yet another lib/vsprintf pointer extension like %pD with some descriptors after the %pD for type, width and precision.
Integer modifier makes more sense to me - it's an integer you're printing in the first place, anyways...
This code seems to work - it produces the same output as my hprint(), anyways:
--- lib/vsprintf.c | 24 +++++++++++++++++++++++- 1 files changed, 23 insertions(+), 1 deletions(-)
lib/vsprintf.c +23 -1
@@ -219,6 +219,7 @@ char *put_dec(char *buf, unsigned long long num)#define LEFT 16/* left justified */ #define SMALL 32/* use lowercase in hex (must be 32 == 0x20) */ #define SPECIAL 64/* prefix hex with "0x", octal with "0" */ +#define HUNITS 128/* Human readable units, i.e. k/M/G/T */ enum format_type {FORMAT_TYPE_NONE ,/* Just a string part */
@@ -258,6 +259,7 @@ char *number(char *buf, char *end, unsigned long long num, {/* we are called with base 8, 10 or 16, only, thus don't need "G..." */ static const char digits[16 ] ="0123456789ABCDEF" ;/* "GHIJKLMNOPQRSTUVWXYZ"; */ +static const char units[] ="?kMGTPEZY" ;char tmp[66 ];char sign;
@@ -310,7 +312,26 @@ char *number(char *buf, char *end, unsigned long long num, num >>= shift; }while (num); }else {/* base 10 */ - i =put_dec (tmp, num) - tmp; +if (spec.flags &HUNITS ) { +int u, rem =0 ; + +for (u =0 ; num >=1024 ; u++) { + rem = num & ~(~0 <<10 ); + num >>=10 ; + } + +if (u) { + tmp[i++] = units[u]; + +if (num <100 ) { + rem /=100 ; + i =put_dec (tmp + i, rem) - tmp; + tmp[i++] ='.' ; + } + } + } + + i =put_dec (tmp + i, num) - tmp; }/* printing 100 using %2d gives "100", not "00" */
@@ -1002,6 +1023,7 @@ int format_decode(const char *fmt, struct printf_spec *spec)case ' ' : spec->flags |=SPACE ;break ;case '#' : spec->flags |=SPECIAL ;break ;case '0' : spec->flags |=ZEROPAD ;break ; +case 'h' : spec->flags |=HUNITS ;break ;default : found = false; }
Signature · 1 line
1.7.9.rc2
Re: [PATCH] Add human-readable units modifier to vsnprintf()
lib/vsprintf.c
Quoted 1 line · Kent Overstreet
lib/vsprintf.c +0
-0
[]
lib/vsprintf.c int format_decode(const char *fmt, struct printf_spec *spec) · line 1029
Quoted 8 lines
lib/vsprintf.c +1
-0@@ -1002,6 +1023,7 @@ int format_decode(const char *fmt, struct printf_spec *spec)case ' ' : spec->flags |=SPACE ;break ;case '#' : spec->flags |=SPECIAL ;break ;case '0' : spec->flags |=ZEROPAD ;break ; +case 'h' : spec->flags |=HUNITS ;break ;default : found = false; }
I think that doesn't work well because gcc __attribute__((format(printf, x, y))) verification fails. It's also possible to confuse it with printf's own 'h' / integer precision use.
$ cat h.c #include <stdio.h> #include <stdlib.h>
int main(int argc, char** argv)
{
printf("%h02u\n", 1);
return 0;
}
$ gcc h.c h.c: In function ‘main’: h.c:6:3: warning: unknown conversion type character ‘0’ in format [-Wformat]
Re: [PATCH] Add human-readable units modifier to vsnprintf()
Quoted 15 lines · Joe Perches
lib/vsprintf.c
Quoted 1 line · Kent Overstreet
lib/vsprintf.c +0
-0[]
lib/vsprintf.c int format_decode(const char *fmt, struct printf_spec *spec) · line 1029
Quoted 8 lines
lib/vsprintf.c +1
-0@@ -1002,6 +1023,7 @@ int format_decode(const char *fmt, struct printf_spec *spec)case ' ' : spec->flags |=SPACE ;break ;case '#' : spec->flags |=SPECIAL ;break ;case '0' : spec->flags |=ZEROPAD ;break ; +case 'h' : spec->flags |=HUNITS ;break ;default : found = false; }I think that doesn't work well because
gcc__attribute__((format(printf, x, y)))
verification fails.
Yeah, I already ran into that. I was looking through the gcc docs to see if there was a way to add modifiers (how is it done for the other kernel specific format strings?) but I haven't found anything yet
Quoted 3 lines
It's also possible
to confuse it with printf's own 'h' /
integer precision use.
Well, that's annoying. Any suggestions?
Quoted 16 lines
$ cat h.c #include <stdio.h> #include <stdlib.h>int
main(int argc, char** argv)
{
printf("%h02u\n", 1);
return 0;
}$ gcc h.c h.c: In function ‘main’: h.c:6:3: warning: unknown conversion type character ‘0’ in format [-Wformat]
Re: [PATCH] Add human-readable units modifier to vsnprintf()
Quoted 17 lines · Kent Overstreet
Quoted 15 lines · Joe Perches
lib/vsprintf.c
Quoted 1 line · Kent Overstreet
lib/vsprintf.c +0
-0[]
lib/vsprintf.c int format_decode(const char *fmt, struct printf_spec *spec) · line 1029
Quoted 8 lines
lib/vsprintf.c +1
-0@@ -1002,6 +1023,7 @@ int format_decode(const char *fmt, struct printf_spec *spec)case ' ' : spec->flags |=SPACE ;break ;case '#' : spec->flags |=SPECIAL ;break ;case '0' : spec->flags |=ZEROPAD ;break ; +case 'h' : spec->flags |=HUNITS ;break ;default : found = false; }I think that doesn't work well because
gcc__attribute__((format(printf, x, y)))
verification fails.
Quoted 3 lines
Yeah, I already ran into that. I was looking through the gcc docs to see if there was a way to add modifiers (how is it done for the other kernel specific format strings?) but I haven't found anything yet
It's done via %p extensions.
gcc doesn't care as long as the %p argument is a pointer.
Quoted 5 lines
Quoted 3 lines
It's also possible
to confuse it with printf's own 'h' /
integer precision use.Well, that's annoying. Any suggestions?
I gave you one already.
Use a new lib/vsprintf.c %p pointer extension like "%pD" or "%pH" and use a pointer to the type as the argument.
It's how it's done for an ipv4 address.
For instance:
be32 address = cpu_to_be32(0xc0a80101);
printk("%pI4" &address);emits "192.168.1.1"
Re: [PATCH] Add human-readable units modifier to vsnprintf()
Quoted 6 lines · Joe Perches
Quoted 3 lines
Yeah, I already ran into that. I was looking through the gcc docs to see if there was a way to add modifiers (how is it done for the other kernel specific format strings?) but I haven't found anything yet
It's done via %p extensions.
gcc doesn't care as long as the %p argument is a pointer.
Ahh
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines
It's also possible
to confuse it with printf's own 'h' /
integer precision use.Well, that's annoying. Any suggestions?
I gave you one already.
Missed the reason for your suggestion :)
Quoted 4 lines
Use a new
lib/vsprintf.c%p pointer extension like "%pD" or "%pH" and use a pointer to the type as the argument.
Only trouble is we lose the existing method of specifying the type of the integer. New flag character would make much more sense... if gcc could be persuaded.
Re: [PATCH] Add human-readable units modifier to vsnprintf()
Quoted 2 lines · Kent Overstreet
Only trouble is we lose the existing method of specifying the type of the integer.
Hi again Kent.
My first suggestion to you was:
---------------------
Quoted 3 lines
or maybe yet another lib/vsprintf pointer extension like %pD with some descriptors after the %pD for type, width and precision.
---------------------
For instance, internet addresses use pointer extensions %pI4 and %pI6 for ipv4 and ipv6 addresses.
Quoted 2 lines
New flag character would make much more sense... if gcc could be persuaded.
It can't be so persuaded though. Maybe a future gcc.
Tejun Heo
maintainerRe: [Bcache v13 09/16] Bcache: generic utility code
In reply to Kent OverstreetHey,
Quoted 6 lines · Kent Overstreet
On Tue, May 22, 2012 at 02:17:06PM -0700, Tejun Heo wrote:
As you can probably tell I'm strongly biased against duplication when it's a question of macros vs. duplication - but if we can make the commend backend code + wrapper approach work that's even better (smaller code size ftw). It'll be tricky to get u64 vs. int64 right, but I'll take a stab at it.
Duplication does suck but it's something which can be traded off too. It's case-by-case, I suppose.
Quoted 6 lines
Quoted 1 line
Also,
sprintf()sucks.Yeah, but considering the output is very bounded... you are technically correct, but I have a hard time caring. Making it a vsnprintf modifier would make that issue go away though. I'll see if I can figure out how to do it.
Block layer just fixed a uuid print buffer overflow bug caused by use of sprintf() - vsnprintf used more delimiters than the code calling it expected. When think kind of bugs trigger on stack buffer, they can be quite a headache. IMHO it's much more preferable to have an extra argument to limit buffer size in most cases.
Quoted 7 lines
Quoted 4 lines
This doesn't build. While loop var decl in for loop is nice, the kernel isn't built in c99 mode. If you want to enable c99 mode kernel-wide, you're welcome to try but you can't flip that per-component.
If patches to enable c99 mode would be accepted I'd be happy to work on them (provided I can find time, but I doubt it'd be much work).
I don't think it's gonna be acceptable. There isn't enough benefit at this point to justify the churn - the only thing I can think of is loop variable declaration which I don't think is enough.
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
+EXPORT_SYMBOL_GPL(parse_uuid);
Hmmm... can't we just enforce fixed format used by
vsnprintf()?I don't follow -
vsnprintf()is output, this is input...
Oh, I meant, can't we just require input to use the same exact format used by vsnprintf(). Kernel being kind on pparameter inputs isn't necessary and often leads to unexpected bugs anyway and if the format is fixed, single call to sscanf() is enough.
Quoted 10 lines
Quoted 9 lines
drivers/block/bcache/util.c line 249
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+ bv->bv_offset = base ? ((unsigned long ) base) %PAGE_SIZE :0 ; + goto start; + +for (; size; bio->bi_vcnt++, bv++) { + bv->bv_offset =0 ; +start: bv->bv_len =min_t (size_t,PAGE_SIZE - bv->bv_offset, + size);Please don't do this.
Quoted 4 lines
I don't really get your objection to jumping into the middle of loops... sure it shouldn't be the first way you try to write something, but I don't see anything inherently wrong with it. IMO it _can_ make the code easier to follow.
Easier for whom is the question, I guess. We have only a couple different patterns of goto in use in kernel. When usage deviates from those, people get annoyed. It's not like goto is a pretty thing to begin with.
Quoted 7 lines
Quoted 6 lines
These are now separate patch series, right? But, please don't use nested functions. Apart from being very unconventional (does gnu99 even allow this?), the implicit outer scope access is dangerous when mixed with context bouncing which is rather common in kernel. We (well, at least I) actually want cross stack frame accesses to be explicit.
Quoted 7 lines
Yes. I took out the nested function in the newer version (I never liked the way allocation worked in the old code and I finally figured out a sane way of doing it).
What do you mean by context bouncing? IME the problem with nested functions in the kernel is the trampolines gcc can silently generate (which is a serious problem).
I meant async execution. e.g. Passing nested function to workqueue. By the time the nested function executes the parent frame is already gone.
Quoted 6 lines
Quoted 1 line
How are these different from
bitmap_weight()?No important difference, I just never saw
bitmap_weight()before - I'll switch to that. (These are faster, but bigger and in the kernel I highly doubt we've a workload where the performance of popcount justifies the extra memory).
I don't think these will be actually faster. bitmap_weight() is pretty heavily optimized with constant shortcuts and arch dependent implementations.
Quoted 10 lines
Quoted 7 lines
drivers/block/bcache/util.h line 69
Quoted 5 lines
drivers/block/bcache/util.h new file +5
-0+static inline void SET_##name(type *k, uint64_t v) \ +{ \ + k->field &= ~(~((uint64_t) ~0 << size) << offset); \ + k->field |= v << offset; \ +}More function defining macros.
This one I'm not getting rid of unless you know a better way of doing it than I do - I use it all over the place and well, duplication.
I think that macro abuses tend to lead to worse code in general. Exotic stuff becomes less painful with magic macros which in turn make people use magic stuff even when more conventional mechanisms can do. Maybe these are simple enough. Maybe, I don't know.
Quoted 11 lines
Quoted 10 lines
drivers/block/bcache/util.h line 77
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_HEAP (type, name) \ + struct { \ + size_t size, used; \ + type *data; \ + } name + +#define init_heap (heap, _size, gfp) \Ummm.... so, essentially templates done in macros. Please don't do that. Definitely NACK on this.
Quoted 4 lines
I have a passionate... dislike of templates, but do you have any alternatives? I don't know any other way of implementing this that doesn't give up typechecking, and especially for the fifo code that typechecking is just too important to give up.
Unsure but either giving up type safety or implementing logic as functions and wrap them with macros doing typechecks would be better. Can't you use the existing prio_tree or prio_heap?
Quoted 4 lines
Quoted 1 line
Is this *really* necessary?
This one might be excessive - but if we can implement
memparse()+ variable limit correctly that would certainly get rid of this.
Is type-dependent variable limit really necessary? A lot of sysfs and other interface doesn't care about input validation as long as it doesn't break kernel.
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 09/16] Bcache: generic utility code
Quoted 5 lines · Tejun Heo
Block layer just fixed a uuid print buffer overflow bug caused by use of
sprintf()- vsnprintf used more delimiters than the code calling it expected. When think kind of bugs trigger on stack buffer, they can be quite a headache. IMHO it's much more preferable to have an extra argument to limit buffer size in most cases.
Well, with the removal of hprint() all uses of sprintf() that are left save one are in sysfs.c, where size would be PAGE_SIZE.
(The one in super.c I really ought to get rid of, though).
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
This doesn't build. While loop var decl in for loop is nice, the kernel isn't built in c99 mode. If you want to enable c99 mode kernel-wide, you're welcome to try but you can't flip that per-component.
Quoted 6 lines
Quoted 2 lines
If patches to enable c99 mode would be accepted I'd be happy to work on them (provided I can find time, but I doubt it'd be much work).
I don't think it's gonna be acceptable. There isn't enough benefit at this point to justify the churn - the only thing I can think of is loop variable declaration which I don't think is enough.
If it would end up being more than a couple patches I'd definitely agree. It would be handy though for compiling the kernel with clang - not that I care about running kernels built with clang, but I would happily take advantage of the compiler warnings.
But for the moment I think I'll chip away at c99 only stuff in bcache, most of the use cases I really don't care about (maybe all of them now).
Quoted 11 lines
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
+EXPORT_SYMBOL_GPL(parse_uuid);
Hmmm... can't we just enforce fixed format used by
vsnprintf()?I don't follow -
vsnprintf()is output, this is input...Oh, I meant, can't we just require input to use the same exact format used by
vsnprintf(). Kernel being kind on pparameter inputs isn't necessary and often leads to unexpected bugs anyway and if the format is fixed, single call tosscanf()is enough.
Ahh - that is a good idea. Yeah, I'll have a look.
Quoted 32 lines
Quoted 11 lines
Quoted 10 lines
drivers/block/bcache/util.c line 249
Quoted 8 lines
drivers/block/bcache/util.c new file +7
-0+ bv->bv_offset = base ? ((unsigned long ) base) %PAGE_SIZE :0 ; + goto start; + +for (; size; bio->bi_vcnt++, bv++) { + bv->bv_offset =0 ; +start: bv->bv_len =min_t (size_t,PAGE_SIZE - bv->bv_offset, + size);Please don't do this.
Quoted 4 lines
I don't really get your objection to jumping into the middle of loops... sure it shouldn't be the first way you try to write something, but I don't see anything inherently wrong with it. IMO it _can_ make the code easier to follow.
Easier for whom is the question, I guess. We have only a couple different patterns of goto in use in kernel. When usage deviates from those, people get annoyed. It's not like goto is a pretty thing to begin with.
Quoted 11 lines
Quoted 6 lines
These are now separate patch series, right? But, please don't use nested functions. Apart from being very unconventional (does gnu99 even allow this?), the implicit outer scope access is dangerous when mixed with context bouncing which is rather common in kernel. We (well, at least I) actually want cross stack frame accesses to be explicit.
Yes. I took out the nested function in the newer version (I never liked the way allocation worked in the old code and I finally figured out a sane way of doing it).
Quoted 7 lines
Quoted 3 lines
What do you mean by context bouncing? IME the problem with nested functions in the kernel is the trampolines gcc can silently generate (which is a serious problem).
I meant async execution. e.g. Passing nested function to workqueue. By the time the nested function executes the parent frame is already gone.
Oh, that would be bad :)
Quoted 11 lines
Quoted 7 lines
Quoted 2 lines
How are these different from
bitmap_weight()?No important difference, I just never saw
bitmap_weight()before - I'll switch to that. (These are faster, but bigger and in the kernel I highly doubt we've a workload where the performance of popcount justifies the extra memory).I don't think these will be actually faster.
bitmap_weight()is pretty heavily optimized with constant shortcuts and arch dependent implementations.
Ah, I didn't notice the arch specific versions in my brief cscoping, just an ffs() based one.
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
drivers/block/bcache/util.h line 69
Quoted 6 lines
drivers/block/bcache/util.h new file +5
-0+static inline void SET_##name(type *k, uint64_t v) \ +{ \ + k->field &= ~(~((uint64_t) ~0 << size) << offset); \ + k->field |= v << offset; \ +}More function defining macros.
Quoted 7 lines
Quoted 2 lines
This one I'm not getting rid of unless you know a better way of doing it than I do - I use it all over the place and well, duplication.
I think that macro abuses tend to lead to worse code in general. Exotic stuff becomes less painful with magic macros which in turn make people use magic stuff even when more conventional mechanisms can do. Maybe these are simple enough. Maybe, I don't know.
Well, I'd really prefer it if C bitfields were well defined enough to use them for an on disk format, but... they aren't. That's all it's implementing.
Quoted 11 lines
Quoted 11 lines
Quoted 10 lines
drivers/block/bcache/util.h line 77
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_HEAP (type, name) \ + struct { \ + size_t size, used; \ + type *data; \ + } name + +#define init_heap (heap, _size, gfp) \Ummm.... so, essentially templates done in macros. Please don't do that. Definitely NACK on this.
Quoted 8 lines
Quoted 4 lines
I have a passionate... dislike of templates, but do you have any alternatives? I don't know any other way of implementing this that doesn't give up typechecking, and especially for the fifo code that typechecking is just too important to give up.
Unsure but either giving up type safety or implementing logic as functions and wrap them with macros doing typechecks would be better. Can't you use the existing
prio_treeorprio_heap?
I'd definitely be fine with implementing both the heap and the fifo code as functions wrapped in macros that provided the typechecking.
The existing prio_heap code isn't sufficient to replace my heap code - it's missing heap_sift() and heap_full() (well, heap_full() could be open coded with prio_heap).
Suppose it wouldn't be that much work to add that to the existing prio_heap code and wrap it in some typechecking macros.
Quoted 3 lines
Is type-dependent variable limit really necessary? A lot of sysfs and other interface doesn't care about input validation as long as it doesn't break kernel.
For raw integers I wouldn't care much, but where integers with human readable units are accepted I'd really hate to lose the input validation as it's really easy for a user to accidently overflow an integer.
Tejun Heo
maintainerRe: [Bcache v13 09/16] Bcache: generic utility code
Hello,
Quoted 8 lines · Kent Overstreet
Quoted 4 lines
I think that macro abuses tend to lead to worse code in general. Exotic stuff becomes less painful with magic macros which in turn make people use magic stuff even when more conventional mechanisms can do. Maybe these are simple enough. Maybe, I don't know.
Well, I'd really prefer it if C bitfields were well defined enough to use them for an on disk format, but... they aren't. That's all it's implementing.
AFAIK, there are two issues with bitfields - layout changing depending on arch / compiler and the recent gcc bug (yes, it's agreed to be a bug) which caused wider RMW cycle leading to corruption of adjacent memory when the type of the bitfield is less than machine word size. It's not like the defined macros can escape the former. In fact, I think the current code is already wrong - I don't see __le/beNN or byte swapping going on. We already __{LITTLE|BIG)_ENDIAN_BITFIELD macros to deal with these, so I think it would be much better to use them instead of the macros.
For the latter, well, it's a compiler bug and as long as you stick to machine-word multiples - which I think already is the case, it shouldn't be a problem. There isn't much point in distorting the code for a compiler bug. If necessary, apply workaround which can be removed / conditionalized later.
Quoted 7 lines
Quoted 3 lines
Unsure but either giving up type safety or implementing logic as functions and wrap them with macros doing typechecks would be better. Can't you use the existing
prio_treeorprio_heap?I'd definitely be fine with implementing both the heap and the fifo code as functions wrapped in macros that provided the typechecking.
Quoted 6 lines
The existing
prio_heapcode isn't sufficient to replace my heap code - it's missingheap_sift()andheap_full()(well,heap_full()could be open coded withprio_heap).Suppose it wouldn't be that much work to add that to the existing
prio_heapcode and wrap it in some typechecking macros.
Yeah, that would be much preferable. Also, while type checking is a nice thing, I don't think it's a must. It's C and all the basic data structures we use don't have typecheck - container_of() doesn't have dynamic typechecking which applies to all node based data structures including list and all the trees, prio_heap doesn't, quicksort doesn't. I don't see why fifo should be any different. Type checking is nice to have but it isn't a must. I think it's actually quite overrated - lack of it seldomly causes actual headache.
Quoted 7 lines
Quoted 3 lines
Is type-dependent variable limit really necessary? A lot of sysfs and other interface doesn't care about input validation as long as it doesn't break kernel.
For raw integers I wouldn't care much, but where integers with human readable units are accepted I'd really hate to lose the input validation as it's really easy for a user to accidently overflow an integer.
I don't think it matters all that much but if you're really concerned maybe make a variant of memparse with min/max range arguments?
Thanks.
Signature · 1 line
tejun
Re: [dm-devel] [Bcache v13 09/16] Bcache: generic utility code
In reply to Tejun HeoQuoted 11 lines · Tejun Heo
Hello, Kent.
I've been reading the code and am still very far from understanding the whole thing but here are general impressions upto this point.
* I started reading from
get_bucket(), mostly because I wasn't sure where to start. It would be very helpful to the reviewers and future participants if there's a design doc and more comments (much more). The code is on the big side and the problem is exacerbated by use of somewhat unconventional constructs and conventions and lack of documentation and comments.
I totally agreed. There is a lot going on and design doc is going to help a lot while reviewing.
Quoted 1 line
* Too many smart macros. Macros suck. Smart ones double suck.
I had the same impression when I tried to read the code last. Too many macros and it makes reading code really difficult.
[..]
Quoted 3 lines
* Somewhat related to the above, I'm not a fan of super-verbose symbols but I *hope* that the variable names were just a tiny bit more descriptive. At least, the ones used as arguments.
Another thing is that keep variable names consistent. Last time I looked, same name "c" was being used to represent cached_dev or cache_set or something else too. If we keep variable name same to represent same data structure, it becomes easier to read the code. I was totally lost and always had to go back to figure out what "c" is representing, what "d" is representing etc.
Thanks
Vivek
Re: [dm-devel] [Bcache v13 09/16] Bcache: generic utility code
Quoted 6 lines · Vivek Goyal
Another thing is that keep variable names consistent. Last time I looked, same name "c" was being used to represent
cached_devorcache_setor something else too. If we keep variable name same to represent same data structure, it becomes easier to read the code. I was totally lost and always had to go back to figure out what "c" is representing, what "d" is representing etc.
Yeah, those examples are both cases where the struct was split - struct cache was broken into struct cache and cache_set, struct bcache_device was split off from cached_dev.
I've been fixing things as I go along, but probably I should just do them all at once and be done with it, damn the code churn.
Tejun Heo
maintainerRe: [Bcache v13 09/16] Bcache: generic utility code
In reply to Kent OverstreetHello, Kent.
I've been reading the code and am still very far from understanding the whole thing but here are general impressions upto this point.
* I started reading from get_bucket(), mostly because I wasn't sure where to start. It would be very helpful to the reviewers and future participants if there's a design doc and more comments (much more). The code is on the big side and the problem is exacerbated by use of somewhat unconventional constructs and conventions and lack of documentation and comments.
* It's unfortunate but true that different parts of kernel are held to different standards in terms of conformance to overall style and structure. Code for obscure device, for example, is allowed to be way more weird than common core code. That said, one thing which is generally frowned upon is bringing in lots of foreign constructs because that tends to make the code much more difficult to follow for other people than plain silly code. Even for drivers, code with a lot of wrappers and foreign constructs (synchronization, data structure...) tend to get nacked and are recommended to convert to existing infrastructure even if that ends up, say, more verbose code.
It is also true that new constructs and mechanisms are constantly being added to kernel and the ones used by bcache could be useful enough to have; however, with my currently limited understanding, the oddities seem too much.
* Too many smart macros. Macros suck. Smart ones double suck.
* For file internal ones, it's okay to be somewhat relaxed with namespace separation but bcache code seems to be going too far. It also becomes a lot worse with macros as warnings and errors from compiler get much more difficult to decipher. e.g. things like next() and end() macros are calling for trouble.
* Somewhat related to the above, I'm not a fan of super-verbose symbols but I *hope* that the variable names were just a tiny bit more descriptive. At least, the ones used as arguments.
* The biggest thing that I dislike about closure is that it's not clear what it does when I see one. Is it a refcount, synchronization construct or asynchronous execution mechanism? To me, it seems like a construct with too many purposes and too much abstraction, which tends to make it difficult to understand, easy to misuse and difficult to debug. IMHO, constructs like this could seem very attractive to small group of people with certain concepts firmly on their minds; however, one man's paradise is another man's hell and we're often better off without either. In many ways, closure feels like kobject and friends.
I'd like to recommend using something more concerete even if that means more verbosity. ie. if there are lots of bio sequencing going on, implement and use bio sequencer. That's way more concerete and concerete stuff tends to be much easier to wrap one's head around mostly because it's much easier to agree upon for different people and they don't have to keep second-guessing what the original author would have on his/her mind while implementing it.
The problem that closure poses is that stuff like this adds a lot to on-going maintenance overhead. bcache could be very successful and something we continue to use for decades but it might as well be mostly meaningless in five years due to developments in storage technology. It'll still have to be maintained and you might not be around for the task and we don't want people to face code body which depends on alien abstract synchronization and async constructs while updating, say, block or workqueue API. Code may be doing complex things but they still better use the constructs and follow the conventions that the rest of the kernel is using as much as possible.
Again, my understanding of the code base is quite limited at this point so I might change my mind but these were the impressions I got upto this point. I'll keep on reading. Specific comments follow.
Quoted 6 lines · Kent Overstreet
drivers/block/bcache/util.c new file +6
-0+#define simple_strtoint (c, end, base) simple_strtol(c, end, base) +#define simple_strtouint (c, end, base) simple_strtoul(c, end, base) + +#define STRTO_H (name, type) \ +int name ## _h(const char *cp, type *res) \ +{ \
...
drivers/block/bcache/util.c line 70
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+} \ +EXPORT_SYMBOL_GPL(name ## _h); + +STRTO_H(strtoint, int) +STRTO_H(strtouint, unsigned int) +STRTO_H(strtoll, long long) +STRTO_H(strtoull, unsigned long long)
Function defining macros == bad. Can't it be implemented with a common backend function with wrappers implementing limits? Why not use memparse()?
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ssize_t hprint (char *buf,int64_t v) +{ +static const char units[] ="?kMGTPEZY" ; +char dec[3 ] ="" ; +int u, t =0 ; + +for (u =0 ; v >=1024 || v <=-1024 ; u++) { + t = v & ~(~0 <<10 ); + v >>=10 ; + }
drivers/block/bcache/util.c line 91
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ +if (!u) +return sprintf (buf,"%llu" , v); + +if (v <100 && v >-100 ) +sprintf (dec,".%i" , t /100 ); + +return sprintf (buf,"%lli%s%c" , v, dec, units[u]); +} +EXPORT_SYMBOL_GPL (hprint);
Not your fault but maybe we want integer vsnprintf modifier for this. Also, sprintf() sucks.
drivers/block/bcache/util.c line 104
Quoted 12 lines
drivers/block/bcache/util.c new file +12
-0+ssize_t sprint_string_list (char *buf,const char *const list[], +size_t selected) +{ +char *out = buf; + +for (size_t i =0 ; list[i]; i++) + out +=sprintf (out, i == selected ?"[%s] " :"%s " , list[i]); + + out[-1 ] ='\n' ; +return out - buf; +} +EXPORT_SYMBOL_GPL (sprint_string_list);
sprintf() sucks.
drivers/block/bcache/util.c line 130
Quoted 3 lines
drivers/block/bcache/util.c new file +3
-0+bool is_zero (const char *p,size_t n) +{ +for (size_t i =0 ; i < n; i++)
This doesn't build. While loop var decl in for loop is nice, the kernel isn't built in c99 mode. If you want to enable c99 mode kernel-wide, you're welcome to try but you can't flip that per-component.
Quoted 24 lines
drivers/block/bcache/util.c new file +23
-0+if (p[i]) +return false; +return true; +} +EXPORT_SYMBOL_GPL (is_zero); +int parse_uuid (const char *s,char *uuid) +{ +size_t i, j, x; +memset (uuid,0 ,16 ); + +for (i =0 , j =0 ; + i <strspn (s,"-0123456789:ABCDEFabcdef" ) && j <32 ; + i++) { + x = s[i] |32 ; + +switch (x) { + case '0'...'9': + x -='0' ; +break ; + case 'a'...'f': + x -='a' -10 ; +break ; +default :
drivers/block/bcache/util.c line 164
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+continue ; + } + +if (!(j &1 )) + x <<=4 ; + uuid[j++ >>1 ] |= x; + } +return i; +} +EXPORT_SYMBOL_GPL (parse_uuid);
Hmmm... can't we just enforce fixed format used by vsnprintf()?
Quoted 13 lines
drivers/block/bcache/util.c new file +13
-0+void time_stats_update (struct time_stats *stats,uint64_t start_time) +{ +uint64_t now =local_clock (); +uint64_t duration =time_after64 (now, start_time) + ? now - start_time :0 ; +uint64_t last =time_after64 (now, stats->last) + ? now - stats->last :0 ; + + stats->max_duration =max (stats->max_duration, duration); + +if (stats->last) { +ewma_add (stats->average_duration, duration,8 ,8 ); +
drivers/block/bcache/util.c line 188
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+if (stats->average_frequency) +ewma_add (stats->average_frequency, last,8 ,8 ); +else + stats->average_frequency = last <<8 ; + }else + stats->average_duration = duration <<8 ; + + stats->last = now ?:1 ; +} +EXPORT_SYMBOL_GPL (time_stats_update);
if {
} else {
}
Even if one of the branches is single line. Also, I'm not sure this (and a few others) is general enough to be common utility.
drivers/block/bcache/util.c line 200
Quoted 11 lines
drivers/block/bcache/util.c new file +11
-0+#ifdef CONFIG_BCACHE_LATENCY_DEBUG +unsigned latency_warn_ms; +#endif + +#ifdef CONFIG_BCACHE_EDEBUG + +static void check_bio (struct bio *bio) +{ +unsigned i, size =0 ; +struct bio_vec *bv; +struct request_queue *q =bdev_get_queue (bio->bi_bdev);
New line missing.
Quoted 12 lines
drivers/block/bcache/util.c new file +12
-0+BUG_ON (!bio->bi_vcnt); +BUG_ON (!bio->bi_size); + +bio_for_each_segment (bv, bio, i) + size += bv->bv_len; + +BUG_ON (size != bio->bi_size); +BUG_ON (size >queue_max_sectors (q) <<9 ); + +blk_recount_segments (q, bio); +BUG_ON (bio->bi_phys_segments >queue_max_segments (q)); +}
...
drivers/block/bcache/util.c line 241
Quoted 8 lines
drivers/block/bcache/util.c new file +8
-0+void bio_map (struct bio *bio,void *base) +{ +size_t size = bio->bi_size; +struct bio_vec *bv = bio->bi_inline_vecs; + +BUG_ON (!bio->bi_size); + bio->bi_vcnt =0 ; + bio->bi_io_vec = bv;
I'd prefer these assignments didn't have indentations.
drivers/block/bcache/util.c line 249
Quoted 7 lines
drivers/block/bcache/util.c new file +7
-0+ bv->bv_offset = base ? ((unsigned long ) base) %PAGE_SIZE :0 ; + goto start; + +for (; size; bio->bi_vcnt++, bv++) { + bv->bv_offset =0 ; +start: bv->bv_len =min_t (size_t,PAGE_SIZE - bv->bv_offset, + size);
Please don't do this.
Quoted 4 lines
drivers/block/bcache/util.c new file +4
-0+if (base) { + bv->bv_page =is_vmalloc_addr (base) + ?vmalloc_to_page (base) + :virt_to_page (base);
drivers/block/bcache/util.c line 263
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ + base += bv->bv_len; + } + + size -= bv->bv_len; + } +} +EXPORT_SYMBOL_GPL (bio_map); + +#undef bio_alloc_pages
Why is this undef necessary? If it's necessary, why isn't there any explanation?
drivers/block/bcache/util.c line 267
Quoted 4 lines
drivers/block/bcache/util.c new file +4
-0+int bio_alloc_pages (struct bio *bio,gfp_t gfp) +{ +int i; +struct bio_vec *bv;
New line missing.
Quoted 18 lines
drivers/block/bcache/util.c new file +18
-0+bio_for_each_segment (bv, bio, i) { + bv->bv_page =alloc_page (gfp); +if (!bv->bv_page) { +while (bv-- != bio->bi_io_vec + bio->bi_idx) +__free_page (bv->bv_page); +return -ENOMEM ; + } + } +return 0 ; +} +EXPORT_SYMBOL_GPL (bio_alloc_pages); + +struct bio *bio_split_front (struct bio *bio,int sectors,bio_alloc_fn *_alloc, +gfp_t gfp,struct bio_set *bs) +{ +unsigned idx, vcnt =0 , nbytes = sectors <<9 ; +struct bio_vec *bv; +struct bio *ret =NULL ;
drivers/block/bcache/util.c line 295
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+ +struct bio *alloc (int n) + { +if (bs) +return bio_alloc_bioset (gfp, n, bs); +else if (_alloc) +return _alloc (gfp, n); +else +return bio_kmalloc (gfp, n); + }
These are now separate patch series, right? But, please don't use nested functions. Apart from being very unconventional (does gnu99 even allow this?), the implicit outer scope access is dangerous when mixed with context bouncing which is rather common in kernel. We (well, at least I) actually want cross stack frame accesses to be explicit.
Quoted 17 lines
drivers/block/bcache/util.c new file +17
-0+unsigned popcount_64 (uint64_t x) +{ +static const uint64_t m1 =0x5555555555555555LLU ; +static const uint64_t m2 =0x3333333333333333LLU ; +static const uint64_t m4 =0x0f0f0f0f0f0f0f0fLLU ; +static const uint64_t h01 =0x0101010101010101LLU ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>56 ; +} +EXPORT_SYMBOL (popcount_64); + +unsigned popcount_32 (uint32_t x) +{ +static const uint32_t m1 =0x55555555 ;
drivers/block/bcache/util.c line 572
Quoted 10 lines
drivers/block/bcache/util.c new file +10
-0+static const uint32_t m2 =0x33333333 ; +static const uint32_t m4 =0x0f0f0f0f ; +static const uint32_t h01 =0x01010101 ; + + x -= (x >>1 ) & m1; + x = (x & m2) + ((x >>2 ) & m2); + x = (x + (x >>4 )) & m4; +return (x * h01) >>24 ; +} +EXPORT_SYMBOL (popcount_32);
How are these different from bitmap_weight()?
drivers/block/bcache/util.h line 16
Quoted 4 lines
drivers/block/bcache/util.h new file +4
-0+#ifndef USHRT_MAX +#define USHRT_MAX ((u16)(~0U)) +#define SHRT_MAX ((s16)(USHRT_MAX>>1)) +#endif
...
These compat macros can be removed for upstream submission, right?
drivers/block/bcache/util.h line 69
Quoted 9 lines
drivers/block/bcache/util.h new file +9
-0+#define BITMASK (name, type, field, offset, size) \ +static inline uint64_t name(const type *k) \ +{ return (k->field >> offset) & ~(((uint64_t) ~0) << size); } \ + \ +static inline void SET_##name(type *k, uint64_t v) \ +{ \ + k->field &= ~(~((uint64_t) ~0 << size) << offset); \ + k->field |= v << offset; \ +}
More function defining macros.
drivers/block/bcache/util.h line 77
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_HEAP (type, name) \ + struct { \ + size_t size, used; \ + type *data; \ + } name + +#define init_heap (heap, _size, gfp) \
Ummm.... so, essentially templates done in macros. Please don't do that. Definitely NACK on this.
drivers/block/bcache/util.h line 164
Quoted 7 lines
drivers/block/bcache/util.h new file +7
-0+#define DECLARE_FIFO (type, name) \ + struct { \ + size_t front, back, size, mask; \ + type *data; \ + } name + +#define fifo_for_each (c, fifo) \
Ditto. Templates are already yucky enough even with compiler support. IMHO, it's a horrible idea to try that with preprocessor in C.
drivers/block/bcache/util.h line 301
Quoted 5 lines
drivers/block/bcache/util.h new file +5
-0+#define DECLARE_ARRAY_ALLOCATOR (type, name, size) \ + struct { \ + type *freelist; \ + type data[size]; \ + } name
Ditto.
drivers/block/bcache/util.h line 372
Quoted 13 lines
drivers/block/bcache/util.h new file +13
-0+#define strtoi_h (cp, res) \ + (__builtin_types_compatible_p(typeof(*res), int) \ + ? strtoint_h(cp, (void *) res) \ + :__builtin_types_compatible_p(typeof(*res), long) \ + ? strtol_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), long long) \ + ? strtoll_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned int) \ + ? strtouint_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned long) \ + ? strtoul_h(cp, (void *) res) \ + : __builtin_types_compatible_p(typeof(*res), unsigned long long)\ + ? strtoull_h(cp, (void *) res) : -EINVAL)
Is this *really* necessary?
Quoted 24 lines
drivers/block/bcache/util.h new file +24
-0+#define strtoul_safe (cp, var) \ +({ \ + unsigned long _v; \ + int _r = strict_strtoul(cp, 10, &_v); \ + if (!_r) \ + var = _v; \ + _r; \ +}) + +#define strtoul_safe_clamp (cp, var, min, max) \ +({ \ + unsigned long _v; \ + int _r = strict_strtoul(cp, 10, &_v); \ + if (!_r) \ + var = clamp_t(typeof(var), _v, min, max); \ + _r; \ +}) + +#define snprint (buf, size, var) \ + snprintf(buf, size, \ + __builtin_types_compatible_p(typeof(var), int) \ + ? "%i\n" : \ + __builtin_types_compatible_p(typeof(var), unsigned) \ + ? "%u\n" : \
drivers/block/bcache/util.h line 407
Quoted 10 lines
drivers/block/bcache/util.h new file +10
-0+__builtin_types_compatible_p (typeof (var), long) \ + ?"%li\n" : \ + __builtin_types_compatible_p(typeof(var), unsigned long)\ + ?"%lu\n" : \ +__builtin_types_compatible_p (typeof (var), int64_t) \ + ?"%lli\n" : \ +__builtin_types_compatible_p (typeof (var), uint64_t) \ + ?"%llu\n" : \ + __builtin_types_compatible_p(typeof(var), const char *) \ + ?"%s\n" :"%i\n" , var)
Ditto.
I'm gonna stop here. It should be pretty clear what I'm bitching about by now. :) Please make it C and, better, kernel C.
Thanks.
--
tejun
PATCH v13 10/16 bcache: Superblock/initialization/sysfs code
In reply to Kent Overstreet--- drivers/block/bcache/stats.c | 243 +++++ drivers/block/bcache/stats.h | 58 ++ drivers/block/bcache/super.c | 2000 ++++++++++++++++++++++++++++++++++++++++++ drivers/block/bcache/sysfs.c | 802 +++++++++++++++++ drivers/block/bcache/sysfs.h | 99 +++ 5 files changed, 3202 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/stats.c create mode 100644 drivers/block/bcache/stats.h create mode 100644 drivers/block/bcache/super.c create mode 100644 drivers/block/bcache/sysfs.c create mode 100644 drivers/block/bcache/sysfs.h
drivers/block/bcache/stats.c new file +243 -0
@@ -0,0 +1,243 @@ +#include "bcache.h" +#include "stats.h" +#include "btree.h" +#include "request.h" +#include "sysfs.h" + +/* We keep absolute totals of various statistics, and addionally a set of three +* rolling averages. +* +* Every so often, a timer goes off and rescales the rolling averages. +* accounting_rescale[] is how many times the timer has to go off before we +* rescale each set of numbers; that gets us half lives of 5 minutes, one hour, +* and one day. +* +* accounting_delay is how often the timer goes off - 22 times in 5 minutes, +* and accounting_weight is what we use to rescale: +* +* pow(31 / 32, 22) ~= 1/2 +* +* So that we don't have to increment each set of numbers every time we (say) +* get a cache hit, we increment a single atomic_t in acc->collector, and when +* the rescale function runs it resets the atomic counter to 0 and adds its +* old value to each of the exported numbers. +* +* To reduce rounding error, the numbers in struct cache_stats are all +* stored left shifted by 16, and scaled back in the sysfs show() function. +*/ + +static const unsigned DAY_RESCALE =288 ; +static const unsigned HOUR_RESCALE =12 ; +static const unsigned FIVE_MINUTE_RESCALE =1 ; +static const unsigned accounting_delay = (HZ *300 ) /22 ; +static const unsigned accounting_weight =32 ; + +/* sysfs reading/writing */ + +read_attribute (cache_hits); +read_attribute (cache_misses); +read_attribute (cache_bypass_hits); +read_attribute (cache_bypass_misses); +read_attribute (cache_hit_ratio); +read_attribute (cache_readaheads); +read_attribute (cache_miss_collisions); +read_attribute (bypassed); + +static struct attribute *accounting_files[] = { + &sysfs_cache_hits, + &sysfs_cache_misses, + &sysfs_cache_bypass_hits, + &sysfs_cache_bypass_misses, + &sysfs_cache_hit_ratio, + &sysfs_cache_readaheads, + &sysfs_cache_miss_collisions, + &sysfs_bypassed, +NULL +}; + +ssize_t cache_stats_show (struct kobject *kobj, +struct attribute *attr, +char *buf) { +struct cache_stats *s = + container_of(kobj, struct cache_stats, kobj); +#define var (stat) (s->stat >> 16) +var_print (cache_hits); +var_print (cache_misses); +var_print (cache_bypass_hits); +var_print (cache_bypass_misses); + +sysfs_print (cache_hit_ratio, +DIV_SAFE (var (cache_hits) *100 , +var (cache_hits) +var (cache_misses))); + +var_print (cache_readaheads); +var_print (cache_miss_collisions); +sysfs_hprint (bypassed,var (sectors_bypassed) <<9 ); +#undef var +return 0 ; +} + +/* kobjects */ + +static void unregister_fake (struct kobject *k) +{ +} + +static const struct sysfs_ops accounting_ops = { + .show = cache_stats_show, + .store =NULL +}; +static struct kobj_type accounting_obj = { + .release = unregister_fake, + .sysfs_ops = &accounting_ops, + .default_attrs = accounting_files +}; + +static void scale_accounting (unsigned long data); + +void init_cache_accounting (struct cache_accounting *acc,struct closure *parent) +{ +kobject_init (&acc->total.kobj, &accounting_obj); +kobject_init (&acc->five_minute.kobj, &accounting_obj); +kobject_init (&acc->hour.kobj, &accounting_obj); +kobject_init (&acc->day.kobj, &accounting_obj); + +closure_init (&acc->cl, parent); +init_timer (&acc->timer); + acc->timer.expires = jiffies + accounting_delay; + acc->timer.data = (unsigned long ) acc; + acc->timer.function = scale_accounting; +add_timer (&acc->timer); +} + +int add_cache_accounting_kobjs (struct cache_accounting *acc, +struct kobject *parent) +{ +int ret =kobject_add (&acc->total.kobj, parent, +"stats_total" ); + ret = ret ?:kobject_add (&acc->five_minute.kobj, parent, +"stats_five_minute" ); + ret = ret ?:kobject_add (&acc->hour.kobj, parent, +"stats_hour" ); + ret = ret ?:kobject_add (&acc->day.kobj, parent, +"stats_day" ); +return ret; +} + +void clear_stats (struct cache_accounting *acc) +{ +memset (&acc->total.cache_hits, +0 , +sizeof (unsigned long ) *7 ); +} + +void destroy_cache_accounting (struct cache_accounting *acc) +{ +kobject_put (&acc->total.kobj); +kobject_put (&acc->five_minute.kobj); +kobject_put (&acc->hour.kobj); +kobject_put (&acc->day.kobj); + +atomic_set (&acc->closing,1 ); +if (del_timer_sync (&acc->timer)) +closure_return (&acc->cl); +} + +/* EWMA scaling */ + +void scale_stat (unsigned long *stat) +{ + *stat =ewma_add (*stat,0 , accounting_weight,0 ); +} + +void scale_stats (struct cache_stats *stats,unsigned long rescale_at) +{ +if (++stats->rescale == rescale_at) { + stats->rescale =0 ; +scale_stat (&stats->cache_hits); +scale_stat (&stats->cache_misses); +scale_stat (&stats->cache_bypass_hits); +scale_stat (&stats->cache_bypass_misses); +scale_stat (&stats->cache_readaheads); +scale_stat (&stats->cache_miss_collisions); +scale_stat (&stats->sectors_bypassed); + } +} + +void scale_accounting (unsigned long data) +{ +struct cache_accounting *acc = (struct cache_accounting *) data; + +#define move_stat (name) do { \ + unsigned long t = atomic_xchg(&acc->collector.name, 0); \ + t <<= 16; \ + acc->five_minute.name += t; \ + acc->hour.name += t; \ + acc->day.name += t; \ + acc->total.name += t; \ +} while (0) + +move_stat (cache_hits); +move_stat (cache_misses); +move_stat (cache_bypass_hits); +move_stat (cache_bypass_misses); +move_stat (cache_readaheads); +move_stat (cache_miss_collisions); +move_stat (sectors_bypassed); + +scale_stats (&acc->total,0 ); +scale_stats (&acc->day,DAY_RESCALE ); +scale_stats (&acc->hour,HOUR_RESCALE ); +scale_stats (&acc->five_minute,FIVE_MINUTE_RESCALE ); + + acc->timer.expires += accounting_delay; + +if (!atomic_read (&acc->closing)) +add_timer (&acc->timer); +else +closure_return (&acc->cl); +} + +static void mark_cache_stats (struct cache_stat_collector *stats, +bool hit,bool bypass) +{ +if (!bypass) +if (hit) +atomic_inc (&stats->cache_hits); +else +atomic_inc (&stats->cache_misses); +else +if (hit) +atomic_inc (&stats->cache_bypass_hits); +else +atomic_inc (&stats->cache_bypass_misses); +} + +void mark_cache_accounting (struct search *s,bool hit,bool bypass) +{ + struct cached_dev *dc = container_of(s->op.d, struct cached_dev, disk); +mark_cache_stats (&dc->accounting.collector, hit, bypass); +mark_cache_stats (&s->op.d->c->accounting.collector, hit, bypass); +#ifdef CONFIG_CGROUP_BCACHE +mark_cache_stats (&(bio_to_cgroup (s->orig_bio)->stats), hit, bypass); +#endif +} + +void mark_cache_readahead (struct search *s) +{ + struct cached_dev *dc = container_of(s->op.d, struct cached_dev, disk); +atomic_inc (&dc->accounting.collector.cache_readaheads); +atomic_inc (&s->op.d->c->accounting.collector.cache_readaheads); +} +void mark_cache_miss_collision (struct btree_op *op) +{ + struct cached_dev *dc = container_of(op->d, struct cached_dev, disk); +atomic_inc (&dc->accounting.collector.cache_miss_collisions); +atomic_inc (&op->d->c->accounting.collector.cache_miss_collisions); +} +void mark_sectors_bypassed (struct search *s,int sectors) +{ + struct cached_dev *dc = container_of(s->op.d, struct cached_dev, disk); +atomic_add (sectors, &dc->accounting.collector.sectors_bypassed); +atomic_add (sectors, &s->op.d->c->accounting.collector.sectors_bypassed); +}
drivers/block/bcache/stats.h new file +58 -0
58 lines not on this page · raw message
drivers/block/bcache/super.c new file +2000 -0
2,000 lines not on this page · raw message
drivers/block/bcache/sysfs.c new file +802 -0
699 lines not on this page · raw message
drivers/block/bcache/sysfs.h new file +99 -0
99 lines not on this page · raw message
The rest of this diff is not shown. View the raw message.
Signature · 1 line
1.7.9.rc2
Part of the diff is not shown here. View the raw message.
PATCH v13 11/16 bcache: Core btree code
In reply to Kent Overstreet--- drivers/block/bcache/bcache.h | 839 +++++++++++++++ drivers/block/bcache/btree.c | 2249 +++++++++++++++++++++++++++++++++++++++++ drivers/block/bcache/btree.h | 272 +++++ 3 files changed, 3360 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/bcache.h create mode 100644 drivers/block/bcache/btree.c create mode 100644 drivers/block/bcache/btree.h
drivers/block/bcache/bcache.h new file +839 -0
@@ -0,0 +1,839 @@ + +#define pr_fmt (fmt) "bcache: %s() " fmt "\n", __func__ + +#include <linux/bio.h> +#include <linux/blktrace_api.h> +#include <linux/closure.h> +#include <linux/kobject.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/rbtree.h> +#include <linux/rwsem.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include "util.h" + +struct bucket { +atomic_t pin; +uint16_t prio; +uint8_t gen; +uint8_t disk_gen; +uint8_t last_gc;/* Most out of date gen in the btree */ +uint8_t gc_gen; + +#define GC_MARK_DIRTY -1 +#define GC_MARK_BTREE -2 +short mark; +}; + +struct bkey { +uint64_t header; +uint64_t key; +uint64_t ptr[]; +}; + +#define BKEY_PADDED (key) \ + union { struct bkey key; uint64_t key ## _pad[8]; } + +/* Version 1: Backing device +* Version 2: Seed pointer into btree node checksum +* Version 3: New UUID format +*/ +#define BCACHE_SB_VERSION 3 + +#define SB_SECTOR 8 +#define SB_SIZE 4096 +#define SB_LABEL_SIZE 32 +#define SB_JOURNAL_BUCKETS 256 +/* SB_JOURNAL_BUCKETS must be divisible by BITS_PER_LONG */ +#define MAX_CACHES_PER_SET 8 + +struct cache_sb { +uint64_t csum; +uint64_t offset;/* sector where this sb was written */ +uint64_t version; +#define CACHE_BACKING_DEV 1 + +uint8_t magic[16 ]; + +uint8_t uuid[16 ]; +union { +uint8_t set_uuid[16 ]; +uint64_t set_magic; + }; +uint8_t label[SB_LABEL_SIZE ]; + +uint64_t flags; +uint64_t seq; +uint64_t pad[8 ]; + +uint64_t nbuckets;/* device size */ +uint16_t block_size;/* sectors */ +uint16_t bucket_size;/* sectors */ + +uint16_t nr_in_set; +uint16_t nr_this_dev; + +uint32_t last_mount;/* time_t */ + +uint16_t first_bucket; +union { +uint16_t njournal_buckets; +uint16_t keys; + }; +uint64_t d[SB_JOURNAL_BUCKETS ];/* journal buckets */ +}; + +BITMASK(CACHE_SYNC, struct cache_sb, flags, 0, 1); +BITMASK(CACHE_DISCARD, struct cache_sb, flags, 1, 1); +BITMASK(CACHE_REPLACEMENT, struct cache_sb, flags, 2, 3); +#define CACHE_REPLACEMENT_LRU 0U +#define CACHE_REPLACEMENT_FIFO 1U +#define CACHE_REPLACEMENT_RANDOM 2U + +BITMASK(BDEV_CACHE_MODE, struct cache_sb, flags, 0, 4); +#define CACHE_MODE_WRITETHROUGH 0U +#define CACHE_MODE_WRITEBACK 1U +#define CACHE_MODE_WRITEAROUND 2U +#define CACHE_MODE_NONE 3U +BITMASK(BDEV_STATE, struct cache_sb, flags, 61, 2); +#define BDEV_STATE_NONE 0U +#define BDEV_STATE_CLEAN 1U +#define BDEV_STATE_DIRTY 2U +#define BDEV_STATE_STALE 3U + +/* Version 1: Seed pointer into btree node checksum +*/ +#define BCACHE_BSET_VERSION 1 + +/* +* This is the on disk format for btree nodes - a btree node on disk is a list +* of these; within each set the keys are sorted +*/ +struct bset { +uint64_t csum; +uint64_t magic; +uint64_t seq; +uint32_t version; +uint32_t keys; + +union { +struct bkey start[0 ]; +uint64_t d[0 ]; + }; +}; + +/* +* On disk format for priorities and gens - see super.c near prio_write() for +* more. +*/ +struct prio_set { +uint64_t csum; +uint64_t magic; +uint64_t seq; +uint32_t version; +uint32_t pad; + +uint64_t next_bucket; + +struct bucket_disk { +uint16_t prio; +uint8_t gen; + } __attribute((packed)) data[]; +}; + +#include "journal.h" +#include "stats.h" +struct search ; +struct btree ; + +struct bcache_device { +struct closure cl; + +struct kobject kobj; + +struct cache_set *c; +unsigned id; +#define BCACHEDEVNAME_SIZE 12 +char name[BCACHEDEVNAME_SIZE ]; + +struct gendisk *disk; + +/* If nonzero, we're closing */ +atomic_t closing; + +/* If nonzero, we're detaching/unregistering from cache set */ +atomic_t detaching; + +atomic_long_t sectors_dirty; +unsigned long sectors_dirty_gc; +unsigned long sectors_dirty_last; +long sectors_dirty_derivative; + +mempool_t *unaligned_bvec; +struct bio_set *bio_split; + +unsigned data_csum:1 ; + +int (*cache_miss)(struct btree *,struct search *,struct bio *,unsigned ); +int (*ioctl) (struct bcache_device *,fmode_t ,unsigned ,unsigned long ); +}; + +struct io { +/* Used to track sequential IO so it can be skipped */ +struct hlist_node hash; +struct list_head lru; + +unsigned long jiffies; +unsigned sequential; +sector_t last; +}; + +struct dirty_io { +struct closure cl; +struct cached_dev *d; +struct bio bio; +}; + +struct dirty { +struct rb_node node; +BKEY_PADDED (key); +struct dirty_io *io; +}; + +struct cached_dev { +struct list_head list; +struct bcache_device disk; +struct block_device *bdev; + +struct cache_sb sb; +struct bio sb_bio; +struct bio_vec sb_bv[1 ]; +struct closure_with_waitlist sb_write; + +/* Refcount on the cache set. Always nonzero when we're caching. */ +atomic_t count; +struct work_struct detach; + +/* +* Device might not be running if it's dirty and the cache set hasn't +* showed up yet. +*/ +atomic_t running; + +mempool_t *bio_passthrough; + +/* +* Writes take a shared lock from start to finish; scanning for dirty +* data to refill the rb tree requires an exclusive lock. +*/ +struct rw_semaphore writeback_lock; + +/* +* Beginning and end of range in dirty rb tree - so that we can skip +* taking dirty_lock and checking the rb tree. Protected by +* writeback_lock. +*/ +sector_t writeback_start; +sector_t writeback_end; + +struct rb_root dirty; +spinlock_t dirty_lock; + +/* +* Nonzero, and writeback has a refcount (d->count), iff there is dirty +* data in the cache. Protected by writeback_lock; must have an +* shared lock to set and exclusive lock to clear. +*/ +atomic_t has_dirty; + +uint64_t next_writeback_io; +struct delayed_work writeback_rate_update; + +/* +* Internal to the writeback code, so refill_dirty() and read_dirty() +* can keep track of where they're at. +*/ +sector_t last_found; +sector_t last_read; + +/* Number of writeback bios in flight */ +atomic_t in_flight; +struct delayed_work refill_dirty; +struct delayed_work read_dirty; + +#define WRITEBACK_SLURP 100 + DECLARE_ARRAY_ALLOCATOR(struct dirty, dirty_freelist, WRITEBACK_SLURP); + +/* For tracking sequential IO */ +#define RECENT_IO_BITS 7 +#define RECENT_IO (1 << RECENT_IO_BITS) +struct io io[RECENT_IO ]; +struct hlist_head io_hash[RECENT_IO +1 ]; +struct list_head io_lru; +spinlock_t io_lock; + +struct cache_accounting accounting; + +/* The rest of this all shows up in sysfs */ +unsigned sequential_cutoff; +unsigned readahead; + +unsigned sequential_merge:1 ; +unsigned verify:1 ; + +unsigned writeback_metadata:1 ; +unsigned writeback_running:1 ; +unsigned char writeback_percent; +unsigned writeback_delay; + +unsigned writeback_rate; +int writeback_rate_change; +int64_t writeback_rate_derivative; +uint64_t writeback_rate_target; + +unsigned writeback_rate_update_seconds; +unsigned writeback_rate_d_term; +unsigned writeback_rate_p_term_inverse; +unsigned writeback_rate_d_smooth; +}; + +struct cache { +struct cache_set *set; +struct cache_sb sb; +struct bio sb_bio; +struct bio_vec sb_bv[1 ]; + +struct kobject kobj; +struct block_device *bdev; + +/* XXX: move to cache_set */ +struct dentry *debug; + +/* XXX: replace with bios allocated from bio_meta mempool */ +struct bio *uuid_bio; + +struct closure prio; +/* XXX: replace with bios allocated from bio_meta mempool */ +struct bio *prio_bio; +struct prio_set *disk_buckets; + +/* +* When allocating new buckets, prio_write() gets first dibs - since we +* may not be allocate at all without writing priorities and gens. +* prio_buckets[] contains the last buckets we wrote priorities to (so +* gc can mark them as metadata), prio_next[] contains the buckets +* allocated for the next prio write. +*/ +uint64_t *prio_buckets; +uint64_t *prio_next; +unsigned prio_write; +unsigned prio_alloc; + +/* > 0: buckets in free_inc have been marked as free +* = 0: buckets in free_inc can't be used until priorities are written +* < 0: priority write in progress +*/ +atomic_t prio_written; + +/* Allocation stuff: */ +struct bucket *buckets; + + DECLARE_HEAP(struct bucket *, heap); + +/* +* max(gen - disk_gen) for all buckets. When it gets too big we have to +* call prio_write() to keep gens from wrapping. +*/ +uint8_t need_save_prio; + +/* +* If nonzero, we know we aren't going to find any buckets to invalidate +* until a gc finishes - otherwise we could pointlessly burn a ton of +* cpu +*/ +unsigned invalidate_needs_gc:1 ; + +size_t fifo_last_bucket; + + DECLARE_FIFO(long, free); + DECLARE_FIFO(long, free_inc); + DECLARE_FIFO(long, unused); + +bool discard;/* Get rid of? */ +struct list_head discards; +struct page *discard_page; + +struct journal_device journal; + +/* The rest of this all shows up in sysfs */ +#define IO_ERROR_SHIFT 20 +atomic_t io_errors; +atomic_t io_count; + +atomic_long_t meta_sectors_written; +atomic_long_t btree_sectors_written; +atomic_long_t sectors_written; +}; + +struct gc_stat { +size_t nodes; +size_t key_bytes; + +size_t nkeys; +uint64_t data;/* sectors */ +uint64_t dirty;/* sectors */ +unsigned in_use;/* percent */ +}; + +struct cache_set { +struct closure cl; + +struct list_head list; +struct kobject kobj; +struct kobject internal; +struct cache_accounting accounting; + +/* +* If nonzero, we're trying to detach from all the devices we're +* caching; otherwise we're merely closing +*/ +atomic_t unregistering; +atomic_t closing; + +struct cache_sb sb; + +struct cache *cache[MAX_CACHES_PER_SET ]; +struct cache *cache_by_alloc[MAX_CACHES_PER_SET ]; +int caches_loaded; + +struct bcache_device **devices; +struct list_head cached_devs; +uint64_t cached_dev_sectors; +struct closure caching; + +struct closure_with_waitlist sb_write; + +mempool_t *search; +mempool_t *bio_meta; +struct bio_set *bio_split; + +/* For the btree cache */ +struct shrinker shrink; + +/* For the btree cache and anything allocation related */ +struct mutex bucket_lock; + +/* log2(bucket_size), in sectors */ +unsigned short bucket_bits; + +/* log2(block_size), in sectors */ +unsigned short block_bits; + +/* +* Default number of pages for a new btree node - may be less than a +* full bucket +*/ +unsigned btree_pages; + +/* +* Lists of struct btrees; lru is the list for structs that have memory +* allocated for actual btree node, freed is for structs that do not. +*/ +struct list_head btree_cache; +struct list_head btree_cache_freeable; +struct list_head btree_cache_freed; + +/* Number of elements in btree_cache + btree_cache_freeable lists */ +unsigned bucket_cache_used; + +/* +* If we need to allocate memory for a new btree node and that +* allocation fails, we can cannibalize another node in the btree cache +* to satisfy the allocation. However, only one thread can be doing this +* at a time, for obvious reasons - try_harder and try_wait are +* basically a lock for this that we can wait on asynchronously. The +* btree_root() macro releases the lock when it returns. +*/ +struct closure *try_harder; +closure_list_t try_wait; +uint64_t try_harder_start; + +/* +* When we free a btree node, we increment the gen of the bucket the +* node is in - but we can't rewrite the prios and gens until we +* finished whatever it is we were doing, otherwise after a crash the +* btree node would be freed but for say a split, we might not have the +* pointers to the new nodes inserted into the btree yet. +* +* This is a refcount that blocks prio_write() until the new keys are +* written. +*/ +atomic_t prio_blocked; +closure_list_t bucket_wait; + +/* +* For any bio we don't skip we subtract the number of sectors from +* rescale; when it hits 0 we rescale all the bucket priorities. +*/ +atomic_t rescale; +/* +* When we invalidate buckets, we use both the priority and the amount +* of good data to determine which buckets to reuse first - to weight +* those together consistently we keep track of the smallest nonzero +* priority of any bucket. +*/ +uint16_t min_prio; + +/* +* max(gen - gc_gen) for all buckets. When it gets too big we have to gc +* to keep gens from wrapping around. +*/ +uint8_t need_gc; +struct gc_stat gc_stats; +size_t nbuckets; + +struct closure_with_waitlist gc; +/* Where in the btree gc currently is */ +struct bkey gc_done; + +/* +* The allocation code needs gc_mark in struct bucket to be correct, but +* it's not while a gc is in progress. Protected by bucket_lock. +*/ +int gc_mark_valid; + +/* Counts how many sectors bio_insert has added to the cache */ +atomic_t sectors_to_gc; + +struct btree *root; + +#ifdef CONFIG_BCACHE_DEBUG +struct btree *verify_data; +struct mutex verify_lock; +#endif + +unsigned nr_uuids; +struct uuid_entry *uuids; +BKEY_PADDED (uuid_bucket); +struct closure_with_waitlist uuid_write; + +/* +* A btree node on disk could have too many bsets for an iterator to fit +* on the stack - this is a single element mempool for btree_read_work() +*/ +struct mutex fill_lock; +struct btree_iter *fill_iter; + +/* +* btree_sort() is a merge sort and requires temporary space - single +* element mempool +*/ +struct mutex sort_lock; +struct bset *sort; + +/* List of buckets we're currently writing data to */ +struct list_head data_buckets; +spinlock_t data_bucket_lock; + +struct journal journal; + +#define CONGESTED_MAX 1024 +unsigned congested_last_us; +atomic_t congested; + +/* The rest of this all shows up in sysfs */ +unsigned congested_read_threshold_us; +unsigned congested_write_threshold_us; + +spinlock_t sort_time_lock; +struct time_stats sort_time; +struct time_stats btree_gc_time; +struct time_stats btree_split_time; +spinlock_t btree_read_time_lock; +struct time_stats btree_read_time; +struct time_stats try_harder_time; + +atomic_long_t cache_read_races; +atomic_long_t writeback_keys_done; +atomic_long_t writeback_keys_failed; +unsigned error_limit; +unsigned error_decay; +unsigned short journal_delay_ms; +unsigned verify:1 ; +unsigned key_merging_disabled:1 ; +unsigned gc_always_rewrite:1 ; +unsigned shrinker_disabled:1 ; + +#define BUCKET_HASH_BITS 12 +struct hlist_head bucket_hash[1 <<BUCKET_HASH_BITS ]; +}; + +static inline bool key_merging_disabled (struct cache_set *c) +{ +#ifdef CONFIG_BCACHE_DEBUG +return c->key_merging_disabled; +#else +return 0 ; +#endif +} + +struct bbio { +unsigned submit_time_us; +union { +struct bkey key; +uint64_t _pad[3 ]; + }; +struct bio bio; +}; + +static inline unsigned local_clock_us (void ) +{ +return local_clock () >>10 ; +} + +#define MAX_BSETS 4 + +#define btree_prio USHRT_MAX +#define initial_prio 32768 + +#define btree_bytes (c) ((c)->btree_pages * PAGE_SIZE) +#define btree_blocks (b) \ + ((unsigned) (KEY_SIZE(&b->key) >> (b)->c->block_bits)) + +#define btree_default_blocks (c) \ + ((unsigned) ((PAGE_SECTORS * (c)->btree_pages) >> (c)->block_bits)) + +#define bucket_pages (c) ((c)->sb.bucket_size / PAGE_SECTORS) +#define bucket_bytes (c) ((c)->sb.bucket_size << 9) +#define block_bytes (c) ((c)->sb.block_size << 9) + +#define __set_bytes (i, k) (sizeof(*(i)) + (k) * sizeof(uint64_t)) +#define set_bytes (i) __set_bytes(i, i->keys) + +#define __set_blocks (i, k, c) DIV_ROUND_UP(__set_bytes(i, k), block_bytes(c)) +#define set_blocks (i, c) __set_blocks(i, (i)->keys, c) + +#define node (i, j) ((struct bkey *) ((i)->d + (j))) +#define end (i) node(i, (i)->keys) + +#define index (i, b) \ + ((size_t) (((void *) i - (void *) (b)->sets[0].data) / \ + block_bytes(b->c))) + +#define btree_data_space (b) (PAGE_SIZE << (b)->page_order) + +#define prios_per_bucket (c) \ + ((bucket_bytes(c) - sizeof(struct prio_set)) / \ + sizeof(struct bucket_disk)) +#define prio_buckets (c) \ + DIV_ROUND_UP((size_t) (c)->sb.nbuckets, prios_per_bucket(c)) + +#define JSET_MAGIC 0x245235c1a3625032 +#define PSET_MAGIC 0x6750e15f87337f91 +#define BSET_MAGIC 0x90135c78b99e07f5 + +#define jset_magic (c) ((c)->sb.set_magic ^ JSET_MAGIC) +#define pset_magic (c) ((c)->sb.set_magic ^ PSET_MAGIC) +#define bset_magic (c) ((c)->sb.set_magic ^ BSET_MAGIC) + +/* Bkey fields: all units are in sectors */ + +#define KEY_FIELD (name, field, offset, size) \ + BITMASK(name, struct bkey, field, offset, size) + +#define PTR_FIELD (name, offset, size) \ + static inline uint64_t name(const struct bkey *k, unsigned i) \ + { return (k->ptr[i] >> offset) & ~(((uint64_t) ~0) << size); } \ + \ + static inline void SET_##name(struct bkey *k, unsigned i, uint64_t v)\ + { \ + k->ptr[i] &= ~(~((uint64_t) ~0 << size) << offset); \ + k->ptr[i] |= v << offset; \ + } + +KEY_FIELD(KEY_PTRS, header, 60, 3) +KEY_FIELD(HEADER_SIZE, header, 58, 2) +KEY_FIELD(KEY_CSUM, header, 56, 2) +KEY_FIELD(KEY_PINNED, header, 55, 1) +KEY_FIELD(KEY_DIRTY, header, 36, 1) + +KEY_FIELD(KEY_SIZE, header, 20, 16) +KEY_FIELD(KEY_DEV, header, 0, 20) + +KEY_FIELD(KEY_SECTOR, key, 16, 47) +KEY_FIELD(KEY_SNAPSHOT, key, 0, 16) + +PTR_FIELD(PTR_DEV, 51, 12) +PTR_FIELD(PTR_OFFSET, 8, 43) +PTR_FIELD(PTR_GEN, 0, 8) + +#define PTR_CHECK_DEV ((1 << 12) - 1) + +#define PTR (gen, offset, dev) \ + ((((uint64_t) dev) << 51) | ((uint64_t) offset) << 8 | gen) + +#define sector_to_bucket (c, s) ((long) ((s) >> (c)->bucket_bits)) +#define bucket_to_sector (c, b) (((sector_t) (b)) << (c)->bucket_bits) +#define bucket_remainder (c, b) ((b) & ((c)->sb.bucket_size - 1)) + +#define PTR_CACHE (c, k, n) ((c)->cache[PTR_DEV(k, n)]) +#define PTR_BUCKET_NR (c, k, n) sector_to_bucket(c, PTR_OFFSET(k, n)) + +#define PTR_BUCKET (c, k, n) \ + (PTR_CACHE(c, k, n)->buckets + PTR_BUCKET_NR(c, k, n)) + +/* Btree key macros */ + +#define KEY_HEADER (len, dev) \ + (((uint64_t) 1 << 63) | ((uint64_t) (len) << 20) | (dev)) + +#define KEY (dev, sector, len) (struct bkey) \ + { .header = KEY_HEADER(len, dev), .key = (sector) } + +#define KEY_START (k) ((k)->key - KEY_SIZE(k)) +#define START_KEY (k) KEY(KEY_DEV(k), KEY_START(k), 0) +#define MAX_KEY KEY(~(~0 << 20), ((uint64_t) ~0) >> 1, 0) +#define ZERO_KEY KEY(0, 0, 0) + +#define csum_set (i) \ + crc64(((void *) (i)) + 8, ((void *) end(i)) - (((void *) (i)) + 8)) + +/* Error handling macros */ + +#define btree_bug (b, ...) \ + ({ if (cache_set_error((b)->c, __VA_ARGS__)) dump_stack(); }) + +#define cache_bug (c, ...) \ + ({ if (cache_set_error(c, __VA_ARGS__)) dump_stack(); }) + +#define btree_bug_on (cond, b, ...) \ + ({ if (cond) btree_bug(b, __VA_ARGS__); }) + +#define cache_bug_on (cond, c, ...) \ + ({ if (cond) cache_bug(c, __VA_ARGS__); }) + +#define cache_set_err_on (cond, c, ...) \ + ({ if (cond) cache_set_error(c, __VA_ARGS__); }) + +/* Looping macros */ + +#define for_each_cache (ca, cs) \ + for (int _i = 0; ca = cs->cache[_i], _i < (cs)->sb.nr_in_set; _i++) + +#define for_each_bucket (b, ca) \ + for (b = (ca)->buckets + (ca)->sb.first_bucket; \ + b < (ca)->buckets + (ca)->sb.nbuckets; b++) + +static inline void __bkey_put (struct cache_set *c,struct bkey *k) +{ +for (unsigned i =0 ; i <KEY_PTRS (k); i++) +atomic_dec_bug (&PTR_BUCKET (c, k, i)->pin); +} + +/* Blktrace macros */ + +#define blktrace_msg (c, fmt, ...) \ +do { \ + struct request_queue *q = bdev_get_queue(c->bdev); \ + if (q) \ + blk_add_trace_msg(q, fmt, ##__VA_ARGS__); \ +} while (0) + +#define blktrace_msg_all (s, fmt, ...) \ +do { \ + struct cache *_c; \ + for_each_cache(_c, (s)) \ + blktrace_msg(_c, fmt, ##__VA_ARGS__); \ +} while (0) + +#define err_printk (...) printk(KERN_ERR "bcache: " __VA_ARGS__) + +static inline void cached_dev_put (struct cached_dev *d) +{ +if (atomic_dec_and_test (&d->count)) +schedule_work (&d->detach); +} + +static inline bool cached_dev_get (struct cached_dev *d) +{ +if (!atomic_inc_not_zero (&d->count)) +return false; + +smp_mb__after_atomic_inc (); +return true; +} + +#define bucket_gc_gen (b) ((uint8_t) ((b)->gen - (b)->last_gc)) +#define bucket_disk_gen (b) ((uint8_t) ((b)->gen - (b)->disk_gen)) + +#define kobj_attribute_write (n, fn) \ + static struct kobj_attribute ksysfs_##n = __ATTR(n, S_IWUSR, NULL, fn) + +#define kobj_attribute_rw (n, show, store) \ + static struct kobj_attribute ksysfs_##n = \ + __ATTR(n, S_IWUSR|S_IRUSR, show, store) + +#define bio_split_get (bio, len, c) \ + __bio_split_get(bio, len, (c)->bio_split) + +/* Forward declarations */ + +bool bcache_in_writeback (struct cached_dev *,sector_t ,unsigned ); +void bcache_writeback_queue (struct cached_dev *); +void bcache_writeback_add (struct cached_dev *,unsigned ); + +void count_io_errors (struct cache *,int ,const char *); +void bcache_endio (struct cache_set *,struct bio *,int ,const char *); +void bbio_free (struct bio *,struct cache_set *); +struct bio *bbio_alloc (struct cache_set *); +struct bio *bbio_kmalloc (gfp_t ,int ); +struct bio *__bio_split_get (struct bio *,int ,struct bio_set *); + +void __submit_bbio (struct bio *,struct cache_set *); +void submit_bbio (struct bio *,struct cache_set *,struct bkey *,unsigned ); +int submit_bbio_split (struct bio *,struct cache_set *, +struct bkey *,unsigned ); + +void cache_read_endio (struct bio *,int ); + +struct bcache_cgroup ; +struct cgroup ; +struct bcache_cgroup *cgroup_to_bcache (struct cgroup *cgroup); +struct bcache_cgroup *bio_to_cgroup (struct bio *bio); + +uint8_t inc_gen (struct cache *,struct bucket *); +void rescale_priorities (struct cache_set *,int ); +bool bucket_add_unused (struct cache *,struct bucket *); +bool can_save_prios (struct cache *); +void free_some_buckets (struct cache *); +void unpop_bucket (struct cache_set *,struct bkey *); +int __pop_bucket_set (struct cache_set *,uint16_t , +struct bkey *,int ,struct closure *); +int pop_bucket_set (struct cache_set *,uint16_t , +struct bkey *,int ,struct closure *); + +bool cache_set_error (struct cache_set *,const char *, ...); + +void prio_write (struct cache *); +void write_bdev_super (struct cached_dev *,struct closure *); + +extern struct workqueue_struct *bcache_wq; +extern const char *const bcache_cache_modes[]; + +struct cache_set *cache_set_alloc (struct cache_sb *); +void free_discards (struct cache *); +int alloc_discards (struct cache *); +void bcache_btree_cache_free (struct cache_set *); +int bcache_btree_cache_alloc (struct cache_set *); +void bcache_writeback_init_cached_dev (struct cached_dev *); + +void bcache_debug_exit (void ); +int bcache_debug_init (struct kobject *); +void bcache_writeback_exit (void ); +int bcache_writeback_init (void ); +void bcache_request_exit (void ); +int bcache_request_init (void ); +void bcache_btree_exit (void ); +int bcache_btree_init (void );
drivers/block/bcache/btree.c new file +2249 -0
2,161 lines not on this page · raw message
drivers/block/bcache/btree.h new file +272 -0
272 lines not on this page · raw message
The rest of this diff is not shown. View the raw message.
Signature · 1 line
1.7.9.rc2
Part of the diff is not shown here. View the raw message.
Re: [Bcache v13 11/16] bcache: Core btree code
Hello, Kent.
drivers/block/bcache/bcache.h line 37
Quoted 2 lines · Kent Overstreet
drivers/block/bcache/bcache.h new file +2
-0+#define BKEY_PADDED (key) \ + union { struct bkey key; uint64_t key ## _pad[8]; }
What does the '8' mean? And why does struct bbio use 3 instead? Does this have to be a macro? Can't we have struct bkey_padded (or whatever)?
drivers/block/bcache/bcache.h line 191
Quoted 9 lines
drivers/block/bcache/bcache.h new file +9
-0+struct io { +/* Used to track sequential IO so it can be skipped */ +struct hlist_node hash; +struct list_head lru; + +unsigned long jiffies; +unsigned sequential; +sector_t last; +};
I don't think bcache can have struct io. :P
Quoted 11 lines
drivers/block/bcache/bcache.h new file +11
-0+struct dirty_io { +struct closure cl; +struct cached_dev *d; +struct bio bio; +}; + +struct dirty { +struct rb_node node; +BKEY_PADDED (key); +struct dirty_io *io; +};
...
Quoted 1 line
+struct cache {
Nor these and so on.
Quoted 20 lines
drivers/block/bcache/bcache.h new file +20
-0+/* Bkey fields: all units are in sectors */ + +#define KEY_FIELD (name, field, offset, size) \ + BITMASK(name, struct bkey, field, offset, size) + +#define PTR_FIELD (name, offset, size) \ + static inline uint64_t name(const struct bkey *k, unsigned i) \ + { return (k->ptr[i] >> offset) & ~(((uint64_t) ~0) << size); } \ + \ + static inline void SET_##name(struct bkey *k, unsigned i, uint64_t v)\ + { \ + k->ptr[i] &= ~(~((uint64_t) ~0 << size) << offset); \ + k->ptr[i] |= v << offset; \ + } + +KEY_FIELD(KEY_PTRS, header, 60, 3) +KEY_FIELD(HEADER_SIZE, header, 58, 2) +KEY_FIELD(KEY_CSUM, header, 56, 2) +KEY_FIELD(KEY_PINNED, header, 55, 1) +KEY_FIELD (KEY_DIRTY , header,36 ,1 )
drivers/block/bcache/bcache.h line 670
Quoted 10 lines
drivers/block/bcache/bcache.h new file +10
-0+ +KEY_FIELD(KEY_SIZE, header, 20, 16) +KEY_FIELD(KEY_DEV, header, 0, 20) + +KEY_FIELD(KEY_SECTOR, key, 16, 47) +KEY_FIELD(KEY_SNAPSHOT, key, 0, 16) + +PTR_FIELD(PTR_DEV, 51, 12) +PTR_FIELD(PTR_OFFSET, 8, 43) +PTR_FIELD (PTR_GEN ,0 ,8 )
So, apart from the the macros, key is 64bit containing the backend device and extent offset and size with the ptr fields somehow point to cache. Am I understanding it correctly? If right, I'm *tiny* bit apprehensive about using only 43bits for offset. While the block 9 bits means 52bit addressing, the 9 bit block size is now there mostly to work as buffer between memory bitness growth and storage device size growth so that we have 9 bit buffer as storage device reaches ulong addressing limit. Probably those days are far out enough.
Quoted 68 lines
drivers/block/bcache/btree.c new file +68
-0+void btree_read_done (struct closure *cl) +{ + struct btree *b = container_of(cl, struct btree, io.cl); +struct bset *i = b->sets[0 ].data; +struct btree_iter *iter = b->c->fill_iter; +const char *err ="bad btree header" ; +BUG_ON (b->nsets || b->written); + +bbio_free (b->bio, b->c); + b->bio =NULL ; + +mutex_lock (&b->c->fill_lock); + iter->used =0 ; + +if (btree_node_io_error (b) || + !i->seq) + goto err; + +for (; + b->written <btree_blocks (b) && i->seq == b->sets[0 ].data->seq; + i =write_block (b)) { + err ="unsupported bset version" ; +if (i->version >BCACHE_BSET_VERSION ) + goto err; + + err ="bad btree header" ; +if (b->written +set_blocks (i, b->c) >btree_blocks (b)) + goto err; + + err ="bad magic" ; +if (i->magic !=bset_magic (b->c)) + goto err; + + err ="bad checksum" ; +switch (i->version) { +case 0 : +if (i->csum !=csum_set (i)) + goto err; +break ; +case BCACHE_BSET_VERSION : +if (i->csum !=btree_csum_set (b, i)) + goto err; +break ; + } + + err ="empty set" ; +if (i != b->sets[0 ].data && !i->keys) + goto err; + +btree_iter_push (iter, i->start,end (i)); + + b->written +=set_blocks (i, b->c); + } + + err ="corrupted btree" ; +for (i =write_block (b); +index (i, b) <btree_blocks (b); + i = ((void *) i) +block_bytes (b->c)) +if (i->seq == b->sets[0 ].data->seq) + goto err; + +btree_sort_and_fix_extents (b, iter); + + i = b->sets[0 ].data; + err ="short btree key" ; +if (b->sets[0 ].size && +bkey_cmp (&b->key, &b->sets[0 ].end) <0 ) + goto err;
drivers/block/bcache/btree.c line 233
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +if (b->written <btree_blocks (b)) +bset_init_next (b); + +if (0 ) { +err:set_btree_node_io_error (b); +cache_set_error (b->c,"%s at bucket %lu, block %zu, %u keys" , + err,PTR_BUCKET_NR (b->c, &b->key,0 ), +index (i, b), i->keys); + }
Please don't do that. Just define out: label, move error specific path to the end of the function and jump to out at the end of that.
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +mutex_unlock (&b->c->fill_lock); + +spin_lock (&b->c->btree_read_time_lock); +time_stats_update (&b->c->btree_read_time, b->io_start_time); +spin_unlock (&b->c->btree_read_time_lock); + +smp_wmb ();/* read_done is our write lock */ +set_btree_node_read_done (b); +
drivers/block/bcache/btree.c line 253
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+closure_return (cl); +} + +static void btree_read_resubmit (struct closure *cl) +{ + struct btree *b = container_of(cl, struct btree, io.cl); + +submit_bbio_split (b->bio, b->c, &b->key,0 ); +continue_at (&b->io.cl, btree_read_done, system_wq); +}
I suppose submit_bbio_split() can't fail here somehow unlike from btree_read() path? If so, please add a comment to explain and maybe WARN_ON_ONCE() on failure. Subtlety to comment ratio is *way* off.
Quoted 6 lines
drivers/block/bcache/btree.c new file +6
-0+static struct btree *mca_bucket_alloc (struct cache_set *c, +struct bkey *k,gfp_t gfp) +{ +struct btree *b =kzalloc (sizeof (struct btree ), gfp); +if (!b) +return NULL ;
drivers/block/bcache/btree.c line 548
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +init_rwsem (&b->lock); +INIT_LIST_HEAD (&b->list); +INIT_DELAYED_WORK (&b->work, btree_write_work); + b->c = c; +closure_init_unlocked (&b->io); + +mca_data_alloc (b, k, gfp); +return b->sets[0 ].data ? b :NULL ; +}
mca_data_alloc() failure path seems like a resource leak but it isn't because mca_data_alloc() puts it in free list. Is the extra level of caching necessary? How is it different from sl?b allocator cache? And either way, comments please.
Quoted 15 lines
drivers/block/bcache/btree.c new file +15
-0+static int mca_reap (struct btree *b,struct closure *cl) +{ +lockdep_assert_held (&b->c->bucket_lock); + +if (!down_write_trylock (&b->lock)) +return -1 ; + +BUG_ON (btree_node_dirty (b) && !b->sets[0 ].data); + +if (cl &&btree_node_dirty (b)) +btree_write (b, true,NULL ); + +if (cl) +closure_wait_event_async (&b->io.wait, cl, +atomic_read (&b->io.cl.remaining) ==-1 );
drivers/block/bcache/btree.c line 574
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +if (btree_node_dirty (b) || +atomic_read (&b->io.cl.remaining) !=-1 || +work_pending (&b->work.work)) { +rw_unlock (true, b); +return -EAGAIN ; + } + +return 0 ; +}
Mixing -1 and -EAGAIN returns usually isn't a good idea.
Quoted 45 lines
drivers/block/bcache/btree.c new file +45
-0+static struct btree *alloc_bucket (struct cache_set *c,struct bkey *k, +int level,struct closure *cl) +{ +struct btree *b, *i; +unsigned page_order =ilog2 (KEY_SIZE (k) /PAGE_SECTORS ?:1 ); + +lockdep_assert_held (&c->bucket_lock); +retry: +if (find_bucket (c, k)) +return NULL ; + +/* btree_free() doesn't free memory; it sticks the node on the end of +* the list. Check if there's any freed nodes there: +*/ +list_for_each_entry (b, &c->btree_cache_freeable, list) +if (page_order <= b->page_order && + !b->key.ptr[0 ] && + !mca_reap (b,NULL )) + goto out; + +/* We never free struct btree itself, just the memory that holds the on +* disk node. Check the freed list before allocating a new one: +*/ +list_for_each_entry (b, &c->btree_cache_freed, list) +if (!mca_reap (b,NULL )) { +mca_data_alloc (b, k, __GFP_NOWARN|GFP_NOIO ); +if (!b->sets[0 ].data) { +rw_unlock (true, b); + goto err; + }else + goto out; + } + + b =mca_bucket_alloc (c, k, __GFP_NOWARN|GFP_NOIO ); +if (!b) + goto err; + +BUG_ON (!down_write_trylock (&b->lock)); +out: +BUG_ON (!closure_is_unlocked (&b->io.cl)); + +bkey_copy (&b->key, k); +list_move (&b->list, &c->btree_cache); +hlist_del_init_rcu (&b->hash); +hlist_add_head_rcu (&b->hash,hash_bucket (c, k));
drivers/block/bcache/btree.c line 796
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+lock_set_subclass (&b->lock.dep_map, level +1 , _THIS_IP_); + + b->flags =0 ; + b->level = level; + b->written =0 ; + b->nsets =0 ; +for (int i =0 ; i <MAX_BSETS ; i++) + b->sets[i].size =0 ; +for (int i =1 ; i <MAX_BSETS ; i++) + b->sets[i].data =NULL ;
Why separate loops?
drivers/block/bcache/btree.c line 801
Quoted 5 lines
drivers/block/bcache/btree.c new file +5
-0+ +return b; +err: +if (current->bio_list) +return ERR_PTR (-EAGAIN );
What does this test do?
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 11/16] bcache: Core btree code
Quoted 9 lines · Tejun Heo
Hello, Kent.
drivers/block/bcache/bcache.h line 37
Quoted 2 lines · Kent Overstreet
drivers/block/bcache/bcache.h new file +2
-0+#define BKEY_PADDED (key) \ + union { struct bkey key; uint64_t key ## _pad[8]; }What does the '8' mean? And why does struct bbio use 3 instead? Does this have to be a macro? Can't we have struct
bkey_padded(or whatever)?
Struct bbio uses 3 because it only ever carries around a single pointer (i.e. the pointer it's doing io to/from). I'll document that.
The 8 here is kind of arbitrary, it should be a constant defined somewhere - it's just a nice round number so it doesn't overflow, means it can fit 6 pointers.
It's a macro because it's used for embedding them in various structs - that way the pad can be in an anonymous union. Doesn't work the way you'd want for bkey_paddeds that are declared on the stack - I never found a solution I liked.
Quoted 11 lines
drivers/block/bcache/bcache.h line 191
Quoted 9 lines
drivers/block/bcache/bcache.h new file +9
-0+struct io { +/* Used to track sequential IO so it can be skipped */ +struct hlist_node hash; +struct list_head lru; + +unsigned long jiffies; +unsigned sequential; +sector_t last; +};I don't think bcache can have struct io. :P
Is your complaint that there's a struct io somewhere else, or just the potential namespace clash in the future? Regardless, I can change it.
Quoted 13 lines
Quoted 12 lines
drivers/block/bcache/bcache.h new file +11
-0+struct dirty_io { +struct closure cl; +struct cached_dev *d; +struct bio bio; +}; + +struct dirty { +struct rb_node node; +BKEY_PADDED (key); +struct dirty_io *io; +};...
Quoted 3 lines
Quoted 1 line
+struct cache {
Nor these and so on.
You want me to prefix all my struct names with bcache_? Blech.
Quoted 32 lines
Quoted 21 lines
drivers/block/bcache/bcache.h new file +20
-0+/* Bkey fields: all units are in sectors */ + +#define KEY_FIELD (name, field, offset, size) \ + BITMASK(name, struct bkey, field, offset, size) + +#define PTR_FIELD (name, offset, size) \ + static inline uint64_t name(const struct bkey *k, unsigned i) \ + { return (k->ptr[i] >> offset) & ~(((uint64_t) ~0) << size); } \ + \ + static inline void SET_##name(struct bkey *k, unsigned i, uint64_t v)\ + { \ + k->ptr[i] &= ~(~((uint64_t) ~0 << size) << offset); \ + k->ptr[i] |= v << offset; \ + } + +KEY_FIELD(KEY_PTRS, header, 60, 3) +KEY_FIELD(HEADER_SIZE, header, 58, 2) +KEY_FIELD(KEY_CSUM, header, 56, 2) +KEY_FIELD(KEY_PINNED, header, 55, 1) +KEY_FIELD (KEY_DIRTY , header,36 ,1 )drivers/block/bcache/bcache.h line 670
Quoted 10 lines
drivers/block/bcache/bcache.h new file +10
-0+ +KEY_FIELD(KEY_SIZE, header, 20, 16) +KEY_FIELD(KEY_DEV, header, 0, 20) + +KEY_FIELD(KEY_SECTOR, key, 16, 47) +KEY_FIELD(KEY_SNAPSHOT, key, 0, 16) + +PTR_FIELD(PTR_DEV, 51, 12) +PTR_FIELD(PTR_OFFSET, 8, 43) +PTR_FIELD (PTR_GEN ,0 ,8 )
Quoted 8 lines
So, apart from the the macros, key is 64bit containing the backend device and extent offset and size with the ptr fields somehow point to cache. Am I understanding it correctly? If right, I'm *tiny* bit apprehensive about using only 43bits for offset. While the block 9 bits means 52bit addressing, the 9 bit block size is now there mostly to work as buffer between memory bitness growth and storage device size growth so that we have 9 bit buffer as storage device reaches ulong addressing limit. Probably those days are far out enough.
You're exactly right. I had similar thoughts about the offset size, but... it'll last until we have 8 exabyte cache devices, and I can't make it bigger without making PTR_DEV smaller or making the whole pointer 16 bytes.
Shouldn't be that hard to make a version of the on disk format with 16 byte pointers that's backwards compatible though, when it becomes necessary. And we can detect it at format time.
Quoted 73 lines
Quoted 73 lines
drivers/block/bcache/btree.c new file +73
-0+void btree_read_done (struct closure *cl) +{ + struct btree *b = container_of(cl, struct btree, io.cl); +struct bset *i = b->sets[0 ].data; +struct btree_iter *iter = b->c->fill_iter; +const char *err ="bad btree header" ; +BUG_ON (b->nsets || b->written); + +bbio_free (b->bio, b->c); + b->bio =NULL ; + +mutex_lock (&b->c->fill_lock); + iter->used =0 ; + +if (btree_node_io_error (b) || + !i->seq) + goto err; + +for (; + b->written <btree_blocks (b) && i->seq == b->sets[0 ].data->seq; + i =write_block (b)) { + err ="unsupported bset version" ; +if (i->version >BCACHE_BSET_VERSION ) + goto err; + + err ="bad btree header" ; +if (b->written +set_blocks (i, b->c) >btree_blocks (b)) + goto err; + + err ="bad magic" ; +if (i->magic !=bset_magic (b->c)) + goto err; + + err ="bad checksum" ; +switch (i->version) { +case 0 : +if (i->csum !=csum_set (i)) + goto err; +break ; +case BCACHE_BSET_VERSION : +if (i->csum !=btree_csum_set (b, i)) + goto err; +break ; + } + + err ="empty set" ; +if (i != b->sets[0 ].data && !i->keys) + goto err; + +btree_iter_push (iter, i->start,end (i)); + + b->written +=set_blocks (i, b->c); + } + + err ="corrupted btree" ; +for (i =write_block (b); +index (i, b) <btree_blocks (b); + i = ((void *) i) +block_bytes (b->c)) +if (i->seq == b->sets[0 ].data->seq) + goto err; + +btree_sort_and_fix_extents (b, iter); + + i = b->sets[0 ].data; + err ="short btree key" ; +if (b->sets[0 ].size && +bkey_cmp (&b->key, &b->sets[0 ].end) <0 ) + goto err; + +if (b->written <btree_blocks (b)) +bset_init_next (b); + +if (0 ) {
Quoted 8 lines
drivers/block/bcache/btree.c line 233
Quoted 5 lines
drivers/block/bcache/btree.c new file +5
-0+err:set_btree_node_io_error (b); +cache_set_error (b->c,"%s at bucket %lu, block %zu, %u keys" , + err,PTR_BUCKET_NR (b->c, &b->key,0 ), +index (i, b), i->keys); + }Please don't do that. Just define out: label, move error specific path to the end of the function and jump to out at the end of that.
Heh. Given that you're the second person to complain about that, I'll change it.
Quoted 16 lines
Quoted 16 lines
drivers/block/bcache/btree.c new file +16
-0+ +mutex_unlock (&b->c->fill_lock); + +spin_lock (&b->c->btree_read_time_lock); +time_stats_update (&b->c->btree_read_time, b->io_start_time); +spin_unlock (&b->c->btree_read_time_lock); + +smp_wmb ();/* read_done is our write lock */ +set_btree_node_read_done (b); + +closure_return (cl); +} + +static void btree_read_resubmit (struct closure *cl) +{ + struct btree *b = container_of(cl, struct btree, io.cl);
Quoted 8 lines
drivers/block/bcache/btree.c line 253
Quoted 4 lines
drivers/block/bcache/btree.c new file +4
-0+ +submit_bbio_split (b->bio, b->c, &b->key,0 ); +continue_at (&b->io.cl, btree_read_done, system_wq); +}I suppose
submit_bbio_split()can't fail here somehow unlike frombtree_read()path? If so, please add a comment to explain and maybeWARN_ON_ONCE()on failure. Subtlety to comment ratio is *way* off.
Yes - because this is the resubmit patch, i.e. runs out of workqueue context if the one in btree_read() failed, which is only possible when it's running under generic_make_request() (because then we don't use the mempool to avoid deadlock).
If the code for making generic_make_request() handle arbitrary sized bios goes in, this code will be deleted :)
Quoted 14 lines
Quoted 14 lines
drivers/block/bcache/btree.c new file +13
-0+static struct btree *mca_bucket_alloc (struct cache_set *c, +struct bkey *k,gfp_t gfp) +{ +struct btree *b =kzalloc (sizeof (struct btree ), gfp); +if (!b) +return NULL ; + +init_rwsem (&b->lock); +INIT_LIST_HEAD (&b->list); +INIT_DELAYED_WORK (&b->work, btree_write_work); + b->c = c; +closure_init_unlocked (&b->io); +
Quoted 8 lines
drivers/block/bcache/btree.c line 548
Quoted 3 lines
drivers/block/bcache/btree.c new file +3
-0+mca_data_alloc (b, k, gfp); +return b->sets[0 ].data ? b :NULL ; +}
mca_data_alloc()failure path seems like a resource leak but it isn't becausemca_data_alloc()puts it in free list. Is the extra level of caching necessary? How is it different from sl?b allocator cache? And either way, comments please.
This btree in memory cache code is probably the nastiest, subtlest, trickiest code in bcache. I have some cleanups in a branch somewhere as part of some other work that I need to finish off.
The btree_cache_freed list isn't for caching - we never free struct btrees except on shutdown, to simplify the code. It doesn't cost much memory since the memory usage is dominated by the buffers struct btree points to, and those are freed when necessary.
btree_cache_freeable is a small cache for the buffers struct btree points to (the btree node as it exists on disk + the bset tree). We have that because high order page allocations can be expensive, and it's quite common to delete and allocate btree nodes in quick succession - but it should never grow past 2 or 3 nodes.
I'm adding all this to where the lists are defined, in struct cache_set.
Quoted 20 lines
Quoted 20 lines
drivers/block/bcache/btree.c new file +19
-0+static int mca_reap (struct btree *b,struct closure *cl) +{ +lockdep_assert_held (&b->c->bucket_lock); + +if (!down_write_trylock (&b->lock)) +return -1 ; + +BUG_ON (btree_node_dirty (b) && !b->sets[0 ].data); + +if (cl &&btree_node_dirty (b)) +btree_write (b, true,NULL ); + +if (cl) +closure_wait_event_async (&b->io.wait, cl, +atomic_read (&b->io.cl.remaining) ==-1 ); + +if (btree_node_dirty (b) || +atomic_read (&b->io.cl.remaining) !=-1 || +work_pending (&b->work.work)) {
Quoted 8 lines
drivers/block/bcache/btree.c line 574
Quoted 6 lines
drivers/block/bcache/btree.c new file +6
-0+rw_unlock (true, b); +return -EAGAIN ; + } + +return 0 ; +}Mixing -1 and
-EAGAINreturns usually isn't a good idea.
Yeah. I'm not going to change anything in this code right away, since it's just to tricky to screw with - but when I finish off those other patches this code will be improved somewhat (this is already fixed in that branch).
Quoted 10 lines
drivers/block/bcache/btree.c line 796
Quoted 8 lines
drivers/block/bcache/btree.c new file +8
-0+ b->flags =0 ; + b->level = level; + b->written =0 ; + b->nsets =0 ; +for (int i =0 ; i <MAX_BSETS ; i++) + b->sets[i].size =0 ; +for (int i =1 ; i <MAX_BSETS ; i++) + b->sets[i].data =NULL ;Why separate loops?
Sceond loop starts at 1, because b->sets[0]->data points to the memory we have allocated. I suppose that merits a comment.
Quoted 8 lines
drivers/block/bcache/btree.c line 801
Quoted 6 lines
drivers/block/bcache/btree.c new file +5
-0+ +return b; +err: +if (current->bio_list) +return ERR_PTR (-EAGAIN );What does this test do?
Trying to free up some memory - i.e. reuse some btree nodes - may require initiating IO to flush the dirty part of the node. If we're running under generic_make_request(), that IO will never finish and we would deadlock - returning -EAGAIN causes the cache lookup code to punt to workqueue and retry.
Quoted 9 lines
Thanks.
Signature · 5 lines
tejun -- To unsubscribe from this list: send the line "unsubscribe linux-bcache" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Tejun Heo
maintainerRe: [Bcache v13 11/16] bcache: Core btree code
Hello,
Quoted 7 lines · Kent Overstreet
Quoted 3 lines
What does the '8' mean? And why does struct bbio use 3 instead? Does this have to be a macro? Can't we have struct
bkey_padded(or whatever)?Struct bbio uses 3 because it only ever carries around a single pointer (i.e. the pointer it's doing io to/from). I'll document that.
Quoted 8 lines
The 8 here is kind of arbitrary, it should be a constant defined somewhere - it's just a nice round number so it doesn't overflow, means it can fit 6 pointers.
It's a macro because it's used for embedding them in various structs - that way the pad can be in an anonymous union. Doesn't work the way you'd want for
bkey_paddedsthat are declared on the stack - I never found a solution I liked.
Hmmm... I would prefer it to be defined explicitly as union. It's rather easy to define it incorrectly (ie. using struct bkey) and then pass it around expecting it to have the pad.
Quoted 12 lines
Quoted 11 lines
drivers/block/bcache/bcache.h line 191
Quoted 9 lines
drivers/block/bcache/bcache.h new file +9
-0+struct io { +/* Used to track sequential IO so it can be skipped */ +struct hlist_node hash; +struct list_head lru; + +unsigned long jiffies; +unsigned sequential; +sector_t last; +};I don't think bcache can have struct io. :P
Quoted 2 lines
Is your complaint that there's a struct io somewhere else, or just the potential namespace clash in the future? Regardless, I can change it.
The latter.
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
drivers/block/bcache/bcache.h new file +11
-0+struct dirty_io { +struct closure cl; +struct cached_dev *d; +struct bio bio; +}; + +struct dirty { +struct rb_node node; +BKEY_PADDED (key); +struct dirty_io *io; +};...
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
+struct cache {
Nor these and so on.
You want me to prefix all my struct names with
bcache_? Blech.
It doesn't have to be full bcache. e.g. words starting with cache can simply have 'b' in front and others can use things like bc_ or whatever.
Quoted 11 lines
Quoted 8 lines
So, apart from the the macros, key is 64bit containing the backend device and extent offset and size with the ptr fields somehow point to cache. Am I understanding it correctly? If right, I'm *tiny* bit apprehensive about using only 43bits for offset. While the block 9 bits means 52bit addressing, the 9 bit block size is now there mostly to work as buffer between memory bitness growth and storage device size growth so that we have 9 bit buffer as storage device reaches ulong addressing limit. Probably those days are far out enough.
You're exactly right. I had similar thoughts about the offset size, but... it'll last until we have 8 exabyte cache devices, and I can't
I'm a bit confused. Cache device or cached device? Isn't the key dev:offset:size of the cached device?
Quoted 5 lines
Quoted 4 lines
mca_data_alloc()failure path seems like a resource leak but it isn't becausemca_data_alloc()puts it in free list. Is the extra level of caching necessary? How is it different from sl?b allocator cache? And either way, comments please.
Quoted 8 lines
This btree in memory cache code is probably the nastiest, subtlest, trickiest code in bcache. I have some cleanups in a branch somewhere as part of some other work that I need to finish off.
The
btree_cache_freedlist isn't for caching - we never free struct btrees except on shutdown, to simplify the code. It doesn't cost much memory since the memory usage is dominated by the buffers struct btree points to, and those are freed when necessary.
Out of curiosity, how does not freeing make the code simpler? Is it something synchronization related?
Quoted 7 lines
btree_cache_freeableis a small cache for the buffers struct btree points to (the btree node as it exists on disk + the bset tree). We have that because high order page allocations can be expensive, and it's quite common to delete and allocate btree nodes in quick succession - but it should never grow past 2 or 3 nodes.I'm adding all this to where the lists are defined, in struct
cache_set.
Great.
Quoted 11 lines
Quoted 10 lines
drivers/block/bcache/btree.c line 796
Quoted 8 lines
drivers/block/bcache/btree.c new file +8
-0+ b->flags =0 ; + b->level = level; + b->written =0 ; + b->nsets =0 ; +for (int i =0 ; i <MAX_BSETS ; i++) + b->sets[i].size =0 ; +for (int i =1 ; i <MAX_BSETS ; i++) + b->sets[i].data =NULL ;Why separate loops?
Quoted 2 lines
Sceond loop starts at 1, because b->sets[0]->data points to the memory we have allocated. I suppose that merits a comment.
Ah, okay.
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 11/16] bcache: Core btree code
Quoted 3 lines · Tejun Heo
Hmmm... I would prefer it to be defined explicitly as union. It's rather easy to define it incorrectly (ie. using struct bkey) and then pass it around expecting it to have the pad.
Thing is, things don't expect the pad - bkeys are normally just in a big chunk of memory concatenated together, and the same functions have to work both with those and with bare bkeys the code occasionally manipulates.
Quoted 3 lines
It doesn't have to be full bcache. e.g. words starting with cache can simply have 'b' in front and others can use things like
bc_or whatever.
Ok, that sounds quite reasonable.
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
So, apart from the the macros, key is 64bit containing the backend device and extent offset and size with the ptr fields somehow point to cache. Am I understanding it correctly? If right, I'm *tiny* bit apprehensive about using only 43bits for offset. While the block 9 bits means 52bit addressing, the 9 bit block size is now there mostly to work as buffer between memory bitness growth and storage device size growth so that we have 9 bit buffer as storage device reaches ulong addressing limit. Probably those days are far out enough.
Quoted 5 lines
Quoted 2 lines
You're exactly right. I had similar thoughts about the offset size, but... it'll last until we have 8 exabyte cache devices, and I can't
I'm a bit confused. Cache device or cached device? Isn't the key dev:offset:size of the cached device?
No - bkey->key is the offset on the cached device, PTR_OFFSET is on the cache.
Confusing, I know. Any ideas for better terminology?
Quoted 10 lines
Quoted 10 lines
Quoted 5 lines
mca_data_alloc()failure path seems like a resource leak but it isn't becausemca_data_alloc()puts it in free list. Is the extra level of caching necessary? How is it different from sl?b allocator cache? And either way, comments please.This btree in memory cache code is probably the nastiest, subtlest, trickiest code in bcache. I have some cleanups in a branch somewhere as part of some other work that I need to finish off.
Quoted 7 lines
Quoted 4 lines
The
btree_cache_freedlist isn't for caching - we never free struct btrees except on shutdown, to simplify the code. It doesn't cost much memory since the memory usage is dominated by the buffers struct btree points to, and those are freed when necessary.Out of curiosity, how does not freeing make the code simpler? Is it something synchronization related?
Yeah - looking up btree nodes in the in memory cache involves checking a lockless hash table (i.e. using hlist_for_each_rcu()).
It would be fairly trivial to free them with kfree_rcu(), but I'd have to go through and make sure there aren't any other places where there could be dangling references - i.e. io related stuff. And I wouldn't be able to delete any code - I need the btree_cache_freed list anyways so I can preallocate a reserve at startup.
I all the changes I've made based on your review feedback so far up -git://evilpiepirate.org/~kent/linux-bcache.git bcache-3.3-dev
Kent Overstreet (7):
Document some things and incorporate some review feedback
bcache: Fix a bug in submit_bbio_split()
bcache: sprint_string_list() -> snprint_string_list() Add human-readable units modifier to vsnprintf()
bcache: Kill hprint()
bcache: Review feedback
bcache: Kill popcount()
Tejun Heo
maintainerRe: [Bcache v13 11/16] bcache: Core btree code
Hello,
Quoted 9 lines · Kent Overstreet
Quoted 3 lines · Tejun Heo
Hmmm... I would prefer it to be defined explicitly as union. It's rather easy to define it incorrectly (ie. using struct bkey) and then pass it around expecting it to have the pad.
Thing is, things don't expect the pad - bkeys are normally just in a big chunk of memory concatenated together, and the same functions have to work both with those and with bare bkeys the code occasionally manipulates.
Hmmm... so it's actually just padding? Padding for what? I thought it was there to provide space for ptr[], no?
Quoted 7 lines
Quoted 2 lines
I'm a bit confused. Cache device or cached device? Isn't the key dev:offset:size of the cached device?
No -
bkey->keyis the offset on the cached device,PTR_OFFSETis on the cache.Confusing, I know. Any ideas for better terminology?
Double confused by the "no" and the following sentence seemingly agreeing with what I wrote. So, bkey->key indexes the backend device
- the slow big disk and the associated PTRs point into the fast SSD caching device, right? If so, I think 'key' is fine, that's the only thing which can be key anyway. As for PTR_XXX, maybe something which can signify they're the cache would be nicer but with proper comments I don't think it's big deal.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 11/16] bcache: Core btree code
In reply to Kent OverstreetHello, Kent.
drivers/block/bcache/bcache.h line 37
Quoted 2 lines · Kent Overstreet
drivers/block/bcache/bcache.h new file +2
-0+#define BKEY_PADDED (key) \ + union { struct bkey key; uint64_t key ## _pad[8]; }
What does the '8' mean? And why does struct bbio use 3 instead? Does this have to be a macro? Can't we have struct bkey_padded (or whatever)?
drivers/block/bcache/bcache.h line 191
Quoted 9 lines
drivers/block/bcache/bcache.h new file +9
-0+struct io { +/* Used to track sequential IO so it can be skipped */ +struct hlist_node hash; +struct list_head lru; + +unsigned long jiffies; +unsigned sequential; +sector_t last; +};
I don't think bcache can have struct io. :P
Quoted 11 lines
drivers/block/bcache/bcache.h new file +11
-0+struct dirty_io { +struct closure cl; +struct cached_dev *d; +struct bio bio; +}; + +struct dirty { +struct rb_node node; +BKEY_PADDED (key); +struct dirty_io *io; +};
...
Quoted 1 line
+struct cache {
Nor these and so on.
Quoted 20 lines
drivers/block/bcache/bcache.h new file +20
-0+/* Bkey fields: all units are in sectors */ + +#define KEY_FIELD (name, field, offset, size) \ + BITMASK(name, struct bkey, field, offset, size) + +#define PTR_FIELD (name, offset, size) \ + static inline uint64_t name(const struct bkey *k, unsigned i) \ + { return (k->ptr[i] >> offset) & ~(((uint64_t) ~0) << size); } \ + \ + static inline void SET_##name(struct bkey *k, unsigned i, uint64_t v)\ + { \ + k->ptr[i] &= ~(~((uint64_t) ~0 << size) << offset); \ + k->ptr[i] |= v << offset; \ + } + +KEY_FIELD(KEY_PTRS, header, 60, 3) +KEY_FIELD(HEADER_SIZE, header, 58, 2) +KEY_FIELD(KEY_CSUM, header, 56, 2) +KEY_FIELD(KEY_PINNED, header, 55, 1) +KEY_FIELD (KEY_DIRTY , header,36 ,1 )
drivers/block/bcache/bcache.h line 670
Quoted 10 lines
drivers/block/bcache/bcache.h new file +10
-0+ +KEY_FIELD(KEY_SIZE, header, 20, 16) +KEY_FIELD(KEY_DEV, header, 0, 20) + +KEY_FIELD(KEY_SECTOR, key, 16, 47) +KEY_FIELD(KEY_SNAPSHOT, key, 0, 16) + +PTR_FIELD(PTR_DEV, 51, 12) +PTR_FIELD(PTR_OFFSET, 8, 43) +PTR_FIELD (PTR_GEN ,0 ,8 )
So, apart from the the macros, key is 64bit containing the backend device and extent offset and size with the ptr fields somehow point to cache. Am I understanding it correctly? If right, I'm *tiny* bit apprehensive about using only 43bits for offset. While the block 9 bits means 52bit addressing, the 9 bit block size is now there mostly to work as buffer between memory bitness growth and storage device size growth so that we have 9 bit buffer as storage device reaches ulong addressing limit. Probably those days are far out enough.
Quoted 68 lines
drivers/block/bcache/btree.c new file +68
-0+void btree_read_done (struct closure *cl) +{ + struct btree *b = container_of(cl, struct btree, io.cl); +struct bset *i = b->sets[0 ].data; +struct btree_iter *iter = b->c->fill_iter; +const char *err ="bad btree header" ; +BUG_ON (b->nsets || b->written); + +bbio_free (b->bio, b->c); + b->bio =NULL ; + +mutex_lock (&b->c->fill_lock); + iter->used =0 ; + +if (btree_node_io_error (b) || + !i->seq) + goto err; + +for (; + b->written <btree_blocks (b) && i->seq == b->sets[0 ].data->seq; + i =write_block (b)) { + err ="unsupported bset version" ; +if (i->version >BCACHE_BSET_VERSION ) + goto err; + + err ="bad btree header" ; +if (b->written +set_blocks (i, b->c) >btree_blocks (b)) + goto err; + + err ="bad magic" ; +if (i->magic !=bset_magic (b->c)) + goto err; + + err ="bad checksum" ; +switch (i->version) { +case 0 : +if (i->csum !=csum_set (i)) + goto err; +break ; +case BCACHE_BSET_VERSION : +if (i->csum !=btree_csum_set (b, i)) + goto err; +break ; + } + + err ="empty set" ; +if (i != b->sets[0 ].data && !i->keys) + goto err; + +btree_iter_push (iter, i->start,end (i)); + + b->written +=set_blocks (i, b->c); + } + + err ="corrupted btree" ; +for (i =write_block (b); +index (i, b) <btree_blocks (b); + i = ((void *) i) +block_bytes (b->c)) +if (i->seq == b->sets[0 ].data->seq) + goto err; + +btree_sort_and_fix_extents (b, iter); + + i = b->sets[0 ].data; + err ="short btree key" ; +if (b->sets[0 ].size && +bkey_cmp (&b->key, &b->sets[0 ].end) <0 ) + goto err;
drivers/block/bcache/btree.c line 233
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +if (b->written <btree_blocks (b)) +bset_init_next (b); + +if (0 ) { +err:set_btree_node_io_error (b); +cache_set_error (b->c,"%s at bucket %lu, block %zu, %u keys" , + err,PTR_BUCKET_NR (b->c, &b->key,0 ), +index (i, b), i->keys); + }
Please don't do that. Just define out: label, move error specific path to the end of the function and jump to out at the end of that.
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +mutex_unlock (&b->c->fill_lock); + +spin_lock (&b->c->btree_read_time_lock); +time_stats_update (&b->c->btree_read_time, b->io_start_time); +spin_unlock (&b->c->btree_read_time_lock); + +smp_wmb ();/* read_done is our write lock */ +set_btree_node_read_done (b); +
drivers/block/bcache/btree.c line 253
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+closure_return (cl); +} + +static void btree_read_resubmit (struct closure *cl) +{ + struct btree *b = container_of(cl, struct btree, io.cl); + +submit_bbio_split (b->bio, b->c, &b->key,0 ); +continue_at (&b->io.cl, btree_read_done, system_wq); +}
I suppose submit_bbio_split() can't fail here somehow unlike from btree_read() path? If so, please add a comment to explain and maybe WARN_ON_ONCE() on failure. Subtlety to comment ratio is *way* off.
Quoted 6 lines
drivers/block/bcache/btree.c new file +6
-0+static struct btree *mca_bucket_alloc (struct cache_set *c, +struct bkey *k,gfp_t gfp) +{ +struct btree *b =kzalloc (sizeof (struct btree ), gfp); +if (!b) +return NULL ;
drivers/block/bcache/btree.c line 548
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +init_rwsem (&b->lock); +INIT_LIST_HEAD (&b->list); +INIT_DELAYED_WORK (&b->work, btree_write_work); + b->c = c; +closure_init_unlocked (&b->io); + +mca_data_alloc (b, k, gfp); +return b->sets[0 ].data ? b :NULL ; +}
mca_data_alloc() failure path seems like a resource leak but it isn't because mca_data_alloc() puts it in free list. Is the extra level of caching necessary? How is it different from sl?b allocator cache? And either way, comments please.
Quoted 15 lines
drivers/block/bcache/btree.c new file +15
-0+static int mca_reap (struct btree *b,struct closure *cl) +{ +lockdep_assert_held (&b->c->bucket_lock); + +if (!down_write_trylock (&b->lock)) +return -1 ; + +BUG_ON (btree_node_dirty (b) && !b->sets[0 ].data); + +if (cl &&btree_node_dirty (b)) +btree_write (b, true,NULL ); + +if (cl) +closure_wait_event_async (&b->io.wait, cl, +atomic_read (&b->io.cl.remaining) ==-1 );
drivers/block/bcache/btree.c line 574
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+ +if (btree_node_dirty (b) || +atomic_read (&b->io.cl.remaining) !=-1 || +work_pending (&b->work.work)) { +rw_unlock (true, b); +return -EAGAIN ; + } + +return 0 ; +}
Mixing -1 and -EAGAIN returns usually isn't a good idea.
Quoted 45 lines
drivers/block/bcache/btree.c new file +45
-0+static struct btree *alloc_bucket (struct cache_set *c,struct bkey *k, +int level,struct closure *cl) +{ +struct btree *b, *i; +unsigned page_order =ilog2 (KEY_SIZE (k) /PAGE_SECTORS ?:1 ); + +lockdep_assert_held (&c->bucket_lock); +retry: +if (find_bucket (c, k)) +return NULL ; + +/* btree_free() doesn't free memory; it sticks the node on the end of +* the list. Check if there's any freed nodes there: +*/ +list_for_each_entry (b, &c->btree_cache_freeable, list) +if (page_order <= b->page_order && + !b->key.ptr[0 ] && + !mca_reap (b,NULL )) + goto out; + +/* We never free struct btree itself, just the memory that holds the on +* disk node. Check the freed list before allocating a new one: +*/ +list_for_each_entry (b, &c->btree_cache_freed, list) +if (!mca_reap (b,NULL )) { +mca_data_alloc (b, k, __GFP_NOWARN|GFP_NOIO ); +if (!b->sets[0 ].data) { +rw_unlock (true, b); + goto err; + }else + goto out; + } + + b =mca_bucket_alloc (c, k, __GFP_NOWARN|GFP_NOIO ); +if (!b) + goto err; + +BUG_ON (!down_write_trylock (&b->lock)); +out: +BUG_ON (!closure_is_unlocked (&b->io.cl)); + +bkey_copy (&b->key, k); +list_move (&b->list, &c->btree_cache); +hlist_del_init_rcu (&b->hash); +hlist_add_head_rcu (&b->hash,hash_bucket (c, k));
drivers/block/bcache/btree.c line 796
Quoted 10 lines
drivers/block/bcache/btree.c new file +10
-0+lock_set_subclass (&b->lock.dep_map, level +1 , _THIS_IP_); + + b->flags =0 ; + b->level = level; + b->written =0 ; + b->nsets =0 ; +for (int i =0 ; i <MAX_BSETS ; i++) + b->sets[i].size =0 ; +for (int i =1 ; i <MAX_BSETS ; i++) + b->sets[i].data =NULL ;
Why separate loops?
drivers/block/bcache/btree.c line 801
Quoted 5 lines
drivers/block/bcache/btree.c new file +5
-0+ +return b; +err: +if (current->bio_list) +return ERR_PTR (-EAGAIN );
What does this test do?
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 11/16] bcache: Core btree code
In reply to Kent OverstreetA couple more comments from this round of reading.
drivers/block/bcache/bcache.h line 599
Quoted 2 lines · Kent Overstreet
drivers/block/bcache/bcache.h new file +2
-0+#define btree_prio USHRT_MAX +#define initial_prio 32768
Why are these in lower case?
drivers/block/bcache/bcache.h line 685
Quoted 2 lines
drivers/block/bcache/bcache.h new file +2
-0+#define PTR_BUCKET (c, k, n) \ + (PTR_CACHE(c, k, n)->buckets + PTR_BUCKET_NR(c, k, n))
PTR_BUCKET(c, k, n)
Awesome. I don't know what type it takes or what each single character argument stands for.
drivers/block/bcache/bcache.h line 764
Quoted 6 lines
drivers/block/bcache/bcache.h new file +6
-0+static inline bool cached_dev_get (struct cached_dev *d) +{ +if (!atomic_inc_not_zero (&d->count)) +return false; + +smp_mb__after_atomic_inc ();
What is this mb() paired with? Whenever using a mb, please specify what the mb is paired with.
Review
Quoted 2 lines
(quoted diff) +2
-0+ return true; +}
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 11/16] bcache: Core btree code
Quoted 7 lines · Tejun Heo
A couple more comments from this round of reading.
Review
Quoted 2 lines · Kent Overstreet
(quoted diff) +2
-0+#define btree_prio USHRT_MAX +#define initial_prio 32768Why are these in lower case?
No good reason. I'll change it.
Quoted 8 lines
Review
Quoted 3 lines
(quoted diff) +2
-0+#define PTR_BUCKET(c, k, n) \ + (PTR_CACHE(c, k, n)->buckets + PTR_BUCKET_NR(c, k, n))
PTR_BUCKET(c, k, n)Awesome. I don't know what type it takes or what each single character argument stands for.
Yeah, that should be an inline function, along with some others. Fixed - well, have to convert the rest of the code to the lowercase names:
Maybe ptr_bucket_idx() instead of ptr_bucket_nr()?
drivers/md/bcache/bcache.h +33 -7
@@ -717,15 +717,41 @@ PTR_FIELD(PTR_GEN, 0, 8)#define PTR (gen, offset, dev) \ ((((uint64_t) dev) << 51) | ((uint64_t) offset) << 8 | gen) -#define sector_to_bucket (c, s) ((long) ((s) >> (c)->bucket_bits)) -#define bucket_to_sector (c, b) (((sector_t) (b)) << (c)->bucket_bits) -#define bucket_remainder (c, b) ((b) & ((c)->sb.bucket_size - 1)) +static inline size_t sector_to_bucket (struct cache_set *c,sector_t s) +{ +return s >> c->bucket_bits; +} + +static inline sector_t bucket_to_sector (struct cache_set *c,size_t b) +{ +return ((sector_t ) b) << c->bucket_bits; +} -#define PTR_CACHE (c, k, n) ((c)->cache[PTR_DEV(k, n)]) -#define PTR_BUCKET_NR (c, k, n) sector_to_bucket(c, PTR_OFFSET(k, n)) +static inline sector_t bucket_remainder (struct cache_set *c,sector_t s) +{ +return s & (c->sb.bucket_size -1 ); +} -#define PTR_BUCKET (c, k, n) \ - (PTR_CACHE(c, k, n)->buckets + PTR_BUCKET_NR(c, k, n)) +static inline struct cache *ptr_cache (struct cache_set *c, +struct bkey *k, +unsigned ptr) +{ +return c->cache[PTR_DEV (k, ptr)]; +} + +static inline size_t ptr_bucket_nr (struct cache_set *c, +struct bkey *k, +unsigned ptr) +{ +return sector_to_bucket (c,PTR_OFFSET (k, ptr)); +} + +static inline struct bucket *ptr_bucket (struct cache_set *c, +struct bkey *k, +unsigned ptr) +{ +return ptr_cache (c, k, ptr)->buckets +ptr_bucket_nr (c, k, ptr); +}/* Btree key macros */
Quoted 10 lines
Review
Quoted 7 lines
(quoted diff) +6
-0+static inline bool cached_dev_get(struct cached_dev *d) +{ + if (!atomic_inc_not_zero(&d->count)) + return false; + + smp_mb__after_atomic_inc();What is this
mb()paired with? Whenever using a mb, please specify what the mb is paired with.
super.c, cached_dev_attach():
smp_wmb(); /* d->c must be set before d->count != 0 */ atomic_set(&d->count, 1);
I'm improving the comments.
Quoted 8 lines
Review
Quoted 3 lines
(quoted diff) +2
-0+ return true; +}Thanks.
Signature · 1 line
tejun
PATCH v13 12/16 bcache: Bset code (lookups within a btree node)
In reply to Kent Overstreet--- drivers/block/bcache/bset.c | 1149 +++++++++++++++++++++++++++++++++++++++++++ drivers/block/bcache/bset.h | 218 ++++++++ 2 files changed, 1367 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/bset.c create mode 100644 drivers/block/bcache/bset.h
drivers/block/bcache/bset.c new file +1149 -0
@@ -0,0 +1,1149 @@ + +#include "bcache.h" +#include "btree.h" +#include "debug.h" + +#include <linux/random.h> + +/* Keylists */ + +void keylist_copy (struct keylist *dest,struct keylist *src) +{ + *dest = *src; + +if (src->list == src->d) { +size_t n = (uint64_t *) src->top - src->d; + dest->top = (struct bkey *) &dest->d[n]; + dest->list = dest->d; + } +} + +int keylist_realloc (struct keylist *l,int nptrs,struct cache_set *c) +{ +unsigned oldsize = (uint64_t *) l->top - l->list; +unsigned newsize = oldsize +2 + nptrs; +uint64_t *new; + +/* The journalling code doesn't handle the case where the keys to insert +* is bigger than an empty write: If we just return -ENOMEM here, +* bio_insert() and bio_invalidate() will insert the keys created so far +* and finish the rest when the keylist is empty. +*/ +if (newsize *sizeof (uint64_t ) >block_bytes (c) -sizeof (struct jset )) +return -ENOMEM ; + + newsize =roundup_pow_of_two (newsize); + +if (newsize <=KEYLIST_INLINE || +roundup_pow_of_two (oldsize) == newsize) +return 0 ; + + new =krealloc (l->list == l->d ?NULL : l->list, +sizeof (uint64_t ) * newsize,GFP_NOIO ); + +if (!new) +return -ENOMEM ; + +if (l->list == l->d) +memcpy (new, l->list,sizeof (uint64_t ) *KEYLIST_INLINE ); + + l->list = new; + l->top = (struct bkey *) (&l->list[oldsize]); + +return 0 ; +} + +struct bkey *keylist_pop (struct keylist *l) +{ +struct bkey *k = l->bottom; + +if (k == l->top) +return NULL ; + +while (next (k) != l->top) + k =next (k); + +return l->top = k; +} + +/* Pointer validation */ + +bool __ptr_invalid (struct cache_set *c,int level,const struct bkey *k) +{ +if (level && (!KEY_PTRS (k) || !KEY_SIZE (k) ||KEY_DIRTY (k))) + goto bad; + +if (!level &&KEY_SIZE (k) > k->key) + goto bad; + +if (!KEY_SIZE (k)) +return true; + +for (unsigned i =0 ; i <KEY_PTRS (k); i++) +if (ptr_available (c, k, i)) { +struct cache *ca =PTR_CACHE (c, k, i); +size_t bucket =PTR_BUCKET_NR (c, k, i); +size_t r =bucket_remainder (c,PTR_OFFSET (k, i)); + +if (KEY_SIZE (k) + r > c->sb.bucket_size || + bucket < ca->sb.first_bucket || + bucket >= ca->sb.nbuckets) + goto bad; + } + +return false; +bad: +cache_bug (c,"spotted bad key %s: %s" ,pkey (k),ptr_status (c, k)); +return true; +} + +bool ptr_invalid (struct btree *b,const struct bkey *k) +{ +return __ptr_invalid (b->c, b->level, k); +} + +bool ptr_bad (struct btree *b,const struct bkey *k) +{ +struct bucket *g; +unsigned i, stale; + +if (!bkey_cmp (k, &ZERO_KEY ) || + !KEY_PTRS (k) || +ptr_invalid (b, k)) +return true; + +if (KEY_PTRS (k) &&PTR_DEV (k,0 ) ==PTR_CHECK_DEV ) +return true; + +for (i =0 ; i <KEY_PTRS (k); i++) +if (ptr_available (b->c, k, i)) { + g =PTR_BUCKET (b->c, k, i); + stale =ptr_stale (b->c, k, i); + +btree_bug_on (stale >96 , b, +"key too stale: %i, need_gc %u" , + stale, b->c->need_gc); + +btree_bug_on (stale &&KEY_DIRTY (k) &&KEY_SIZE (k), + b,"stale dirty pointer" ); + +if (stale) +return true; + +#ifdef CONFIG_BCACHE_EDEBUG +if (!mutex_trylock (&b->c->bucket_lock)) +continue ; + +if (b->level) { +if (KEY_DIRTY (k) || + g->prio != btree_prio || + (b->c->gc_mark_valid && + g->mark !=GC_MARK_BTREE )) + goto bug; + + }else { +if (g->prio == btree_prio) + goto bug; + +if (KEY_DIRTY (k) && + b->c->gc_mark_valid && + g->mark !=GC_MARK_DIRTY ) + goto bug; + } +mutex_unlock (&b->c->bucket_lock); +#endif + } + +return false; +#ifdef CONFIG_BCACHE_EDEBUG +bug: +mutex_unlock (&b->c->bucket_lock); +btree_bug (b,"inconsistent pointer %s: bucket %li pin %i " +"prio %i gen %i last_gc %i mark %i gc_gen %i" ,pkey (k), +PTR_BUCKET_NR (b->c, k, i),atomic_read (&g->pin), + g->prio, g->gen, g->last_gc, g->mark, g->gc_gen); +return true; +#endif +} + +/* Key/pointer manipulation */ + +void bkey_copy_single_ptr (struct bkey *dest,const struct bkey *src,unsigned i) +{ +BUG_ON (i >KEY_PTRS (src)); + +/* Only copy the header, key, and one pointer. */ +memcpy (dest, src,2 *sizeof (uint64_t )); + dest->ptr[0 ] = src->ptr[i]; +SET_KEY_PTRS (dest,1 ); +/* We didn't copy the checksum so clear that bit. */ +SET_KEY_CSUM (dest,0 ); +} + +bool __cut_front (const struct bkey *where,struct bkey *k) +{ +unsigned len =0 ; + +if (bkey_cmp (where, &START_KEY (k)) <=0 ) +return false; + +if (bkey_cmp (where, k) <0 ) + len = k->key - where->key; +else +bkey_copy_key (k, where); + +for (unsigned i =0 ; i <KEY_PTRS (k); i++) +SET_PTR_OFFSET (k, i,PTR_OFFSET (k, i) +KEY_SIZE (k) - len); + +BUG_ON (len >KEY_SIZE (k)); +SET_KEY_SIZE (k, len); +return true; +} + +bool __cut_back (const struct bkey *where,struct bkey *k) +{ +unsigned len =0 ; + +if (bkey_cmp (where, k) >=0 ) +return false; + +BUG_ON (KEY_DEV (where) !=KEY_DEV (k)); + +if (bkey_cmp (where, &START_KEY (k)) >0 ) + len = where->key -KEY_START (k); + +bkey_copy_key (k, where); + +BUG_ON (len >KEY_SIZE (k)); +SET_KEY_SIZE (k, len); +return true; +} + +static uint64_t merge_chksums (struct bkey *l,struct bkey *r) +{ +return (l->ptr[KEY_PTRS (l)] + r->ptr[KEY_PTRS (r)]) & + ~((uint64_t )1 <<63 ); +} + +/* Tries to merge l and r: l should be lower than r +* Returns true if we were able to merge. If we did merge, l will be the merged +* key, r will be untouched. +*/ +bool bkey_try_merge (struct btree *b,struct bkey *l,struct bkey *r) +{ +if (key_merging_disabled (b->c)) +return false; + +if (KEY_PTRS (l) !=KEY_PTRS (r) || +KEY_DIRTY (l) !=KEY_DIRTY (r) || +bkey_cmp (l, &START_KEY (r))) +return false; + +for (unsigned j =0 ; j <KEY_PTRS (l); j++) +if (l->ptr[j] +PTR (0 ,KEY_SIZE (l),0 ) != r->ptr[j] || +PTR_BUCKET_NR (b->c, l, j) !=PTR_BUCKET_NR (b->c, r, j)) +return false; + +/* Keys with no pointers aren't restricted to one bucket and could +* overflow KEY_SIZE +*/ +if (KEY_SIZE (l) +KEY_SIZE (r) >USHRT_MAX ) { + l->key +=USHRT_MAX -KEY_SIZE (l); +SET_KEY_SIZE (l,USHRT_MAX ); + +cut_front (l, r); +return false; + } + +if (KEY_CSUM (l)) { +if (KEY_CSUM (r)) + l->ptr[KEY_PTRS (l)] =merge_chksums (l, r); +else +SET_KEY_CSUM (l,0 ); + } + +SET_KEY_SIZE (l,KEY_SIZE (l) +KEY_SIZE (r)); + l->key +=KEY_SIZE (r); + +return true; +} + +/* Binary tree stuff for auxiliary search trees */ + +static unsigned inorder_next (unsigned j,unsigned size) +{ +if (j *2 +1 < size) { + j = j *2 +1 ; + +while (j *2 < size) + j *=2 ; + }else + j >>=ffz (j) +1 ; + +return j; +} + +static unsigned inorder_prev (unsigned j,unsigned size) +{ +if (j *2 < size) { + j = j *2 ; + +while (j *2 +1 < size) + j = j *2 +1 ; + }else + j >>=ffs (j); + +return j; +} + +/* I have no idea why this code works... and I'm the one who wrote it +* +* However, I do know what it does: +* Given a binary tree constructed in an array (i.e. how you normally implement +* a heap), it converts a node in the tree - referenced by array index - to the +* index it would have if you did an inorder traversal. +* +* The binary tree starts at array index 1, not 0 +* extra is a function of size: +* extra = (size - rounddown_pow_of_two(size - 1)) << 1; +*/ +static unsigned __to_inorder (unsigned j,unsigned size,unsigned extra) +{ +unsigned b =fls (j); +unsigned shift =fls (size -1 ) - b; + + j ^=1U << (b -1 ); + j <<=1 ; + j |=1 ; + j <<= shift; + +if (j > extra) + j -= (j - extra) >>1 ; + +return j; +} + +static unsigned to_inorder (unsigned j,struct bset_tree *t) +{ +return __to_inorder (j, t->size, t->extra); +} + +static unsigned __inorder_to_tree (unsigned j,unsigned size,unsigned extra) +{ +unsigned shift; + +if (j > extra) + j += j - extra; + + shift =ffs (j); + + j >>= shift; + j |=roundup_pow_of_two (size) >> shift; + +return j; +} + +static unsigned inorder_to_tree (unsigned j,struct bset_tree *t) +{ +return __inorder_to_tree (j, t->size, t->extra); +} + +#if 0 +void inorder_test (void ) +{ +unsigned long done =0 ; +ktime_t start =ktime_get (); + +for (unsigned size =2 ; + size <65536000 ; + size++) { +unsigned extra = (size -rounddown_pow_of_two (size -1 )) <<1 ; +unsigned i =1 , j =rounddown_pow_of_two (size -1 ); + +if (!(size %4096 )) +printk (KERN_NOTICE "loop %u, %llu per us\n" , size, + done /ktime_us_delta (ktime_get (), start)); + +while (1 ) { +if (__inorder_to_tree (i, size, extra) != j) +panic ("size %10u j %10u i %10u" , size, j, i); + +if (__to_inorder (j, size, extra) != i) +panic ("size %10u j %10u i %10u" , size, j, i); + +if (j ==rounddown_pow_of_two (size) -1 ) +break ; + +BUG_ON (inorder_prev (inorder_next (j, size), size) != j); + + j =inorder_next (j, size); + i++; + } + + done += size -1 ; + } +} +#endif + +/* +* Cacheline/offset <-> bkey pointer arithmatic: +* +* t->tree is a binary search tree in an array; each node corresponds to a key +* in one cacheline in t->set (BSET_CACHELINE bytes). +* +* This means we don't have to store the full index of the key that a node in +* the binary tree points to; to_inorder() gives us the cacheline, and then +* bkey_float->m gives us the offset within that cacheline, in units of 8 bytes. +* +* cacheline_to_bkey() and friends abstract out all the pointer arithmatic to +* make this work. +* +* To construct the bfloat for an arbitrary key we need to know what the key +* immediately preceding it is: we have to check if the two keys differ in the +* bits we're going to store in bkey_float->mantissa. t->prev[j] stores the size +* of the previous key so we can walk backwards to it from t->tree[j]'s key. +*/ + +static struct bkey *cacheline_to_bkey (struct bset_tree *t,unsigned cacheline, +unsigned offset) +{ +return ((void *) t->data) + cacheline *BSET_CACHELINE + offset *8 ; +} + +static unsigned bkey_to_cacheline (struct bset_tree *t,struct bkey *k) +{ +return ((void *) k - (void *) t->data) /BSET_CACHELINE ; +} + +static unsigned bkey_to_cacheline_offset (struct bkey *k) +{ +return ((size_t ) k & (BSET_CACHELINE -1 )) /sizeof (uint64_t ); +} + +static struct bkey *tree_to_bkey (struct bset_tree *t,unsigned j) +{ +return cacheline_to_bkey (t,to_inorder (j, t), t->tree[j].m); +} + +static struct bkey *tree_to_prev_bkey (struct bset_tree *t,unsigned j) +{ +return (void *) (((uint64_t *)tree_to_bkey (t, j)) - t->prev[j]); +} + +/* +* For the write set - the one we're currently inserting keys into - we don't +* maintain a full search tree, we just keep a simple lookup table in t->prev. +*/ +struct bkey *table_to_bkey (struct bset_tree *t,unsigned cacheline) +{ +return cacheline_to_bkey (t, cacheline, t->prev[cacheline]); +} + +/* Auxiliary search trees */ + +static inline uint64_t shrd128 (uint64_t high,uint64_t low,uint8_t shift) +{ +#ifdef CONFIG_X86_64 + asm("shrd %[shift],%[high],%[low]" + : [low]"+Rm" (low) + : [high]"R" (high), + [shift]"ci" (shift) + :"cc" ); +#else + low >>= shift; + low |= (high <<1 ) << (63U - shift); +#endif +return low; +} + +static inline unsigned bfloat_mantissa (const struct bkey *k, +struct bkey_float *f) +{ +const uint64_t *p = &k->key - (f->exponent >>6 ); +return shrd128 (p[-1 ], p[0 ], f->exponent &63 ) &BKEY_MANTISSA_MASK ; +} + +static void make_bfloat (struct bset_tree *t,unsigned j) +{ +struct bkey_float *f = &t->tree[j]; +struct bkey *m =tree_to_bkey (t, j); +struct bkey *p =tree_to_prev_bkey (t, j); + +struct bkey *l =is_power_of_2 (j) + ? t->data->start + :tree_to_prev_bkey (t, j >>ffs (j)); + +struct bkey *r =is_power_of_2 (j +1 ) + ?node (t->data, t->data->keys -bkey_u64s (&t->end)) + :tree_to_bkey (t, j >> (ffz (j) +1 )); + +BUG_ON (m < l || m > r); +BUG_ON (next (p) != m); + +if (KEY_DEV (l) !=KEY_DEV (r)) + f->exponent =fls64 (KEY_DEV (r) ^KEY_DEV (l)) +64 ; +else + f->exponent =fls64 (r->key ^ l->key); + + f->exponent =max_t (int, f->exponent -BKEY_MANTISSA_BITS ,0 ); + +if (bfloat_mantissa (m, f) !=bfloat_mantissa (p, f)) + f->mantissa =bfloat_mantissa (m, f) -1 ; +else + f->exponent =127 ; +} + +static void bset_alloc_tree (struct btree *b,struct bset_tree *t) +{ +if (t != b->sets) { +unsigned j =roundup (t[-1 ].size, +64 /sizeof (struct bkey_float )); + + t->tree = t[-1 ].tree + j; + t->prev = t[-1 ].prev + j; + } + +while (t < b->sets +MAX_BSETS ) + t++->size =0 ; +} + +static void bset_build_unwritten_tree (struct btree *b) +{ +struct bset_tree *t = b->sets + b->nsets; + +bset_alloc_tree (b, t); + +if (t->tree != b->sets->tree +bset_tree_space (b)) { + t->prev[0 ] =bkey_to_cacheline_offset (t->data->start); + t->size =1 ; + } +} + +static void bset_build_written_tree (struct btree *b) +{ +struct bset_tree *t = b->sets + b->nsets; +struct bkey *k = t->data->start; +unsigned j, cacheline =1 ; + +bset_alloc_tree (b, t); + + t->size =min_t (unsigned, +bkey_to_cacheline (t,end (t->data)), + b->sets->tree +bset_tree_space (b) - t->tree); + +if (t->size <2 ) { + t->size =0 ; +return ; + } + + t->extra = (t->size -rounddown_pow_of_two (t->size -1 )) <<1 ; + +/* First we figure out where the first key in each cacheline is */ +for (j =inorder_next (0 , t->size); + j; + j =inorder_next (j, t->size)) { +while (bkey_to_cacheline (t, k) != cacheline) + k =next (k); + + t->prev[j] =bkey_u64s (k); + k =next (k); + cacheline++; + t->tree[j].m =bkey_to_cacheline_offset (k); + } + +while (next (k) !=end (t->data)) + k =next (k); + + t->end = *k; + +/* Then we build the tree */ +for (j =inorder_next (0 , t->size); + j; + j =inorder_next (j, t->size)) +make_bfloat (t, j); +} + +void bset_fix_invalidated_key (struct btree *b,struct bkey *k) +{ +struct bset_tree *t; +unsigned inorder, j =1 ; + +for (t = b->sets; t <= &b->sets[b->nsets]; t++) +if (k <end (t->data)) + goto found_set; + +BUG (); +found_set: +if (!t->size || !bset_written (b, t)) +return ; + + inorder =bkey_to_cacheline (t, k); + +if (k == t->data->start) + goto fix_left; + +if (next (k) ==end (t->data)) { + t->end = *k; + goto fix_right; + } + + j =inorder_to_tree (inorder, t); + +if (j && + j < t->size && + k ==tree_to_bkey (t, j)) +fix_left:do { +make_bfloat (t, j); + j = j *2 ; + }while (j < t->size); + + j =inorder_to_tree (inorder +1 , t); + +if (j && + j < t->size && + k ==tree_to_prev_bkey (t, j)) +fix_right:do { +make_bfloat (t, j); + j = j *2 +1 ; + }while (j < t->size); +} + +void bset_fix_lookup_table (struct btree *b,struct bkey *k) +{ +struct bset_tree *t = &b->sets[b->nsets]; +unsigned shift =bkey_u64s (k); +unsigned j =bkey_to_cacheline (t, k); + +/* We're getting called from btree_split() or btree_gc, just bail out */ +if (!t->size) +return ; + +/* k is the key we just inserted; we need to find the entry in the +* lookup table for the first key that is strictly greater than k: +* it's either k's cacheline or the next one +*/ +if (j < t->size && +table_to_bkey (t, j) <= k) + j++; + +/* Adjust all the lookup table entries, and find a new key for any that +* have gotten too big +*/ +for (; j < t->size; j++) { + t->prev[j] += shift; + +if (t->prev[j] >7 ) { + k =table_to_bkey (t, j -1 ); + +while (k <cacheline_to_bkey (t, j,0 )) + k =next (k); + + t->prev[j] =bkey_to_cacheline_offset (k); + } + } + +if (t->size == b->sets->tree +bset_tree_space (b) - t->tree) +return ; + +/* Possibly add a new entry to the end of the lookup table */ + +for (k =table_to_bkey (t, t->size -1 ); + k !=end (t->data); + k =next (k)) +if (t->size ==bkey_to_cacheline (t, k)) { + t->prev[t->size] =bkey_to_cacheline_offset (k); + t->size++; + } +} + +void bset_init_next (struct btree *b) +{ +struct bset *i =write_block (b); + +if (i != b->sets[0 ].data) { + b->sets[++b->nsets].data = i; + i->seq = b->sets[0 ].data->seq; + }else +get_random_bytes (&i->seq,sizeof (uint64_t )); + + i->magic =bset_magic (b->c); + i->version =0 ; + i->keys =0 ; + +bset_build_unwritten_tree (b); +} + +struct bset_search_iter { +struct bkey *l, *r; +}; + +__attribute__((optimize (3 ))) +static struct bset_search_iter bset_search_write_set (struct btree *b, +struct bset_tree *t, +const struct bkey *search) +{ +unsigned li =0 , ri = t->size; + +BUG_ON (!b->nsets && + t->size <bkey_to_cacheline (t,end (t->data))); + +while (li +1 != ri) { +unsigned m = (li + ri) >>1 ; + +if (bkey_cmp (table_to_bkey (t, m), search) >0 ) + ri = m; +else + li = m; + } + +return (struct bset_search_iter ) { +table_to_bkey (t, li), + ri < t->size ?table_to_bkey (t, ri) :end (t->data) + }; +} + +__attribute__((optimize (3 ))) +static struct bset_search_iter bset_search_tree (struct btree *b, +struct bset_tree *t, +const struct bkey *search) +{ +struct bkey *l, *r; +struct bkey_float *f; +unsigned inorder, j, n =1 ; + +do { +unsigned p = n <<4 ; + p &= ((int ) (p - t->size)) >>31 ; + +prefetch (&t->tree[p]); + + j = n; + f = &t->tree[j]; + +/* +* n = (f->mantissa > bfloat_mantissa()) +* ? j * 2 +* : j * 2 + 1; +* +* We need to subtract 1 from f->mantissa for the sign bit trick +* to work - that's done in make_bfloat() +*/ +if (likely (f->exponent !=127 )) + n = j *2 + (((unsigned ) + (f->mantissa - +bfloat_mantissa (search, f))) >>31 ); +else + n = (bkey_cmp (tree_to_bkey (t, j), search) >0 ) + ? j *2 + : j *2 +1 ; + }while (n < t->size); + + inorder =to_inorder (j, t); + +/* +* n would have been the node we recursed to - the low bit tells us if +* we recursed left or recursed right. +*/ +if (n &1 ) { + l =cacheline_to_bkey (t, inorder, f->m); + +if (++inorder != t->size) { + f = &t->tree[inorder_next (j, t->size)]; + r =cacheline_to_bkey (t, inorder, f->m); + }else + r =end (t->data); + }else { + r =cacheline_to_bkey (t, inorder, f->m); + +if (--inorder) { + f = &t->tree[inorder_prev (j, t->size)]; + l =cacheline_to_bkey (t, inorder, f->m); + }else + l = t->data->start; + } + +return (struct bset_search_iter ) {l, r}; +} + +__attribute__((optimize (3 ))) +struct bkey *__bset_search (struct btree *b,struct bset_tree *t, +const struct bkey *search) +{ +struct bset_search_iter i; + +/* +* First, we search for a cacheline, then lastly we do a linear search +* within that cacheline. +* +* To search for the cacheline, there's three different possibilities: +* * The set is too small to have a search tree, so we just do a linear +* search over the whole set. +* * The set is the one we're currently inserting into; keeping a full +* auxiliary search tree up to date would be too expensive, so we +* use a much simpler lookup table to do a binary search - +* bset_search_write_set(). +* * Or we use the auxiliary search tree we constructed earlier - +* bset_search_tree() +*/ + +if (unlikely (!t->size)) { + i.l = t->data->start; + i.r =end (t->data); + }else if (bset_written (b, t)) { +/* +* Each node in the auxiliary search tree covers a certain range +* of bits, and keys above and below the set it covers might +* differ outside those bits - so we have to special case the +* start and end - handle that here: +*/ + +if (unlikely (bkey_cmp (search, &t->end) >=0 )) +return end (t->data); + +if (unlikely (bkey_cmp (search, t->data->start) <0 )) +return t->data->start; + + i =bset_search_tree (b, t, search); + }else + i =bset_search_write_set (b, t, search); + +#ifdef CONFIG_BCACHE_EDEBUG +BUG_ON (bset_written (b, t) && + i.l != t->data->start && +bkey_cmp (tree_to_prev_bkey (t, +inorder_to_tree (bkey_to_cacheline (t, i.l), t)), + search) >0 ); + +BUG_ON (i.r !=end (t->data) && +bkey_cmp (i.r, search) <=0 ); +#endif + +while (likely (i.l != i.r) && +bkey_cmp (i.l, search) <=0 ) + i.l =next (i.l); + +return i.l; +} + +/* Btree iterator */ + +static inline bool btree_iter_cmp (struct btree_iter_set l, +struct btree_iter_set r) +{ +int64_t c =bkey_cmp (&START_KEY (l.k), &START_KEY (r.k)); + +return c ? c >0 : l.k < r.k; +} + +static inline bool btree_iter_end (struct btree_iter *iter) +{ +return !iter->used; +} + +void btree_iter_push (struct btree_iter *iter,struct bkey *k,struct bkey *end) +{ +if (k != end) +BUG_ON (!heap_add (iter, + ((struct btree_iter_set ) { k, end }), + btree_iter_cmp)); +} + +struct bkey *__btree_iter_init (struct btree *b,struct btree_iter *iter, +struct bkey *search,struct bset_tree *start) +{ +struct bkey *ret =NULL ; + iter->size =ARRAY_SIZE (iter->data); + iter->used =0 ; + +for (; start <= &b->sets[b->nsets]; start++) { + ret =bset_search (b, start, search); +btree_iter_push (iter, ret,end (start->data)); + } + +return ret; +} + +struct bkey *btree_iter_next (struct btree_iter *iter) +{ +struct btree_iter_set unused; +struct bkey *ret =NULL ; + +if (!btree_iter_end (iter)) { + ret = iter->data->k; + iter->data->k =next (iter->data->k); + +if (iter->data->k > iter->data->end) { +__WARN (); + iter->data->k = iter->data->end; + } + +if (iter->data->k == iter->data->end) +heap_pop (iter, unused, btree_iter_cmp); +else +heap_sift (iter,0 , btree_iter_cmp); + } + +return ret; +} + +struct bkey *next_recurse_key (struct btree *b,struct bkey *search) +{ +struct bkey *ret; +struct btree_iter iter; +btree_iter_init (b, &iter, search); + +do + ret =btree_iter_next (&iter); +while (ret &&ptr_bad (b, ret)); + +return ret; +} + +/* Mergesort */ + +static void btree_sort_fixup (struct btree_iter *iter) +{ +while (iter->used >1 ) { +struct btree_iter_set *top = iter->data, *i = top +1 ; +struct bkey *k; + +if (iter->used >2 && +btree_iter_cmp (i[0 ], i[1 ])) + i++; + +for (k = i->k; + k != i->end &&bkey_cmp (top->k, &START_KEY (k)) >0 ; + k =next (k)) +if (top->k > i->k) +__cut_front (top->k, k); +else if (KEY_SIZE (k)) +cut_back (&START_KEY (k), top->k); + +if (top->k < i->k || k == i->k) +break ; + +heap_sift (iter, i - top, btree_iter_cmp); + } +} + +static void btree_mergesort (struct btree *b,struct bset *out, +struct btree_iter *iter, +bool fixup,bool remove_stale) +{ +struct bkey *k, *last =NULL ; +bool (*bad)(struct btree *,const struct bkey *) = remove_stale + ? ptr_bad + : ptr_invalid; + +while (!btree_iter_end (iter)) { +if (fixup && !b->level) +btree_sort_fixup (iter); + + k =btree_iter_next (iter); +if (bad (b, k)) +continue ; + +if (!last) { + last = out->start; +bkey_copy (last, k); + }else if (b->level || + !bkey_try_merge (b, last, k)) { + last =next (last); +bkey_copy (last, k); + } + } + + out->keys = last ? (uint64_t *)next (last) - out->d :0 ; + +pr_debug ("sorted %i keys" , out->keys); +check_key_order (b, out); +} + +static void __btree_sort (struct btree *b,struct btree_iter *iter, +unsigned start,unsigned order,bool fixup) +{ +uint64_t start_time; +bool remove_stale = !b->written; +struct bset *out = (void *)__get_free_pages (__GFP_NOWARN|GFP_NOIO , + order); +if (!out) { +mutex_lock (&b->c->sort_lock); + out = b->c->sort; + order =ilog2 (bucket_pages (b->c)); + } + + start_time =local_clock (); + +btree_mergesort (b, out, iter, fixup, remove_stale); + b->nsets = start; + +if (!fixup && !start && b->written) +btree_verify (b, out); + +if (!start && order == b->page_order) { + out->magic =bset_magic (b->c); + out->seq = b->sets[0 ].data->seq; + out->version = b->sets[0 ].data->version; +swap (out, b->sets[0 ].data); + +if (b->c->sort == b->sets[0 ].data) + b->c->sort = out; + }else { + b->sets[start].data->keys = out->keys; +memcpy (b->sets[start].data->start, out->start, + (void *)end (out) - (void *) out->start); + } + +if (out == b->c->sort) +mutex_unlock (&b->c->sort_lock); +else +free_pages ((unsigned long ) out, order); + +if (b->written) +bset_build_written_tree (b); + +if (!start) { +spin_lock (&b->c->sort_time_lock); +time_stats_update (&b->c->sort_time, start_time); +spin_unlock (&b->c->sort_time_lock); + } +} + +void btree_sort_partial (struct btree *b,unsigned start) +{ +size_t oldsize =0 , order = b->page_order, keys =0 ; +struct btree_iter iter; +__btree_iter_init (b, &iter,NULL , &b->sets[start]); + +BUG_ON (b->sets[b->nsets].data ==write_block (b) && + (b->sets[b->nsets].size || b->nsets)); + +if (b->written) + oldsize =count_data (b); + +if (start) { +struct bset *i; +for_each_sorted_set_start (b, i, start) + keys += i->keys; + + order =roundup_pow_of_two (__set_bytes (i, keys)) /PAGE_SIZE ; +if (order) + order =ilog2 (order); + } + +__btree_sort (b, &iter, start, order, false); + +EBUG_ON (b->written &&count_data (b) != oldsize); +} + +void btree_sort_and_fix_extents (struct btree *b,struct btree_iter *iter) +{ +BUG_ON (!b->written); +__btree_sort (b, iter,0 , b->page_order, true); +} + +void btree_sort_into (struct btree *b,struct btree *new) +{ +uint64_t start_time =local_clock (); + +struct btree_iter iter; +btree_iter_init (b, &iter,NULL ); + +btree_mergesort (b, new->sets->data, &iter, false, true); + +spin_lock (&b->c->sort_time_lock); +time_stats_update (&b->c->sort_time, start_time); +spin_unlock (&b->c->sort_time_lock); + +bkey_copy_key (&new->key, &b->key); + new->sets->size =0 ; +} + +void btree_sort_lazy (struct btree *b) +{ +if (b->nsets) { +struct bset *i; +unsigned keys =0 , total; + +for_each_sorted_set (b, i) + keys += i->keys; + total = keys; + +for (unsigned j =0 ; j < b->nsets; j++) { +if (keys *2 < total || + keys <1000 ) { +btree_sort_partial (b, j); +return ; + } + + keys -= b->sets[j].data->keys; + } + +/* Must sort if b->nsets == 3 or we'll overflow */ +if (b->nsets >= (MAX_BSETS -1 ) - b->level) { +btree_sort (b); +return ; + } + } + +bset_build_written_tree (b); +} + +/* Sysfs stuff */ + +struct bset_stats { +size_t writes, sets, keys, trees, floats, failed, tree_space; +}; + +static int btree_bset_stats (struct btree *b,struct btree_op *op, +struct bset_stats *stats) +{ +struct bkey *k; + +if (btree_node_dirty (b)) + stats->writes++; + stats->sets += b->nsets +1 ; + stats->tree_space +=bset_tree_space (b); + +for (int i =0 ; i <MAX_BSETS && b->sets[i].size; i++) { + stats->trees++; + stats->keys += b->sets[i].data->keys *sizeof (uint64_t ); + stats->floats += b->sets[i].size -1 ; + +for (size_t j =1 ; j < b->sets[i].size; j++) +if (b->sets[i].tree[j].exponent ==127 ) + stats->failed++; + } + +if (b->level) +for_each_key_filter (b, k, ptr_bad) { +int ret =btree (bset_stats, k, b, op, stats); +if (ret) +return ret; + } + +return 0 ; +} + +int bset_print_stats (struct cache_set *c,char *buf) +{ +struct btree_op op; +struct bset_stats t; + +btree_op_init_stack (&op); +memset (&t,0 ,sizeof (struct bset_stats )); + +btree_root (bset_stats, c, &op, &t); + +return snprintf (buf,PAGE_SIZE , +"sets: %zu\n" +"write sets: %zu\n" +"key bytes: %zu\n" +"trees: %zu\n" +"tree space: %zu\n" +"floats: %zu\n" +"bytes/float: %zu\n" +"failed: %zu\n" , + t.sets, t.writes, t.keys, t.trees, t.tree_space, + t.floats,DIV_SAFE (t.keys, t.floats), t.failed); +}
drivers/block/bcache/bset.h new file +218 -0
218 lines not on this page · raw message
Signature · 1 line
1.7.9.rc2
Tejun Heo
maintainerRe: [Bcache v13 12/16] bcache: Bset code (lookups within a btree node)
Hello, again.
drivers/block/bcache/bset.c line 176
Quoted 6 lines · Kent Overstreet
drivers/block/bcache/bset.c new file +6
-0+void bkey_copy_single_ptr (struct bkey *dest,const struct bkey *src,unsigned i) +{ +BUG_ON (i >KEY_PTRS (src)); + +/* Only copy the header, key, and one pointer. */ +memcpy (dest, src,2 *sizeof (uint64_t ));
I hope this could be better somehow.
Quoted 6 lines
(quoted diff) +6
-0+ dest->ptr[0] = src->ptr[i]; + SET_KEY_PTRS(dest, 1); + /* We didn't copy the checksum so clear that bit. */ + SET_KEY_CSUM(dest, 0); +} +/* I have no idea why this code works... and I'm the one who wrote it
Review
Quoted 10 lines
(quoted diff) +10
-0+ * + * However, I do know what it does: + * Given a binary tree constructed in an array (i.e. how you normally implement + * a heap), it converts a node in the tree - referenced by array index - to the + * index it would have if you did an inorder traversal. + * + * The binary tree starts at array index 1, not 0 + * extra is a function of size: + * extra = (size - rounddown_pow_of_two(size - 1)) << 1; + */
It's kinda problematic if you can't understand or explain it. I'm scared. :(
Quoted 15 lines
drivers/block/bcache/bset.c new file +15
-0+static unsigned __to_inorder (unsigned j,unsigned size,unsigned extra) +{ +unsigned b =fls (j); +unsigned shift =fls (size -1 ) - b; + + j ^=1U << (b -1 ); + j <<=1 ; + j |=1 ; + j <<= shift; + +if (j > extra) + j -= (j - extra) >>1 ; + +return j; +}
...
drivers/block/bcache/bset.c line 508
Quoted 13 lines
drivers/block/bcache/bset.c new file +13
-0+static void bset_alloc_tree (struct btree *b,struct bset_tree *t) +{ +if (t != b->sets) { +unsigned j =roundup (t[-1 ].size, +64 /sizeof (struct bkey_float )); + + t->tree = t[-1 ].tree + j; + t->prev = t[-1 ].prev + j; + } + +while (t < b->sets +MAX_BSETS ) + t++->size =0 ; +}
I don't know. This is cryptic and I feel dirty and stupid.
drivers/block/bcache/bset.c line 512
Quoted 3 lines
drivers/block/bcache/bset.c new file +3
-0+static void bset_build_unwritten_tree (struct btree *b) +{ +struct bset_tree *t = b->sets + b->nsets;
Why not &b->sets[b->nsets]?
drivers/block/bcache/bset.c line 516
Quoted 4 lines
drivers/block/bcache/bset.c new file +4
-0+ +bset_alloc_tree (b, t); + +if (t->tree != b->sets->tree +bset_tree_space (b)) {
And why are we switching between sets[N].XXX and sets->XXX? Do they signify something? The usages don't seem to be consistent tho.
Quoted 4 lines
drivers/block/bcache/bset.c new file +4
-0+ t->prev[0 ] =bkey_to_cacheline_offset (t->data->start); + t->size =1 ; + } +}
...
Quoted 12 lines
drivers/block/bcache/bset.c new file +12
-0+void bset_init_next (struct btree *b) +{ +struct bset *i =write_block (b); + +if (i != b->sets[0 ].data) { + b->sets[++b->nsets].data = i; + i->seq = b->sets[0 ].data->seq; + }else +get_random_bytes (&i->seq,sizeof (uint64_t )); + + i->magic =bset_magic (b->c); + i->version =0 ;
drivers/block/bcache/bset.c line 680
Quoted 10 lines
drivers/block/bcache/bset.c new file +10
-0+ i->keys =0 ; + +bset_build_unwritten_tree (b); +} + +struct bset_search_iter { +struct bkey *l, *r; +}; + +__attribute__((optimize(3)))
So, this sets different optimization level per function. Is this really necessary? If so, you need to make compiler independent in include/linux/compiler*.h.
drivers/block/bcache/bset.c line 973
Quoted 12 lines
drivers/block/bcache/bset.c new file +12
-0+static void __btree_sort (struct btree *b,struct btree_iter *iter, +unsigned start,unsigned order,bool fixup) +{ +uint64_t start_time; +bool remove_stale = !b->written; +struct bset *out = (void *)__get_free_pages (__GFP_NOWARN|GFP_NOIO , + order); +if (!out) { +mutex_lock (&b->c->sort_lock); + out = b->c->sort; + order =ilog2 (bucket_pages (b->c)); + }
So, this is implementing simplistic mempool, right? If so, why not just use mempool?
Quoted 13 lines
drivers/block/bcache/bset.c new file +13
-0+ start_time =local_clock (); + +btree_mergesort (b, out, iter, fixup, remove_stale); + b->nsets = start; + +if (!fixup && !start && b->written) +btree_verify (b, out); + +if (!start && order == b->page_order) { + out->magic =bset_magic (b->c); + out->seq = b->sets[0 ].data->seq; + out->version = b->sets[0 ].data->version; +swap (out, b->sets[0 ].data);
drivers/block/bcache/bset.c line 997
Quoted 10 lines
drivers/block/bcache/bset.c new file +10
-0+ +if (b->c->sort == b->sets[0 ].data) + b->c->sort = out; + }else { + b->sets[start].data->keys = out->keys; +memcpy (b->sets[start].data->start, out->start, + (void *)end (out) - (void *) out->start); + } + +if (out == b->c->sort)
And is this test correct given the above swap(out, b->sets[0].data)?
Quoted 13 lines
drivers/block/bcache/bset.c new file +13
-0+mutex_unlock (&b->c->sort_lock); + else + free_pages((unsigned long) out, order); + +if (b->written) +bset_build_written_tree (b); + +if (!start) { +spin_lock (&b->c->sort_time_lock); +time_stats_update (&b->c->sort_time, start_time); +spin_unlock (&b->c->sort_time_lock); + } +}
...
drivers/block/bcache/bset.h line 189
Quoted 13 lines
drivers/block/bcache/bset.h new file +13
-0+#define BKEY_MID_BITS 3 +#define BKEY_MID_MAX (~(~0 << (BKEY_MID_BITS - 1))) +#define BKEY_MID_MIN (-1 - BKEY_MID_MAX) + +#define BKEY_EXPONENT_BITS 7 +#define BKEY_MANTISSA_BITS 22 +#define BKEY_MANTISSA_MASK ((1 << BKEY_MANTISSA_BITS) - 1) + +struct bkey_float { +unsigned exponent:BKEY_EXPONENT_BITS ; +unsigned m:BKEY_MID_BITS ; +unsigned mantissa:BKEY_MANTISSA_BITS ; +} __packed;
I *suppose* bkey_float is used to somehow compactly represent key values for compacter search tree. Wouldn't things like this need boatload of explanation?
drivers/block/bcache/bset.h line 192
Quoted 2 lines
drivers/block/bcache/bset.h new file +2
-0+#define BSET_CACHELINE 128 +#define BSET_CACHELINE_BITS ilog2(BSET_CACHELINE)
Hmm... what if CACHELINE isn't 128 bytes? What are the effects? Wouldn't it be better to name it something else (I don't know, BSET_UNIT_BYTES or whatever) and then explain that it's defined to be close to popular cacheline size and the effects of it not coinciding with actual cacheline size? Another nit, it's more customary to define BITS or SHIFT first and then define size as << SHIFT.
drivers/block/bcache/bset.h line 197
Quoted 4 lines
drivers/block/bcache/bset.h new file +4
-0+#define bset_tree_space (b) (btree_data_space(b) >> BSET_CACHELINE_BITS) + +#define bset_tree_bytes (b) (bset_tree_space(b) * sizeof(struct bkey_float)) +#define bset_prev_bytes (b) (bset_tree_bytes(b) >> 2)
Ummm.... cryptic. What does that >> 2 mean? Why not bset_tree_space(b) * sizeof(whatever prev type is)?
drivers/block/bcache/bset.h line 207
Quoted 9 lines
drivers/block/bcache/bset.h new file +9
-0+void bset_init_next (struct btree *); + +void bset_fix_invalidated_key (struct btree *,struct bkey *); +void bset_fix_lookup_table (struct btree *,struct bkey *); + +struct bkey *__bset_search (struct btree *,struct bset_tree *, +const struct bkey *); +#define bset_search (b, t, search) \ + ((search) ? __bset_search(b, t, search) : (t)->data->start)
Why oh why is this a macro? And can we please have descriptive variable names in prototypes? btree_sort_into(btree *, btree *) - how is one supposed to know from which into which?
drivers/block/bcache/bset.h line 216
Quoted 8 lines
drivers/block/bcache/bset.h new file +8
-0+bool bkey_try_merge (struct btree *,struct bkey *,struct bkey *); +void btree_sort_lazy (struct btree *); +void btree_sort_into (struct btree *,struct btree *); +void btree_sort_and_fix_extents (struct btree *,struct btree_iter *); +void btree_sort_partial (struct btree *,unsigned ); +#define btree_sort (b) btree_sort_partial(b, 0) + +int bset_print_stats (struct cache_set *,char *);
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 12/16] bcache: Bset code (lookups within a btree node)
Quoted 11 lines · Tejun Heo
Hello, again.
drivers/block/bcache/bset.c line 176
Quoted 6 lines · Kent Overstreet
drivers/block/bcache/bset.c new file +6
-0+void bkey_copy_single_ptr (struct bkey *dest,const struct bkey *src,unsigned i) +{ +BUG_ON (i >KEY_PTRS (src)); + +/* Only copy the header, key, and one pointer. */ +memcpy (dest, src,2 *sizeof (uint64_t ));I hope this could be better somehow.
I don't know a great way of writing it, but I suppose sizeof(struct bkey) + sizeof(uint64_t) would be an improvement.
Quoted 12 lines
Quoted 12 lines
(quoted diff) +11
-0+ dest->ptr[0] = src->ptr[i]; + SET_KEY_PTRS(dest, 1); + /* We didn't copy the checksum so clear that bit. */ + SET_KEY_CSUM(dest, 0); +} +/* I have no idea why this code works... and I'm the one who wrote it + * + * However, I do know what it does: + * Given a binary tree constructed in an array (i.e. how you normally implement + * a heap), it converts a node in the tree - referenced by array index - to the + * index it would have if you did an inorder traversal.
Quoted 8 lines
Quoted 5 lines
+ *
+ * The binary tree starts at array index 1, not 0
+ * extra is a function of size:
+ * extra = (size -rounddown_pow_of_two(size - 1)) << 1;
+ */It's kinda problematic if you can't understand or explain it. I'm scared. :(
Hehe. I spent several days staring at pictures of binary trees, then staring at integers in binary from my code doing inorder travels until eventually I saw the pattern. Never figured out the math behind it.
But I did test every j for every binary tree up to size somewhere around 6 million. I suppose I'll mention that in the comment.
Quoted 17 lines
Quoted 16 lines
drivers/block/bcache/bset.c new file +15
-0+static unsigned __to_inorder (unsigned j,unsigned size,unsigned extra) +{ +unsigned b =fls (j); +unsigned shift =fls (size -1 ) - b; + + j ^=1U << (b -1 ); + j <<=1 ; + j |=1 ; + j <<= shift; + +if (j > extra) + j -= (j - extra) >>1 ; + +return j; +}...
Quoted 15 lines
drivers/block/bcache/bset.c line 508
Quoted 13 lines
drivers/block/bcache/bset.c new file +13
-0+static void bset_alloc_tree (struct btree *b,struct bset_tree *t) +{ +if (t != b->sets) { +unsigned j =roundup (t[-1 ].size, +64 /sizeof (struct bkey_float )); + + t->tree = t[-1 ].tree + j; + t->prev = t[-1 ].prev + j; + } + +while (t < b->sets +MAX_BSETS ) + t++->size =0 ; +}I don't know. This is cryptic and I feel dirty and stupid.
Now you know how I feel :) It really irritates me that I never did figure out the math.
Quoted 6 lines
drivers/block/bcache/bset.c line 512
Quoted 4 lines
drivers/block/bcache/bset.c new file +3
-0+static void bset_build_unwritten_tree (struct btree *b) +{ +struct bset_tree *t = b->sets + b->nsets;Why not &b->sets[b->nsets]?
Yeah, that'd be more consistent.
Quoted 8 lines
drivers/block/bcache/bset.c line 516
Quoted 5 lines
drivers/block/bcache/bset.c new file +4
-0+ +bset_alloc_tree (b, t); + +if (t->tree != b->sets->tree +bset_tree_space (b)) {And why are we switching between sets[N].XXX and
sets->XXX? Do they signify something? The usages don't seem to be consistent tho.
I tried to generally use the sets-> notation when I'm referring to the allocated memory - that's what's going on here.
Quoted 24 lines
Quoted 5 lines
drivers/block/bcache/bset.c new file +4
-0+ t->prev[0 ] =bkey_to_cacheline_offset (t->data->start); + t->size =1 ; + } +}...
Quoted 18 lines
drivers/block/bcache/bset.c new file +18
-0+void bset_init_next (struct btree *b) +{ +struct bset *i =write_block (b); + +if (i != b->sets[0 ].data) { + b->sets[++b->nsets].data = i; + i->seq = b->sets[0 ].data->seq; + }else +get_random_bytes (&i->seq,sizeof (uint64_t )); + + i->magic =bset_magic (b->c); + i->version =0 ; + i->keys =0 ; + +bset_build_unwritten_tree (b); +} + +struct bset_search_iter {
Quoted 8 lines
drivers/block/bcache/bset.c line 680
Quoted 4 lines
drivers/block/bcache/bset.c new file +4
-0+struct bkey *l, *r; +}; + +__attribute__((optimize(3)))So, this sets different optimization level per function. Is this really necessary? If so, you need to make compiler independent in
include/linux/compiler*.h.
The bset_search_*() functions are definitely hot enough to justify it, but I can't remember if I ever checked whether that attribute made a difference.
Quoted 16 lines
drivers/block/bcache/bset.c line 973
Quoted 13 lines
drivers/block/bcache/bset.c new file +12
-0+static void __btree_sort (struct btree *b,struct btree_iter *iter, +unsigned start,unsigned order,bool fixup) +{ +uint64_t start_time; +bool remove_stale = !b->written; +struct bset *out = (void *)__get_free_pages (__GFP_NOWARN|GFP_NOIO , + order); +if (!out) { +mutex_lock (&b->c->sort_lock); + out = b->c->sort; + order =ilog2 (bucket_pages (b->c)); + }So, this is implementing simplistic mempool, right? If so, why not just use mempool?
It is (btree_read_done() has another). I'm not sure if this one would benefit from being switched to a mempool since it tries to allocate only the size buffer it needs (which may be smaller than what the mempool has), but the one in btree_read_done() could probably be switched to a mempool. I'll try it out.
Quoted 18 lines
Quoted 18 lines
drivers/block/bcache/bset.c new file +17
-0+ start_time =local_clock (); + +btree_mergesort (b, out, iter, fixup, remove_stale); + b->nsets = start; + +if (!fixup && !start && b->written) +btree_verify (b, out); + +if (!start && order == b->page_order) { + out->magic =bset_magic (b->c); + out->seq = b->sets[0 ].data->seq; + out->version = b->sets[0 ].data->version; +swap (out, b->sets[0 ].data); + +if (b->c->sort == b->sets[0 ].data) + b->c->sort = out; + }else {
Quoted 8 lines
drivers/block/bcache/bset.c line 997
Quoted 6 lines
drivers/block/bcache/bset.c new file +6
-0+ b->sets[start].data->keys = out->keys; +memcpy (b->sets[start].data->start, out->start, + (void *)end (out) - (void *) out->start); + } + +if (out == b->c->sort)And is this test correct given the above
swap(out, b->sets[0].data)?
Yes. It's difficult to read but I think it's just as bad if the check is before the swap(). It's avoiding a memcpy() and just swapping buffers when possible - I added a comment to that effect.
Quoted 24 lines
Quoted 14 lines
drivers/block/bcache/bset.c new file +13
-0+mutex_unlock (&b->c->sort_lock); + else + free_pages((unsigned long) out, order); + +if (b->written) +bset_build_written_tree (b); + +if (!start) { +spin_lock (&b->c->sort_time_lock); +time_stats_update (&b->c->sort_time, start_time); +spin_unlock (&b->c->sort_time_lock); + } +}...
Quoted 9 lines
drivers/block/bcache/bset.h new file +9
-0+#define BKEY_MID_BITS 3 +#define BKEY_MID_MAX (~(~0 << (BKEY_MID_BITS - 1))) +#define BKEY_MID_MIN (-1 - BKEY_MID_MAX) + +#define BKEY_EXPONENT_BITS 7 +#define BKEY_MANTISSA_BITS 22 +#define BKEY_MANTISSA_MASK ((1 << BKEY_MANTISSA_BITS) - 1) + +struct bkey_float {
Quoted 8 lines
drivers/block/bcache/bset.h line 189
Quoted 4 lines
drivers/block/bcache/bset.h new file +4
-0+ unsigned exponent:BKEY_EXPONENT_BITS; + unsigned m:BKEY_MID_BITS; + unsigned mantissa:BKEY_MANTISSA_BITS; +} __packed;I *suppose*
bkey_floatis used to somehow compactly represent key values for compacter search tree. Wouldn't things like this need boatload of explanation?
Oh man, I don't even know where to begin. But yeah.
Quoted 9 lines
drivers/block/bcache/bset.h line 192
Quoted 3 lines
drivers/block/bcache/bset.h new file +2
-0+#define BSET_CACHELINE 128 +#define BSET_CACHELINE_BITS ilog2(BSET_CACHELINE)Hmm... what if CACHELINE isn't 128 bytes? What are the effects? Wouldn't it be better to name it something else (I don't know,
BSET_UNIT_BYTESor whatever) and then explain that it's defined to be close to popular cacheline size and the effects of it not coinciding with actual cacheline size?
It's poorly named, yeah. Nothing bad happens if it's not the same size as hardware cachelines (as it's not now) - it used to be 64, but I realized the lookup code would touch slightly less memory if it was 128.
What it's defining is the number of bytes per bset_float, if you missed that - when we're done searching the bset_float tree we have this many bytes left that we do a linear search over.
Since (after level 5, if my math is right) every level of the bset_tree is on a new cacheline, we're touching one fewer cacheline in the bset tree in exchange for one more cacheline in the linear search - but the linear search might stop before it gets to the second cacheline.
Quoted 2 lines
Another nit, it's more customary to
define BITS or SHIFT first and then define size as << SHIFT.
Yeah, usually I do it that way... I kind of like having the units in bytes one be the explicitly defined one, but I don't really care either way.
Quoted 8 lines
drivers/block/bcache/bset.h line 197
Quoted 5 lines
drivers/block/bcache/bset.h new file +4
-0+#define bset_tree_space (b) (btree_data_space(b) >> BSET_CACHELINE_BITS) + +#define bset_tree_bytes (b) (bset_tree_space(b) * sizeof(struct bkey_float)) +#define bset_prev_bytes (b) (bset_tree_bytes(b) >> 2)Ummm.... cryptic. What does that >> 2 mean? Why not
bset_tree_space(b)* sizeof(whatever prev type is)?
That'd be better (wtf was I thinking when I wrote than?)
Quoted 12 lines
drivers/block/bcache/bset.h line 207
Quoted 10 lines
drivers/block/bcache/bset.h new file +9
-0+void bset_init_next (struct btree *); + +void bset_fix_invalidated_key (struct btree *,struct bkey *); +void bset_fix_lookup_table (struct btree *,struct bkey *); + +struct bkey *__bset_search (struct btree *,struct bset_tree *, +const struct bkey *); +#define bset_search (b, t, search) \ + ((search) ? __bset_search(b, t, search) : (t)->data->start)Why oh why is this a macro?
No particular reason. I don't care one way or the other about these one line wrappers, but I'll switch it to a static inline function if you prefer.
Quoted 3 lines
And can we please have descriptive
variable names in prototypes?btree_sort_into(btree *, btree *)- how is one supposed to know from which into which?
Well - my intent isn't for these to be documentation, they're just there to make the compiler happy... I'd rather refer to the function definitions. That seems to me to be the norm within the kernel, function documentation goes with the definition and I've seen dropped variable names before (but then, that was block layer stuff :)
I'll change it if you want, though.
Tejun Heo
maintainerRe: [Bcache v13 12/16] bcache: Bset code (lookups within a btree node)
Hello,
Quoted 1 line · Kent Overstreet
Quoted 1 line
Quoted 1 line
+/* I have no idea why this code works... and I'm the one who wrote it
Ooh, BTW, the preferred multiline comment format is fully winged one -
/*
* blah blah.
* blah blah.
*/
Quoted 14 lines
Quoted 11 lines
Quoted 10 lines
+ *
+ * However, I do know what it does:
+ * Given a binary tree constructed in an array (i.e. how you normally implement
+ * a heap), it converts a node in the tree - referenced by array index - to the
+ * index it would have if you did an inorder traversal.
+ *
+ * The binary tree starts at array index 1, not 0
+ * extra is a function of size:
+ * extra = (size -rounddown_pow_of_two(size - 1)) << 1;
+ */Quoted 2 lines
It's kinda problematic if you can't understand or explain it. I'm scared. :(
Quoted 6 lines
Hehe. I spent several days staring at pictures of binary trees, then staring at integers in binary from my code doing inorder travels until eventually I saw the pattern. Never figured out the math behind it.
But I did test every j for every binary tree up to size somewhere around 6 million. I suppose I'll mention that in the comment.
How useful is this? Any chance we can use something simpler (or understandable at least)?
Quoted 9 lines
Quoted 5 lines
Quoted 1 line
+__attribute__((optimize(3)))
So, this sets different optimization level per function. Is this really necessary? If so, you need to make compiler independent in
include/linux/compiler*.h.The bset_search_*() functions are definitely hot enough to justify it, but I can't remember if I ever checked whether that attribute made a difference.
Let's drop it for now. -O3 is different from -O2 but not necessarily better.
Quoted 26 lines
Quoted 17 lines
Quoted 17 lines
drivers/block/bcache/bset.c new file +17
-0+ start_time =local_clock (); + +btree_mergesort (b, out, iter, fixup, remove_stale); + b->nsets = start; + +if (!fixup && !start && b->written) +btree_verify (b, out); + +if (!start && order == b->page_order) { + out->magic =bset_magic (b->c); + out->seq = b->sets[0 ].data->seq; + out->version = b->sets[0 ].data->version; +swap (out, b->sets[0 ].data); + +if (b->c->sort == b->sets[0 ].data) + b->c->sort = out; + }else {Quoted 8 lines
drivers/block/bcache/bset.c line 997
Quoted 6 lines
drivers/block/bcache/bset.c new file +6
-0+ b->sets[start].data->keys = out->keys; +memcpy (b->sets[start].data->start, out->start, + (void *)end (out) - (void *) out->start); + } + +if (out == b->c->sort)And is this test correct given the above
swap(out, b->sets[0].data)?
Quoted 3 lines
Yes. It's difficult to read but I think it's just as bad if the check is before the
swap(). It's avoiding amemcpy()and just swapping buffers when possible - I added a comment to that effect.
Ah, okay, if (b->c->sort == b->sets[0].data) updates b->c->sort to match the swap. Wouldn't it be easier to read if it did something like following?
bool using_backup; // or whatever better name you can think of
out = alloc;
if (!out) {
lock;
....
using_backup = true;
}
Do whatever;
if (using_backup) {
/* explain that @out may have changed */
b->c->sort = out;
unlock;
} else {
free;
}It would also be nicer to not do operations which may fail during var decl.
Quoted 13 lines
Quoted 8 lines
drivers/block/bcache/bset.h line 192
Quoted 2 lines
drivers/block/bcache/bset.h new file +2
-0+#define BSET_CACHELINE 128 +#define BSET_CACHELINE_BITS ilog2(BSET_CACHELINE)Hmm... what if CACHELINE isn't 128 bytes? What are the effects? Wouldn't it be better to name it something else (I don't know,
BSET_UNIT_BYTESor whatever) and then explain that it's defined to be close to popular cacheline size and the effects of it not coinciding with actual cacheline size?It's poorly named, yeah. Nothing bad happens if it's not the same size as hardware cachelines (as it's not now) - it used to be 64, but I realized the lookup code would touch slightly less memory if it was 128.
Quoted 8 lines
What it's defining is the number of bytes per
bset_float, if you missed that - when we're done searching thebset_floattree we have this many bytes left that we do a linear search over.Since (after level 5, if my math is right) every level of the
bset_treeis on a new cacheline, we're touching one fewer cacheline in the bset tree in exchange for one more cacheline in the linear search - but the linear search might stop before it gets to the second cacheline.
How much benefit are we gaining by doing this float thing? Any chance we can do something simpler? As long as it's properly encapsulated, it shouldn't be too bad but I keep having the suspicion that a lot of complexity is added for unnecessary level of optimization.
Quoted 12 lines
Quoted 11 lines
drivers/block/bcache/bset.h line 207
Quoted 9 lines
drivers/block/bcache/bset.h new file +9
-0+void bset_init_next (struct btree *); + +void bset_fix_invalidated_key (struct btree *,struct bkey *); +void bset_fix_lookup_table (struct btree *,struct bkey *); + +struct bkey *__bset_search (struct btree *,struct bset_tree *, +const struct bkey *); +#define bset_search (b, t, search) \ + ((search) ? __bset_search(b, t, search) : (t)->data->start)Why oh why is this a macro?
Quoted 3 lines
No particular reason. I don't care one way or the other about these one line wrappers, but I'll switch it to a static inline function if you prefer.
In general, functions are preferable unless there are specific reasons to use macros like mucking with types.
Quoted 11 lines
Quoted 3 lines
And can we please have descriptive
variable names in prototypes?btree_sort_into(btree *, btree *)- how is one supposed to know from which into which?Well - my intent isn't for these to be documentation, they're just there to make the compiler happy... I'd rather refer to the function definitions. That seems to me to be the norm within the kernel, function documentation goes with the definition and I've seen dropped variable names before (but then, that was block layer stuff :)
I'll change it if you want, though.
Yes, please do so. Just match them to the actual function definition.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 12/16] bcache: Bset code (lookups within a btree node)
Quoted 4 lines · Tejun Heo
How much benefit are we gaining by doing this float thing? Any chance we can do something simpler? As long as it's properly encapsulated, it shouldn't be too bad but I keep having the suspicion that a lot of complexity is added for unnecessary level of optimization.
e.g. how much performance gain does it provide compared to just using u64 in binary search tree combined with last search result hint for sequential accesses?
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 12/16] bcache: Bset code (lookups within a btree node)
Hello, Kent.
Quoted 9 lines · Tejun Heo
Quoted 4 lines · Tejun Heo
How much benefit are we gaining by doing this float thing? Any chance we can do something simpler? As long as it's properly encapsulated, it shouldn't be too bad but I keep having the suspicion that a lot of complexity is added for unnecessary level of optimization.
e.g. how much performance gain does it provide compared to just using u64 in binary search tree combined with last search result hint for sequential accesses?
I've been thinking a bit more about it last night and have one more question, so the use of floating numbers in binary search tree is to use less memory and thus lower cacheline overhead on lookups, and the tree is built between the min and max values of the bset so that cluster of large keys don't get lower resolution from high exponent.
Assuming resolution of 512k - 2^19, 32bit can cover 2^41 - 2TiB. If having compact search tree is so important (I kinda have hard time accepting that too tho), why not just use bin search tree of either u32 or u64 depending on the range? In most cases u32 will be used and it's not like float can cover large areas well anyway. It can degenerate into essentially linear search depending on key distribution (e.g. partition table read at sector 0 followed by bunch of high partition accesses) - u64 tree will show much better bound behavior in cases where key range becomes large. Wouldn't that be *far* simpler and easier to understand and maintain without much downside?
Thanks.
Signature · 1 line
tejun
PATCH v13 13/16 bcache: Journalling
In reply to Kent Overstreet--- drivers/block/bcache/journal.c | 722 ++++++++++++++++++++++++++++++++++++++++ drivers/block/bcache/journal.h | 113 +++++++ 2 files changed, 835 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/journal.c create mode 100644 drivers/block/bcache/journal.h
drivers/block/bcache/journal.c new file +722 -0
@@ -0,0 +1,722 @@ + +#include "bcache.h" +#include "btree.h" +#include "debug.h" +#include "request.h" + +/* +* Journal replay/recovery: +* +* This code is all driven from run_cache_set(); we first read the journal +* entries, do some other stuff, then we mark all the keys in the journal +* entries (same as garbage collection would), then we replay them - reinserting +* them into the cache in precisely the same order as they appear in the +* journal. +* +* We only journal keys that go in leaf nodes, which simplifies things quite a +* bit. +*/ + +static void journal_read_endio (struct bio *bio,int error) +{ +struct closure *cl = bio->bi_private; +bio_put (bio); +closure_put (cl); +} + +static int journal_read_bucket (struct cache *ca,struct list_head *list, +struct btree_op *op,unsigned bucket_index) +{ +struct journal_device *ja = &ca->journal; +struct bio *bio = &ja->bio; + +struct journal_replay *i; +struct jset *j, *data = ca->set->journal.w[0 ].data; +unsigned len, left, offset =0 ; +int ret =0 ; +sector_t bucket =bucket_to_sector (ca->set, ca->sb.d[bucket_index]); + +pr_debug ("reading %llu" , (uint64_t ) bucket); + +while (offset < ca->sb.bucket_size) { +reread: left = ca->sb.bucket_size - offset; + len =min_t (unsigned, left,PAGE_SECTORS *8 ); + +bio_reset (bio); + bio->bi_sector = bucket + offset; + bio->bi_bdev = ca->bdev; + bio->bi_rw =READ ; + bio->bi_size = len <<9 ; + + bio->bi_end_io = journal_read_endio; + bio->bi_private = &op->cl; +bio_map (bio, data); + +closure_bio_submit (bio, &op->cl, ca->set->bio_split); +closure_sync (&op->cl); + +/* This function could be simpler now since we no longer write +* journal entries that overlap bucket boundaries; this means +* the start of a bucket will always have a valid journal entry +* if it has any journal entries at all. +*/ + + j = data; +while (len) { +struct list_head *where; +size_t blocks, bytes =set_bytes (j); + +if (j->magic !=jset_magic (ca->set)) +return ret; + +if (bytes > left <<9 ) +return ret; + +if (bytes > len <<9 ) + goto reread; + +if (j->csum !=csum_set (j)) +return ret; + + blocks =set_blocks (j, ca->set); + +while (!list_empty (list)) { + i =list_first_entry (list, + struct journal_replay, list); +if (i->j.seq >= j->last_seq) +break ; +list_del (&i->list); +kfree (i); + } + +list_for_each_entry_reverse (i, list, list) { +if (j->seq == i->j.seq) + goto next_set; + +if (j->seq < i->j.last_seq) + goto next_set; + +if (j->seq > i->j.seq) { + where = &i->list; + goto add; + } + } + + where = list; +add: + i =kmalloc (offsetof(struct journal_replay , j) + + bytes,GFP_KERNEL ); +if (!i) +return -ENOMEM ; +memcpy (&i->j, j, bytes); +list_add (&i->list, where); + ret =1 ; + + ja->seq[bucket_index] = j->seq; +next_set: + offset += blocks * ca->sb.block_size; + len -= blocks * ca->sb.block_size; + j = ((void *) j) + blocks *block_bytes (ca); + } + } + +return ret; +} + +int bcache_journal_read (struct cache_set *c,struct list_head *list, +struct btree_op *op) +{ +#define read_bucket (b) \ + ({ \ + int ret = journal_read_bucket(ca, list, op, b); \ + __set_bit(b, bitmap); \ + if (ret < 0) \ + return ret; \ + ret; \ + }) + +struct cache *ca; + +for_each_cache (ca, c) { +struct journal_device *ja = &ca->journal; +unsigned long bitmap[SB_JOURNAL_BUCKETS /BITS_PER_LONG ]; +unsigned l, r, m; +uint64_t seq; + +bitmap_zero (bitmap,SB_JOURNAL_BUCKETS ); +pr_debug ("%u journal buckets" , ca->sb.njournal_buckets); + +/* Read journal buckets ordered by golden ratio hash to quickly +* find a sequence of buckets with valid journal entries +*/ +for (unsigned i =0 ; i < ca->sb.njournal_buckets; i++) { + l = (i *2654435769U ) % ca->sb.njournal_buckets; + +if (test_bit (l, bitmap)) +break ; + +if (read_bucket (l)) + goto bsearch; + } + +/* If that fails, check all the buckets we haven't checked +* already +*/ +pr_debug ("falling back to linear search" ); + +for (l =0 ; l < ca->sb.njournal_buckets; l++) { +if (test_bit (l, bitmap)) +continue ; + +if (read_bucket (l)) + goto bsearch; + } +bsearch: +/* Binary search */ + m = r =find_next_bit (bitmap, ca->sb.njournal_buckets, l +1 ); +pr_debug ("starting binary search, l %u r %u" , l, r); + +while (l +1 < r) { + m = (l + r) >>1 ; + +if (read_bucket (m)) + l = m; +else + r = m; + } + +/* Read buckets in reverse order until we stop finding more +* journal entries +*/ +pr_debug ("finishing up" ); + l = m; + +while (1 ) { +if (!l--) + l = ca->sb.njournal_buckets -1 ; + +if (l == m) +break ; + +if (test_bit (l, bitmap)) +continue ; + +if (!read_bucket (l)) +break ; + } + + seq =0 ; + +for (unsigned i =0 ; i < ca->sb.njournal_buckets; i++) +if (ja->seq[i] > seq) { + seq = ja->seq[i]; + ja->cur = ja->last = i; + + } + } + + c->journal.seq =list_entry (list->prev, + struct journal_replay, + list)->j.seq; + +return 0 ; +#undef read_bucket +} + +void bcache_journal_mark (struct cache_set *c,struct list_head *list) +{ +atomic_t p = {0 }; +struct journal_replay *i; +struct journal *j = &c->journal; +uint64_t last = j->seq; + +/* +* journal.pin should never fill up - we never write a journal +* entry when it would fill up. But if for some reason it does, we +* iterate over the list in reverse order so that we can just skip that +* refcount instead of bugging. +*/ + +list_for_each_entry_reverse (i, list, list) { +BUG_ON (last < i->j.seq); + i->pin =NULL ; + +while (last-- != i->j.seq) +if (fifo_free (&j->pin) >1 ) { +fifo_push_front (&j->pin, p); +atomic_set (&fifo_front (&j->pin),0 ); + } + +if (fifo_free (&j->pin) >1 ) { +fifo_push_front (&j->pin, p); + i->pin = &fifo_front (&j->pin); +atomic_set (i->pin,1 ); + } + +for (struct bkey *k = i->j.start; k <end (&i->j); k =next (k)) { +for (unsigned j =0 ; j <KEY_PTRS (k); j++) { +struct bucket *g =PTR_BUCKET (c, k, j); +atomic_inc (&g->pin); + +if (g->prio == btree_prio && + !ptr_stale (c, k, j)) + g->prio = initial_prio; + } + +__btree_mark_key (c,0 , k); + } + } +} + +int bcache_journal_replay (struct cache_set *s,struct list_head *list, +struct btree_op *op) +{ +int ret =0 , keys =0 , entries =0 ; +struct journal_replay *i = + list_entry(list->prev, struct journal_replay, list); + +uint64_t start = i->j.last_seq, end = i->j.seq, n = start; + +list_for_each_entry (i, list, list) { +BUG_ON (i->pin &&atomic_read (i->pin) !=1 ); + +if (n != i->j.seq) +err_printk ("journal entries %llu-%llu " +"missing! (replaying %llu-%llu)\n" , + n, i->j.seq -1 , start, end); + +for (struct bkey *k = i->j.start; k <end (&i->j); k =next (k)) { +pr_debug ("%s" ,pkey (k)); +bkey_copy (op->keys.top, k); +keylist_push (&op->keys); + + op->journal = i->pin; +atomic_inc (op->journal); + + ret =bcache_btree_insert (op, s); +if (ret) + goto err; + +BUG_ON (!keylist_empty (&op->keys)); + keys++; + } + +if (i->pin) +atomic_dec (i->pin); + n = i->j.seq +1 ; + entries++; + } + +printk (KERN_INFO "bcache: journal replay done, %i keys in %i " +"entries, seq %llu-%llu\n" , keys, entries, start, end); + +while (!list_empty (list)) { + i = list_first_entry(list, struct journal_replay, list); +list_del (&i->list); +kfree (i); + } +err: +closure_sync (&op->cl); +return ret; +} + +/* Journalling */ + +static void btree_flush_write (struct cache_set *c) +{ +/* +* Try to find the btree node with that references the oldest journal +* entry, best is our current candidate and is locked if non NULL: +*/ +struct btree *b, *best; + +/* +* The root of the btree isn't on the lru list. Normally this is fine +* because only leaf nodes can have references to journal entries - +* unless the root _is_ a leaf node. So we have to special case that: +*/ + +while (!c->root->level) { + best = c->root; +rw_lock (true, best,0 ); + +if (best == c->root && !best->level) + goto found; +rw_unlock (true, best); + } + +mutex_lock (&c->bucket_lock); + + best =NULL ; +list_for_each_entry (b, &c->btree_cache, list) { +if (!down_write_trylock (&b->lock)) +continue ; + +if (!btree_node_dirty (b) || + !btree_current_write (b)->journal) { +rw_unlock (true, b); +continue ; + } + +if (!best) + best = b; +else if (journal_pin_cmp (c, +btree_current_write (best), +btree_current_write (b))) { +rw_unlock (true, best); + best = b; + }else +rw_unlock (true, b); + } + +if (best) + goto out; + +/* We can't find the best btree node, just pick the first */ +list_for_each_entry (b, &c->btree_cache, list) +if (!b->level &&btree_node_dirty (b)) { + best = b; +mutex_unlock (&c->bucket_lock); +rw_lock (true, best, best->level); + goto found; + } + +out: +mutex_unlock (&c->bucket_lock); + +if (!best) +return ; +found: +if (btree_node_dirty (best)) +btree_write (best, true,NULL ); +rw_unlock (true, best); +} + +#define last_seq (j) ((j)->seq - fifo_used(&(j)->pin) + 1) + +static void journal_reclaim (struct cache_set *c) +{ +struct bkey *k = &c->journal.key; +struct cache *ca; +uint64_t last_seq; +unsigned n =0 ; +atomic_t p; + +while (!atomic_read (&fifo_front (&c->journal.pin))) +fifo_pop (&c->journal.pin, p); + + last_seq =last_seq (&c->journal); + +for_each_cache (ca, c) { +struct journal_device *ja = &ca->journal; + +while (ja->last != ja->cur && + ja->seq[ja->last] < last_seq) +if (++ja->last == ca->sb.njournal_buckets) + ja->last =0 ; + } + +if (c->journal.blocks_free) +return ; + +/* +* Now we allocate: +* XXX: Sort by free journal space +*/ + +for_each_cache (ca, c) { +struct journal_device *ja = &ca->journal; +unsigned next = (ja->cur +1 ) % ca->sb.njournal_buckets; + +if (next == ja->last) +continue ; + + ja->cur = next; + k->ptr[n++] =PTR (0 , +bucket_to_sector (c, ca->sb.d[ja->cur]), + ca->sb.nr_this_dev); + } + + k->header =KEY_HEADER (0 ,0 ); +SET_KEY_PTRS (k, n); + +if (n) + c->journal.blocks_free = c->sb.bucket_size >> c->block_bits; + +if (!journal_full (&c->journal)) +__closure_wake_up (&c->journal.wait); +} + +void bcache_journal_next (struct journal *j) +{ +atomic_t p = {1 }; + + j->cur = (j->cur == j->w) + ? &j->w[1 ] + : &j->w[0 ]; + +/* +* The fifo_push() needs to happen at the same time as j->seq is +* incremented for last_seq() to be calculated correctly +*/ +BUG_ON (!fifo_push (&j->pin, p)); +atomic_set (&fifo_back (&j->pin),1 ); + + j->cur->data->seq = ++j->seq; + j->cur->need_write = false; + j->cur->data->keys =0 ; + +if (fifo_full (&j->pin)) +pr_debug ("journal_pin full (%zu)" ,fifo_used (&j->pin)); +} + +static void journal_write_endio (struct bio *bio,int error) +{ +struct journal_write *w = bio->bi_private; + +cache_set_err_on (error, w->c,"journal io error" ); + +bio_put (bio); +closure_put (&w->c->journal.io.cl); +} + +static void journal_write (struct closure *); + +static void journal_write_done (struct closure *cl) +{ + struct journal *j = container_of(cl, struct journal, io.cl); + struct cache_set *c = container_of(j, struct cache_set, journal); + +struct journal_write *w = (j->cur == j->w) + ? &j->w[1 ] + : &j->w[0 ]; + +__closure_wake_up (&w->wait); + +if (c->journal_delay_ms) +closure_sleep (&j->io,msecs_to_jiffies (c->journal_delay_ms)); + +continue_at (cl, journal_write, system_wq); +} + +static void journal_write_unlocked (struct closure *cl) +{ + struct cache_set *c = container_of(cl, struct cache_set, journal.io.cl); +struct cache *ca; +struct journal_write *w = c->journal.cur; +struct bkey *k = &c->journal.key; +unsigned sectors =set_blocks (w->data, c) * c->sb.block_size; + +struct bio *bio; +struct bio_list list; +bio_list_init (&list); + +if (!w->need_write) { +/* +* XXX: have to unlock closure before we unlock journal lock, +* else we race with bcache_journal(). But this way we race +* against cache set unregister. Doh. +*/ +set_closure_fn (cl,NULL ,NULL ); +closure_sub (cl,CLOSURE_RUNNING +1 ); +spin_unlock (&c->journal.lock); +return ; + }else if (journal_full (&c->journal)) { +journal_reclaim (c); +spin_unlock (&c->journal.lock); + +btree_flush_write (c); +continue_at (cl, journal_write, system_wq); + } + + c->journal.blocks_free -=set_blocks (w->data, c); + + w->data->btree_level = c->root->level; + +bkey_copy (&w->data->btree_root, &c->root->key); +bkey_copy (&w->data->uuid_bucket, &c->uuid_bucket); + +for_each_cache (ca, c) + w->data->prio_bucket[ca->sb.nr_this_dev] = ca->prio_buckets[0 ]; + + w->data->magic =jset_magic (c); + w->data->version =BCACHE_JSET_VERSION ; + w->data->last_seq =last_seq (&c->journal); + w->data->csum =csum_set (w->data); + +for (unsigned i =0 ; i <KEY_PTRS (k); i++) { + ca =PTR_CACHE (c, k, i); + bio = &ca->journal.bio; + +atomic_long_add (sectors, &ca->meta_sectors_written); + +bio_reset (bio); + bio->bi_sector =PTR_OFFSET (k, i); + bio->bi_bdev = ca->bdev; + bio->bi_rw =REQ_WRITE |REQ_SYNC |REQ_META |REQ_FLUSH ; + bio->bi_size = sectors <<9 ; + + bio->bi_end_io = journal_write_endio; + bio->bi_private = w; +bio_map (bio, w->data); + +trace_bcache_journal_write (bio); +bio_list_add (&list, bio); + +SET_PTR_OFFSET (k, i,PTR_OFFSET (k, i) + sectors); + + ca->journal.seq[ca->journal.cur] = w->data->seq; + } + +atomic_dec_bug (&fifo_back (&c->journal.pin)); +bcache_journal_next (&c->journal); +journal_reclaim (c); + +spin_unlock (&c->journal.lock); + +while ((bio =bio_list_pop (&list))) +closure_bio_submit (bio, cl, c->bio_split); + +continue_at (cl, journal_write_done,NULL ); +} + +static void journal_write (struct closure *cl) +{ + struct cache_set *c = container_of(cl, struct cache_set, journal.io.cl); + +spin_lock (&c->journal.lock); +journal_write_unlocked (cl); +} + +static void __journal_try_write (struct cache_set *c,bool noflush) +{ +struct closure *cl = &c->journal.io.cl; + +if (!closure_trylock (cl, &c->cl)) +spin_unlock (&c->journal.lock); +else if (noflush &&journal_full (&c->journal)) { +spin_unlock (&c->journal.lock); +continue_at (cl, journal_write, system_wq); + }else +journal_write_unlocked (cl); +} + +#define journal_try_write (c) __journal_try_write(c, false) + +void bcache_journal_meta (struct cache_set *c,struct closure *cl) +{ +struct journal_write *w; + +if (CACHE_SYNC (&c->sb)) { +spin_lock (&c->journal.lock); + + w = c->journal.cur; + w->need_write = true; + +if (cl) +BUG_ON (!closure_wait (&w->wait, cl)); + +__journal_try_write (c, true); + } +} + +/* +* Entry point to the journalling code - bio_insert() and btree_invalidate() +* pass bcache_journal() a list of keys to be journalled, and then +* bcache_journal() hands those same keys off to btree_insert_async() +*/ + +void bcache_journal (struct closure *cl) +{ + struct btree_op *op = container_of(cl, struct btree_op, cl); +struct cache_set *c = op->d->c; +struct journal_write *w; +size_t b, n = ((uint64_t *) op->keys.top) - op->keys.list; + +if (op->type !=BTREE_INSERT || + !CACHE_SYNC (&c->sb)) + goto out; + +/* +* If we're looping because we errored, might already be waiting on +* another journal write: +*/ +while (atomic_read (&cl->parent->remaining) &CLOSURE_WAITING ) +closure_sync (cl->parent); + +spin_lock (&c->journal.lock); + +if (journal_full (&c->journal)) { +/* XXX: tracepoint */ +closure_wait (&c->journal.wait, cl); + +journal_reclaim (c); +spin_unlock (&c->journal.lock); + +btree_flush_write (c); +continue_at (cl, bcache_journal, bcache_wq); + } + + w = c->journal.cur; + w->need_write = true; + b =__set_blocks (w->data, w->data->keys + n, c); + +if (b * c->sb.block_size >PAGE_SECTORS <<JSET_BITS || + b > c->journal.blocks_free) { +/* XXX: If we were inserting so many keys that they won't fit in +* an _empty_ journal write, we'll deadlock. For now, handle +* this in keylist_realloc() - but something to think about. +*/ +BUG_ON (!w->data->keys); + +/* XXX: tracepoint */ +BUG_ON (!closure_wait (&w->wait, cl)); + +closure_flush (&c->journal.io); + +journal_try_write (c); +continue_at (cl, bcache_journal, bcache_wq); + } + +memcpy (end (w->data), op->keys.list, n *sizeof (uint64_t )); + w->data->keys += n; + + op->journal = &fifo_back (&c->journal.pin); +atomic_inc (op->journal); + +if (op->flush_journal) { +closure_flush (&c->journal.io); +closure_wait (&w->wait, cl->parent); + } + +journal_try_write (c); +out: +bcache_btree_insert_async (cl); +} + +void bcache_journal_free (struct cache_set *c) +{ +free_pages ((unsigned long ) c->journal.w[1 ].data,JSET_BITS ); +free_pages ((unsigned long ) c->journal.w[0 ].data,JSET_BITS ); +free_fifo (&c->journal.pin); +} + +int bcache_journal_alloc (struct cache_set *c) +{ +struct journal *j = &c->journal; + +closure_init_unlocked (&j->io); +spin_lock_init (&j->lock); + + c->journal_delay_ms =100 ; + + j->w[0 ].c = c; + j->w[1 ].c = c; + +if (!(init_fifo (&j->pin,JOURNAL_PIN ,GFP_KERNEL )) || + !(j->w[0 ].data = (void *)__get_free_pages (GFP_KERNEL ,JSET_BITS )) || + !(j->w[1 ].data = (void *)__get_free_pages (GFP_KERNEL ,JSET_BITS ))) +return -ENOMEM ; + +return 0 ; +}
drivers/block/bcache/journal.h new file +113 -0
113 lines not on this page · raw message
Signature · 1 line
1.7.9.rc2
PATCH v13 14/16 bcache: Request, io and allocation code
In reply to Kent Overstreet--- drivers/block/bcache/alloc.c | 591 ++++++++++++++++ drivers/block/bcache/io.c | 198 ++++++ drivers/block/bcache/request.c | 1470 ++++++++++++++++++++++++++++++++++++++++ drivers/block/bcache/request.h | 58 ++ 4 files changed, 2317 insertions(+), 0 deletions(-) create mode 100644 drivers/block/bcache/alloc.c create mode 100644 drivers/block/bcache/io.c create mode 100644 drivers/block/bcache/request.c create mode 100644 drivers/block/bcache/request.h
drivers/block/bcache/alloc.c new file +591 -0
@@ -0,0 +1,591 @@ + +#include "bcache.h" +#include "btree.h" + +#include <linux/random.h> + +/* +* Allocation in bcache is done in terms of buckets: +* +* Each bucket has associated an 8 bit gen; this gen corresponds to the gen in +* btree pointers - they must match for the pointer to be considered valid. +* +* Thus (assuming a bucket has no dirty data or metadata in it) we can reuse a +* bucket simply by incrementing its gen. +* +* The gens (along with the priorities; it's really the gens are important but +* the code is named as if it's the priorities) are written in an arbitrary list +* of buckets on disk, with a pointer to them in the journal header. +* +* When we invalidate a bucket, we have to write its new gen to disk and wait +* for that write to complete before we use it - otherwise after a crash we +* could have pointers that appeared to be good but pointed to data that had +* been overwritten. +* +* Since the gens and priorities are all stored contiguously on disk, we can +* batch this up: We fill up the free_inc list with freshly invalidated buckets, +* call prio_write() - and when prio_write() eventually finishes it toggles +* c->prio_written and the buckets in free_inc are now ready to be used. When +* the free_inc list empties, we toggle c->prio_written and the cycle repeats. +* +* free_inc isn't the only freelist - if it was, we'd often to sleep while +* priorities and gens were being written before we could allocate. c->free is a +* smaller freelist, and buckets on that list are always ready to be used. +* +* If we've got discards enabled, that happens when a bucket moves from the +* free_inc list to the free list. +* +* There is another freelist, because sometimes we have buckets that we know +* have nothing pointing into them - these we can reuse without waiting for +* priorities to be rewritten. These come from freed btree nodes and buckets +* that garbage collection discovered no longer had valid keys pointing into +* them (because they were overwritten). That's the unused list - buckets on the +* unused list move to the free list, optionally being discarded in the process. +* +* It's also important to ensure that gens don't wrap around - with respect to +* either the oldest gen in the btree or the gen on disk. This is quite +* difficult to do in practice, but we explicitly guard against it anyways - if +* a bucket is in danger of wrapping around we simply skip invalidating it that +* time around, and we garbage collect or rewrite the priorities sooner than we +* would have otherwise. +* +* pop_bucket() allocates a single bucket from a specific cache. +* +* pop_bucket_set() allocates one or more buckets from different caches out of a +* cache set. +* +* free_some_buckets() drives all the processes described above. It's called +* from pop_bucket() and a few other places that need to make sure free buckets +* are ready. +* +* invalidate_buckets_(lru|fifo)() find buckets that are available to be +* invalidated, and then invalidate them and stick them on the free_inc list - +* in either lru or fifo order. +*/ + +static void do_discard (struct cache *); + +/* Bucket heap / gen */ + +uint8_t inc_gen (struct cache *c,struct bucket *b) +{ +uint8_t ret = ++b->gen; + + c->set->need_gc =max (c->set->need_gc,bucket_gc_gen (b)); +BUG_ON (c->set->need_gc >97 ); + +if (CACHE_SYNC (&c->set->sb)) { + c->need_save_prio =max (c->need_save_prio,bucket_disk_gen (b)); +BUG_ON (c->need_save_prio >96 ); + } + +return ret; +} + +void rescale_priorities (struct cache_set *c,int sectors) +{ +struct cache *ca; +struct bucket *b; +unsigned next = c->nbuckets * c->sb.bucket_size /1024 ; +int r; + +atomic_sub (sectors, &c->rescale); + +do { + r =atomic_read (&c->rescale); + +if (r >=0 ) +return ; + }while (atomic_cmpxchg (&c->rescale, r, r + next) != r); + +mutex_lock (&c->bucket_lock); + +for_each_cache (ca, c) +for_each_bucket (b, ca) +if (b->prio && + b->prio != btree_prio && + !atomic_read (&b->pin)) { + b->prio--; + c->min_prio =min (c->min_prio, b->prio); + } + +mutex_unlock (&c->bucket_lock); +} + +static long pop_freed (struct cache *c) +{ +long r; + +if ((!CACHE_SYNC (&c->set->sb) || + !atomic_read (&c->set->prio_blocked)) && +fifo_pop (&c->unused, r)) +return r; + +if ((!CACHE_SYNC (&c->set->sb) || +atomic_read (&c->prio_written) >0 ) && +fifo_pop (&c->free_inc, r)) +return r; + +return -1 ; +} + +/* Discard/TRIM */ + +struct discard { +struct list_head list; +struct work_struct work; +struct cache *c; +long bucket; + +struct bio bio; +struct bio_vec bv; +}; + +static void discard_finish (struct work_struct *w) +{ + struct discard *d = container_of(w, struct discard, work); +struct cache *c = d->c; +char buf[BDEVNAME_SIZE ]; +bool run = false; + +if (!test_bit (BIO_UPTODATE , &d->bio.bi_flags)) { +printk (KERN_NOTICE "bcache: discard error on %s, disabling\n" , +bdevname (c->bdev, buf)); + d->c->discard =0 ; + } + +mutex_lock (&c->set->bucket_lock); +if (fifo_empty (&c->free) || +fifo_used (&c->free) ==8 ) + run = true; + +fifo_push (&c->free, d->bucket); + +list_add (&d->list, &c->discards); + +do_discard (c); +mutex_unlock (&c->set->bucket_lock); + +if (run) +closure_wake_up (&c->set->bucket_wait); + +closure_put (&c->set->cl); +} + +static void discard_endio (struct bio *bio,int error) +{ + struct discard *d = container_of(bio, struct discard, bio); + +PREPARE_WORK (&d->work, discard_finish); +schedule_work (&d->work); +} + +static void discard_work (struct work_struct *w) +{ + struct discard *d = container_of(w, struct discard, work); +submit_bio (0 , &d->bio); +} + +static void do_discard (struct cache *c) +{ +struct request_queue *q =bdev_get_queue (c->bdev); +int s = q->limits.logical_block_size; + +while (c->discard && + !atomic_read (&c->set->closing) && + !list_empty (&c->discards) && +fifo_free (&c->free) >=8 ) { +struct discard *d =list_first_entry (&c->discards, + struct discard, list); + + d->bucket =pop_freed (c); +if (d->bucket ==-1 ) +break ; + +list_del (&d->list); +closure_get (&c->set->cl); + +bio_init (&d->bio); +memset (&d->bv,0 ,sizeof (struct bio_vec )); +bio_set_prio (&d->bio,IOPRIO_PRIO_VALUE (IOPRIO_CLASS_IDLE ,0 )); + + d->bio.bi_sector =bucket_to_sector (c->set, d->bucket); + d->bio.bi_bdev = c->bdev; + d->bio.bi_rw =REQ_WRITE |(1 <<BIO_RW_DISCARD ); + d->bio.bi_max_vecs =1 ; + d->bio.bi_io_vec = d->bio.bi_inline_vecs; + d->bio.bi_end_io = discard_endio; + +if (bio_add_pc_page (q, &d->bio, c->discard_page, s,0 ) < s) { +printk (KERN_DEBUG "bcache: bio_add_pc_page failed\n" ); + c->discard =0 ; +fifo_push (&c->free, d->bucket); +list_add (&d->list, &c->discards); +break ; + } + + d->bio.bi_size =bucket_bytes (c); + +schedule_work (&d->work); + } +} + +void free_discards (struct cache *ca) +{ +struct discard *d; + +while (!list_empty (&ca->discards)) { + d = list_first_entry(&ca->discards, struct discard, list); +cancel_work_sync (&d->work); +list_del (&d->list); +kfree (d); + } +} + +int alloc_discards (struct cache *ca) +{ +for (int i =0 ; i <8 ; i++) { +struct discard *d =kzalloc (sizeof (*d),GFP_KERNEL ); +if (!d) +return -ENOMEM ; + + d->c = ca; +INIT_WORK (&d->work, discard_work); +list_add (&d->list, &ca->discards); + } + +return 0 ; +} + +/* Allocation */ + +bool bucket_add_unused (struct cache *c,struct bucket *b) +{ +if (c->prio_alloc ==prio_buckets (c) && +CACHE_REPLACEMENT (&c->sb) ==CACHE_REPLACEMENT_FIFO ) +return false; + + b->prio =0 ; + +if (bucket_gc_gen (b) <96U && +bucket_disk_gen (b) <64U && +fifo_push (&c->unused, b - c->buckets)) { +atomic_inc (&b->pin); +return true; + } + +return false; +} + +static bool can_invalidate_bucket (struct cache *c,struct bucket *b) +{ +return b->mark >=0 && + !atomic_read (&b->pin) && +bucket_gc_gen (b) <96U && +bucket_disk_gen (b) <64U ; +} + +static void invalidate_one_bucket (struct cache *c,struct bucket *b) +{ +inc_gen (c, b); + b->prio = initial_prio; +atomic_inc (&b->pin); +fifo_push (&c->free_inc, b - c->buckets); +} + +static void invalidate_buckets_lru (struct cache *c) +{ +unsigned bucket_prio (struct bucket *b) + { +return ((unsigned ) (b->prio - c->set->min_prio)) * b->mark; + } + +bool bucket_max_cmp (struct bucket *l,struct bucket *r) + { +return bucket_prio (l) <bucket_prio (r); + } + +bool bucket_min_cmp (struct bucket *l,struct bucket *r) + { +return bucket_prio (l) >bucket_prio (r); + } + +struct bucket *b; + + c->heap.used =0 ; + +for_each_bucket (b, c) { +if (!can_invalidate_bucket (c, b)) +continue ; + +if (!b->mark) { +if (!bucket_add_unused (c, b)) +return ; + }else { +if (!heap_full (&c->heap)) +heap_add (&c->heap, b, bucket_max_cmp); +else if (bucket_max_cmp (b,heap_peek (&c->heap))) { + c->heap.data[0 ] = b; +heap_sift (&c->heap,0 , bucket_max_cmp); + } + } + } + +if (c->heap.used *2 < c->heap.size) +bcache_queue_gc (c->set); + +for (ssize_t i = c->heap.used /2 -1 ; i >=0 ; --i) +heap_sift (&c->heap, i, bucket_min_cmp); + +while (!fifo_full (&c->free_inc)) { +if (!heap_pop (&c->heap, b, bucket_min_cmp)) { +/* We don't want to be calling invalidate_buckets() +* multiple times when it can't do anything +*/ + c->invalidate_needs_gc =1 ; +bcache_queue_gc (c->set); +return ; + } + +invalidate_one_bucket (c, b); + } +} + +static void invalidate_buckets_fifo (struct cache *c) +{ +struct bucket *b; +size_t checked =0 ; + +while (!fifo_full (&c->free_inc)) { +if (c->fifo_last_bucket < c->sb.first_bucket || + c->fifo_last_bucket >= c->sb.nbuckets) + c->fifo_last_bucket = c->sb.first_bucket; + + b = c->buckets + c->fifo_last_bucket++; + +if (can_invalidate_bucket (c, b)) +invalidate_one_bucket (c, b); + +if (++checked >= c->sb.nbuckets) { + c->invalidate_needs_gc =1 ; +bcache_queue_gc (c->set); +return ; + } + } +} + +static void invalidate_buckets_random (struct cache *c) +{ +struct bucket *b; +size_t checked =0 ; + +while (!fifo_full (&c->free_inc)) { +size_t n; +get_random_bytes (&n,sizeof (n)); + + n %= (size_t ) (c->sb.nbuckets - c->sb.first_bucket); + n += c->sb.first_bucket; + + b = c->buckets + n; + +if (can_invalidate_bucket (c, b)) +invalidate_one_bucket (c, b); + +if (++checked >= c->sb.nbuckets /2 ) { + c->invalidate_needs_gc =1 ; +bcache_queue_gc (c->set); +return ; + } + } +} + +static void invalidate_buckets (struct cache *c) +{ +/* free_some_buckets() may just need to write priorities to keep gens +* from wrapping around +*/ +if (!c->set->gc_mark_valid || + c->invalidate_needs_gc) +return ; + +switch (CACHE_REPLACEMENT (&c->sb)) { +case CACHE_REPLACEMENT_LRU : +invalidate_buckets_lru (c); +break ; +case CACHE_REPLACEMENT_FIFO : +invalidate_buckets_fifo (c); +break ; +case CACHE_REPLACEMENT_RANDOM : +invalidate_buckets_random (c); +break ; + } +} + +bool can_save_prios (struct cache *c) +{ +return ((c->need_save_prio >64 || + (c->set->gc_mark_valid && + !c->invalidate_needs_gc)) && + !atomic_read (&c->prio_written) && + !atomic_read (&c->set->prio_blocked)); +} + +void free_some_buckets (struct cache *c) +{ +long r; + +/* +* XXX: do_discard(), prio_write() take refcounts on the cache set. How +* do we know that refcount is nonzero? +*/ + +do_discard (c); + +while (!fifo_full (&c->free) && + (fifo_used (&c->free) <=8 || + !c->discard) && + (r =pop_freed (c)) !=-1 ) +fifo_push (&c->free, r); + +while (c->prio_alloc !=prio_buckets (c) && +fifo_pop (&c->free, r)) { +struct bucket *b = c->buckets + r; + c->prio_next[c->prio_alloc++] = r; + + b->mark =GC_MARK_BTREE ; +atomic_dec_bug (&b->pin); + } + +if (!CACHE_SYNC (&c->set->sb)) { +if (fifo_empty (&c->free_inc)) +invalidate_buckets (c); +return ; + } + +/* XXX: tracepoint for when c->need_save_prio > 64 */ + +if (c->need_save_prio <=64 && +fifo_used (&c->unused) > c->unused.size /2 ) +return ; + +if (atomic_read (&c->prio_written) >0 && + (fifo_empty (&c->free_inc) || + c->need_save_prio >64 )) +atomic_set (&c->prio_written,0 ); + +if (!can_save_prios (c)) +return ; + +invalidate_buckets (c); + +if (!fifo_empty (&c->free_inc) || + c->need_save_prio >64 ) +prio_write (c); +} + +static long pop_bucket (struct cache *c,uint16_t priority,struct closure *cl) +{ +long r =-1 ; +again: +free_some_buckets (c); + +if ((priority == btree_prio || +fifo_used (&c->free) >8 ) && +fifo_pop (&c->free, r)) { +struct bucket *b = c->buckets + r; +#ifdef CONFIG_BCACHE_EDEBUG +long i; +for (unsigned j =0 ; j <prio_buckets (c); j++) +BUG_ON (c->prio_buckets[j] == (uint64_t ) r); +for (unsigned j =0 ; j < c->prio_alloc; j++) +BUG_ON (c->prio_next[j] == (uint64_t ) r); + +fifo_for_each (i, &c->free) +BUG_ON (i == r); +fifo_for_each (i, &c->free_inc) +BUG_ON (i == r); +fifo_for_each (i, &c->unused) +BUG_ON (i == r); +#endif +BUG_ON (atomic_read (&b->pin) !=1 ); + + b->prio = priority; + b->mark = priority == btree_prio + ?GC_MARK_BTREE + : c->sb.bucket_size; +return r; + } + +pr_debug ("no free buckets, prio_written %i, blocked %i, " +"free %zu, free_inc %zu, unused %zu" , +atomic_read (&c->prio_written), +atomic_read (&c->set->prio_blocked),fifo_used (&c->free), +fifo_used (&c->free_inc),fifo_used (&c->unused)); + +if (cl) { +if (closure_blocking (cl)) +mutex_unlock (&c->set->bucket_lock); + +closure_wait_event (&c->set->bucket_wait, cl, +atomic_read (&c->prio_written) >0 || +can_save_prios (c)); + +if (closure_blocking (cl)) { +mutex_lock (&c->set->bucket_lock); + goto again; + } + } + +return -1 ; +} + +void unpop_bucket (struct cache_set *c,struct bkey *k) +{ +for (unsigned i =0 ; i <KEY_PTRS (k); i++) { +struct bucket *b =PTR_BUCKET (c, k, i); + + b->mark =0 ; +bucket_add_unused (PTR_CACHE (c, k, i), b); + } +} + +int __pop_bucket_set (struct cache_set *c,uint16_t prio, +struct bkey *k,int n,struct closure *cl) +{ +lockdep_assert_held (&c->bucket_lock); +BUG_ON (!n || n > c->caches_loaded || n >8 ); + + k->header =KEY_HEADER (0 ,0 ); + +/* sort by free space/prio of oldest data in caches */ + +for (int i =0 ; i < n; i++) { +struct cache *ca = c->cache_by_alloc[i]; +long b =pop_bucket (ca, prio, cl); + +if (b ==-1 ) + goto err; + + k->ptr[i] =PTR (ca->buckets[b].gen, +bucket_to_sector (c, b), + ca->sb.nr_this_dev); + +SET_KEY_PTRS (k, i +1 ); + } + +return 0 ; +err: +unpop_bucket (c, k); +__bkey_put (c, k); +return -1 ; +} + +int pop_bucket_set (struct cache_set *c,uint16_t prio, +struct bkey *k,int n,struct closure *cl) +{ +int ret; +mutex_lock (&c->bucket_lock); + ret =__pop_bucket_set (c, prio, k, n, cl); +mutex_unlock (&c->bucket_lock); +return ret; +}
drivers/block/bcache/io.c new file +198 -0
198 lines not on this page · raw message
drivers/block/bcache/request.c new file +1470 -0
1,470 lines not on this page · raw message
drivers/block/bcache/request.h new file +58 -0
58 lines not on this page · raw message
Signature · 1 line
1.7.9.rc2
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
Quoted 22 lines · Kent Overstreet
drivers/block/bcache/io.c new file +22
-0+int submit_bbio_split (struct bio *bio,struct cache_set *c, +struct bkey *k,unsigned ptr) +{ +struct closure *cl = bio->bi_private; +struct bbio *b; +struct bio *n; +unsigned sectors_done =0 ; + +closure_get (cl); + + bio->bi_sector =PTR_OFFSET (k, ptr); + bio->bi_bdev =PTR_CACHE (c, k, ptr)->bdev; + + do { + n = bio_split_get(bio, bio_max_sectors(bio), c); + if (!n) { + closure_put(cl); + return -ENOMEM; + } + + b = container_of(n, struct bbio, bio); +
drivers/block/bcache/io.c line 114
Quoted 10 lines
drivers/block/bcache/io.c new file +10
-0+bkey_copy_single_ptr (&b->key, k, ptr); +SET_KEY_SIZE (&b->key,KEY_SIZE (k) - sectors_done); +SET_PTR_OFFSET (&b->key,0 ,PTR_OFFSET (k, ptr) + sectors_done); + + b->submit_time_us =local_clock_us (); +generic_make_request (n); + }while (n != bio); + +return 0 ; +}
Hmmm... where is @sectors_done updated?
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
I'm gonna grovel through the code history, but that looks to me like dead code.
Quoted 44 lines · Tejun Heo
Quoted 22 lines · Kent Overstreet
drivers/block/bcache/io.c new file +22
-0+int submit_bbio_split (struct bio *bio,struct cache_set *c, + struct bkey *k, unsigned ptr) +{ + struct closure *cl = bio->bi_private; + struct bbio *b; + struct bio *n; + unsigned sectors_done = 0; + + closure_get(cl); + + bio->bi_sector = PTR_OFFSET(k, ptr); + bio->bi_bdev = PTR_CACHE(c, k, ptr)->bdev; + + do { + n = bio_split_get(bio, bio_max_sectors(bio), c); + if (!n) { + closure_put(cl); + return -ENOMEM; + } + + b = container_of(n, struct bbio, bio); +drivers/block/bcache/io.c line 114
Quoted 10 lines
drivers/block/bcache/io.c new file +10
-0+ bkey_copy_single_ptr(&b->key, k, ptr); + SET_KEY_SIZE(&b->key, KEY_SIZE(k) - sectors_done); + SET_PTR_OFFSET(&b->key, 0, PTR_OFFSET(k, ptr) + sectors_done); + + b->submit_time_us = local_clock_us(); + generic_make_request(n); + } while (n != bio); + + return 0; +}Hmmm... where is @sectors_done updated?
Thanks.
--
tejun
--
To unsubscribe from this list: send the line "unsubscribe linux-bcache" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
In reply to Tejun HeoQuoted 27 lines · Tejun Heo
Quoted 26 lines · Kent Overstreet
drivers/block/bcache/io.c new file +26
-0+int submit_bbio_split (struct bio *bio,struct cache_set *c, +struct bkey *k,unsigned ptr) +{ +struct closure *cl = bio->bi_private; +struct bbio *b; +struct bio *n; +unsigned sectors_done =0 ; + +closure_get (cl); + + bio->bi_sector =PTR_OFFSET (k, ptr); + bio->bi_bdev =PTR_CACHE (c, k, ptr)->bdev; + + do { + n = bio_split_get(bio, bio_max_sectors(bio), c); + if (!n) { + closure_put(cl); + return -ENOMEM; + } + + b = container_of(n, struct bbio, bio); + + bkey_copy_single_ptr(&b->key, k, ptr); + SET_KEY_SIZE(&b->key, KEY_SIZE(k) - sectors_done); + SET_PTR_OFFSET(&b->key, 0, PTR_OFFSET(k, ptr) + sectors_done); +
Quoted 8 lines
drivers/block/bcache/io.c line 114
Quoted 6 lines
drivers/block/bcache/io.c new file +6
-0+ b->submit_time_us =local_clock_us (); +generic_make_request (n); + }while (n != bio); + +return 0 ; +}Hmmm... where is @sectors_done updated?
That's a bug - thanks!
Tejun Heo
maintainerRe: [Bcache v13 14/16] bcache: Request, io and allocation code
In reply to Kent OverstreetQuoted 22 lines · Kent Overstreet
drivers/block/bcache/io.c new file +22
-0+int submit_bbio_split (struct bio *bio,struct cache_set *c, +struct bkey *k,unsigned ptr) +{ +struct closure *cl = bio->bi_private; +struct bbio *b; +struct bio *n; +unsigned sectors_done =0 ; + +closure_get (cl); + + bio->bi_sector =PTR_OFFSET (k, ptr); + bio->bi_bdev =PTR_CACHE (c, k, ptr)->bdev; + + do { + n = bio_split_get(bio, bio_max_sectors(bio), c); + if (!n) { + closure_put(cl); + return -ENOMEM; + } + + b = container_of(n, struct bbio, bio); +
drivers/block/bcache/io.c line 114
Quoted 10 lines
drivers/block/bcache/io.c new file +10
-0+bkey_copy_single_ptr (&b->key, k, ptr); +SET_KEY_SIZE (&b->key,KEY_SIZE (k) - sectors_done); +SET_PTR_OFFSET (&b->key,0 ,PTR_OFFSET (k, ptr) + sectors_done); + + b->submit_time_us =local_clock_us (); +generic_make_request (n); + }while (n != bio); + +return 0 ; +}
Where is @sectors_done updated?
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
In reply to Kent OverstreetHello, Kent.
I followed the control from cached_dev_make_request() this time.
Quoted 2 lines · Kent Overstreet
+ * Since the gens and priorities are all stored contiguously on disk, we can
+ * batch this up: We fill up thefree_inclist with freshly invalidated buckets,
What does "inc" in free_inc stand for?
Quoted 6 lines
drivers/block/bcache/alloc.c new file +6
-0+static void discard_finish (struct work_struct *w) +{ + struct discard *d = container_of(w, struct discard, work); +struct cache *c = d->c; +char buf[BDEVNAME_SIZE ]; +bool run = false;
drivers/block/bcache/alloc.c line 159
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+ +if (!test_bit (BIO_UPTODATE , &d->bio.bi_flags)) { +printk (KERN_NOTICE "bcache: discard error on %s, disabling\n" , +bdevname (c->bdev, buf)); + d->c->discard =0 ; + } + +mutex_lock (&c->set->bucket_lock); +if (fifo_empty (&c->free) || +fifo_used (&c->free) ==8 )
I'm getting scared of the number 8. What does this mean? Is it the new 666? Oh, now I think about it, it's 2^3 thus 2*2*2. It's 3 of 2s. I think I can see the connection now. Oh devil!
drivers/block/bcache/alloc.c line 166
Quoted 7 lines
drivers/block/bcache/alloc.c new file +7
-0+ run = true; + +fifo_push (&c->free, d->bucket); + +list_add (&d->list, &c->discards); + +do_discard (c);
So, this chains discard issuing. Wouldn't some explanation be nice?
Quoted 5 lines
drivers/block/bcache/alloc.c new file +5
-0+mutex_unlock (&c->set->bucket_lock); + +if (run) +closure_wake_up (&c->set->bucket_wait); +
drivers/block/bcache/alloc.c line 181
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+closure_put (&c->set->cl); +} + +static void discard_endio (struct bio *bio,int error) +{ + struct discard *d = container_of(bio, struct discard, bio); + +PREPARE_WORK (&d->work, discard_finish); +schedule_work (&d->work); +}
Why is a work item used here? Why not closure? What's the difference? This pattern of bouncing completion processing to process context is also something common in bio sequencing. I keep thinking what we want is bio sequencer.
drivers/block/bcache/alloc.c line 193
Quoted 12 lines
drivers/block/bcache/alloc.c new file +12
-0+ +static void discard_work (struct work_struct *w) +{ + struct discard *d = container_of(w, struct discard, work); +submit_bio (0 , &d->bio); +} + +static void do_discard (struct cache *c) +{ +struct request_queue *q =bdev_get_queue (c->bdev); +int s = q->limits.logical_block_size; +
Prolly can use lockdep_assert_held().
drivers/block/bcache/alloc.c line 197
Quoted 4 lines
drivers/block/bcache/alloc.c new file +4
-0+while (c->discard && + !atomic_read (&c->set->closing) && + !list_empty (&c->discards) && +fifo_free (&c->free) >=8 ) {
What's the magic 8? Is this 8 someway related to other 8s in this file? How is one supposed to know what they mean or the rationale behind it?
Quoted 34 lines
drivers/block/bcache/alloc.c new file +34
-0+struct discard *d =list_first_entry (&c->discards, + struct discard, list); + + d->bucket =pop_freed (c); +if (d->bucket ==-1 ) +break ; + +list_del (&d->list); +closure_get (&c->set->cl); + +bio_init (&d->bio); +memset (&d->bv,0 ,sizeof (struct bio_vec )); +bio_set_prio (&d->bio,IOPRIO_PRIO_VALUE (IOPRIO_CLASS_IDLE ,0 )); + + d->bio.bi_sector =bucket_to_sector (c->set, d->bucket); + d->bio.bi_bdev = c->bdev; + d->bio.bi_rw =REQ_WRITE |(1 <<BIO_RW_DISCARD ); + d->bio.bi_max_vecs =1 ; + d->bio.bi_io_vec = d->bio.bi_inline_vecs; + d->bio.bi_end_io = discard_endio; + +if (bio_add_pc_page (q, &d->bio, c->discard_page, s,0 ) < s) { +printk (KERN_DEBUG "bcache: bio_add_pc_page failed\n" ); + c->discard =0 ; +fifo_push (&c->free, d->bucket); +list_add (&d->list, &c->discards); +break ; + } + + d->bio.bi_size =bucket_bytes (c); + +schedule_work (&d->work); + } +}
...
Quoted 4 lines
drivers/block/bcache/alloc.c new file +4
-0+int alloc_discards (struct cache *ca) +{ +for (int i =0 ; i <8 ; i++) { +struct discard *d =kzalloc (sizeof (*d),GFP_KERNEL );
drivers/block/bcache/alloc.c line 258
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+if (!d) +return -ENOMEM ; + + d->c = ca; +INIT_WORK (&d->work, discard_work); +list_add (&d->list, &ca->discards); + } + +return 0 ; +}
Why maintain separate pool of discards? It it to throttle discard commands? And another evil number!
drivers/block/bcache/alloc.c line 285
Quoted 6 lines
drivers/block/bcache/alloc.c new file +6
-0+static bool can_invalidate_bucket (struct cache *c,struct bucket *b) +{ +return b->mark >=0 && + !atomic_read (&b->pin) && +bucket_gc_gen (b) <96U && +bucket_disk_gen (b) <64U ;
Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
Quoted 12 lines
drivers/block/bcache/alloc.c new file +12
-0+static void invalidate_buckets_lru (struct cache *c) +{ +unsigned bucket_prio (struct bucket *b) + { +return ((unsigned ) (b->prio - c->set->min_prio)) * b->mark; + } + +bool bucket_max_cmp (struct bucket *l,struct bucket *r) + { +return bucket_prio (l) <bucket_prio (r); + } +
drivers/block/bcache/alloc.c line 317
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+bool bucket_min_cmp (struct bucket *l,struct bucket *r) + { +return bucket_prio (l) >bucket_prio (r); + } + +struct bucket *b; + + c->heap.used =0 ; + +for_each_bucket (b, c) {
So, this iterates all buckets in the cache, right? Maybe it warrants cond_resched()?
Quoted 38 lines
drivers/block/bcache/alloc.c new file +38
-0+if (!can_invalidate_bucket (c, b)) +continue ; + +if (!b->mark) { +if (!bucket_add_unused (c, b)) +return ; + }else { +if (!heap_full (&c->heap)) +heap_add (&c->heap, b, bucket_max_cmp); +else if (bucket_max_cmp (b,heap_peek (&c->heap))) { + c->heap.data[0 ] = b; +heap_sift (&c->heap,0 , bucket_max_cmp); + } + } + } + +if (c->heap.used *2 < c->heap.size) +bcache_queue_gc (c->set); + +for (ssize_t i = c->heap.used /2 -1 ; i >=0 ; --i) +heap_sift (&c->heap, i, bucket_min_cmp); + +while (!fifo_full (&c->free_inc)) { +if (!heap_pop (&c->heap, b, bucket_min_cmp)) { +/* We don't want to be calling invalidate_buckets() +* multiple times when it can't do anything +*/ + c->invalidate_needs_gc =1 ; +bcache_queue_gc (c->set); +return ; + } + +invalidate_one_bucket (c, b); + } +} +void free_some_buckets (struct cache *c) +{ +long r;
drivers/block/bcache/alloc.c line 445
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+ +/* +* XXX: do_discard(), prio_write() take refcounts on the cache set. How +* do we know that refcount is nonzero? +*/ + +do_discard (c); + + while (!fifo_full(&c->free) && + (fifo_used(&c->free) <= 8 ||
Ooh, devil!
Quoted 12 lines
drivers/block/bcache/alloc.c new file +12
-0+ !c->discard) && + (r = pop_freed(c)) != -1) +fifo_push (&c->free, r); + +while (c->prio_alloc !=prio_buckets (c) && +fifo_pop (&c->free, r)) { +struct bucket *b = c->buckets + r; + c->prio_next[c->prio_alloc++] = r; + + b->mark =GC_MARK_BTREE ; +atomic_dec_bug (&b->pin); + }
drivers/block/bcache/alloc.c line 467
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+ +if (!CACHE_SYNC (&c->set->sb)) { +if (fifo_empty (&c->free_inc)) +invalidate_buckets (c); +return ; + } + +/* XXX: tracepoint for when c->need_save_prio > 64 */ + + if (c->need_save_prio <= 64 &&
Ooh, devil * devil!
Quoted 17 lines
drivers/block/bcache/alloc.c new file +17
-0+ fifo_used(&c->unused) > c->unused.size / 2) + return; + +if (atomic_read (&c->prio_written) >0 && + (fifo_empty (&c->free_inc) || + c->need_save_prio >64 )) +atomic_set (&c->prio_written,0 ); + +if (!can_save_prios (c)) +return ; + +invalidate_buckets (c); + +if (!fifo_empty (&c->free_inc) || + c->need_save_prio >64 ) +prio_write (c); +}
drivers/block/bcache/alloc.c line 494
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+ +static long pop_bucket (struct cache *c,uint16_t priority,struct closure *cl) +{ +long r =-1 ; +again: +free_some_buckets (c); + +if ((priority == btree_prio || +fifo_used (&c->free) >8 ) && +fifo_pop (&c->free, r)) {
This is unconventional and more difficult to read than
if ((priority == btree_prio || fifo_used(&c->free) > 8) &&
fifo_pop(&c->free, r)) {It harms readability by both being unnecessarily different and visually less distinguishible. Why do this?
Quoted 33 lines
drivers/block/bcache/alloc.c new file +33
-0+struct bucket *b = c->buckets + r; +#ifdef CONFIG_BCACHE_EDEBUG +long i; +for (unsigned j =0 ; j <prio_buckets (c); j++) +BUG_ON (c->prio_buckets[j] == (uint64_t ) r); +for (unsigned j =0 ; j < c->prio_alloc; j++) +BUG_ON (c->prio_next[j] == (uint64_t ) r); + +fifo_for_each (i, &c->free) +BUG_ON (i == r); +fifo_for_each (i, &c->free_inc) +BUG_ON (i == r); +fifo_for_each (i, &c->unused) +BUG_ON (i == r); +#endif +BUG_ON (atomic_read (&b->pin) !=1 ); + + b->prio = priority; + b->mark = priority == btree_prio + ?GC_MARK_BTREE + : c->sb.bucket_size; +return r; + } + +pr_debug ("no free buckets, prio_written %i, blocked %i, " +"free %zu, free_inc %zu, unused %zu" , +atomic_read (&c->prio_written), +atomic_read (&c->set->prio_blocked),fifo_used (&c->free), +fifo_used (&c->free_inc),fifo_used (&c->unused)); + +if (cl) { +if (closure_blocking (cl)) +mutex_unlock (&c->set->bucket_lock);
drivers/block/bcache/alloc.c line 537
Quoted 10 lines
drivers/block/bcache/alloc.c new file +10
-0+ +closure_wait_event (&c->set->bucket_wait, cl, +atomic_read (&c->prio_written) >0 || +can_save_prios (c)); + +if (closure_blocking (cl)) { +mutex_lock (&c->set->bucket_lock); + goto again; + } + }
How is this different from using @gfp_flags (for %__GFP_WAIT) or bool @may_block + -EAGAIN return?
Quoted 43 lines
drivers/block/bcache/alloc.c new file +43
-0+return -1 ; +} + +void unpop_bucket (struct cache_set *c,struct bkey *k) +{ +for (unsigned i =0 ; i <KEY_PTRS (k); i++) { +struct bucket *b =PTR_BUCKET (c, k, i); + + b->mark =0 ; +bucket_add_unused (PTR_CACHE (c, k, i), b); + } +} + +int __pop_bucket_set (struct cache_set *c,uint16_t prio, +struct bkey *k,int n,struct closure *cl) +{ +lockdep_assert_held (&c->bucket_lock); +BUG_ON (!n || n > c->caches_loaded || n >8 ); + + k->header =KEY_HEADER (0 ,0 ); + +/* sort by free space/prio of oldest data in caches */ + +for (int i =0 ; i < n; i++) { +struct cache *ca = c->cache_by_alloc[i]; +long b =pop_bucket (ca, prio, cl); + +if (b ==-1 ) + goto err; + + k->ptr[i] =PTR (ca->buckets[b].gen, +bucket_to_sector (c, b), + ca->sb.nr_this_dev); + +SET_KEY_PTRS (k, i +1 ); + } + +return 0 ; +err: +unpop_bucket (c, k); +__bkey_put (c, k); +return -1 ; +}
...
drivers/block/bcache/request.c line 238
Quoted 11 lines
drivers/block/bcache/request.c new file +11
-0+static void bio_invalidate (struct closure *cl) +{ + struct btree_op *op = container_of(cl, struct btree_op, cl); + struct search *s = container_of(op, struct search, op); +struct bio *bio = s->cache_bio; + +pr_debug ("invalidating %i sectors from %llu" , +bio_sectors (bio), (uint64_t ) bio->bi_sector); + +while (bio_sectors (bio)) { +unsigned len =min (bio_sectors (bio),1U <<14 );
New line missing. Again, I'm not gonna complain about this more but please follow the usual formatting. There are cases where deviating can be beneficial / tolerated but IMHO you're deviating way too often for no good reasons (well, they're not apparent to me anyway).
Quoted 14 lines
drivers/block/bcache/request.c new file +14
-0+if (keylist_realloc (&s->op.keys,0 , s->op.d->c)) + goto out; + + bio->bi_sector += len; + bio->bi_size -= len <<9 ; + +keylist_add (&s->op.keys, + &KEY (s->op.d->id, bio->bi_sector, len)); + } + + s->bio_insert_done = true; +out: +continue_at (cl, bcache_journal, bcache_wq); +}
...
drivers/block/bcache/request.c line 330
Quoted 7 lines
drivers/block/bcache/request.c new file +7
-0+static struct open_bucket *get_data_bucket (struct bkey *search, +struct search *s) +{ +struct closure cl, *w =NULL ; +struct cache_set *c = s->op.d->c; +struct open_bucket *l, *ret, *ret_task; +
Unnecessary new line.
drivers/block/bcache/request.c line 331
Quoted 1 line
drivers/block/bcache/request.c new file +1
-0+BKEY_PADDED (key) alloc;
Why BKEY_PADDED()? What does it do? Can we not do this?
Quoted 6 lines
drivers/block/bcache/request.c new file +6
-0+struct bkey *k =NULL ; + +if (s->writeback) { +closure_init_stack (&cl); + w = &cl; + }
drivers/block/bcache/request.c line 347
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+again: + ret = ret_task =NULL ; + +spin_lock (&c->data_bucket_lock); +list_for_each_entry_reverse (l, &c->data_buckets, list) +if (!bkey_cmp (&l->key, search)) { + ret = l; + goto found; + }else if (l->last == s->task) + ret_task = l;
if () {
} else if () {
}
Also, it's better to always use braces in outer constructs if inner needs one.
And, it seems the bucket allocations follow the task. What's the benefit of doing so? Why isn't there any explanation?
drivers/block/bcache/request.c line 360
Quoted 13 lines
drivers/block/bcache/request.c new file +13
-0+ + ret = ret_task ?:list_first_entry (&c->data_buckets, + struct open_bucket, list); +found: +if (!ret->sectors_free) { +if (!k) { +spin_unlock (&c->data_bucket_lock); + k = &alloc.key; + +if (pop_bucket_set (c, initial_prio, k,1 , w)) +return NULL ; + + goto again;
So, this is "try-locked-first, on failure, unlock-alloc-retry" dancing. Constructs like this aren't too atypical but they still warrant short explanation. Please be nice to people trying to read your code.
Quoted 11 lines
drivers/block/bcache/request.c new file +11
-0+ } + +bkey_copy (&ret->key, k); + k =NULL ; + + ret->sectors_free = c->sb.bucket_size; + }else +for (unsigned i =0 ; i <KEY_PTRS (&ret->key); i++) +EBUG_ON (ptr_stale (c, &ret->key, i)); + +if (k)
drivers/block/bcache/request.c line 381
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+__bkey_put (c, k); + +if (w) +for (unsigned i =0 ; i <KEY_PTRS (&ret->key); i++) +PTR_BUCKET (c, &ret->key, i)->mark =GC_MARK_DIRTY ; + + ret->last = s->task; +bkey_copy_key (&ret->key, search); + +list_move_tail (&ret->list, &c->data_buckets);
This too. It's a rather common pattern and people would be able to just read through without thinking if it had one line comment saying something like /* @ret is hot now, put it at the head of queue */.
Quoted 2 lines
(quoted diff) +2
-0+ return ret; +}
...
Quoted 1 line
+static void
bio_insert(struct closure *cl)
Too generic name. This and a lot of others. Note that there are issues other than direct compile time symbol collision - it makes the code difficult to grep for and stack traces difficult to decipher. I'm not gonna complain more about too generic names but please review the code and fix other instances too.
Another thing is that why this function and its friends take @cl when they always expect @cl contained inside search->op. Why not take @search instead? Using @cl is more dangerous and less readable. Why do it?
Quoted 14 lines
drivers/block/bcache/request.c new file +14
-0+{ + struct btree_op *op = container_of(cl, struct btree_op, cl); + struct search *s = container_of(op, struct search, op); +struct bio *bio = s->cache_bio; + +if (!s->skip) { + bio->bi_end_io = bio_insert_endio; + bio->bi_private = cl; +bio_get (bio); + } + +keylist_init (&op->keys); +bio_insert_loop (cl); +}
...
drivers/block/bcache/request.c line 617
Quoted 6 lines
drivers/block/bcache/request.c new file +6
-0+static void __do_bio_hook (struct search *s) +{ +struct bio *bio = &s->bio.bio; +memcpy (bio, s->orig_bio,sizeof (struct bio )); + +#ifdef CONFIG_DISKMON
Contamination.
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+ bio->bi_flowid =NULL ; +#endif + bio->bi_end_io = request_endio; + bio->bi_private = &s->cl; + bio->bi_destructor =NULL ; +atomic_set (&bio->bi_cnt,3 ); +} + +static struct search *do_bio_hook (struct bio *bio,struct bcache_device *d) +{
drivers/block/bcache/request.c line 637
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+struct bio_vec *bv; +struct search *s =mempool_alloc (d->c->search,GFP_NOIO ); + memset(s, 0, offsetof(struct search, op.keys)); + +__closure_init (&s->cl,NULL ); +__closure_init (&s->op.cl, &s->cl); + + s->op.d = d; + s->op.lock =-1 ; + s->task =get_current ();
Please use current instead of get_current().
Quoted 17 lines
drivers/block/bcache/request.c new file +17
-0+ s->orig_bio = bio; + s->write = bio->bi_rw &REQ_WRITE ; + s->op.flush_journal = bio->bi_rw &REQ_FLUSH ; + s->recoverable =1 ; +__do_bio_hook (s); + +if (bio->bi_size !=bio_segments (bio) *PAGE_SIZE ) { + bv =mempool_alloc (d->unaligned_bvec,GFP_NOIO ); +memcpy (bv,bio_iovec (bio), +sizeof (struct bio_vec ) *bio_segments (bio)); + + s->bio.bio.bi_io_vec = bv; + s->unaligned_bvec =1 ; + } + +return s; +}
...
drivers/block/bcache/request.c line 935
Quoted 7 lines
drivers/block/bcache/request.c new file +7
-0+static bool should_writeback (struct cached_dev *d,struct bio *bio) +{ +return !atomic_read (&d->disk.detaching) && +cache_mode (d, bio) ==CACHE_MODE_WRITEBACK && + (d->disk.c->gc_stats.in_use < (bio->bi_rw &REQ_SYNC ) + ?CUTOFF_WRITEBACK_SYNC + :CUTOFF_WRITEBACK );
The formatting of the CUTOFF_WRITEBACK* test is atrocious. It almost looks like it's trying to mislead the reader. For $DIETY's sake, use a local variable for the threshold value or split the condition into a few "if () return;" clauses.
drivers/block/bcache/request.c line 948
Quoted 13 lines
drivers/block/bcache/request.c new file +13
-0+} + +static void request_write_resubmit (struct closure *cl) +{ + struct btree_op *op = container_of(cl, struct btree_op, cl); + struct search *s = container_of(op, struct search, op); +struct bio *bio = &s->bio.bio; + +closure_bio_submit (bio, &s->cl, op->d->c->bio_split); + +bio_insert (&s->op.cl); +continue_at (&s->cl, cached_dev_write_complete,NULL ); +}
I'm kinda turned off by closure here too. It doesn't really seem to simplify the actual painful points of async programming. The user still has to compose separate paths for sync and async paths. I keep thinking domain-specific sequencer would be much better.
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+ +static void request_write (struct cached_dev *d,struct search *s) +{ +struct closure *cl = &s->cl; +struct bio *bio = &s->bio.bio; + +check_should_skip (d, s); +down_read_non_owner (&d->writeback_lock); + +if (bcache_in_writeback (d, bio->bi_sector,bio_sectors (bio))) {
drivers/block/bcache/request.c line 968
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+ s->skip = false; + s->writeback = true; + } + +if (s->skip) { +skip: s->cache_bio = s->orig_bio; +bio_get (s->cache_bio); +trace_bcache_write_skip (s->orig_bio); + +if (bio->bi_rw & (1 <<BIO_RW_DISCARD )) {
This isn't bcache's problem but it probably would be better to make block layer handle this in generic manner. blk_queue_discard() already indicates whether a queue supports discard or not. generic_make_request() can simply treat discards as no-op if unsupported.
Quoted 28 lines
drivers/block/bcache/request.c new file +28
-0+closure_get (cl); + +if (blk_queue_discard (bdev_get_queue (d->bdev))) +generic_make_request (bio); +else +bio_endio (bio,0 ); + + goto out; + }else + goto submit; + } + +if (should_writeback (d, s->orig_bio)) + s->writeback = true; + +if (!s->writeback) { + s->cache_bio =bbio_kmalloc (GFP_NOIO , bio->bi_max_vecs); +if (!s->cache_bio) { + s->skip = true; + goto skip; + } + +__bio_clone (s->cache_bio, bio); +trace_bcache_writethrough (s->orig_bio); +submit: +if (closure_bio_submit (bio, cl, s->op.d->c->bio_split)) + continue_at(&s->op.cl, + request_write_resubmit,
drivers/block/bcache/request.c line 1006
Quoted 10 lines
drivers/block/bcache/request.c new file +10
-0+ bcache_wq); + }else { + s->cache_bio = bio; +trace_bcache_writeback (s->orig_bio); +bcache_writeback_add (d,bio_sectors (bio)); + } +out: +bio_insert (&s->op.cl); +continue_at (cl, cached_dev_write_complete,NULL ); +}
I hate how gotos are being abused in this function. This ain't 80s and we don't build complex control flow using gotos. If common logic is used in different parts of the control flow in a way that the usual control constructs don't really yield pretty code, factor out the common part into a function. Are you not doing that because you don't wanna put continue_at() inside another function which may obscure the closure control flow? If that's the case, I think it just shows how retarded wrapping return in macros is. :(
drivers/block/bcache/request.c line 1238
Quoted 8 lines
drivers/block/bcache/request.c new file +8
-0+static void cached_dev_make_request (struct request_queue *q,struct bio *bio) +{ +struct search *s; +struct bcache_device *d = bio->bi_bdev->bd_disk->private_data; + struct cached_dev *dc = container_of(d, struct cached_dev, disk); + + bio->bi_bdev = dc->bdev; + bio->bi_sector +=16 ;
^^^^
Please don't do this. Use of magic numbers seems pretty common throughout the code base. This is error-prone and cryptic. Use enums or macros (this is what macros are for, actually) to give them meaningful names. If applicable, explain why the specific number is chosen on constant definition. I assume this is the size of superblock but I can't ask cscope about it or grep for it.
drivers/block/bcache/request.c line 1246
Quoted 8 lines
drivers/block/bcache/request.c new file +8
-0+ +if (cached_dev_get (dc)) { + s =do_bio_hook (bio, d); +trace_bcache_request_start (&s->op, bio); + + (!bio_has_data (bio) ? request_nodata : + bio->bi_rw &REQ_WRITE ? request_write : + request_read)(dc, s);
Don't do this. Among other things, it should be possible to search / grep for "FUNCTION_NAME(" and find all the direct invocations. Just use if/else. You're not gaining anything from doing things like the above while basically aggravating other developers trying to understand your code.
drivers/block/bcache/request.c line 1249
Quoted 3 lines
drivers/block/bcache/request.c new file +3
-0+ }else +bio_passthrough (dc, bio); +}
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
Quoted 9 lines · Tejun Heo
Hello, Kent.
I followed the control from
cached_dev_make_request()this time.Quoted 2 lines · Kent Overstreet
+ * Since the gens and priorities are all stored contiguously on disk, we can
+ * batch this up: We fill up thefree_inclist with freshly invalidated buckets,What does "inc" in
free_incstand for?
Incoming:
drivers/md/bcache/bcache.h +19 -6
@@ -342,6 +342,25 @@ struct cache {*/ atomic_t prio_written; +/* +* free: Buckets that are ready to be used +* +* free_inc: Incoming buckets - these are buckets that currently have +* cached data in them, and we can't reuse them until after we write +* their new gen to disk. After prio_write() finishes writing the new +* gens/prios, they'll be moved to the free list (and possibly discarded +* in the process) +* +* unused: GC found nothing pointing into these buckets (possibly +* because all the data they contained was overwritten), so we only +* need to discard them before they can be moved to the free list. +*/ +DECLARE_FIFO (long, free); +DECLARE_FIFO (long, free_inc); +DECLARE_FIFO (long, unused); + +size_t fifo_last_bucket; +/* Allocation stuff: */ struct bucket *buckets;
@@ -360,12 +379,6 @@ struct cache {*/ unsigned invalidate_needs_gc:1; -size_t fifo_last_bucket; - -DECLARE_FIFO (long, free); -DECLARE_FIFO (long, free_inc); -DECLARE_FIFO (long, unused); -bool discard;/* Get rid of? */ struct list_head discards;struct page *discard_page;
Quoted 17 lines
Quoted 7 lines
(quoted diff) +6
-0+static void discard_finish(struct work_struct *w) +{ + struct discard *d = container_of(w, struct discard, work); + struct cache *c = d->c; + char buf[BDEVNAME_SIZE]; + bool run = false;Review
Quoted 10 lines
(quoted diff) +10
-0+ + if (!test_bit(BIO_UPTODATE, &d->bio.bi_flags)) { + printk(KERN_NOTICE "bcache: discard error on %s, disabling\n", + bdevname(c->bdev, buf)); + d->c->discard = 0; + } + + mutex_lock(&c->set->bucket_lock); + if (fifo_empty(&c->free) || + fifo_used(&c->free) == 8)
Oh, ugh. That isn't even right - at _least_ the second check should be fifo_used() >= 8, not ==. It should probably be deleted entirely and just always do the wakeup, but the allocation code is prone to spinning and burning 100% cpu if stuff like that is wrong - I'll have to test it carefully.
Quoted 3 lines
I'm getting scared of the number 8. What does this mean? Is it the new 666? Oh, now I think about it, it's 2^3 thus 2*2*2. It's 3 of 2s. I think I can see the connection now. Oh devil!
It's a reserve, for btree bucket allocation - enough buckets for a btree split to succeed and for garbage collection to get some work done.
I have code in my copying gc branch that gets rid of that magic number - copying gc requires another reserve, and there's yet another reserve for writing priorities - the new code consolidates all that.
Quoted 10 lines
Review
Quoted 8 lines
(quoted diff) +7
-0+ run = true; + + fifo_push(&c->free, d->bucket); + + list_add(&d->list, &c->discards); + + do_discard(c);So, this chains discard issuing. Wouldn't some explanation be nice?
Yeah. Does this help?
drivers/md/bcache/bcache.h +7 -0
@@ -380,6 +380,13 @@ struct cache { unsigned invalidate_needs_gc:1;bool discard;/* Get rid of? */ + +/* +* We preallocate structs for issuing discards to buckets, and keep them +* on this list when they're not in use; do_discard() issues discards +* whenever there's work to do and is called by free_some_buckets() and +* when a discard finishes. +*/ struct list_head discards;struct page *discard_page;
Quoted 13 lines
Quoted 13 lines
(quoted diff) +12
-0+ mutex_unlock(&c->set->bucket_lock); + + if (run) + closure_wake_up(&c->set->bucket_wait); + + closure_put(&c->set->cl); +} + +static void discard_endio(struct bio *bio, int error) +{ + struct discard *d = container_of(bio, struct discard, bio); +
Quoted 8 lines
Review
Quoted 3 lines
(quoted diff) +3
-0+ PREPARE_WORK(&d->work, discard_finish); + schedule_work(&d->work); +}Why is a work item used here? Why not closure? What's the difference? This pattern of bouncing completion processing to process context is also something common in bio sequencing. I keep thinking what we want is bio sequencer.
That's a good question, I hadn't quite thought about explaining closures from that angle.
The main thing about closures, and the thing that separates them from a lot of other existing constructs is they have a well defined lifetime. That's IMO kind of natural if you think of a closure as a refcount on steroids, and it's very useful - it's why the parent makes sense (and using that well is what made a lot of tricky refcounting and other issues go away).
The end result - the way I think about it, anyways - is that closures are more threadlike and work_structs make more sense in the context of state machines.
Anyways, from that angle it probably would make sense to use closures here - the discard has a well defined lifetime, and it holds a refcount on the cache_set while it exists (i.e. the cache_set would be the parent closure).
I thought about it a bit when I originally wrote that code, and IIRC I ended up not using closures just because it wasn't necessary and wouldn't have made the code any smaller - the discard itself doesn't need a refcount for anything.
But I've also been trying to get rid of bare closure_puts() wherever reasonably possible - the idea being that things are more consistent and harder to screw up if closure_put() is only ever called on something that code logically owns (e.g. how it's used in most of the endio functions). Switching it to use a closure instead of a work_struct would mean the closure_put() on the cache_set's closure would go away, it would instead be done by the final closure_return().
Quoted 6 lines
Quoted 6 lines
(quoted diff) +6
-0+ +static void discard_work(struct work_struct *w) +{ + struct discard *d = container_of(w, struct discard, work); + submit_bio(0, &d->bio); +}
Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ +static void do_discard(struct cache *c) +{ + struct request_queue *q = bdev_get_queue(c->bdev); + int s = q->limits.logical_block_size; +Prolly can use
lockdep_assert_held().
Yeah, good idea.
Quoted 9 lines
Review
Quoted 5 lines
(quoted diff) +4
-0+ while (c->discard && + !atomic_read(&c->set->closing) && + !list_empty(&c->discards) && + fifo_free(&c->free) >= 8) {What's the magic 8? Is this 8 someway related to other 8s in this file? How is one supposed to know what they mean or the rationale behind it?
This one is making sure there'll be room to stick the bucket on the free list - the 8 is the maximum number of discards that could be in flight.
I'm fixing that now. Didn't realize how many bare 8s there were in this file that meant different things.
Quoted 45 lines
Quoted 35 lines
(quoted diff) +34
-0+ struct discard *d = list_first_entry(&c->discards, + struct discard, list); + + d->bucket = pop_freed(c); + if (d->bucket == -1) + break; + + list_del(&d->list); + closure_get(&c->set->cl); + + bio_init(&d->bio); + memset(&d->bv, 0, sizeof(struct bio_vec)); + bio_set_prio(&d->bio, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)); + + d->bio.bi_sector = bucket_to_sector(c->set, d->bucket); + d->bio.bi_bdev = c->bdev; + d->bio.bi_rw = REQ_WRITE|(1 << BIO_RW_DISCARD); + d->bio.bi_max_vecs = 1; + d->bio.bi_io_vec = d->bio.bi_inline_vecs; + d->bio.bi_end_io = discard_endio; + + if (bio_add_pc_page(q, &d->bio, c->discard_page, s, 0) < s) { + printk(KERN_DEBUG "bcache: bio_add_pc_page failed\n"); + c->discard = 0; + fifo_push(&c->free, d->bucket); + list_add(&d->list, &c->discards); + break; + } + + d->bio.bi_size = bucket_bytes(c); + + schedule_work(&d->work); + } +}...
Quoted 9 lines
(quoted diff) +9
-0+int alloc_discards(struct cache *ca) +{ + for (int i = 0; i < 8; i++) { + struct discard *d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->c = ca; + INIT_WORK(&d->work, discard_work);
Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ list_add(&d->list, &ca->discards); + } + + return 0; +}Why maintain separate pool of discards? It it to throttle discard commands? And another evil number!
Yeah, it's just a mempool that also throttles. I don't particularly care for doing it that way, any suggestions? (Just fixed the magic number, at least).
Quoted 9 lines
Review
Quoted 7 lines
(quoted diff) +6
-0+static bool can_invalidate_bucket(struct cache *c, struct bucket *b) +{ + return b->mark >= 0 && + !atomic_read(&b->pin) && + bucket_gc_gen(b) < 96U && + bucket_disk_gen(b) < 64U;Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
b->mark >= 0 is pretty terrible (and it's also something fixed in my copy gc branch, heh).
In this code if mark is >= 0 it means there's only clean cached data in this bucket (the values for btree data and dirty data are negative), and if it's positive it's also the number of sectors gc found in this bucket (so that invalidate_buckets_lru() can preferentially invalidate buckets with less data in them).
The latest code splits out mark and sectors_used.
The !atomic_read() should be fairly self explanatory. I'm going to get rid of the per bucket refcount eventually, right now it's just used when we allocate a bucket so we can ensure it's not garbage collected until we insert a pointer to it into the btree.
bucket_gc_gen(), bucket_disk_gen() are the differences between the bucket's gen and the oldest gen in the btree (gc gen), and the gen on disk - those checks ensure the gen doesn't wrap around.
This improve things?
drivers/md/bcache/alloc.c +10 -6
@@ -74,11 +74,11 @@ uint8_t inc_gen(struct cache *c, struct bucket *b)uint8_t ret = ++b->gen; c->set->need_gc =max (c->set->need_gc,bucket_gc_gen (b)); -BUG_ON (c->set->need_gc >97 ); +WARN_ON_ONCE (c->set->need_gc >BUCKET_GC_GEN_MAX );if (CACHE_SYNC (&c->set->sb)) { c->need_save_prio =max (c->need_save_prio,bucket_disk_gen (b)); -BUG_ON (c->need_save_prio >96 ); +WARN_ON_ONCE (c->need_save_prio >BUCKET_DISK_GEN_MAX ); }return ret;
@@ -263,6 +263,12 @@ int alloc_discards(struct cache *ca)/* Allocation */ +static inline bool can_inc_bucket_gen (struct bucket *b) +{ +return bucket_gc_gen (b) <BUCKET_GC_GEN_MAX && +bucket_disk_gen (b) <BUCKET_DISK_GEN_MAX ; +} +bool bucket_add_unused (struct cache *c,struct bucket *b) { if (c->prio_alloc == prio_buckets(c) &&
@@ -271,8 +277,7 @@ bool bucket_add_unused(struct cache *c, struct bucket *b) b->prio =0 ; -if (bucket_gc_gen (b) <96U && -bucket_disk_gen (b) <64U && +if (can_inc_bucket_gen (b) &&fifo_push (&c->unused, b - c->buckets)) {atomic_inc (&b->pin);return true;
@@ -285,8 +290,7 @@ static bool can_invalidate_bucket(struct cache *c, struct bucket *b) {return b->mark >=0 && !atomic_read (&b->pin) && -bucket_gc_gen (b) <96U && -bucket_disk_gen (b) <64U ; +can_inc_bucket_gen (b); }static void invalidate_one_bucket (struct cache *c,struct bucket *b)
drivers/md/bcache/bcache.h +20 -2
@@ -821,8 +821,26 @@ static inline bool cached_dev_get(struct cached_dev *d)return true; } -#define bucket_gc_gen (b) ((uint8_t) ((b)->gen - (b)->last_gc)) -#define bucket_disk_gen (b) ((uint8_t) ((b)->gen - (b)->disk_gen)) +/* +* bucket_gc_gen() returns the difference between the bucket's current gen and +* the oldest gen of any pointer into that bucket in the btree (last_gc). +* +* bucket_disk_gen() returns the difference between the current gen and the gen +* on disk; they're both used to make sure gens don't wrap around. +*/ + +static inline uint8_t bucket_gc_gen (struct bucket *b) +{ +return b->gen - b->last_gc; +} + +static inline uint8_t bucket_disk_gen (struct bucket *b) +{ +return b->gen - b->disk_gc; +} + +#define BUCKET_GC_GEN_MAX 96U +#define BUCKET_DISK_GEN_MAX 64U#define kobj_attribute_write (n, fn) \ static struct kobj_attribute ksysfs_##n = __ATTR(n, S_IWUSR, NULL, fn)
Quoted 18 lines
Quoted 18 lines
(quoted diff) +17
-0+static void invalidate_buckets_lru(struct cache *c) +{ + unsigned bucket_prio(struct bucket *b) + { + return ((unsigned) (b->prio - c->set->min_prio)) * b->mark; + } + + bool bucket_max_cmp(struct bucket *l, struct bucket *r) + { + return bucket_prio(l) < bucket_prio(r); + } + + bool bucket_min_cmp(struct bucket *l, struct bucket *r) + { + return bucket_prio(l) > bucket_prio(r); + } +
Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ struct bucket *b; + + c->heap.used = 0; + + for_each_bucket(b, c) {So, this iterates all buckets in the cache, right? Maybe it warrants
cond_resched()?
This is probably on the order of ~1 ms but - it can't hurt, and I haven't actually measured it.
Quoted 43 lines
Quoted 43 lines
(quoted diff) +42
-0+ if (!can_invalidate_bucket(c, b)) + continue; + + if (!b->mark) { + if (!bucket_add_unused(c, b)) + return; + } else { + if (!heap_full(&c->heap)) + heap_add(&c->heap, b, bucket_max_cmp); + else if (bucket_max_cmp(b, heap_peek(&c->heap))) { + c->heap.data[0] = b; + heap_sift(&c->heap, 0, bucket_max_cmp); + } + } + } + + if (c->heap.used * 2 < c->heap.size) + bcache_queue_gc(c->set); + + for (ssize_t i = c->heap.used / 2 - 1; i >= 0; --i) + heap_sift(&c->heap, i, bucket_min_cmp); + + while (!fifo_full(&c->free_inc)) { + if (!heap_pop(&c->heap, b, bucket_min_cmp)) { + /* We don't want to be calling invalidate_buckets() + * multiple times when it can't do anything + */ + c->invalidate_needs_gc = 1; + bcache_queue_gc(c->set); + return; + } + + invalidate_one_bucket(c, b); + } +} +void free_some_buckets(struct cache *c) +{ + long r; + + /* + * XXX: do_discard(), prio_write() take refcounts on the cache set. How + * do we know that refcount is nonzero?
Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ */ + + do_discard(c); + + while (!fifo_full(&c->free) && + (fifo_used(&c->free) <= 8 ||Ooh, devil!
I'm deleting that fifo_free() <= 8 check. The idea is that if we're under our reserve we'll skip the discard and just allocate the bucket immediately, but there's no real need for it and it's going to complicate things later.
Quoted 56 lines
Quoted 13 lines
(quoted diff) +12
-0+ !c->discard) && + (r = pop_freed(c)) != -1) + fifo_push(&c->free, r); + + while (c->prio_alloc != prio_buckets(c) && + fifo_pop(&c->free, r)) { + struct bucket *b = c->buckets + r; + c->prio_next[c->prio_alloc++] = r; + + b->mark = GC_MARK_BTREE; + atomic_dec_bug(&b->pin); + }Review
Quoted 10 lines
(quoted diff) +10
-0+ + if (!CACHE_SYNC(&c->set->sb)) { + if (fifo_empty(&c->free_inc)) + invalidate_buckets(c); + return; + } + + /* XXX: tracepoint for when c->need_save_prio > 64 */ + + if (c->need_save_prio <= 64 &&Ooh, devil * devil!
Quoted 17 lines
(quoted diff) +17
-0+ fifo_used(&c->unused) > c->unused.size / 2) + return; + + if (atomic_read(&c->prio_written) > 0 && + (fifo_empty(&c->free_inc) || + c->need_save_prio > 64)) + atomic_set(&c->prio_written, 0); + + if (!can_save_prios(c)) + return; + + invalidate_buckets(c); + + if (!fifo_empty(&c->free_inc) || + c->need_save_prio > 64) + prio_write(c); +}Review
Quoted 10 lines
(quoted diff) +10
-0+ +static long pop_bucket(struct cache *c, uint16_t priority, struct closure *cl) +{ + long r = -1; +again: + free_some_buckets(c); + + if ((priority == btree_prio || + fifo_used(&c->free) > 8) && + fifo_pop(&c->free, r)) {This is unconventional and more difficult to read than
Quoted 5 lines
if ((priority == btree_prio || fifo_used(&c->free) > 8) && fifo_pop(&c->free, r)) {It harms readability by both being unnecessarily different and visually less distinguishible. Why do this?
I prefer extra linebreaks in complicated if statements as the indentation makes the way the parentheses group things clearer, but for this one I'm fine with your version.
This is again the btree reserve. I'm gonna see if I can pull the improved reserve code out of my copying gc branch (I haven't worked on it awhile and some of the code could use more testing).
Quoted 39 lines
Quoted 39 lines
(quoted diff) +38
-0+ struct bucket *b = c->buckets + r; +#ifdef CONFIG_BCACHE_EDEBUG + long i; + for (unsigned j = 0; j < prio_buckets(c); j++) + BUG_ON(c->prio_buckets[j] == (uint64_t) r); + for (unsigned j = 0; j < c->prio_alloc; j++) + BUG_ON(c->prio_next[j] == (uint64_t) r); + + fifo_for_each(i, &c->free) + BUG_ON(i == r); + fifo_for_each(i, &c->free_inc) + BUG_ON(i == r); + fifo_for_each(i, &c->unused) + BUG_ON(i == r); +#endif + BUG_ON(atomic_read(&b->pin) != 1); + + b->prio = priority; + b->mark = priority == btree_prio + ? GC_MARK_BTREE + : c->sb.bucket_size; + return r; + } + + pr_debug("no free buckets, prio_written %i, blocked %i, " + "free %zu, free_inc %zu, unused %zu", + atomic_read(&c->prio_written), + atomic_read(&c->set->prio_blocked), fifo_used(&c->free), + fifo_used(&c->free_inc), fifo_used(&c->unused)); + + if (cl) { + if (closure_blocking(cl)) + mutex_unlock(&c->set->bucket_lock); + + closure_wait_event(&c->set->bucket_wait, cl, + atomic_read(&c->prio_written) > 0 || + can_save_prios(c)); +
Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ if (closure_blocking(cl)) { + mutex_lock(&c->set->bucket_lock); + goto again; + } + }How is this different from using @gfp_flags (for %__GFP_WAIT) or bool @may_block +
-EAGAINreturn?
It's not fundamentally different. The reason I originally added it was for the closure_wait_event() in get_bucket() - for cache lookups (i.e. handling a read, which at least starts out running under generic_make_request()) it's critical that the wait be asynchronous, but for insertions that code doesn't want to wait asynchronously (and drop locks). The closure_blocking() flag means that get_bucket() doesn't have to care, as long as it handles async sync will also work.
But I'm thinking it probably was a mistake and should be ripped out, in favor of some kind of explicit parameter like you suggest.
Quoted 45 lines
Quoted 44 lines
(quoted diff) +43
-0+ return -1; +} + +void unpop_bucket(struct cache_set *c, struct bkey *k) +{ + for (unsigned i = 0; i < KEY_PTRS(k); i++) { + struct bucket *b = PTR_BUCKET(c, k, i); + + b->mark = 0; + bucket_add_unused(PTR_CACHE(c, k, i), b); + } +} + +int __pop_bucket_set(struct cache_set *c, uint16_t prio, + struct bkey *k, int n, struct closure *cl) +{ + lockdep_assert_held(&c->bucket_lock); + BUG_ON(!n || n > c->caches_loaded || n > 8); + + k->header = KEY_HEADER(0, 0); + + /* sort by free space/prio of oldest data in caches */ + + for (int i = 0; i < n; i++) { + struct cache *ca = c->cache_by_alloc[i]; + long b = pop_bucket(ca, prio, cl); + + if (b == -1) + goto err; + + k->ptr[i] = PTR(ca->buckets[b].gen, + bucket_to_sector(c, b), + ca->sb.nr_this_dev); + + SET_KEY_PTRS(k, i + 1); + } + + return 0; +err: + unpop_bucket(c, k); + __bkey_put(c, k); + return -1; +}...
Quoted 16 lines
Review
Quoted 11 lines
(quoted diff) +11
-0+static void bio_invalidate(struct closure *cl) +{ + struct btree_op *op = container_of(cl, struct btree_op, cl); + struct search *s = container_of(op, struct search, op); + struct bio *bio = s->cache_bio; + + pr_debug("invalidating %i sectors from %llu", + bio_sectors(bio), (uint64_t) bio->bi_sector); + + while (bio_sectors(bio)) { + unsigned len = min(bio_sectors(bio), 1U << 14);New line missing. Again, I'm not gonna complain about this more but please follow the usual formatting. There are cases where deviating can be beneficial / tolerated but IMHO you're deviating way too often for no good reasons (well, they're not apparent to me anyway).
I'll argue about some of the newlines you consider extra but I won't argue about any missing newlines - fixed.
Quoted 16 lines
Quoted 15 lines
(quoted diff) +14
-0+ if (keylist_realloc(&s->op.keys, 0, s->op.d->c)) + goto out; + + bio->bi_sector += len; + bio->bi_size -= len << 9; + + keylist_add(&s->op.keys, + &KEY(s->op.d->id, bio->bi_sector, len)); + } + + s->bio_insert_done = true; +out: + continue_at(cl, bcache_journal, bcache_wq); +}...
Quoted 9 lines
Review
Quoted 7 lines
(quoted diff) +7
-0+static struct open_bucket *get_data_bucket(struct bkey *search, + struct search *s) +{ + struct closure cl, *w = NULL; + struct cache_set *c = s->op.d->c; + struct open_bucket *l, *ret, *ret_task; +Unnecessary new line.
That one serves no purpose, fixed.
Quoted 3 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ BKEY_PADDED(key) alloc;Why
BKEY_PADDED()? What does it do? Can we not do this?
Just a bare struct bkey on the stack won't be big enough for any pointers.
I _suppose_ I could do something like:
struct bkey_padded alloc_pad;
struct bkey *alloc = &alloc_pad.key;
I like that marginally better. Any better ideas?
Quoted 12 lines
Quoted 12 lines
(quoted diff) +12
-0+ struct bkey *k = NULL; + + if (s->writeback) { + closure_init_stack(&cl); + w = &cl; + } +again: + ret = ret_task = NULL; + + spin_lock(&c->data_bucket_lock); + list_for_each_entry_reverse(l, &c->data_buckets, list) + if (!bkey_cmp(&l->key, search)) {
Quoted 8 lines
Review
Quoted 4 lines
(quoted diff) +4
-0+ ret = l; + goto found; + } else if (l->last == s->task) + ret_task = l;if () {
} else if () {
}
Wasn't aware of that convention when I wrote most of this code - fixed.
Quoted 5 lines
Also, it's better to always use braces in outer constructs if inner needs one.
And, it seems the bucket allocations follow the task. What's the benefit of doing so? Why isn't there any explanation?
drivers/md/bcache/request.c +22 -0
@@ -322,6 +322,28 @@ static void put_data_bucket(struct open_bucket *b, struct cache_set *c,spin_unlock (&c->data_bucket_lock); } +/** +* get_data_bucket - pick out a bucket to write some data to, possibly +* allocating a new one. +* +* We keep multiple buckets open for writes, and try to segregate different +* write streams for better cache utilization: first we look for a bucket where +* the last write to it was sequential with the current write, and failing that +* we look for a bucket that was last used by the same task. +* +* The ideas is if you've got multiple tasks pulling data into the cache at the +* same time, you'll get better cache utilization if you try to segregate their +* data and preserve locality. +* +* For example, say you've starting Firefox at the same time you're copying a +* bunch of files. Firefox will likely end up being fairly hot and stay in the +* cache awhile, but the data you copied might not be; if you wrote all that +* data to the same buckets it'd get invalidated at the same time. +* +* Both of those tasks will be doing fairly random IO so we can't rely on +* detecting sequential IO to segregate their data, but going off of the task +* should be a sane heuristic. +*/ static struct open_bucket *get_data_bucket (struct bkey *search,struct search *s) {
Quoted 11 lines
Quoted 11 lines
(quoted diff) +10
-0+ + ret = ret_task ?: list_first_entry(&c->data_buckets, + struct open_bucket, list); +found: + if (!ret->sectors_free) { + if (!k) { + spin_unlock(&c->data_bucket_lock); + k = &alloc.key; + + if (pop_bucket_set(c, initial_prio, k, 1, w))
Quoted 8 lines
Review
Quoted 3 lines
(quoted diff) +3
-0+ return NULL; + + goto again;So, this is "try-locked-first, on failure, unlock-alloc-retry" dancing. Constructs like this aren't too atypical but they still warrant short explanation. Please be nice to people trying to read your code.
drivers/md/bcache/request.c +10 -0
@@ -357,6 +357,11 @@ static struct open_bucket *get_data_bucket(struct bkey *search,closure_init_stack (&cl); w = &cl; } +/* +* We might have to allocate a new bucket, which we can't do with a +* spinlock held. So if we have to allocate, we drop the lock, allocate +* and then retry. +*/ again: ret = ret_task =NULL ;
@@ -393,6 +398,11 @@ found:EBUG_ON (ptr_stale (c, &ret->key, i)); } +/* +* If we had to allocate and then retry, we might discover that we raced +* and no longer need to allocate. Therefore, if we allocated a bucket +* but didn't use it, drop the refcount pop_bucket_set() took: +*/ if (k)__bkey_put (c, k);
Quoted 17 lines
Quoted 17 lines
(quoted diff) +17
-0+ } + + bkey_copy(&ret->key, k); + k = NULL; + + ret->sectors_free = c->sb.bucket_size; + } else + for (unsigned i = 0; i < KEY_PTRS(&ret->key); i++) + EBUG_ON(ptr_stale(c, &ret->key, i)); + + if (k) + __bkey_put(c, k); + + if (w) + for (unsigned i = 0; i < KEY_PTRS(&ret->key); i++) + PTR_BUCKET(c, &ret->key, i)->mark = GC_MARK_DIRTY; +
Quoted 8 lines
Review
Quoted 4 lines
(quoted diff) +4
-0+ ret->last = s->task; + bkey_copy_key(&ret->key, search); + + list_move_tail(&ret->list, &c->data_buckets);This too. It's a rather common pattern and people would be able to just read through without thinking if it had one line comment saying something like /* @ret is hot now, put it at the head of queue */.
Done.
Quoted 11 lines
Quoted 3 lines
(quoted diff) +2
-0+ return ret; +}...
Quoted 1 line
+static void
bio_insert(struct closure *cl)Too generic name. This and a lot of others. Note that there are issues other than direct compile time symbol collision - it makes the code difficult to grep for and stack traces difficult to decipher. I'm not gonna complain more about too generic names but please review the code and fix other instances too.
I'm pretty terrible at naming, so if at some point you could help me come up with names that are descriptive _to you_, it'd be a big help.
Quoted 5 lines
Another thing is that why this function and its friends take @cl when they always expect @cl contained inside
search->op. Why not take @search instead? Using @cl is more dangerous and less readable. Why do it?
bio_insert() used to be the closure function, but after breaking out bio_insert_loop() that's no longer true - so yeah, it should've been changed.
I've also been trying to get rid of all of bio_insert()'s dependencies on struct search. Not quite done.
Quoted 16 lines
Quoted 15 lines
(quoted diff) +14
-0+{ + struct btree_op *op = container_of(cl, struct btree_op, cl); + struct search *s = container_of(op, struct search, op); + struct bio *bio = s->cache_bio; + + if (!s->skip) { + bio->bi_end_io = bio_insert_endio; + bio->bi_private = cl; + bio_get(bio); + } + + keylist_init(&op->keys); + bio_insert_loop(cl); +}...
Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+static void __do_bio_hook(struct search *s) +{ + struct bio *bio = &s->bio.bio; + memcpy(bio, s->orig_bio, sizeof(struct bio)); + +#ifdef CONFIG_DISKMONContamination.
Ugh, yep.
Quoted 15 lines
Quoted 15 lines
(quoted diff) +14
-0+ bio->bi_flowid = NULL; +#endif + bio->bi_end_io = request_endio; + bio->bi_private = &s->cl; + bio->bi_destructor = NULL; + atomic_set(&bio->bi_cnt, 3); +} + +static struct search *do_bio_hook(struct bio *bio, struct bcache_device *d) +{ + struct bio_vec *bv; + struct search *s = mempool_alloc(d->c->search, GFP_NOIO); + memset(s, 0, offsetof(struct search, op.keys)); +
Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ __closure_init(&s->cl, NULL); + __closure_init(&s->op.cl, &s->cl); + + s->op.d = d; + s->op.lock = -1; + s->task = get_current();Please use current instead of
get_current().
Done.
Quoted 19 lines
Quoted 18 lines
(quoted diff) +17
-0+ s->orig_bio = bio; + s->write = bio->bi_rw & REQ_WRITE; + s->op.flush_journal = bio->bi_rw & REQ_FLUSH; + s->recoverable = 1; + __do_bio_hook(s); + + if (bio->bi_size != bio_segments(bio) * PAGE_SIZE) { + bv = mempool_alloc(d->unaligned_bvec, GFP_NOIO); + memcpy(bv, bio_iovec(bio), + sizeof(struct bio_vec) * bio_segments(bio)); + + s->bio.bio.bi_io_vec = bv; + s->unaligned_bvec = 1; + } + + return s; +}...
Quoted 12 lines
Review
Quoted 7 lines
(quoted diff) +7
-0+static bool should_writeback(struct cached_dev *d, struct bio *bio) +{ + return !atomic_read(&d->disk.detaching) && + cache_mode(d, bio) == CACHE_MODE_WRITEBACK && + (d->disk.c->gc_stats.in_use < (bio->bi_rw & REQ_SYNC) + ? CUTOFF_WRITEBACK_SYNC + : CUTOFF_WRITEBACK);The formatting of the CUTOFF_WRITEBACK* test is atrocious. It almost looks like it's trying to mislead the reader. For $DIETY's sake, use a local variable for the threshold value or split the condition into a few "if () return;" clauses.
I clearly have different tastes than you here (it parses easily to me), but the local variable for the threshold is a great idea - doing that.
Quoted 11 lines
Quoted 11 lines
(quoted diff) +10
-0+} + +static void request_write_resubmit(struct closure *cl) +{ + struct btree_op *op = container_of(cl, struct btree_op, cl); + struct search *s = container_of(op, struct search, op); + struct bio *bio = &s->bio.bio; + + closure_bio_submit(bio, &s->cl, op->d->c->bio_split); +
Quoted 8 lines
Review
Quoted 3 lines
(quoted diff) +3
-0+ bio_insert(&s->op.cl); + continue_at(&s->cl, cached_dev_write_complete, NULL); +}I'm kinda turned off by closure here too. It doesn't really seem to simplify the actual painful points of async programming. The user still has to compose separate paths for sync and async paths. I keep thinking domain-specific sequencer would be much better.
Not really following your complaint. The resubmit path is required when allocating a bio split fails because we're running under generic_make_request() - it's ugly, but I'm not sure how you're going to make it any cleaner.
I need to rebase this code onto my block cleanup patch series, the patch to make generic_make_request() handle arbitrary sized bios makes this go away.
Quoted 18 lines
Quoted 18 lines
(quoted diff) +18
-0+ +static void request_write(struct cached_dev *d, struct search *s) +{ + struct closure *cl = &s->cl; + struct bio *bio = &s->bio.bio; + + check_should_skip(d, s); + down_read_non_owner(&d->writeback_lock); + + if (bcache_in_writeback(d, bio->bi_sector, bio_sectors(bio))) { + s->skip = false; + s->writeback = true; + } + + if (s->skip) { +skip: s->cache_bio = s->orig_bio; + bio_get(s->cache_bio); + trace_bcache_write_skip(s->orig_bio);
Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ + if (bio->bi_rw & (1 << BIO_RW_DISCARD)) {This isn't bcache's problem but it probably would be better to make block layer handle this in generic manner.
blk_queue_discard()already indicates whether a queue supports discard or not.generic_make_request()can simply treat discards as no-op if unsupported.
Good idea (especially since a no-op is a valid implementation of trim).
Quoted 34 lines
Quoted 34 lines
(quoted diff) +33
-0+ closure_get(cl); + + if (blk_queue_discard(bdev_get_queue(d->bdev))) + generic_make_request(bio); + else + bio_endio(bio, 0); + + goto out; + } else + goto submit; + } + + if (should_writeback(d, s->orig_bio)) + s->writeback = true; + + if (!s->writeback) { + s->cache_bio = bbio_kmalloc(GFP_NOIO, bio->bi_max_vecs); + if (!s->cache_bio) { + s->skip = true; + goto skip; + } + + __bio_clone(s->cache_bio, bio); + trace_bcache_writethrough(s->orig_bio); +submit: + if (closure_bio_submit(bio, cl, s->op.d->c->bio_split)) + continue_at(&s->op.cl, + request_write_resubmit, + bcache_wq); + } else { + s->cache_bio = bio; + trace_bcache_writeback(s->orig_bio); + bcache_writeback_add(d, bio_sectors(bio));
Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ } +out: + bio_insert(&s->op.cl); + continue_at(cl, cached_dev_write_complete, NULL); +}I hate how gotos are being abused in this function. This ain't 80s and we don't build complex control flow using gotos.
It is a nasty piece of code (but 10x better than it used to be).
Quoted 7 lines
If common logic
is used in different parts of the control flow in a way that the usual control constructs don't really yield pretty code, factor out the common part into a function. Are you not doing that because you don't wanna putcontinue_at()inside another function which may obscure the closure control flow? If that's the case, I think it just shows how retarded wrapping return in macros is. :(
That is a major downside (that's one reason I liked your idea of returning an opaque cookie).
But making generic_make_request() handle arbitrary size bios is going to make a lot of the pain in this function go away (there'll only be a single continue_at() in request_write() after that) - with that and your discard idea the skip path boils down to almost nothing.
I'm just gonna rebase this code onto my block cleanup branch.
Quoted 11 lines
Review
Quoted 9 lines
(quoted diff) +8
-0+static void cached_dev_make_request(struct request_queue *q, struct bio *bio) +{ + struct search *s; + struct bcache_device *d = bio->bi_bdev->bd_disk->private_data; + struct cached_dev *dc = container_of(d, struct cached_dev, disk); + + bio->bi_bdev = dc->bdev; + bio->bi_sector += 16;^^^^
Quoted 6 lines
Please don't do this. Use of magic numbers seems pretty common throughout the code base. This is error-prone and cryptic. Use enums or macros (this is what macros are for, actually) to give them meaningful names. If applicable, explain why the specific number is chosen on constant definition. I assume this is the size of superblock but I can't ask cscope about it or grep for it.
Just added BDEV_DATA_START.
Quoted 15 lines
Review
Quoted 9 lines
(quoted diff) +8
-0+ + if (cached_dev_get(dc)) { + s = do_bio_hook(bio, d); + trace_bcache_request_start(&s->op, bio); + + (!bio_has_data(bio) ? request_nodata : + bio->bi_rw & REQ_WRITE ? request_write : + request_read)(dc, s);Don't do this. Among other things, it should be possible to search / grep for "FUNCTION_NAME(" and find all the direct invocations. Just use if/else. You're not gaining anything from doing things like the above while basically aggravating other developers trying to understand your code.
I don't buy the argument about finding direct invocations (tons of stuff is indirectly invoked today and i can't remember ever caring about direct invocations vs. calls via a pointer, and worse for grepping is all the set wrappers for things like merge_bvec_fn (it's inconsistent and what is this, C++?)
But I give up, changed it :P
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
Hey, Kent.
Quoted 18 lines · Kent Overstreet
Quoted 9 lines
Quoted 9 lines
(quoted diff) +9
-0+int alloc_discards(struct cache *ca) +{ + for (int i = 0; i < 8; i++) { + struct discard *d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->c = ca; + INIT_WORK(&d->work, discard_work);Quoted 8 lines
drivers/md/bcache/bcache.h static inline bool cached_dev_get(struct cached_dev *d) · line 840
Quoted 5 lines
drivers/md/bcache/bcache.h +5
-0+list_add (&d->list, &ca->discards); + } + +return 0 ; +}Why maintain separate pool of discards? It it to throttle discard commands? And another evil number!
Quoted 3 lines
Yeah, it's just a mempool that also throttles. I don't particularly care for doing it that way, any suggestions? (Just fixed the magic number, at least).
I think the list is fine. I'm curious how the 8 was chosen tho and what are the implications of higher or lower number. If it's just a number you just liked and didn't cause any problem, that's fine too as long as it's explained as such.
Quoted 15 lines
Quoted 15 lines
Review
Quoted 9 lines
(quoted diff) +9
-0+static long pop_bucket(struct cache *c, uint16_t priority, struct closure *cl) +{ + long r = -1; +again: + free_some_buckets(c); + + if ((priority == btree_prio || + fifo_used(&c->free) > 8) && + fifo_pop(&c->free, r)) {This is unconventional and more difficult to read than
if ((priority == btree_prio || fifo_used(&c->free) > 8) && fifo_pop(&c->free, r)) {
Quoted 6 lines
Quoted 2 lines
It harms readability by both being unnecessarily different and visually less distinguishible. Why do this?
I prefer extra linebreaks in complicated if statements as the indentation makes the way the parentheses group things clearer, but for this one I'm fine with your version.
Hmmm.... more on the subject of style conformance at the end.
Quoted 19 lines
Quoted 9 lines
Review
Quoted 7 lines
(quoted diff) +7
-0+static struct open_bucket *get_data_bucket(struct bkey *search, + struct search *s) +{ + struct closure cl, *w = NULL; + struct cache_set *c = s->op.d->c; + struct open_bucket *l, *ret, *ret_task; +Unnecessary new line.
That one serves no purpose, fixed.
Quoted 3 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ BKEY_PADDED(key) alloc;Why
BKEY_PADDED()? What does it do? Can we not do this?Just a bare struct bkey on the stack won't be big enough for any pointers.
Quoted 6 lines
I _suppose_ I could do something like:
struct
bkey_paddedalloc_pad;
struct bkey *alloc = &alloc_pad.key;I like that marginally better. Any better ideas?
Looks sane enough to me. Maybe just use padded_key.key directly?
Quoted 3 lines
+/**
+ *get_data_bucket- pick out a bucket to write some data to, possibly
+ * allocating a new one.
If you're gonna do /** comments, please also add arguments section. DocBook stuff may get unhappy.
Quoted 21 lines
+ * We keep multiple buckets open for writes, and try to segregate different
+ * write streams for better cache utilization: first we look for a bucket where
+ * the last write to it was sequential with the current write, and failing that
+ * we look for a bucket that was last used by the same task.
+ *
+ * The ideas is if you've got multiple tasks pulling data into the cache at the
+ * same time, you'll get better cache utilization if you try to segregate their
+ * data and preserve locality.
+ *
+ * For example, say you've starting Firefox at the same time you're copying a
+ * bunch of files. Firefox will likely end up being fairly hot and stay in the
+ * cache awhile, but the data you copied might not be; if you wrote all that
+ * data to the same buckets it'd get invalidated at the same time.
+ *
+ * Both of those tasks will be doing fairly random IO so we can't rely on
+ * detecting sequential IO to segregate their data, but going off of the task
+ * should be a sane heuristic.
+ */
static structopen_bucket*get_data_bucket(struct bkey *search,
struct search *s)
{
...
Quoted 10 lines
Quoted 7 lines
Quoted 1 line
+static void
bio_insert(struct closure *cl)Too generic name. This and a lot of others. Note that there are issues other than direct compile time symbol collision - it makes the code difficult to grep for and stack traces difficult to decipher. I'm not gonna complain more about too generic names but please review the code and fix other instances too.
I'm pretty terrible at naming, so if at some point you could help me come up with names that are descriptive
_toyou_, it'd be a big help.
The problem I'm having with names are two-fold.
* Name not descriptive enough. It doesn't have to be super verbose but something which people can look at and associate with its type and/or role would be great. e.g. instead of k, use key, d -> cdev, and so on.
* Generic names. bio_insert() belongs to block layer. Block layer may use it in the future and when one sees bio_insert(), it's natural for that person to assume that it's a block layer thing doing something with bio. As for better name, I'm not sure. Maybe just prefix it with bc_?
Note that this isn't a 100% strict rule. You can and people do get away with some generic names and many of them don't cause any problem over the lifetime of the code but it's just way too prevalent in this code. You can bend stuff only so much.
Quoted 15 lines
Quoted 6 lines
Quoted 6 lines
(quoted diff) +6
-0+ + if (cached_dev_get(dc)) { + s = do_bio_hook(bio, d); + trace_bcache_request_start(&s->op, bio); + + (!bio_has_data(bio) ? request_nodata :Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ bio->bi_rw & REQ_WRITE ? request_write : + request_read)(dc, s);Don't do this. Among other things, it should be possible to search / grep for "FUNCTION_NAME(" and find all the direct invocations. Just use if/else. You're not gaining anything from doing things like the above while basically aggravating other developers trying to understand your code.
Quoted 7 lines
I don't buy the argument about finding direct invocations (tons of stuff is indirectly invoked today and i can't remember ever caring about direct invocations vs. calls via a pointer, and worse for grepping is all the set wrappers for things like
merge_bvec_fn(it's inconsistent and what is this, C++?)But I give up, changed it :P
So, I don't know. I guess I might be too conservative but the followings are the points I want to make.
* These small style issues don't really matter one way or the other. They're mostly the matter or taste or preference after all. Most technical merits or shortcomings one perceives in this area tend to be highly subjective and marginal, and the overhead of being unusual tends to be way higher. So, my or your taste doesn't weight that much unless there actually are tangible technical merits which people can agree upon. I don't see that here (or in many other bcache oddities).
* These aren't hard and fast rules. There isn't one true fixed standard that everyone conforms to. There still are different styles and people adopt the ones they think are pretty. That's how it evolves, but it too is a problem of degree and you can bend only so far before people start complaining.
For example, here's a fictionalized process that I got irritated and felt generally hostile against the code base.
1. Reading code. Full of macros.
2. Reading macros. Single char arguments. Starting to get irritated.
3. Encountered closures. Still unsure what they mean. This one looks like a simple refcnt. Wonders what the hell is wrong with the usual refcnts. Maybe somehow this gets used differently somewhere else? Of course, no explanation.
4. While trying to follow the control flow, encounters a function with full of cryptic numbers with "different" formatting. No high level explanation or anything. Getting irritated.
5. Now in the lower half of the function, forgot how @d was being used. Pressed ctrl-s and then d to highlight the variable in the function. Ooh, pretty.
6. Saw the next function which seems static. Tries to look for the immediate caller by searching for FUNC_NAME( - hmmm... no match. Maybe it's called indirectly somehow. Looks for FUNC_NAME assignments / used as arguments. Find the function symbol buried in ?:.
7. kajga;wioegja;sdklbja; bkjhaw83ua;kdgjasl;dgjk aw;eig93ua;kgNACK NACK NACK NACK NACK
So, at least to me, you seem to be pushing unique style / concepts too far and I can't identify the technical merits of doing so. Code is to be read and worked together on by peer developers.
Thanks.
Signature · 1 line
tejun
Re: [Bcache v13 14/16] bcache: Request, io and allocation code
Quoted 21 lines · Tejun Heo
Hey, Kent.
Quoted 18 lines · Kent Overstreet
Quoted 17 lines
Quoted 4 lines
(quoted diff) +4
-0+int alloc_discards(struct cache *ca) +{ + for (int i = 0; i < 8; i++) { + struct discard *d = kzalloc(sizeof(*d), GFP_KERNEL);Review
Quoted 10 lines
(quoted diff) +10
-0+ if (!d) + return -ENOMEM; + + d->c = ca; + INIT_WORK(&d->work, discard_work); + list_add(&d->list, &ca->discards); + } + + return 0; +}Why maintain separate pool of discards? It it to throttle discard commands? And another evil number!
Quoted 8 lines
Quoted 3 lines
Yeah, it's just a mempool that also throttles. I don't particularly care for doing it that way, any suggestions? (Just fixed the magic number, at least).
I think the list is fine. I'm curious how the 8 was chosen tho and what are the implications of higher or lower number. If it's just a number you just liked and didn't cause any problem, that's fine too as long as it's explained as such.
Just that, it was pretty arbitrary. Might actually be worth testing with it set to 1-2 since it's an unqueued command in SATA, though.
Quoted 1 line
Looks sane enough to me. Maybe just use padded_key.key directly?
Well, that is the current situation with the stack allocated ones. I'll do something.
BKEY_PADDED() was originally for keys in other structs that needed padded, where the transparent union thing actually works. Are you against the macro in general or just for stack allocated keys?
Quoted 2 lines
If you're gonna do /** comments, please also add arguments section. DocBook stuff may get unhappy.
Whoops, will do.
Quoted 6 lines
The problem I'm having with names are two-fold.
* Name not descriptive enough. It doesn't have to be super verbose but something which people can look at and associate with its type and/or role would be great. e.g. instead of k, use key, d -> cdev, and so on.
Some of those I'm not sure would be improved by expanding them - k -> key mainly, key is still fairly generic but also k for a struct bkey is used all over the place and fairly consistently (if I find k used for anything else, I'll fix it).
I kind of dislike any-single-letter + dev, mainly because dev is used all over the place and can mean damn near anything. (There's struct device which is commonly referred to as dev, there's struct bdev, and I'm pretty sure I could come up with more in a minute or two).
I would like more descriptive/unique variable names for cache, cache_set, bcache_device and cached_dev, I'm just not coming up with anything that's reasonably terse and seems to me like an improvement.
(I'm not really against cdev, it's just that bcache_device should then be bdev by analogy and that's taken... argh).
Anyways, I'm not really attached to the current variable names like I am some of the style issues, I just can't come up with anything I like.
Quoted 5 lines
* Generic names.
bio_insert()belongs to block layer. Block layer may use it in the future and when one seesbio_insert(), it's natural for that person to assume that it's a block layer thing doing something with bio. As for better name, I'm not sure. Maybe just prefix it withbc_?
Agreed on bio_insert() (there's a fair number of functions that I've been meaning to rename for similarish reasons - get_bucket() and pop_bucket() don't have anything to do with each other, for example).
I think the function names are easier to improve than variabe names, though. The main obstacle (for me) to renaming stuff is that when I do I want to come up with names that are patterned on the relationships in the code, and that'll take some work.
Quoted 4 lines
Note that this isn't a 100% strict rule. You can and people do get away with some generic names and many of them don't cause any problem over the lifetime of the code but it's just way too prevalent in this code. You can bend stuff only so much.
I'm really not arguing with you about the naming. It's just been neglected for way too long.
Quoted 26 lines
So, I don't know. I guess I might be too conservative but the followings are the points I want to make.
* These small style issues don't really matter one way or the other. They're mostly the matter or taste or preference after all. Most technical merits or shortcomings one perceives in this area tend to be highly subjective and marginal, and the overhead of being unusual tends to be way higher. So, my or your taste doesn't weight that much unless there actually are tangible technical merits which people can agree upon. I don't see that here (or in many other bcache oddities).
* These aren't hard and fast rules. There isn't one true fixed standard that everyone conforms to. There still are different styles and people adopt the ones they think are pretty. That's how it evolves, but it too is a problem of degree and you can bend only so far before people start complaining.
For example, here's a fictionalized process that I got irritated and felt generally hostile against the code base.
1. Reading code. Full of macros.
2. Reading macros. Single char arguments. Starting to get irritated.
Quoted 8 lines
3. Encountered closures. Still unsure what they mean. This one looks like a simple refcnt. Wonders what the hell is wrong with the usual refcnts. Maybe somehow this gets used differently somewhere else? Of course, no explanation.
4. While trying to follow the control flow, encounters a function with full of cryptic numbers with "different" formatting. No high level explanation or anything. Getting irritated.
Which function was that?
Quoted 10 lines
5. Now in the lower half of the function, forgot how @d was being used. Pressed ctrl-s and then d to highlight the variable in the function. Ooh, pretty.
6. Saw the next function which seems static. Tries to look for the immediate caller by searching for FUNC_NAME( - hmmm... no match. Maybe it's called indirectly somehow. Looks for
FUNC_NAMEassignments / used as arguments. Find the function symbol buried in ?:.
Quoted 6 lines
7. kajga;wioegja;sdklbja; bkjhaw83ua;kdgjasl;dgjk aw;eig93ua;kgNACK NACK NACK NACK NACK
So, at least to me, you seem to be pushing unique style / concepts too far and I can't identify the technical merits of doing so. Code is to be read and worked together on by peer developers.
Well, there are a lot of style issues I'm happy to give on.
I think also I haven't been explicit about it but I do try to differentiate between style issues that are of no consequence (whoever feels more strongly about those can decide, for all I care) and things that make the codebase harder to read and understand.
Hell, I've never pretended to be any good at writing code that's understandable by others. There's things I _am_ good at - writing fast code, writing complex asynchronous code that _reliably works_ - but understandable code? Hell no.
(And I know I said I'd be writing design level documentation... I haven't forgotten, it's just been busy).
Anyways, there's huge room for improvement in bcache in that respect and while it's not going to happen overnight I am trying to steadily chip away at it, and while I certainly understand your frustration in reading the code it's not wasted.
That said, it does bug me when things are rejected out of hand merely for being different and foreign.
Closures being the main example - I'd be more receptive to criticism if I'd seen a half decent solution before, or if you or anyone else was actually trying to come up with something better.
I'd really like to see what you come up with if you implement that bio sequencer you've been talking about, because either you'll come up with something better or you'll have a better understanding of the problems I was trying to solve.
But, the thing about pushing a single purpose solution is - you might as well tell the person who first invented the red-black tree to come back with a simpler, single purpose solution to whatever he was working on.
If you want to tell me my sorted data structure/asynchronous programming model is crap and should be done _better_, that's completely different. I know closures could be improved, anyways.
But the previous tools for writing this kind of asynchronous code _sucked_, and I can trivially do things that were near impossible before, and my code is considerably more flexible and easier to work with than it was before. So if you want to convince me there's a better way to do what I'm doing - there is a genuinely high bar there.
Tejun Heo
maintainerRe: [Bcache v13 14/16] bcache: Request, io and allocation code
Hello, Kent.
Quoted 3 lines · Kent Overstreet
BKEY_PADDED()was originally for keys in other structs that needed padded, where the transparent union thing actually works. Are you against the macro in general or just for stack allocated keys?
Macro in general. If it needs an extra union / struct member deref, so be it. No need to be smart for things like this.
Quoted 9 lines
I would like more descriptive/unique variable names for cache,
cache_set,bcache_deviceandcached_dev, I'm just not coming up with anything that's reasonably terse and seems to me like an improvement.(I'm not really against cdev, it's just that
bcache_deviceshould then be bdev by analogy and that's taken... argh).Anyways, I'm not really attached to the current variable names like I am some of the style issues, I just can't come up with anything I like.
Heh, yeah, naming isn't easy. In most cases, it just has to be something consistent which has some association tho.
Quoted 6 lines
Quoted 5 lines
* Generic names.
bio_insert()belongs to block layer. Block layer may use it in the future and when one seesbio_insert(), it's natural for that person to assume that it's a block layer thing doing something with bio. As for better name, I'm not sure. Maybe just prefix it withbc_?
Quoted 8 lines
Agreed on
bio_insert()(there's a fair number of functions that I've been meaning to rename for similarish reasons -get_bucket()andpop_bucket()don't have anything to do with each other, for example).I think the function names are easier to improve than variabe names, though. The main obstacle (for me) to renaming stuff is that when I do I want to come up with names that are patterned on the relationships in the code, and that'll take some work.
It doesn't have to be perfect. Naming schemes like anything else tend to lose consistency over time anyway, so something good enough is usually good enough.
Quoted 19 lines
Quoted 19 lines
* These aren't hard and fast rules. There isn't one true fixed standard that everyone conforms to. There still are different styles and people adopt the ones they think are pretty. That's how it evolves, but it too is a problem of degree and you can bend only so far before people start complaining.
For example, here's a fictionalized process that I got irritated and felt generally hostile against the code base.
1. Reading code. Full of macros.
2. Reading macros. Single char arguments. Starting to get irritated.
3. Encountered closures. Still unsure what they mean. This one looks like a simple refcnt. Wonders what the hell is wrong with the usual refcnts. Maybe somehow this gets used differently somewhere else? Of course, no explanation.
Quoted 5 lines
Quoted 3 lines
4. While trying to follow the control flow, encounters a function with full of cryptic numbers with "different" formatting. No high level explanation or anything. Getting irritated.
Which function was that?
Heh, this was dramatized for maximum effect. I was thinking about the condition check functions / macros with hard-coded constants.
...
Quoted 9 lines
I think also I haven't been explicit about it but I do try to differentiate between style issues that are of no consequence (whoever feels more strongly about those can decide, for all I care) and things that make the codebase harder to read and understand.
Hell, I've never pretended to be any good at writing code that's understandable by others. There's things I _am_ good at - writing fast code, writing complex asynchronous code that
_reliablyworks_- but understandable code? Hell no.
I don't think your style is inferior or anything. It's just different from the one used in the kernel. I don't think it's anything difficult either. It's just a matter of habit.
...
Quoted 11 lines
That said, it does bug me when things are rejected out of hand merely for being different and foreign.
Closures being the main example - I'd be more receptive to criticism if I'd seen a half decent solution before, or if you or anyone else was actually trying to come up with something better.
I'd really like to see what you come up with if you implement that bio sequencer you've been talking about, because either you'll come up with something better or you'll have a better understanding of the problems I was trying to solve.
I still don't understand what's being made better by closure. At least the ones I've seen didn't seem to be justified, so if you have some examples which highlight the benefits of closure, please go ahead and explain them.
I'll write more about it later but here are some of my current thoughts.
* I personally think async (using something other than stack for execution state) programming inherently sucks, no matter how it's done. The programming language and even the processor we use assume that stack is used to track execution context after all. My opinion about async programming can essentially be summarized as "avoid it as much as possible".
* This itself may be too abstract but excess is often worse than lack, especially for abstraction and design. Problems caused by excess is much more difficult to rectify as the problems are intentional and systematic. That's the part I dislike the most about kobject and friends.
* I don't (yet anyway) believe that you have something drastically better in closure. You just have something very different and maybe a bit more convenient. Both async and refcnting are well known problems with established patterns.
In a sense, I think it's similar to the style argument although at a higher level. I don't think the amount of benefit justifies the amount of deviation.
So, at least to me, the bar for highly abstract refcnty async thingy is pretty high. It's a ugly problem and I'd prefer to leave it ugly and painful unless it can be drastically better.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 14/16] bcache: Request, io and allocation code
Hello, again, Kent.
Quoted 7 lines · Tejun Heo
I still don't understand what's being made better by closure. At least the ones I've seen didn't seem to be justified, so if you have some examples which highlight the benefits of closure, please go ahead and explain them.
I'll write more about it later but here are some of my current thoughts.
I'm gonna try my (personal, of course) reasoning on closure. It's gonna be long-winded and fairly abstract. The issue at hand is an abstract one and I don't know how to communicate it better. :(
My objection to closure is largely two-fold.
1. Excessive abstraction / mosaicing of functions.
Abstraction itself isn't a goal onto itself - it is a (pretty important) tool that we rely on to manage complexities. By the very nature, it's heavily dependent on what concepts one develops on the subjct and those concepts may grow substantially removed from the concrete requirements and functionality, and as they get further removed from the concrete, different people tend to arrive at conclusions vastly far apart. One man's paradise can easily be another's hell. Concepts clear to one mind can be completely ambiguous or disturbing to others.
It's a problem with no fast solution but IMHO trying to minimize the distance between the needed functionality (requirements) and the abstraction. You brought up RB tree earlier in the discussion and that's a great example. It provides a clear functionality of balanced search tree without much extra abstracting. Most software developers can agree on the need of searching an element based on a key value and RB tree implements that specific functionality. If one understands the need, the abstraction is fundamental and immediate and thus self-evident.
One effective way of reaching at a bad abstraction, I think, is overly generalizing limited local usage patterns. When one sees certain pattern repeated, it's tempting to overly generalize and make it some fundamental abstraction when the only things which actually exist are local usage patterns.
Such over-generalization often ends up bundling things which aren't fundamentally associated. After all, there aren't clear fundamental requirements such an abstraction is serving - it is a mosaic of incidental set of features which happen to be in use for the specific part of code. Generalized ambiguity can be very generous - combining A and B resulted in this awesome generic super construct, adding C on top would woule make it super-duper, right?
This harms both the local usage and the code base in general. Local code develops unnecessary (generic things tend to have lives of their own) distance between the actual requirements and the mechanism implementing them making it more difficult to understand, and the rest of code base gets something which seems generic but is ambiguos, difficult to understand and easy to misuse.
I personally think kobject is a good example of this. Its name seems to suggest it's something like the base type for kernel objects. Pretty ambiguous and ambitious. If you look at it, it's a combination of refcnting, elements for sysfs representation, weird looking type system mostly born out from sysfs usage and device driver model, userland notification mechanism and some more. It's ass-ugly but seems to fit device driver model okish. Unfortunately, as soon as it gets dragged out of device driver model, it isn't clear what different parts of the dang thing are supposed to do.
For some device-driver-model specific reason, a directory in sysfs hierarchy maps to a kobject (there are attr dirs which are exceptions tho). As soon as you start using sysfs outside device driver model, it becomes weird. An entity which wants to expose something via sysfs somehow has to use kobject which brings in its object lifetime management which may not fit the existing usage model.
Let's say there are entities A, B and C, where A and B follow mostly the same life-cycle rules and C is dependent on B, and A and C have something to expose via sysfs. So, A and C embed kobjects inside them. What about kobj life-cycle management then? Let's say A and C use kobj life-cycle management. Hmmm... C is dependent on B. How does that play out? What if some in-kernel entity represents two separate userland-visible entities? What happens then? Do people have to suppress / circumvent kobj lifecycle mechanism? Wouldn't that make the code unnecessarily difficult to understand - extra cruft tends to be very effective at confusing people?
The stupid thing is that sysfs doesn't need any of the kobject silliness. sysfs should have provided interface fundamental to its features which device driver model should have used to implement whatever specific abstraction necessary and keep that inside. The association between certain type of life cycle management and sysfs usage wasn't anything fundamental. It was a device driver model local thing and the mechanism too should have stayed local inside the device driver model.
At least for me, closure triggers all the alarms for bad generic abstraction. The patch description you wrote for closure starts with "asynchronous refcounty thingies" and it's quite apt. It's a combination of object life-cycle management, async execution, object hierarchy, event mechanism and even locking. It isn't anything fundamental. It's a kitchen sink with spoon, knife, chainsaw and toilet welded together.
2. Making async programming better, or glaze-coating turd.
As I wrote before, I personally think that async programming - using something other than stack for execution state - is inherently inferior. The programming languages we program in and the processors the resulting programs run on depend on stack for execution context after all. To my mind, async programming is inherently a turd.
That isn't to say we can do away with async programming completely. At times, turds are unavoidable and even essential (and the line between turds and !turds isn't clear to begin with). Process context can be expensive in terms of switching and/or memory overhead. It would be silly to keep ten thousand threads lying around listening to ten thousand idle clients which may or may not make another request. It could also be silly to switch to a process context just to relay completion notification to another process context (although block layer kinda does that for a different reason).
In block layer, the need for async programming rises mostly from the disconnection between issue and completion contexts. The issuing context may not wait for the completion and we end up with IOs without matching process contexts. The problem isn't too bad tho. We require and thus have process context on the issue path and the completion paths tend to be not too complex.
When we have to deal with async programming, I think the best approach is to identify why they're necessary, restrict their scopes to minimum and clearly contain them. For example, bounce to wq as soon as things start to get complex, don't try to be smart with process contexts while something complex is going on, and relinquish context judicially when not doing so results in noticeable performance / resource hit and hopefully where doing so doesn't complicate things too much (e.g. it's a clear retry boundary).
If you follow such patterns, there isn't too much need for fancy async mechanism to blur the pain. Properly identified and consolidated, sync / async boundaries shouldn't be too painful to manage and the kernel already provides ample mechanisms to support such patterns.
The problem with closure is that it's a mechanism to glaze-coat turd. By glaze-coating, it encourages littering turds around instead of consolidating and isolating them, so in that sense, I prefer async programming to remain painful and explicit. People tend to do bat-shit crazy stuff otherwise.
To conclude, I *hope* that bcache followed more usual async programming conventions, much like I hope it followed more usual coding style. These aren't cut-and-dry issues and there probably are aspects that I haven't considered or lost balance on. Please feel free to point them out.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerRe: [Bcache v13 14/16] bcache: Request, io and allocation code
In reply to Kent OverstreetHey, Kent.
Quoted 18 lines · Kent Overstreet
Quoted 9 lines
Quoted 9 lines
(quoted diff) +9
-0+int alloc_discards(struct cache *ca) +{ + for (int i = 0; i < 8; i++) { + struct discard *d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->c = ca; + INIT_WORK(&d->work, discard_work);Quoted 8 lines
drivers/md/bcache/bcache.h static inline bool cached_dev_get(struct cached_dev *d) · line 840
Quoted 5 lines
drivers/md/bcache/bcache.h +5
-0+list_add (&d->list, &ca->discards); + } + +return 0 ; +}Why maintain separate pool of discards? It it to throttle discard commands? And another evil number!
Quoted 3 lines
Yeah, it's just a mempool that also throttles. I don't particularly care for doing it that way, any suggestions? (Just fixed the magic number, at least).
I think the list is fine. I'm curious how the 8 was chosen tho and what are the implications of higher or lower number. If it's just a number you just liked and didn't cause any problem, that's fine too as long as it's explained as such.
Quoted 15 lines
Quoted 15 lines
Review
Quoted 9 lines
(quoted diff) +9
-0+static long pop_bucket(struct cache *c, uint16_t priority, struct closure *cl) +{ + long r = -1; +again: + free_some_buckets(c); + + if ((priority == btree_prio || + fifo_used(&c->free) > 8) && + fifo_pop(&c->free, r)) {This is unconventional and more difficult to read than
if ((priority == btree_prio || fifo_used(&c->free) > 8) && fifo_pop(&c->free, r)) {
Quoted 6 lines
Quoted 2 lines
It harms readability by both being unnecessarily different and visually less distinguishible. Why do this?
I prefer extra linebreaks in complicated if statements as the indentation makes the way the parentheses group things clearer, but for this one I'm fine with your version.
Hmmm.... more on the subject of style conformance at the end.
Quoted 19 lines
Quoted 9 lines
Review
Quoted 7 lines
(quoted diff) +7
-0+static struct open_bucket *get_data_bucket(struct bkey *search, + struct search *s) +{ + struct closure cl, *w = NULL; + struct cache_set *c = s->op.d->c; + struct open_bucket *l, *ret, *ret_task; +Unnecessary new line.
That one serves no purpose, fixed.
Quoted 3 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ BKEY_PADDED(key) alloc;Why
BKEY_PADDED()? What does it do? Can we not do this?Just a bare struct bkey on the stack won't be big enough for any pointers.
Quoted 6 lines
I _suppose_ I could do something like:
struct
bkey_paddedalloc_pad;
struct bkey *alloc = &alloc_pad.key;I like that marginally better. Any better ideas?
Looks sane enough to me. Maybe just use padded_key.key directly?
Quoted 3 lines
+/**
+ *get_data_bucket- pick out a bucket to write some data to, possibly
+ * allocating a new one.
If you're gonna do /** comments, please also add arguments section. DocBook stuff may get unhappy.
Quoted 21 lines
+ * We keep multiple buckets open for writes, and try to segregate different
+ * write streams for better cache utilization: first we look for a bucket where
+ * the last write to it was sequential with the current write, and failing that
+ * we look for a bucket that was last used by the same task.
+ *
+ * The ideas is if you've got multiple tasks pulling data into the cache at the
+ * same time, you'll get better cache utilization if you try to segregate their
+ * data and preserve locality.
+ *
+ * For example, say you've starting Firefox at the same time you're copying a
+ * bunch of files. Firefox will likely end up being fairly hot and stay in the
+ * cache awhile, but the data you copied might not be; if you wrote all that
+ * data to the same buckets it'd get invalidated at the same time.
+ *
+ * Both of those tasks will be doing fairly random IO so we can't rely on
+ * detecting sequential IO to segregate their data, but going off of the task
+ * should be a sane heuristic.
+ */
static structopen_bucket*get_data_bucket(struct bkey *search,
struct search *s)
{
...
Quoted 10 lines
Quoted 7 lines
Quoted 1 line
+static void
bio_insert(struct closure *cl)Too generic name. This and a lot of others. Note that there are issues other than direct compile time symbol collision - it makes the code difficult to grep for and stack traces difficult to decipher. I'm not gonna complain more about too generic names but please review the code and fix other instances too.
I'm pretty terrible at naming, so if at some point you could help me come up with names that are descriptive
_toyou_, it'd be a big help.
The problem I'm having with names are two-fold.
* Name not descriptive enough. It doesn't have to be super verbose but something which people can look at and associate with its type and/or role would be great. e.g. instead of k, use key, d -> cdev, and so on.
* Generic names. bio_insert() belongs to block layer. Block layer may use it in the future and when one sees bio_insert(), it's natural for that person to assume that it's a block layer thing doing something with bio. As for better name, I'm not sure. Maybe just prefix it with bc_?
Note that this isn't a 100% strict rule. You can and people do get away with some generic names and many of them don't cause any problem over the lifetime of the code but it's just way too prevalent in this code. You can bend stuff only so much.
Quoted 15 lines
Quoted 6 lines
Quoted 6 lines
(quoted diff) +6
-0+ + if (cached_dev_get(dc)) { + s = do_bio_hook(bio, d); + trace_bcache_request_start(&s->op, bio); + + (!bio_has_data(bio) ? request_nodata :Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ bio->bi_rw & REQ_WRITE ? request_write : + request_read)(dc, s);Don't do this. Among other things, it should be possible to search / grep for "FUNCTION_NAME(" and find all the direct invocations. Just use if/else. You're not gaining anything from doing things like the above while basically aggravating other developers trying to understand your code.
Quoted 7 lines
I don't buy the argument about finding direct invocations (tons of stuff is indirectly invoked today and i can't remember ever caring about direct invocations vs. calls via a pointer, and worse for grepping is all the set wrappers for things like
merge_bvec_fn(it's inconsistent and what is this, C++?)But I give up, changed it :P
So, I don't know. I guess I might be too conservative but the followings are the points I want to make.
* These small style issues don't really matter one way or the other. They're mostly the matter or taste or preference after all. Most technical merits or shortcomings one perceives in this area tend to be highly subjective and marginal, and the overhead of being unusual tends to be way higher. So, my or your taste doesn't weight that much unless there actually are tangible technical merits which people can agree upon. I don't see that here (or in many other bcache oddities).
* These aren't hard and fast rules. There isn't one true fixed standard that everyone conforms to. There still are different styles and people adopt the ones they think are pretty. That's how it evolves, but it too is a problem of degree and you can bend only so far before people start complaining.
For example, here's a fictionalized process that I got irritated and felt generally hostile against the code base.
1. Reading code. Full of macros.
2. Reading macros. Single char arguments. Starting to get irritated.
3. Encountered closures. Still unsure what they mean. This one looks like a simple refcnt. Wonders what the hell is wrong with the usual refcnts. Maybe somehow this gets used differently somewhere else? Of course, no explanation.
4. While trying to follow the control flow, encounters a function with full of cryptic numbers with "different" formatting. No high level explanation or anything. Getting irritated.
5. Now in the lower half of the function, forgot how @d was being used. Pressed ctrl-s and then d to highlight the variable in the function. Ooh, pretty.
6. Saw the next function which seems static. Tries to look for the immediate caller by searching for FUNC_NAME( - hmmm... no match. Maybe it's called indirectly somehow. Looks for FUNC_NAME assignments / used as arguments. Find the function symbol buried in ?:.
7. kajga;wioegja;sdklbja; bkjhaw83ua;kdgjasl;dgjk aw;eig93ua;kgNACK NACK NACK NACK NACK
So, at least to me, you seem to be pushing unique style / concepts too far and I can't identify the technical merits of doing so. Code is to be read and worked together on by peer developers.
Thanks.
Signature · 1 line
tejun