[dm-devel] [PATCH] dm-crypt: limit the number of allocated pages

Mikulas Patocka mpatocka at redhat.com
Tue Aug 15 00:07:30 UTC 2017



On Mon, 14 Aug 2017, John Stoffel wrote:

> >>>>> "Mikulas" == Mikulas Patocka <mpatocka at redhat.com> writes:
> 
> Mikulas> dm-crypt consumes excessive amount memory when the user attempts to zero
> Mikulas> a dm-crypt device with "blkdiscard -z". The command "blkdiscard -z" calls
> Mikulas> the BLKZEROOUT ioctl, it goes to the function __blkdev_issue_zeroout,
> Mikulas> __blkdev_issue_zeroout sends large amount of write bios that contain the
> Mikulas> zero page as their payload.
> 
> Mikulas> For each incoming page, dm-crypt allocates another page that holds the
> Mikulas> encrypted data, so when processing "blkdiscard -z", dm-crypt tries to
> Mikulas> allocate the amount of memory that is equal to the size of the device.
> Mikulas> This can trigger OOM killer or cause system crash.
> 
> Mikulas> This patch fixes the bug by limiting the amount of memory that dm-crypt
> Mikulas> allocates to 2% of total system memory.
> 
> Is this limit per-device or system wide?  it's not clear to me if the
> minimum is just one page, or more so it might be nice to clarify
> that.

The limit is system-wide. The limit is divided by the number of active 
dm-crypt devices and each device receives an equal share.

There is a lower bound of BIO_MAX_PAGES * 16. The per-device limit is 
never lower than this.

> Also, for large memory systems, that's still alot of pages.  Maybe it
> should be more exponential in it's clamping as memory sizes go up?  2%
> of 2T is 4G, which is pretty darn big...

The more we restrict it, the less parallelism is used in dm-crypt, so it 
makes sense to have a big value on machines with many cores.

> Mikulas> Signed-off-by: Mikulas Patocka <mpatocka at redhat.com>
> Mikulas> Cc: stable at vger.kernel.org
> 
> Mikulas> ---
> Mikulas>  drivers/md/dm-crypt.c |   60 +++++++++++++++++++++++++++++++++++++++++++++++++-
> Mikulas>  1 file changed, 59 insertions(+), 1 deletion(-)
> 
> Mikulas> Index: linux-2.6/drivers/md/dm-crypt.c
> Mikulas> ===================================================================
> Mikulas> --- linux-2.6.orig/drivers/md/dm-crypt.c
> Mikulas> +++ linux-2.6/drivers/md/dm-crypt.c
> Mikulas> @@ -148,6 +148,8 @@ struct crypt_config {
> Mikulas>  	mempool_t *tag_pool;
> Mikulas>  	unsigned tag_pool_max_sectors;
>  
> Mikulas> +	struct percpu_counter n_allocated_pages;
> Mikulas> +
> Mikulas>  	struct bio_set *bs;
> Mikulas>  	struct mutex bio_alloc_lock;
>  
> Mikulas> @@ -219,6 +221,12 @@ struct crypt_config {
> Mikulas>  #define MAX_TAG_SIZE	480
> Mikulas>  #define POOL_ENTRY_SIZE	512
>  
> Mikulas> +static DEFINE_SPINLOCK(dm_crypt_clients_lock);
> Mikulas> +static unsigned dm_crypt_clients_n = 0;
> Mikulas> +static volatile unsigned long dm_crypt_pages_per_client;
> Mikulas> +#define DM_CRYPT_MEMORY_PERCENT			2
> Mikulas> +#define DM_CRYPT_MIN_PAGES_PER_CLIENT		(BIO_MAX_PAGES * 16)
> Mikulas> +
> Mikulas>  static void clone_init(struct dm_crypt_io *, struct bio *);
> Mikulas>  static void kcryptd_queue_crypt(struct dm_crypt_io *io);
> Mikulas>  static struct scatterlist *crypt_get_sg_data(struct crypt_config *cc,
> Mikulas> @@ -2158,6 +2166,37 @@ static int crypt_wipe_key(struct crypt_c
> Mikulas>  	return r;
> Mikulas>  }
>  
> Mikulas> +static void crypt_calculate_pages_per_client(void)
> Mikulas> +{
> Mikulas> +	unsigned long pages = (totalram_pages - totalhigh_pages) * DM_CRYPT_MEMORY_PERCENT / 100;
> Mikulas> +	if (!dm_crypt_clients_n)
> Mikulas> +		return;
> Mikulas> +	pages /= dm_crypt_clients_n;
> 
> Would it make sense to use a shift here, or does this only run once on
> setup?  And shouldn't you return a value here, if only to make it
> clear what you're defaulting to?  

This piece of code is executed only when a dm-crypt device is created or 
deactivated, so performance doesn't matter.

> Mikulas> +	if (pages < DM_CRYPT_MIN_PAGES_PER_CLIENT)
> Mikulas> +		pages = DM_CRYPT_MIN_PAGES_PER_CLIENT;
> Mikulas> +	dm_crypt_pages_per_client = pages;
> Mikulas> +}
> Mikulas> +
> Mikulas> +static void *crypt_page_alloc(gfp_t gfp_mask, void *pool_data)
> Mikulas> +{
> Mikulas> +	struct crypt_config *cc = pool_data;
> Mikulas> +	struct page *page;
> Mikulas> +	if (unlikely(percpu_counter_compare(&cc->n_allocated_pages, dm_crypt_pages_per_client) >= 0) &&
> Mikulas> +	    likely(gfp_mask & __GFP_NORETRY))
> Mikulas> +		return NULL;
> Mikulas> +	page = alloc_page(gfp_mask);
> Mikulas> +	if (likely(page != NULL))
> Mikulas> +		percpu_counter_add(&cc->n_allocated_pages, 1);
> Mikulas> +	return page;
> Mikulas> +}
> Mikulas> +
> Mikulas> +static void crypt_page_free(void *page, void *pool_data)
> Mikulas> +{
> Mikulas> +	struct crypt_config *cc = pool_data;
> Mikulas> +	__free_page(page);
> Mikulas> +	percpu_counter_sub(&cc->n_allocated_pages, 1);
> Mikulas> +}
> Mikulas> +
> Mikulas>  static void crypt_dtr(struct dm_target *ti)
> Mikulas>  {
> Mikulas>  	struct crypt_config *cc = ti->private;
> Mikulas> @@ -2184,6 +2223,10 @@ static void crypt_dtr(struct dm_target *
> Mikulas>  	mempool_destroy(cc->req_pool);
> Mikulas>  	mempool_destroy(cc->tag_pool);
>  
> Mikulas> +	if (cc->page_pool)
> Mikulas> +		WARN_ON(percpu_counter_sum(&cc->n_allocated_pages) != 0);
> Mikulas> +	percpu_counter_destroy(&cc->n_allocated_pages);
> Mikulas> +
> Mikulas>  	if (cc->iv_gen_ops && cc->iv_gen_ops->dtr)
> cc-> iv_gen_ops->dtr(cc);
>  
> Mikulas> @@ -2198,6 +2241,12 @@ static void crypt_dtr(struct dm_target *
>  
> Mikulas>  	/* Must zero key material before freeing */
> Mikulas>  	kzfree(cc);
> Mikulas> +
> Mikulas> +	spin_lock(&dm_crypt_clients_lock);
> Mikulas> +	WARN_ON(!dm_crypt_clients_n);
> Mikulas> +	dm_crypt_clients_n--;
> Mikulas> +	crypt_calculate_pages_per_client();
> Mikulas> +	spin_unlock(&dm_crypt_clients_lock);
> Mikulas>  }
>  
> Mikulas>  static int crypt_ctr_ivmode(struct dm_target *ti, const char *ivmode)
> Mikulas> @@ -2636,6 +2685,15 @@ static int crypt_ctr(struct dm_target *t
>  
> ti-> private = cc;
>  
> Mikulas> +	spin_lock(&dm_crypt_clients_lock);
> Mikulas> +	dm_crypt_clients_n++;
> Mikulas> +	crypt_calculate_pages_per_client();
> Mikulas> +	spin_unlock(&dm_crypt_clients_lock);
> Mikulas> +
> Mikulas> +	ret = percpu_counter_init(&cc->n_allocated_pages, 0, GFP_KERNEL);
> Mikulas> +	if (ret < 0)
> Mikulas> +		goto bad;
> Mikulas> +
> Mikulas>  	/* Optional parameters need to be read before cipher constructor */
> Mikulas>  	if (argc > 5) {
> Mikulas>  		ret = crypt_ctr_optional(ti, argc - 5, &argv[5]);
> Mikulas> @@ -2690,7 +2748,7 @@ static int crypt_ctr(struct dm_target *t
> Mikulas>  		ALIGN(sizeof(struct dm_crypt_io) + cc->dmreq_start + additional_req_size,
> Mikulas>  		      ARCH_KMALLOC_MINALIGN);
>  
> Mikulas> -	cc->page_pool = mempool_create_page_pool(BIO_MAX_PAGES, 0);
> Mikulas> +	cc->page_pool = mempool_create(BIO_MAX_PAGES, crypt_page_alloc, crypt_page_free, cc);
> Mikulas>  	if (!cc->page_pool) {
> ti-> error = "Cannot allocate page mempool";
> Mikulas>  		goto bad;
> 
> Mikulas> --
> Mikulas> dm-devel mailing list
> Mikulas> dm-devel at redhat.com
> Mikulas> https://www.redhat.com/mailman/listinfo/dm-devel
> 




More information about the dm-devel mailing list