You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grub2/SOURCES/0320-mm-Adjust-new-region-s...

146 lines
6.1 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zhang Boyang <zhangboyang.id@gmail.com>
Date: Sun, 29 Jan 2023 19:49:31 +0800
Subject: [PATCH] mm: Adjust new region size to take management overhead into
account
When grub_memalign() encounters out-of-memory, it will try
grub_mm_add_region_fn() to request more memory from system firmware.
However, the size passed to it doesn't take region management overhead
into account. Adding a memory area of "size" bytes may result in a heap
region of less than "size" bytes really available. Thus, the new region
may not be adequate for current allocation request, confusing
out-of-memory handling code.
This patch introduces GRUB_MM_MGMT_OVERHEAD to address the region
management overhead (e.g. metadata, padding). The value of this new
constant must be large enough to make sure grub_memalign(align, size)
always succeeds after a successful call to
grub_mm_init_region(addr, size + align + GRUB_MM_MGMT_OVERHEAD),
for any given addr and size (assuming no integer overflow).
The size passed to grub_mm_add_region_fn() is now correctly adjusted,
thus if grub_mm_add_region_fn() succeeded, current allocation request
can always succeed.
Signed-off-by: Zhang Boyang <zhangboyang.id@gmail.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit 2282cbfe5aa1ff6c1bbcbdcd2003089ad7c03ba3)
---
grub-core/kern/mm.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 61 insertions(+), 3 deletions(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index da1ac9427c..f29a3e5cbd 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -83,6 +83,46 @@
+/*
+ * GRUB_MM_MGMT_OVERHEAD is an upper bound of management overhead of
+ * each region, with any possible padding taken into account.
+ *
+ * The value must be large enough to make sure grub_memalign(align, size)
+ * always succeeds after a successful call to
+ * grub_mm_init_region(addr, size + align + GRUB_MM_MGMT_OVERHEAD),
+ * for any given addr, align and size (assuming no interger overflow).
+ *
+ * The worst case which has maximum overhead is shown in the figure below:
+ *
+ * +-- addr
+ * v |<- size + align ->|
+ * +---------+----------------+----------------+------------------+---------+
+ * | padding | grub_mm_region | grub_mm_header | usable bytes | padding |
+ * +---------+----------------+----------------+------------------+---------+
+ * |<- a ->|<- b ->|<- c ->|<- d ->|<- e ->|
+ * ^
+ * b == sizeof (struct grub_mm_region) | / Assuming no other suitable
+ * c == sizeof (struct grub_mm_header) | | block is available, then:
+ * d == size + align +-| If align == 0, this will be
+ * | the pointer returned by next
+ * Assuming addr % GRUB_MM_ALIGN == 1, then: | grub_memalign(align, size).
+ * a == GRUB_MM_ALIGN - 1 | If align > 0, this chunk may
+ * | need to be split to fulfill
+ * Assuming d % GRUB_MM_ALIGN == 1, then: | alignment requirements, and
+ * e == GRUB_MM_ALIGN - 1 | the returned pointer may be
+ * \ inside these usable bytes.
+ * Therefore, the maximum overhead is:
+ * a + b + c + e == (GRUB_MM_ALIGN - 1) + sizeof (struct grub_mm_region)
+ * + sizeof (struct grub_mm_header) + (GRUB_MM_ALIGN - 1)
+ */
+#define GRUB_MM_MGMT_OVERHEAD ((GRUB_MM_ALIGN - 1) \
+ + sizeof (struct grub_mm_region) \
+ + sizeof (struct grub_mm_header) \
+ + (GRUB_MM_ALIGN - 1))
+
+/* The size passed to grub_mm_add_region_fn() is aligned up by this value. */
+#define GRUB_MM_HEAP_GROW_ALIGN 4096
+
grub_mm_region_t grub_mm_base;
grub_mm_add_region_func_t grub_mm_add_region_fn;
@@ -230,6 +270,11 @@ grub_mm_init_region (void *addr, grub_size_t size)
grub_dprintf ("regions", "No: considering a new region at %p of size %" PRIxGRUB_SIZE "\n",
addr, size);
+ /*
+ * If you want to modify the code below, please also take a look at
+ * GRUB_MM_MGMT_OVERHEAD and make sure it is synchronized with the code.
+ */
+
/* Allocate a region from the head. */
r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
@@ -410,6 +455,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
{
grub_mm_region_t r;
grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
+ grub_size_t grow;
int count = 0;
if (!grub_mm_base)
@@ -418,10 +464,22 @@ grub_memalign (grub_size_t align, grub_size_t size)
if (size > ~(grub_size_t) align)
goto fail;
+ /*
+ * Pre-calculate the necessary size of heap growth (if applicable),
+ * with region management overhead taken into account.
+ */
+ if (grub_add (size + align, GRUB_MM_MGMT_OVERHEAD, &grow))
+ goto fail;
+
+ /* Align up heap growth to make it friendly to CPU/MMU. */
+ if (grow > ~(grub_size_t) (GRUB_MM_HEAP_GROW_ALIGN - 1))
+ goto fail;
+ grow = ALIGN_UP (grow, GRUB_MM_HEAP_GROW_ALIGN);
+
/* We currently assume at least a 32-bit grub_size_t,
so limiting allocations to <adress space size> - 1MiB
in name of sanity is beneficial. */
- if ((size + align) > ~(grub_size_t) 0x100000)
+ if (grow > ~(grub_size_t) 0x100000)
goto fail;
align = (align >> GRUB_MM_ALIGN_LOG2);
@@ -447,7 +505,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
count++;
if (grub_mm_add_region_fn != NULL &&
- grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE)
+ grub_mm_add_region_fn (grow, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE)
goto again;
/* fallthrough */
@@ -462,7 +520,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
* Try again even if this fails, in case it was able to partially
* satisfy the request
*/
- grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_NONE);
+ grub_mm_add_region_fn (grow, GRUB_MM_ADD_REGION_NONE);
goto again;
}