The upper limit on the cache size of a client is currently determined by dividing the total cache size by the number of clients. However, in some cases it is beneficial to give one client a higher limit than others; an example is a device with many dm-verity targets, where one target has a very large hashtree, and all the others have a small hashtree. Giving the target with the large hashtree a higher limit will be beneficial. Another example is dm-verity-fec: FEC is only used in (rare) error conditions, yet for every dm-verity target with FEC, we create two FEC dm-bufio clients, which together have a higher cache limit than the dm-verity target itself. This patchset allows a client to indicate a maximum cache size for its client; if that maximum is lower than the calculated per-client limit, that maximum will be used instead, and the freed up cache size will be allocated to other clients (that haven't set a maximum). Note that this algorithm is not perfect; if we have 100MB with 3 clients, where the first set a max of 1MB, the second set a max of 40MB, and the third set no maximumm, the ideal allocation would be 1:40:59, respectively. However, because the initial per-client limit is 100 / 3 =~33MB, the requested max of 40MB is over the per-client limit, and instead the allocation will end up being ~ 1:40:49. This is still better than the original 33:33:33 allocation. An iterative algorithm could do better, but it also complicates the code significantly. Signed-off-by: Martijn Coenen <maco@xxxxxxxxxxx> --- drivers/md/dm-bufio.c | 60 +++++++++++++++++++++++++++++++++++++--- include/linux/dm-bufio.h | 7 +++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index b6b5acc92ca2d..d116030107c54 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -25,9 +25,20 @@ * Memory management policy: * Limit the number of buffers to DM_BUFIO_MEMORY_PERCENT of main memory * or DM_BUFIO_VMALLOC_PERCENT of vmalloc memory (whichever is lower). - * Always allocate at least DM_BUFIO_MIN_BUFFERS buffers. - * Start background writeback when there are DM_BUFIO_WRITEBACK_PERCENT - * dirty buffers. + * + * By default, all clients have an equal memory limit, which is the total + * cache size divided by the number of clients. On devices with few + * clients, this can be quite a large amount, and clients that know an + * upper bound on their desired cache size can call + * dm_bufio_set_maximum_buffers() to indicate this, to allow more "needy" + * clients to get higher limits. In that case, if the per-client memory + * limit exceeds the requested maximum, we use the requested maximum + * instead, and divide the freed up space evenly with other clients that + * haven't requested a maximum. + * + * Always allocate at least DM_BUFIO_MIN_BUFFERS buffers. Start + * background writeback when there are DM_BUFIO_WRITEBACK_PERCENT dirty + * buffers. */ #define DM_BUFIO_MIN_BUFFERS 8 @@ -98,6 +109,7 @@ struct dm_bufio_client { unsigned need_reserved_buffers; unsigned minimum_buffers; + unsigned maximum_buffers; struct rb_root buffer_tree; wait_queue_head_t free_buffer_wait; @@ -310,6 +322,11 @@ static void adjust_total_allocated(unsigned char data_mode, long diff) */ static void __cache_size_refresh(void) { + unsigned long max_cache_size_per_client; + unsigned long remaining_cache_size_to_divide; + struct dm_bufio_client *c; + unsigned int num_clients_to_divide = 0; + BUG_ON(!mutex_is_locked(&dm_bufio_clients_lock)); BUG_ON(dm_bufio_client_count < 0); @@ -324,8 +341,22 @@ static void __cache_size_refresh(void) dm_bufio_cache_size_latch = dm_bufio_default_cache_size; } - dm_bufio_cache_size_per_client = dm_bufio_cache_size_latch / + remaining_cache_size_to_divide = dm_bufio_cache_size_latch; + max_cache_size_per_client = dm_bufio_cache_size_latch / (dm_bufio_client_count ? : 1); + + list_for_each_entry(c, &dm_bufio_all_clients, client_list) { + unsigned long max = (unsigned long) c->maximum_buffers * + c->block_size; + + if (max > 0 && max < max_cache_size_per_client) + remaining_cache_size_to_divide -= max; + else + num_clients_to_divide++; + } + + dm_bufio_cache_size_per_client = remaining_cache_size_to_divide / + (num_clients_to_divide ? : 1); } /* @@ -928,6 +959,15 @@ static void __get_memory_limit(struct dm_bufio_client *c, else buffers /= c->block_size; + /* + * Note that dm_bufio_cache_size_per_client already takes into account + * clients requesting less than is available; but that means the + * available cache size per client has increased, and if they were + * below the per-client limit then, they will still be below the limit + * now. + */ + if ((c->maximum_buffers > 0) && buffers > c->maximum_buffers) + buffers = c->maximum_buffers; if (buffers < c->minimum_buffers) buffers = c->minimum_buffers; @@ -1450,6 +1490,17 @@ void dm_bufio_set_minimum_buffers(struct dm_bufio_client *c, unsigned n) } EXPORT_SYMBOL_GPL(dm_bufio_set_minimum_buffers); +void dm_bufio_set_maximum_buffers(struct dm_bufio_client *c, unsigned n) +{ + mutex_lock(&dm_bufio_clients_lock); + + c->maximum_buffers = n; + __cache_size_refresh(); + + mutex_unlock(&dm_bufio_clients_lock); +} +EXPORT_SYMBOL(dm_bufio_set_maximum_buffers); + unsigned dm_bufio_get_block_size(struct dm_bufio_client *c) { return c->block_size; @@ -1664,6 +1715,7 @@ struct dm_bufio_client *dm_bufio_client_create(struct block_device *bdev, unsign c->need_reserved_buffers = reserved_buffers; dm_bufio_set_minimum_buffers(c, DM_BUFIO_MIN_BUFFERS); + c->maximum_buffers = 0; init_waitqueue_head(&c->free_buffer_wait); c->async_write_error = 0; diff --git a/include/linux/dm-bufio.h b/include/linux/dm-bufio.h index 3c8b7d274bd9b..89f2f04c16ef2 100644 --- a/include/linux/dm-bufio.h +++ b/include/linux/dm-bufio.h @@ -136,6 +136,13 @@ void dm_bufio_forget(struct dm_bufio_client *c, sector_t block); */ void dm_bufio_set_minimum_buffers(struct dm_bufio_client *c, unsigned n); +/* + * Set the maximum number of buffers a client can hold. If called with a value + * of 0 (which is also the default), the maximum number of buffers is equal to + * the total cache size divided by the number of clients. + */ +void dm_bufio_set_maximum_buffers(struct dm_bufio_client *c, unsigned n); + unsigned dm_bufio_get_block_size(struct dm_bufio_client *c); sector_t dm_bufio_get_device_size(struct dm_bufio_client *c); sector_t dm_bufio_get_block_number(struct dm_buffer *b); -- 2.23.0.162.g0b9fbb3734-goog -- dm-devel mailing list dm-devel@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/dm-devel