From 339ce1d9502ab7af5c5660b6e1642716923168a5 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Tue, 12 Jul 2022 16:54:55 +0200 Subject: [PATCH] Memory management: add generic memory cleaner interface With this change, it is possible to register different "memory cleaner" closures, which will be invoked to free up memory when the system is low on available physical memory. The virtio balloon deflater and the pagecache drain function have been converted to use this interface. Partially addresses #1130. --- src/config.h | 5 +-- src/kernel/init.c | 77 +++++++++++++++++++++++++++++-------- src/kernel/kernel.h | 4 +- src/virtio/virtio_balloon.c | 5 ++- 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/config.h b/src/config.h index 56eadcdab..e513030e9 100644 --- a/src/config.h +++ b/src/config.h @@ -60,16 +60,13 @@ #define XENNET_TX_SERVICEQUEUE_DEPTH 512 /* mm stuff */ -#define PAGECACHE_DRAIN_CUTOFF (64 * MB) +#define MEM_CLEAN_THRESHOLD (64 * MB) #define PAGECACHE_SCAN_PERIOD_SECONDS 5 #define LOW_MEMORY_THRESHOLD (64 * MB) /* don't go below this minimum amount of physical memory when inflating balloon */ #define BALLOON_MEMORY_MINIMUM (16 * MB) -/* attempt to deflate balloon when physical memory is below this threshold */ -#define BALLOON_DEFLATE_THRESHOLD (16 * MB) - /* must be large enough for vendor code that use malloc/free interface */ #define MAX_MCACHE_ORDER 16 diff --git a/src/kernel/init.c b/src/kernel/init.c index 79a80b6ff..4852ef3db 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -16,6 +16,11 @@ #define init_debug(x, ...) #endif +typedef struct mm_cleaner { + struct list l; + mem_cleaner cleaner; +} *mm_cleaner; + BSS_RO_AFTER_INIT filesystem root_fs; BSS_RO_AFTER_INIT static kernel_heaps init_heaps; @@ -184,11 +189,47 @@ closure_function(2, 2, void, fsstarted, #define mm_debug(x, ...) do { } while(0) #endif -static balloon_deflater mm_balloon_deflater; +static struct list mm_cleaners; +static struct spinlock mm_lock; + +static u64 mm_clean(u64 clean_bytes) +{ + s64 remain = clean_bytes; + spin_lock(&mm_lock); + list end = list_end(&mm_cleaners); + list last = end->prev; + list e = list_begin(&mm_cleaners); + while (e != end) { + mm_cleaner mmc = struct_from_list(e, mm_cleaner, l); + remain -= apply(mmc->cleaner, remain); + if (remain <= 0) + break; + + /* This cleaner couldn't satisfy the clean request: move it to the back of the list, i.e. + * de-prioritize it for future requests. */ + list next = e->next; + list_delete(e); + list_push_back(&mm_cleaners, e); + + if (e == last) + /* any further elements down the list are cleaners that couldn't satisfy this request */ + break; + e = next; + } + spin_unlock(&mm_lock); + return clean_bytes - remain; +} -void mm_register_balloon_deflater(balloon_deflater deflater) +boolean mm_register_mem_cleaner(mem_cleaner cleaner) { - mm_balloon_deflater = deflater; + mm_cleaner mmc = allocate(heap_locked(init_heaps), sizeof(*mmc)); + if (mmc == INVALID_ADDRESS) + return false; + mmc->cleaner = cleaner; + spin_lock(&mm_lock); + list_push_back(&mm_cleaners, &mmc->l); + spin_unlock(&mm_lock); + return true; } void mm_service(void) @@ -197,20 +238,11 @@ void mm_service(void) u64 free = heap_free(phys); mm_debug("%s: total %ld, alloc %ld, free %ld\n", __func__, heap_total(phys), heap_allocated(phys), free); - if (free < PAGECACHE_DRAIN_CUTOFF) { - u64 drain_bytes = PAGECACHE_DRAIN_CUTOFF - free; - u64 drained = pagecache_drain(drain_bytes); - if (drained > 0) - mm_debug(" drained %ld / %ld requested...\n", drained, drain_bytes); - free = heap_free(phys); - } - - if (mm_balloon_deflater && free < BALLOON_DEFLATE_THRESHOLD) { - u64 deflate_bytes = BALLOON_DEFLATE_THRESHOLD - free; - mm_debug(" requesting %ld bytes from deflater\n", deflate_bytes); - u64 deflated = apply(mm_balloon_deflater, deflate_bytes); - mm_debug(" deflated %ld bytes\n", deflated); - (void)deflated; + if (free < MEM_CLEAN_THRESHOLD) { + u64 clean_bytes = MEM_CLEAN_THRESHOLD - free; + u64 cleaned = mm_clean(clean_bytes); + if (cleaned > 0) + mm_debug(" cleaned %ld / %ld requested...\n", cleaned, clean_bytes); } } @@ -315,6 +347,12 @@ closure_function(0, 3, void, attach_storage, apply(req_handler, &req); } +closure_function(0, 1, u64, mm_pagecache_cleaner, + u64, clean_bytes) +{ + return pagecache_drain(clean_bytes); +} + void kernel_runtime_init(kernel_heaps kh) { heap misc = heap_general(kh); @@ -327,7 +365,12 @@ void kernel_runtime_init(kernel_heaps kh) #endif init_runtime(misc, locked); init_sg(locked); + list_init(&mm_cleaners); + spin_lock_init(&mm_lock); init_pagecache(locked, (heap)heap_linear_backed(kh), (heap)heap_physical(kh), PAGESIZE); + mem_cleaner pc_cleaner = closure(misc, mm_pagecache_cleaner); + assert(pc_cleaner != INVALID_ADDRESS); + assert(mm_register_mem_cleaner(pc_cleaner)); unmap(0, PAGESIZE); /* unmap zero page */ init_extra_prints(); init_pci(kh); diff --git a/src/kernel/kernel.h b/src/kernel/kernel.h index 7d99598e5..c7b4948cf 100644 --- a/src/kernel/kernel.h +++ b/src/kernel/kernel.h @@ -757,8 +757,8 @@ void init_scheduler(heap); void init_scheduler_cpus(heap h); void mm_service(void); -typedef closure_type(balloon_deflater, u64, u64); -void mm_register_balloon_deflater(balloon_deflater deflater); +typedef closure_type(mem_cleaner, u64, u64); +boolean mm_register_mem_cleaner(mem_cleaner cleaner); kernel_heaps get_kernel_heaps(void); diff --git a/src/virtio/virtio_balloon.c b/src/virtio/virtio_balloon.c index a85d54550..95aa11dc0 100644 --- a/src/virtio/virtio_balloon.c +++ b/src/virtio/virtio_balloon.c @@ -386,9 +386,10 @@ static boolean virtio_balloon_attach(heap general, backed_heap backed, id_heap p vtdev_set_status(v, VIRTIO_CONFIG_STATUS_DRIVER_OK); update_actual_pages(0); virtio_balloon_update(); - balloon_deflater bd = closure(general, virtio_balloon_deflater); + mem_cleaner bd = closure(general, virtio_balloon_deflater); assert(bd != INVALID_ADDRESS); - mm_register_balloon_deflater(bd); + if (!mm_register_mem_cleaner(bd)) + deallocate_closure(bd); if (balloon_has_stats_vq()) virtio_balloon_init_statsq(); return true;