* sb_region.c
* Superblock allocator memory region manager.
*
+ * The superblock allocator operates on ranges of pages managed by a
+ * FreePageManager and reverse-mapped by an sb_map. When it's asked to
+ * free an object, it just gets a pointer address; our job is to figure
+ * out which page range contains that object and locate the
+ * FreePageManager, sb_map, and other metadata that the superblock
+ * allocator will need to do its thing. Moreover, when allocating an
+ * object, the caller is only required to provide the superblock allocator
+ * with a pointer to the sb_allocator object, which could be in either
+ * shared or backend-private memory; our job again is to know which it
+ * is and provide pointers to the appropriate supporting data structures.
+ * To do all this, we have to keep track of where all dynamic shared memory
+ * segments configured for memory allocation are located; and we also have
+ * to keep track of where all chunks of memory obtained from the operating
+ * system for backend-private allocations are located.
+ *
+ * On a 32-bit system, the number of chunks can never get very big, so
+ * we just store them all in a single array and use binary search for
+ * lookups. On a 64-bit system, this might get dicey, so we maintain
+ * one such array for every 4GB of address space; chunks that span a 4GB
+ * boundary require multiple entries.
+ *
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
#include "postgres.h"
#include "utils/sb_region.h"
+
+/*
+ * On 64-bit systems, we use a two-level radix tree to find the data for
+ * the relevant 4GB range. The radix tree is deliberately unbalanced, with
+ * more entries at the first level than at the second level. We expect this
+ * to save memory, because the first level has a cache, and the full array
+ * is only instantiated if the cache overflows. Since each L2 entry
+ * covers 2^44 bytes of address space (16TB), we expect overflows of the
+ * four-entry cache to happen essentially never.
+ */
+#define SB_LOOKUP_ROOT_BITS 20
+#define SB_LOOKUP_ROOT_ENTRIES (1 << SB_LOOKUP_ROOT_BITS)
+#define SB_LOOKUP_ROOT_CACHE_SIZE 4
+#define SB_LOOKUP_L2_BITS 12
+#define SB_LOOKUP_L2_ENTRIES (1 << SB_LOOKUP_L2_BITS)
+
+/* Lookup data for a 4GB range of address space. */
+typedef struct
+{
+ int nused;
+ int nallocated;
+ sb_region **region;
+} sb_lookup_leaf;
+
+/* Lookup data for a 16TB range of address space, direct mapped. */
+typedef struct
+{
+ sb_lookup_leaf *leaf[SB_LOOKUP_L2_ENTRIES];
+} sb_lookup_l2;
+
+/* Lookup data for an entire 64-bit address space. */
+typedef struct
+{
+ uint32 ncached;
+ uint32 cache_key[SB_LOOKUP_ROOT_CACHE_SIZE];
+ sb_lookup_l2 *cache_value[SB_LOOKUP_L2_ENTRIES];
+ sb_lookup_l2 **l2;
+} sb_lookup_root;
+
+#if SIZEOF_SIZE_T > 4
+static sb_lookup_root lookup_root;
+#else
+static sb_lookup_leaf lookup_root_leaf;
+#endif
+
+/*
+ * Find the region to which a pointer belongs.
+ */
+sb_region *
+sb_lookup_region(void *ptr)
+{
+ Size p = (Size) ptr;
+ sb_lookup_leaf *leaf = NULL;
+ int high, low;
+
+ /*
+ * If this is a 64-bit system, locate the lookup table that pertains
+ * to the upper 32 bits of ptr. On a 32-bit system, there's only one
+ * lookup table.
+ */
+#if SIZEOF_SIZE_T > 4
+ {
+ uint32 highbits = p >> 32;
+ sb_lookup_l2 *l2 = NULL;
+ int i;
+
+ /* Check for entry in cache. */
+ for (i = 0; i < lookup_root.ncached; ++i)
+ if (lookup_root.cache_key[i] == highbits)
+ l2 = lookup_root.cache_value[i];
+
+ /*
+ * If there's nothing in cache but the full table has been initialized,
+ * find the l2 entry there and pull it into the cache. Since we expect
+ * this path to be taken virtually never, we don't worry about LRU but
+ * just pick a slot more or less arbitrarily.
+ */
+ if (l2 == NULL && lookup_root.l2 != NULL)
+ {
+ uint32 rootbits = highbits >> SB_LOOKUP_L2_BITS;
+ rootbits &= SB_LOOKUP_ROOT_ENTRIES - 1;
+ l2 = lookup_root.l2[rootbits];
+
+ if (l2 != NULL)
+ {
+ i = highbits % SB_LOOKUP_ROOT_CACHE_SIZE;
+ lookup_root.cache_key[i] = highbits;
+ lookup_root.cache_value[i] = l2;
+ }
+ }
+
+ /* Now use the L2 map (if any) to find the correct leaf node. */
+ if (l2 != NULL)
+ leaf = l2->leaf[highbits & (SB_LOOKUP_L2_ENTRIES - 1)];
+
+ /* No lookup table for this 4GB range? OK, no matching region. */
+ if (leaf == NULL)
+ return NULL;
+ }
+#else
+ leaf = &lookup_root_leaf;
+#endif
+
+ /* Now we use binary search on the sb_lookup_leaf. */
+ high = leaf->nused;
+ low = 0;
+ while (low < high)
+ {
+ int mid;
+ sb_region *region;
+
+ mid = (high + low) / 2;
+ region = leaf->region[mid];
+ if (region->region_start > (char *) ptr)
+ high = mid;
+ else if (region->region_start + region->region_size < (char *) ptr)
+ low = mid + 1;
+ else
+ return region;
+ }
+ return NULL;
+}