b84f06c2be
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>
87 lines
3.1 KiB
C
87 lines
3.1 KiB
C
#ifndef RAMLIST_H
|
|
#define RAMLIST_H
|
|
|
|
#include "qemu/queue.h"
|
|
#include "qemu/thread.h"
|
|
#include "qemu/rcu.h"
|
|
#include "qemu/rcu_queue.h"
|
|
|
|
typedef struct RAMBlockNotifier RAMBlockNotifier;
|
|
|
|
#define DIRTY_MEMORY_VGA 0
|
|
#define DIRTY_MEMORY_CODE 1
|
|
#define DIRTY_MEMORY_MIGRATION 2
|
|
#define DIRTY_MEMORY_NUM 3 /* num of dirty bits */
|
|
|
|
/* The dirty memory bitmap is split into fixed-size blocks to allow growth
|
|
* under RCU. The bitmap for a block can be accessed as follows:
|
|
*
|
|
* rcu_read_lock();
|
|
*
|
|
* DirtyMemoryBlocks *blocks =
|
|
* qatomic_rcu_read(&ram_list.dirty_memory[DIRTY_MEMORY_MIGRATION]);
|
|
*
|
|
* ram_addr_t idx = (addr >> TARGET_PAGE_BITS) / DIRTY_MEMORY_BLOCK_SIZE;
|
|
* unsigned long *block = blocks.blocks[idx];
|
|
* ...access block bitmap...
|
|
*
|
|
* rcu_read_unlock();
|
|
*
|
|
* Remember to check for the end of the block when accessing a range of
|
|
* addresses. Move on to the next block if you reach the end.
|
|
*
|
|
* Organization into blocks allows dirty memory to grow (but not shrink) under
|
|
* RCU. When adding new RAMBlocks requires the dirty memory to grow, a new
|
|
* DirtyMemoryBlocks array is allocated with pointers to existing blocks kept
|
|
* the same. Other threads can safely access existing blocks while dirty
|
|
* memory is being grown. When no threads are using the old DirtyMemoryBlocks
|
|
* anymore it is freed by RCU (but the underlying blocks stay because they are
|
|
* pointed to from the new DirtyMemoryBlocks).
|
|
*/
|
|
#define DIRTY_MEMORY_BLOCK_SIZE ((ram_addr_t)256 * 1024 * 8)
|
|
typedef struct {
|
|
struct rcu_head rcu;
|
|
unsigned long *blocks[];
|
|
} DirtyMemoryBlocks;
|
|
|
|
typedef struct RAMList {
|
|
QemuMutex mutex;
|
|
RAMBlock *mru_block;
|
|
/* 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;
|
|
extern RAMList ram_list;
|
|
|
|
/* Should be holding either ram_list.mutex, or the RCU lock. */
|
|
#define INTERNAL_RAMBLOCK_FOREACH(block) \
|
|
QLIST_FOREACH_RCU(block, &ram_list.blocks, next)
|
|
/* Never use the INTERNAL_ version except for defining other macros */
|
|
#define RAMBLOCK_FOREACH(block) INTERNAL_RAMBLOCK_FOREACH(block)
|
|
|
|
void qemu_mutex_lock_ramlist(void);
|
|
void qemu_mutex_unlock_ramlist(void);
|
|
|
|
struct RAMBlockNotifier {
|
|
void (*ram_block_added)(RAMBlockNotifier *n, void *host, size_t size,
|
|
size_t max_size);
|
|
void (*ram_block_removed)(RAMBlockNotifier *n, void *host, size_t size,
|
|
size_t max_size);
|
|
void (*ram_block_resized)(RAMBlockNotifier *n, void *host, size_t old_size,
|
|
size_t new_size);
|
|
QLIST_ENTRY(RAMBlockNotifier) next;
|
|
};
|
|
|
|
void ram_block_notifier_add(RAMBlockNotifier *n);
|
|
void ram_block_notifier_remove(RAMBlockNotifier *n);
|
|
void ram_block_notify_add(void *host, size_t size, size_t max_size);
|
|
void ram_block_notify_remove(void *host, size_t size, size_t max_size);
|
|
void ram_block_notify_resize(void *host, size_t old_size, size_t new_size);
|
|
|
|
GString *ram_block_format(void);
|
|
|
|
#endif /* RAMLIST_H */
|