softmmu/physmem: fix memory leak in dirty_memory_extend()
As reported by Peter, we might be leaking memory when removing the highest RAMBlock (in the weird ram_addr_t space), and adding a new one. We will fail to realize that we already allocated bitmaps for more dirty memory blocks, and effectively discard the pointers to them. Fix it by getting rid of last_ram_page() and by remembering the number of dirty memory blocks that have been allocated already. While at it, let's use "unsigned int" for the number of blocks, which should be sufficient until we reach ~32 exabytes. Looks like this leak was introduced as we switched from using a single bitmap_zero_extend() to allocating multiple bitmaps: bitmap_zero_extend() relies on g_renew() which should have taken care of this. Resolves: https://lkml.kernel.org/r/CAFEAcA-k7a+VObGAfCFNygQNfCKL=AfX6A4kScq=VSSK0peqPg@mail.gmail.com Reported-by: Peter Maydell <peter.maydell@linaro.org> Fixes:5b82b703b6
("memory: RCU ram_list.dirty_memory[] for safe RAM hotplug") Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Peter Xu <peterx@redhat.com> Tested-by: Peter Maydell <peter.maydell@linaro.org> Cc: qemu-stable@nongnu.org Cc: Stefan Hajnoczi <stefanha@redhat.com> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Peter Xu <peterx@redhat.com> Cc: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: David Hildenbrand <david@redhat.com> Link: https://lore.kernel.org/r/20240828090743.128647-1-david@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> (cherry picked from commitb84f06c2be
) Signed-off-by: Michael Tokarev <mjt@tls.msk.ru> (Mjt: context fix due to lack of v9.0.0-rc4-49-g15f7a80c49cb "RAMBlock: Add support of KVM private guest memfd")
This commit is contained in:
parent
382ee888fb
commit
68318a7a90
@ -50,6 +50,7 @@ typedef struct RAMList {
|
||||
/* RCU-enabled, writes protected by the ramlist lock. */
|
||||
QLIST_HEAD(, RAMBlock) blocks;
|
||||
DirtyMemoryBlocks *dirty_memory[DIRTY_MEMORY_NUM];
|
||||
unsigned int num_dirty_blocks;
|
||||
uint32_t version;
|
||||
QLIST_HEAD(, RAMBlockNotifier) ramblock_notifiers;
|
||||
} RAMList;
|
||||
|
@ -1497,18 +1497,6 @@ static ram_addr_t find_ram_offset(ram_addr_t size)
|
||||
return offset;
|
||||
}
|
||||
|
||||
static unsigned long last_ram_page(void)
|
||||
{
|
||||
RAMBlock *block;
|
||||
ram_addr_t last = 0;
|
||||
|
||||
RCU_READ_LOCK_GUARD();
|
||||
RAMBLOCK_FOREACH(block) {
|
||||
last = MAX(last, block->offset + block->max_length);
|
||||
}
|
||||
return last >> TARGET_PAGE_BITS;
|
||||
}
|
||||
|
||||
static void qemu_ram_setup_dump(void *addr, ram_addr_t size)
|
||||
{
|
||||
int ret;
|
||||
@ -1762,12 +1750,10 @@ void qemu_ram_msync(RAMBlock *block, ram_addr_t start, ram_addr_t length)
|
||||
}
|
||||
|
||||
/* Called with ram_list.mutex held */
|
||||
static void dirty_memory_extend(ram_addr_t old_ram_size,
|
||||
ram_addr_t new_ram_size)
|
||||
static void dirty_memory_extend(ram_addr_t new_ram_size)
|
||||
{
|
||||
ram_addr_t old_num_blocks = DIV_ROUND_UP(old_ram_size,
|
||||
DIRTY_MEMORY_BLOCK_SIZE);
|
||||
ram_addr_t new_num_blocks = DIV_ROUND_UP(new_ram_size,
|
||||
unsigned int old_num_blocks = ram_list.num_dirty_blocks;
|
||||
unsigned int new_num_blocks = DIV_ROUND_UP(new_ram_size,
|
||||
DIRTY_MEMORY_BLOCK_SIZE);
|
||||
int i;
|
||||
|
||||
@ -1800,6 +1786,8 @@ static void dirty_memory_extend(ram_addr_t old_ram_size,
|
||||
g_free_rcu(old_blocks, rcu);
|
||||
}
|
||||
}
|
||||
|
||||
ram_list.num_dirty_blocks = new_num_blocks;
|
||||
}
|
||||
|
||||
static void ram_block_add(RAMBlock *new_block, Error **errp)
|
||||
@ -1808,11 +1796,9 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
|
||||
const bool shared = qemu_ram_is_shared(new_block);
|
||||
RAMBlock *block;
|
||||
RAMBlock *last_block = NULL;
|
||||
ram_addr_t old_ram_size, new_ram_size;
|
||||
ram_addr_t ram_size;
|
||||
Error *err = NULL;
|
||||
|
||||
old_ram_size = last_ram_page();
|
||||
|
||||
qemu_mutex_lock_ramlist();
|
||||
new_block->offset = find_ram_offset(new_block->max_length);
|
||||
|
||||
@ -1840,11 +1826,8 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
new_ram_size = MAX(old_ram_size,
|
||||
(new_block->offset + new_block->max_length) >> TARGET_PAGE_BITS);
|
||||
if (new_ram_size > old_ram_size) {
|
||||
dirty_memory_extend(old_ram_size, new_ram_size);
|
||||
}
|
||||
ram_size = (new_block->offset + new_block->max_length) >> TARGET_PAGE_BITS;
|
||||
dirty_memory_extend(ram_size);
|
||||
/* Keep the list sorted from biggest to smallest block. Unlike QTAILQ,
|
||||
* QLIST (which has an RCU-friendly variant) does not have insertion at
|
||||
* tail, so save the last element in last_block.
|
||||
|
Loading…
Reference in New Issue
Block a user