[alsa-devel] [PATCH 1/2] ALSA: pcm: Set per-card upper limit of PCM buffer allocations
Keyon Jie
yang.jie at linux.intel.com
Tue Jan 21 03:13:20 CET 2020
On 2020/1/20 下午8:44, Takashi Iwai wrote:
> Currently, the available buffer allocation size for a PCM stream
> depends on the preallocated size; when a buffer has been preallocated,
> the max buffer size is set to that size, so that application won't
> re-allocate too much memory. OTOH, when no preallocation is done,
> each substream may allocate arbitrary size of buffers as long as
> snd_pcm_hardware.buffer_bytes_max allows -- which can be quite high,
> HD-audio sets 1GB there.
>
> It means that the system may consume a high amount of pages for PCM
> buffers, and they are pinned and never swapped out. This can lead to
> OOM easily.
>
> For avoiding such a situation, this patch adds the upper limit per
> card. Each snd_pcm_lib_malloc_pages() and _free_pages() calls are
> tracked and it will return an error if the total amount of buffers
> goes over the defined upper limit. The default value is set to 32MB,
Is this typo here? In the patch, "max_alloc_per_card = 32UL * 1024UL *
1024UL * 1024UL", should be 32GB?
~Keyon
> which should be really large enough for usual operations.
>
> If larger buffers are needed for any specific usage, it can be
> adjusted (also dynamically) via snd_pcm.max_alloc_per_card option.
> Setting zero there means no chceck is performed, and again, unlimited
> amount of buffers are allowed.
>
> Signed-off-by: Takashi Iwai <tiwai at suse.de>
> ---
> include/sound/core.h | 3 +++
> sound/core/init.c | 1 +
> sound/core/pcm_memory.c | 69 ++++++++++++++++++++++++++++++++++++-------------
> 3 files changed, 55 insertions(+), 18 deletions(-)
>
> diff --git a/include/sound/core.h b/include/sound/core.h
> index 0e14b7a3e67b..ac8b692b69b4 100644
> --- a/include/sound/core.h
> +++ b/include/sound/core.h
> @@ -120,6 +120,9 @@ struct snd_card {
> int sync_irq; /* assigned irq, used for PCM sync */
> wait_queue_head_t remove_sleep;
>
> + size_t total_pcm_alloc_bytes; /* total amount of allocated buffers */
> + struct mutex memory_mutex; /* protection for the above */
> +
> #ifdef CONFIG_PM
> unsigned int power_state; /* power state */
> wait_queue_head_t power_sleep;
> diff --git a/sound/core/init.c b/sound/core/init.c
> index faa9f03c01ca..b02a99766351 100644
> --- a/sound/core/init.c
> +++ b/sound/core/init.c
> @@ -211,6 +211,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid,
> INIT_LIST_HEAD(&card->ctl_files);
> spin_lock_init(&card->files_lock);
> INIT_LIST_HEAD(&card->files_list);
> + mutex_init(&card->memory_mutex);
> #ifdef CONFIG_PM
> init_waitqueue_head(&card->power_sleep);
> #endif
> diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
> index d4702cc1d376..a18ebf2341ca 100644
> --- a/sound/core/pcm_memory.c
> +++ b/sound/core/pcm_memory.c
> @@ -27,6 +27,38 @@ MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA m
>
> static const size_t snd_minimum_buffer = 16384;
>
> +static unsigned long max_alloc_per_card = 32UL * 1024UL * 1024UL * 1024UL;
> +module_param(max_alloc_per_card, ulong, 0644);
> +MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card.");
> +
> +static int do_alloc_pages(struct snd_card *card, int type, struct device *dev,
> + size_t size, struct snd_dma_buffer *dmab)
> +{
> + int err;
> +
> + if (max_alloc_per_card &&
> + card->total_pcm_alloc_bytes + size > max_alloc_per_card)
> + return -ENOMEM;
> + err = snd_dma_alloc_pages(type, dev, size, dmab);
> + if (!err) {
> + mutex_lock(&card->memory_mutex);
> + card->total_pcm_alloc_bytes += dmab->bytes;
> + mutex_unlock(&card->memory_mutex);
> + }
> + return err;
> +}
> +
> +static void do_free_pages(struct snd_card *card, struct snd_dma_buffer *dmab)
> +{
> + if (!dmab->area)
> + return;
> + mutex_lock(&card->memory_mutex);
> + WARN_ON(card->total_pcm_alloc_bytes < dmab->bytes);
> + card->total_pcm_alloc_bytes -= dmab->bytes;
> + mutex_unlock(&card->memory_mutex);
> + snd_dma_free_pages(dmab);
> + dmab->area = NULL;
> +}
>
> /*
> * try to allocate as the large pages as possible.
> @@ -37,16 +69,15 @@ static const size_t snd_minimum_buffer = 16384;
> static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size)
> {
> struct snd_dma_buffer *dmab = &substream->dma_buffer;
> + struct snd_card *card = substream->pcm->card;
> size_t orig_size = size;
> int err;
>
> do {
> - if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,
> - size, dmab)) < 0) {
> - if (err != -ENOMEM)
> - return err; /* fatal error */
> - } else
> - return 0;
> + err = do_alloc_pages(card, dmab->dev.type, dmab->dev.dev,
> + size, dmab);
> + if (err != -ENOMEM)
> + return err;
> size >>= 1;
> } while (size >= snd_minimum_buffer);
> dmab->bytes = 0; /* tell error */
> @@ -62,10 +93,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t siz
> */
> static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream)
> {
> - if (substream->dma_buffer.area == NULL)
> - return;
> - snd_dma_free_pages(&substream->dma_buffer);
> - substream->dma_buffer.area = NULL;
> + do_free_pages(substream->pcm->card, &substream->dma_buffer);
> }
>
> /**
> @@ -130,6 +158,7 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
> struct snd_info_buffer *buffer)
> {
> struct snd_pcm_substream *substream = entry->private_data;
> + struct snd_card *card = substream->pcm->card;
> char line[64], str[64];
> size_t size;
> struct snd_dma_buffer new_dmab;
> @@ -150,9 +179,10 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
> memset(&new_dmab, 0, sizeof(new_dmab));
> new_dmab.dev = substream->dma_buffer.dev;
> if (size > 0) {
> - if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
> - substream->dma_buffer.dev.dev,
> - size, &new_dmab) < 0) {
> + if (do_alloc_pages(card,
> + substream->dma_buffer.dev.type,
> + substream->dma_buffer.dev.dev,
> + size, &new_dmab) < 0) {
> buffer->error = -ENOMEM;
> return;
> }
> @@ -161,7 +191,7 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
> substream->buffer_bytes_max = UINT_MAX;
> }
> if (substream->dma_buffer.area)
> - snd_dma_free_pages(&substream->dma_buffer);
> + do_free_pages(card, &substream->dma_buffer);
> substream->dma_buffer = new_dmab;
> } else {
> buffer->error = -EINVAL;
> @@ -346,6 +376,7 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne
> */
> int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
> {
> + struct snd_card *card = substream->pcm->card;
> struct snd_pcm_runtime *runtime;
> struct snd_dma_buffer *dmab = NULL;
>
> @@ -374,9 +405,10 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
> if (! dmab)
> return -ENOMEM;
> dmab->dev = substream->dma_buffer.dev;
> - if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
> - substream->dma_buffer.dev.dev,
> - size, dmab) < 0) {
> + if (do_alloc_pages(card,
> + substream->dma_buffer.dev.type,
> + substream->dma_buffer.dev.dev,
> + size, dmab) < 0) {
> kfree(dmab);
> return -ENOMEM;
> }
> @@ -397,6 +429,7 @@ EXPORT_SYMBOL(snd_pcm_lib_malloc_pages);
> */
> int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)
> {
> + struct snd_card *card = substream->pcm->card;
> struct snd_pcm_runtime *runtime;
>
> if (PCM_RUNTIME_CHECK(substream))
> @@ -406,7 +439,7 @@ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)
> return 0;
> if (runtime->dma_buffer_p != &substream->dma_buffer) {
> /* it's a newly allocated buffer. release it now. */
> - snd_dma_free_pages(runtime->dma_buffer_p);
> + do_free_pages(card, runtime->dma_buffer_p);
> kfree(runtime->dma_buffer_p);
> }
> snd_pcm_set_runtime_buffer(substream, NULL);
>
More information about the Alsa-devel
mailing list