From 4f18086d7849cad65a4451a1f01d3252108965be Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 6 Mar 2026 12:34:49 +0200
Subject: [PATCH v2 2/4] Introduce a new mechanism for registering shared
 memory areas

Each shared memory area is registered with a "descriptor struct" that
contains the parameters like name and size. A struct makes it easier
to add optional fields in the future; the additional fields can just
be left as zeros.

This merges the separate [Subsystem]ShmemSize() and
[Subsystem]ShmemInit() phases at postmaster startup. Each subsystem is
now called into just once, before the shared memory segment has been
allocated, to register the subsystems shared memory areas. The
registration includes the size, which replaces the
[Subsystem]ShmemSize() calls, and a pointer to an initialization
callback function, which replaces the [Subsystem]ShmemInit()
calls. This is more ergonomic, as you only need to calculate the size
once, when you register the struct.

This replaces ShmemInitStruct() and ShmemInitHash(), which become just
backwards-compatibility wrappers around the new functions. In future
commits, I plan to replace all ShmemInitStruct() and ShmemInitHash()
calls with the new functions, although we'll still need to keep them
around for extensions.
---
 doc/src/sgml/system-views.sgml          |   4 +-
 doc/src/sgml/xfunc.sgml                 | 119 ++--
 src/backend/bootstrap/bootstrap.c       |   2 +
 src/backend/postmaster/launch_backend.c |   4 +
 src/backend/postmaster/postmaster.c     |   5 +
 src/backend/storage/ipc/ipci.c          |  57 +-
 src/backend/storage/ipc/shmem.c         | 821 +++++++++++++++++-------
 src/backend/tcop/postgres.c             |   3 +
 src/include/storage/ipc.h               |   1 +
 src/include/storage/shmem.h             | 136 +++-
 10 files changed, 831 insertions(+), 321 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index e5fe423fc61..a0baed339fe 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4254,8 +4254,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
   <para>
    Anonymous allocations are allocations that have been made
    with <literal>ShmemAlloc()</literal> directly, rather than via
-   <literal>ShmemInitStruct()</literal> or
-   <literal>ShmemInitHash()</literal>.
+   <literal>ShmemRegisterStruct()</literal> or
+   <literal>ShmemRegisterHash()</literal>.
   </para>
 
   <para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 70e815b8a2c..dfe03557fa9 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3628,58 +3628,91 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
       Add-ins can reserve shared memory on server startup.  To do so, the
       add-in's shared library must be preloaded by specifying it in
       <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
-      The shared library should also register a
-      <literal>shmem_request_hook</literal> in its
-      <function>_PG_init</function> function.  This
-      <literal>shmem_request_hook</literal> can reserve shared memory by
-      calling:
-<programlisting>
-void RequestAddinShmemSpace(Size size)
-</programlisting>
-      Each backend should obtain a pointer to the reserved shared memory by
-      calling:
+      The shared library should register the shared memory allocation in
+      its <function>_PG_init</function> function.  Here is an example:
 <programlisting>
-void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
-</programlisting>
-      If this function sets <literal>foundPtr</literal> to
-      <literal>false</literal>, the caller should proceed to initialize the
-      contents of the reserved shared memory.  If <literal>foundPtr</literal>
-      is set to <literal>true</literal>, the shared memory was already
-      initialized by another backend, and the caller need not initialize
-      further.
-     </para>
+typedef struct MyShmemData {
+    ... shared memory contents ...
+    LWLock	   *lock;
+} MyShmemData;
+
+static MyShmemData *MyShmem; /* pointer to the struct in shared memory */
+
+static void my_shmem_request(void);
+static void my_shmem_init(void *arg);
+
+static ShmemStructDesc MyShmemDesc = {
+    .name = "My shmem area",
+    .size = sizeof(MyShmemData),
+    .init_fn = my_shmem_init,
+    .ptr = &amp;MyShmem,
+};
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+    /*
+     * In order to create our shared memory area, we have to be loaded via
+     * shared_preload_libraries.
+     */
+    if (!process_shared_preload_libraries_in_progress)
+        return;
 
-     <para>
-      To avoid race conditions, each backend should use the LWLock
-      <function>AddinShmemInitLock</function> when initializing its allocation
-      of shared memory, as shown here:
-<programlisting>
-static mystruct *ptr = NULL;
-bool        found;
+    /* Register our shared memory needs */
+    ShmemRegisterStruct(&amp;MyShmemDesc);
+
+    /* Set up a hook for requesting additional resources */
+    prev_shmem_request_hook = shmem_request_hook;
+    shmem_request_hook = my_shmem_request;
+}
 
-LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-ptr = ShmemInitStruct("my struct name", size, &amp;found);
-if (!found)
+static void
+my_shmem_request(void *arg)
+{
+    if (prev_shmem_request_hook)
+        prev_shmem_request_hook();
+
+    RequestNamedLWLockTranche("my tranche name", 1);
+}
+
+/* callback to initialize the contents of the MyShmem area at startup */
+static void
+my_shmem_init(void *arg)
 {
     ... initialize contents of shared memory ...
-    ptr->locks = GetNamedLWLockTranche("my tranche name");
+    MyShmem->locks = GetNamedLWLockTranche("my tranche name");
 }
-LWLockRelease(AddinShmemInitLock);
+
 </programlisting>
-      <literal>shmem_startup_hook</literal> provides a convenient place for the
-      initialization code, but it is not strictly required that all such code
-      be placed in this hook.  On Windows (and anywhere else where
-      <literal>EXEC_BACKEND</literal> is defined), each backend executes the
-      registered <literal>shmem_startup_hook</literal> shortly after it
-      attaches to shared memory, so add-ins should still acquire
-      <function>AddinShmemInitLock</function> within this hook, as shown in the
-      example above.  On other platforms, only the postmaster process executes
-      the <literal>shmem_startup_hook</literal>, and each backend automatically
-      inherits the pointers to shared memory.
+      The <function>ShmemRegisterStruct()</function> call doesn't immediately
+      allocate or initialize the memory, it merely reserves the space for when
+      the shared memory segment is allocated later in the startup sequence.
+      When the memory is allocated, the registered
+      <function>init_fn</function> callback is called to initialize it.
+     </para>
+     <para>
+       The <function>init_fn()</function> callback is normally called at
+       postmaster startup, when no other processes are running yet and no
+       locking is required.  However, if shared memory area is registered
+       after system start, e.g. in an extension that is not in
+       <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
+       <function>ShmemRegisterStruct()</function> will immediately call
+       the <function>init_fn</function> callback.  In that case, it holds a
+       lock internally that prevents concurrent shmem allocations.
+     </para>
+     <para>
+      On Windows, the <literal>attach_fn</literal> callback is additionally
+      called at every backend startup.  It can be used for initializing
+      additional per-backend state related to the shared memory area that is
+      inherited via <function>fork()</function> on other systems.  On other
+      platforms, the <literal>attach_fn</literal> callback is only called for
+      structs that are registered after system startup.
      </para>
-
      <para>
-      An example of a <literal>shmem_request_hook</literal> and
+      An complete example of a <literal>shmem_request_hook</literal> and
       <literal>shmem_startup_hook</literal> can be found in
       <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in
       the <productname>PostgreSQL</productname> source tree.
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index e7699be55aa..42322f9a9e1 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -329,6 +329,8 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 
 	InitializeFastPathLocks();
 
+	RegisterShmemStructs();
+
 	CreateSharedMemoryAndSemaphores();
 
 	/*
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 30357845729..fecae827e5b 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -49,6 +49,7 @@
 #include "replication/walreceiver.h"
 #include "storage/dsm.h"
 #include "storage/io_worker.h"
+#include "storage/ipc.h"
 #include "storage/pg_shmem.h"
 #include "tcop/backend_startup.h"
 #include "utils/memutils.h"
@@ -677,7 +678,10 @@ SubPostmasterMain(int argc, char *argv[])
 
 	/* Restore basic shared memory pointers */
 	if (UsedShmemSegAddr != NULL)
+	{
 		InitShmemAllocator(UsedShmemSegAddr);
+		RegisterShmemStructs();
+	}
 
 	/*
 	 * Run the appropriate Main function
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3fac46c402b..702c646f0d7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -958,6 +958,9 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	InitializeFastPathLocks();
 
+	/* Register the shared memory needs of all core subsystems. */
+	RegisterShmemStructs();
+
 	/*
 	 * Give preloaded libraries a chance to request additional shared memory.
 	 */
@@ -3236,6 +3239,8 @@ PostmasterStateMachine(void)
 		LocalProcessControlFile(true);
 
 		/* re-create shared memory and semaphores */
+		ResetShmemAllocator();
+		RegisterShmemStructs();
 		CreateSharedMemoryAndSemaphores();
 
 		UpdatePMState(PM_STARTUP);
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 1f7e933d500..97b2de994f2 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -98,10 +98,12 @@ CalculateShmemSize(void)
 	 * during the actual allocation phase.
 	 */
 	size = 100000;
-	size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
-											 sizeof(ShmemIndexEnt)));
+	size = add_size(size, ShmemRegisteredSize());
+
 	size = add_size(size, dsm_estimate_size());
 	size = add_size(size, DSMRegistryShmemSize());
+
+	/* legacy subsystems */
 	size = add_size(size, BufferManagerShmemSize());
 	size = add_size(size, LockManagerShmemSize());
 	size = add_size(size, PredicateLockShmemSize());
@@ -217,6 +219,10 @@ CreateSharedMemoryAndSemaphores(void)
 	 */
 	InitShmemAllocator(seghdr);
 
+	/* Reserve space for semaphores. */
+	if (!IsUnderPostmaster)
+		PGReserveSemaphores(ProcGlobalSemas());
+
 	/* Initialize subsystems */
 	CreateOrAttachShmemStructs();
 
@@ -230,6 +236,22 @@ CreateSharedMemoryAndSemaphores(void)
 		shmem_startup_hook();
 }
 
+/*
+ * Early initialization of various subsystems, giving them a change to
+ * register their shared memory needs before the shared memory segment is
+ * allocated.
+ */
+void
+RegisterShmemStructs(void)
+{
+	/*
+	 * TODO: Not used in any built-in subsystems yet.  In the future, most of
+	 * the calls *ShmemInit() calls in CreateOrAttachShmemStructs(), and
+	 * *ShmemSize() calls in CalculateShmemSize() will be replaced by calls
+	 * into the subsystems from here.
+	 */
+}
+
 /*
  * Initialize various subsystems, setting up their data structures in
  * shared memory.
@@ -248,16 +270,27 @@ CreateSharedMemoryAndSemaphores(void)
 static void
 CreateOrAttachShmemStructs(void)
 {
-	/*
-	 * Now initialize LWLocks, which do shared memory allocation and are
-	 * needed for InitShmemIndex.
-	 */
-	CreateLWLocks();
-
-	/*
-	 * Set up shmem.c index hashtable
-	 */
-	InitShmemIndex();
+#ifdef EXEC_BACKEND
+	if (IsUnderPostmaster)
+	{
+		/*
+		 * ShmemAttachRegistered() uses LWLocks. Fortunately, LWLocks don't
+		 * need any special attaching.
+		 */
+		ShmemAttachRegistered();
+	}
+	else
+#endif
+	{
+		/*
+		 * Initialize LWLocks first, in case any of the shmem init function
+		 * use LWLocks.  (Nothing else can be running during startup though,
+		 * so it's pretty useless for them to do any locking, but we still
+		 * allow it.)
+		 */
+		CreateLWLocks();
+		ShmemInitRegistered();
+	}
 
 	dsm_shmem_init();
 	DSMRegistryShmemInit();
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index cfdd92bb2b5..c20bec2bafb 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -19,48 +19,95 @@
  * methods).  The routines in this file are used for allocating and
  * binding to shared memory data structures.
  *
- * NOTES:
- *		(a) There are three kinds of shared memory data structures
- *	available to POSTGRES: fixed-size structures, queues and hash
- *	tables.  Fixed-size structures contain things like global variables
- *	for a module and should never be allocated after the shared memory
- *	initialization phase.  Hash tables have a fixed maximum size, but
- *	their actual size can vary dynamically.  When entries are added
- *	to the table, more space is allocated.  Queues link data structures
- *	that have been allocated either within fixed-size structures or as hash
- *	buckets.  Each shared data structure has a string name to identify
- *	it (assigned in the module that declares it).
- *
- *		(b) During initialization, each module looks for its
- *	shared data structures in a hash table called the "Shmem Index".
- *	If the data structure is not present, the caller can allocate
- *	a new one and initialize it.  If the data structure is present,
- *	the caller "attaches" to the structure by initializing a pointer
- *	in the local address space.
- *		The shmem index has two purposes: first, it gives us
- *	a simple model of how the world looks when a backend process
- *	initializes.  If something is present in the shmem index,
- *	it is initialized.  If it is not, it is uninitialized.  Second,
- *	the shmem index allows us to allocate shared memory on demand
- *	instead of trying to preallocate structures and hard-wire the
- *	sizes and locations in header files.  If you are using a lot
- *	of shared memory in a lot of different places (and changing
- *	things during development), this is important.
- *
- *		(c) In standard Unix-ish environments, individual backends do not
- *	need to re-establish their local pointers into shared memory, because
- *	they inherit correct values of those variables via fork() from the
- *	postmaster.  However, this does not work in the EXEC_BACKEND case.
- *	In ports using EXEC_BACKEND, new backends have to set up their local
- *	pointers using the method described in (b) above.
- *
- *		(d) memory allocation model: shared memory can never be
- *	freed, once allocated.   Each hash table has its own free list,
- *	so hash buckets can be reused when an item is deleted.  However,
- *	if one hash table grows very large and then shrinks, its space
- *	cannot be redistributed to other tables.  We could build a simple
- *	hash bucket garbage collector if need be.  Right now, it seems
- *	unnecessary.
+ * There are two kinds of shared memory data structures: fixed-size structures
+ * and hash tables.  Fixed-size structures contain things like global
+ * variables for a module and should never be allocated after the shared
+ * memory initialization phase.  Hash tables have a fixed maximum size, but
+ * their actual size can vary dynamically.  When entries are added to the
+ * table, more space is allocated. Each shared data structure and hash has a
+ * string name to identify it, specified in the descriptor when its
+ * registered.
+ *
+ * Shared memory structures (and hash table entries) are mapped to the address
+ * in each backend process, so you can safely use pointers to other parts of
+ * shared memory in the shared memory structures.
+ *
+ * Shared memory can never be freed, once allocated.  Each hash table has its
+ * own free list, so hash buckets can be reused when an item is deleted.
+ * However, if one hash table grows very large and then shrinks, its space
+ * cannot be redistributed to other tables.  We could build a simple hash
+ * bucket garbage collector if need be.  Right now, it seems unnecessary.
+ *
+ * Usage
+ * -----
+ *
+ * To allocate a shared memory area, fill in the name, size, and any other
+ * options in ShmemStructDesc, and call ShmemRegisterStruct().  Leave any
+ * unused fields as zeros.
+ *
+ *	typedef struct MyShmemData {
+ *		...
+ *	} MyShmemData;
+ *
+ *	static MyShmemData *MyShmem;
+ *
+ *	static void my_shmem_init(void *arg);
+ *
+ *	static ShmemStructDesc MyShmemDesc = {
+ *		.name = "My shmem area",
+ *		.size = sizeof(MyShmemData),
+ *		.init_fn = my_shmem_init,
+ *		.ptr = &MyShmem,
+ *	};
+ *
+ * In the subsystem's initialization code, or in _PG_init() or shmem_request_hook
+ * in extensions, call ShmemRegisterStruct():
+ *
+ *	ShmemRegisterStruct(&MyShmemDesc)
+ *
+ *
+ * Lifecycle
+ * ---------
+ *
+ * RegisterShmemStructs() is called at postmaster startup, before deciding the
+ * size of the global shared memory segment. To add a new shared memory area,
+ * call its Register function from RegisterShmemStructs().
+ *
+ * Once all the registrations have been done, postmaster calls
+ * ShmemRegisteredSize() to add up the sizes of all the registered areas
+ *
+ * After allocating the shared memory segment, postmaster calls
+ * ShmemInitRegistered(), which calls the init_fn callbacks of each registered
+ * area, in the order that they were registered.
+ *
+ * In standard Unix-ish environments, individual backends do not need to
+ * re-establish their local pointers into shared memory, because they inherit
+ * correct values of those variables via fork() from the postmaster.  However,
+ * this does not work in the EXEC_BACKEND case.  In ports using EXEC_BACKEND,
+ * backend startup also calls RegisterShmemStructs(), followed by
+ * ShmemAttachRegistered(), which re-establishes the pointer variables
+ * (*ShmemStructDesc->ptr), and calls the attach_fn callback, if any, for
+ * additional per-backend setup.
+ *
+ * Legacy ShmemInitStruct()/ShmemInitHash() functions
+ * --------------------------------------------------
+ *
+ * ShmemInitStruct()/ShmemInitHash() is another way of registring shmem
+ * areas. It pre-dates the ShmemRegisterStruct()/ShmemRegisterHash()
+ * functions, and should not be used in new code, but as of this writing it is
+ * still widely used in extensions.
+ *
+ * To allocate a shmem area with ShmemInitStruct(), you need to separately
+ * register the size needed for the area by calling RequestAddinShmemSpace()
+ * from the extension's shmem_request_hook, and allocate the area by calling
+ * ShmemInitStruct() from the extension's shmem_startup_hook.  There are no
+ * init/attach callbacks; the caller of ShmemInitStruct() must check the
+ * return status of ShmemInitStruct() and initialize the struct if it was not
+ * previously initialized.
+ *
+ *
+ * More legacy: Calling ShmemAlloc() directly
+ * ------------------------------------------
  */
 
 #include "postgres.h"
@@ -76,6 +123,24 @@
 #include "storage/spin.h"
 #include "utils/builtins.h"
 
+/*
+ * Array of registered shared memory areas.
+ *
+ * This is in process private memory, although on Unix-like systems, we expect
+ * all the registrations to happen at postmaster startup time, and be
+ * inherited by all the child processes. Extensions may register additional
+ * areas after startup, but only areas registered at postmaster startup are
+ * included in the estimate for the total memory needed for shared memory.  If
+ * any non-trivial allocations are made after startup, there might not be
+ * enough shared memory available.
+ */
+static ShmemStructDesc **registered_shmem_areas;
+static int num_registered_shmem_areas = 0;
+static int max_registered_shmem_areas = 0;	/* allocated size of the array */
+
+/* estimated size of registered_shmem_areas (not a hard limit) */
+#define INITIAL_REGISTRY_SIZE		 (64)
+
 /*
  * This is the first data structure stored in the shared memory segment, at
  * the offset that PGShmemHeader->content_offset points to.  Allocations by
@@ -95,6 +160,9 @@ typedef struct ShmemAllocatorData
 
 static void *ShmemAllocRaw(Size size, Size *allocated_size);
 
+static void shmem_hash_init(void *arg);
+static void shmem_hash_attach(void *arg);
+
 /* shared memory global variables */
 
 static PGShmemHeader *ShmemSegHdr;	/* shared mem segment header */
@@ -103,24 +171,328 @@ static void *ShmemEnd;			/* end+1 address of shared memory */
 
 static ShmemAllocatorData *ShmemAllocator;
 slock_t    *ShmemLock;			/* points to ShmemAllocator->shmem_lock */
-static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */
+
+
+/*
+ * ShmemIndex is a global directory of shmem areas, itself also stored in the
+ * shared memory.
+ */
+static HTAB *ShmemIndex;
+
+ /* max size of data structure string name */
+#define SHMEM_INDEX_KEYSIZE		 (48)
+
+/*
+ * # of additional entries to reserve in the shmem index table, for allocations
+ * after postmaster startup (not a hard limit)
+ */
+#define SHMEM_INDEX_ADDITIONAL_SIZE		 (64)
+
+/* this is a hash bucket in the shmem index table */
+typedef struct
+{
+	char		key[SHMEM_INDEX_KEYSIZE];	/* string name */
+	void	   *location;		/* location in shared mem */
+	Size		size;			/* # bytes requested for the structure */
+	Size		allocated_size; /* # bytes actually allocated */
+} ShmemIndexEnt;
 
 /* To get reliable results for NUMA inquiry we need to "touch pages" once */
 static bool firstNumaTouch = true;
 
 Datum		pg_numa_available(PG_FUNCTION_ARGS);
 
+static bool shmem_initialized = false;
+
+/*
+ *	ShmemRegisterStruct() --- register a shared memory struct
+ *
+ * Subsystems call this to register their shared memory needs.  That should be
+ * done early in postmaster startup, before the shared memory segment has been
+ * created, so that the size can be included in the estimate for total amount
+ * of shared memory needed.  We set aside a small amount of memory for
+ * allocations that happen later, for the benefit of non-preloaded extensions,
+ * but that should not be relied upon.
+ *
+ * In core subsystems, each subsystem's registration functions is called from
+ * RegisterShmemStructs().  In extensions, this should be called from the
+ * _PG_init() initializer or the 'shmem_request_hook'. In EXEC_BACKEND mode,
+ * this also needs to be called in each child process, to reattach and set the
+ * pointer to the shared memory area, usually in a global variable.  Calling
+ * this from the _PG_init() initializer or the 'shmem_request_hook' takes
+ * care of that too.
+ *
+ * Returns true if the struct was already initialized in shared memory, and
+ * we merely attached to it.
+ */
+bool
+ShmemRegisterStruct(ShmemStructDesc *desc)
+{
+	bool		found;
+
+	/* Check that it's not already registered in this process */
+	for (int i = 0; i < num_registered_shmem_areas; i++)
+	{
+		ShmemStructDesc *existing = registered_shmem_areas[i];
+
+		if (strcmp(existing->name, desc->name) == 0)
+			elog(ERROR, "shared memory struct \"%s\" is already registered",
+				 desc->name);
+	}
+
+	/* desc->ptr can be non-NULL when re-initializing after crash */
+	if (desc->ptr)
+		*desc->ptr = NULL;
+
+	/* Add the descriptor to the array, growing the array if needed */
+	if (num_registered_shmem_areas == max_registered_shmem_areas)
+	{
+		int			new_size;
+
+		if (registered_shmem_areas)
+		{
+			new_size = max_registered_shmem_areas * 2;
+			registered_shmem_areas = repalloc(registered_shmem_areas,
+											  new_size * sizeof(ShmemStructDesc *));
+		}
+		else
+		{
+			new_size = INITIAL_REGISTRY_SIZE;
+			registered_shmem_areas = MemoryContextAlloc(TopMemoryContext,
+														new_size * sizeof(ShmemStructDesc *));
+		}
+		max_registered_shmem_areas = new_size;
+	}
+	registered_shmem_areas[num_registered_shmem_areas++] = desc;
+
+	/*
+	 * If called after postmaster startup, we need to immediately also
+	 * initialize or attach to the area.
+	 */
+	if (shmem_initialized)
+	{
+		ShmemIndexEnt *index_entry;
+
+		LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+
+		/* look it up in the shmem index */
+		index_entry = (ShmemIndexEnt *)
+			hash_search(ShmemIndex, desc->name, HASH_ENTER_NULL, &found);
+		if (!index_entry)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OUT_OF_MEMORY),
+					 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
+							desc->name)));
+		}
+		if (found)
+		{
+			/* Already present, just attach to it */
+			if (index_entry->size != desc->size)
+				elog(ERROR, "shared memory struct \"%s\" is already registered with different size",
+					 desc->name);
+			if (desc->ptr)
+				*desc->ptr = index_entry->location;
+			if (desc->attach_fn)
+				desc->attach_fn(desc->attach_fn_arg);
+		}
+		else
+		{
+			/* This is the first time. Initialize it like ShmemInitRegistered() would */
+			size_t		allocated_size;
+			void	   *structPtr;
+
+			structPtr = ShmemAllocRaw(desc->size, &allocated_size);
+			if (structPtr == NULL)
+			{
+				/* out of memory; remove the failed ShmemIndex entry */
+				hash_search(ShmemIndex, desc->name, HASH_REMOVE, NULL);
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("not enough shared memory for data structure"
+								" \"%s\" (%zu bytes requested)",
+								desc->name, desc->size)));
+			}
+			index_entry->size = desc->size;
+			index_entry->allocated_size = allocated_size;
+			index_entry->location = structPtr;
+			if (desc->ptr)
+				*desc->ptr = index_entry->location;
+
+			/* XXX: if this errors out, the areas is left in a half-initialized state */
+			if (desc->init_fn)
+				desc->init_fn(desc->init_fn_arg);
+		}
+
+		LWLockRelease(ShmemIndexLock);
+	}
+	else
+		found = false;
+
+	return found;
+}
+
+/*
+ *	ShmemRegisteredSize() --- estimate the total size of all pre-registered
+ *                            shared memory structures.
+ *
+ * This runs once at postmaster startup, before the shared memory segment has
+ * been created.
+ */
+size_t
+ShmemRegisteredSize(void)
+{
+	size_t		size;
+
+	/* memory needed for the ShmemIndex */
+	size = hash_estimate_size(num_registered_shmem_areas + SHMEM_INDEX_ADDITIONAL_SIZE,
+							  sizeof(ShmemIndexEnt));
+
+	/* memory needed for all the registered areas */
+	for (int i = 0; i < num_registered_shmem_areas; i++)
+	{
+		ShmemStructDesc *desc = registered_shmem_areas[i];
+
+		size = add_size(size, desc->size);
+		size = add_size(size, desc->extra_size);
+	}
+
+	return size;
+}
+
+/*
+ *	ShmemInitRegistered() --- allocate and initialize pre-registered shared
+ *                            memory structures.
+ *
+ * This runs once at postmaster startup, after the shared memory segment has
+ * been created.
+ */
+void
+ShmemInitRegistered(void)
+{
+	/* Should be called only by the postmaster or a standalone backend. */
+	Assert(!IsUnderPostmaster);
+	Assert(!shmem_initialized);
+
+	/*
+	 * Initialize all the registered memory areas.  There are no concurrent
+	 * processes yet, so no need for locking.
+	 */
+	for (int i = 0; i < num_registered_shmem_areas; i++)
+	{
+		ShmemStructDesc *desc = registered_shmem_areas[i];
+		size_t		allocated_size;
+		void	   *structPtr;
+		bool		found;
+		ShmemIndexEnt *index_entry;
+
+		/* look it up in the shmem index */
+		index_entry = (ShmemIndexEnt *)
+			hash_search(ShmemIndex, desc->name, HASH_ENTER_NULL, &found);
+		if (!index_entry)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OUT_OF_MEMORY),
+					 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
+							desc->name)));
+		}
+		if (found)
+			elog(ERROR, "shmem struct \"%s\" is already initialized", desc->name);
+
+		/* allocate and initialize it */
+		structPtr = ShmemAllocRaw(desc->size, &allocated_size);
+		if (structPtr == NULL)
+		{
+			/* out of memory; remove the failed ShmemIndex entry */
+			hash_search(ShmemIndex, desc->name, HASH_REMOVE, NULL);
+			ereport(ERROR,
+					(errcode(ERRCODE_OUT_OF_MEMORY),
+					 errmsg("not enough shared memory for data structure"
+							" \"%s\" (%zu bytes requested)",
+							desc->name, desc->size)));
+		}
+		index_entry->size = desc->size;
+		index_entry->allocated_size = allocated_size;
+		index_entry->location = structPtr;
+
+		*(desc->ptr) = structPtr;
+		if (desc->init_fn)
+			desc->init_fn(desc->init_fn_arg);
+	}
+
+	shmem_initialized = true;
+}
+
+/*
+ * Call the attach_fn callbacks of all registered
+ */
+#ifdef EXEC_BACKEND
+void
+ShmemAttachRegistered(void)
+{
+	/* Must be initializing a (non-standalone) backend */
+	Assert(IsUnderPostmaster);
+	Assert(ShmemAllocator->index != NULL);
+
+	/* XXX: document this locking with attach_fn */
+	LWLockAcquire(ShmemIndexLock, LW_SHARED);
+
+	for (int i = 0; i < num_registered_shmem_areas; i++)
+	{
+		ShmemStructDesc *desc = registered_shmem_areas[i];
+		bool		found;
+		ShmemIndexEnt *result;
+
+		/* look it up in the shmem index */
+		result = (ShmemIndexEnt *)
+			hash_search(ShmemIndex, desc->name, HASH_FIND, &found);
+		if (!found)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OUT_OF_MEMORY),
+					 errmsg("could not find ShmemIndex entry for data structure \"%s\"",
+							desc->name)));
+		}
+
+		if (desc->ptr)
+			*desc->ptr = result->location;
+		if (desc->attach_fn)
+			desc->attach_fn(desc->attach_fn_arg);
+	}
+
+	LWLockRelease(ShmemIndexLock);
+
+	shmem_initialized = true;
+}
+#endif
+
+void
+ResetShmemAllocator(void)
+{
+	shmem_initialized = false;
+	num_registered_shmem_areas = 0;
+
+	/* FIXME: this leaks the allocations in TopMemoryContext */
+}
+
 /*
  *	InitShmemAllocator() --- set up basic pointers to shared memory.
  *
  * Called at postmaster or stand-alone backend startup, to initialize the
  * allocator's data structure in the shared memory segment.  In EXEC_BACKEND,
- * this is also called at backend startup, to set up pointers to the shared
- * memory areas.
+ * also called at backend startup, to set up pointers to the
+ * already-initialized data structure.
  */
 void
 InitShmemAllocator(PGShmemHeader *seghdr)
 {
+	Size		offset;
+	int			hash_size;
+	HASHCTL		info;
+	int			hash_flags;
+	size_t		size;
+
+	Assert(!shmem_initialized);
 	Assert(seghdr != NULL);
 
 	/*
@@ -134,41 +506,47 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 	ShmemBase = seghdr;
 	ShmemEnd = (char *) ShmemBase + seghdr->totalsize;
 
-#ifndef EXEC_BACKEND
-	Assert(!IsUnderPostmaster);
-#endif
-	if (IsUnderPostmaster)
-	{
-		PGShmemHeader *shmhdr = ShmemSegHdr;
-
-		ShmemAllocator = (ShmemAllocatorData *) ((char *) shmhdr + shmhdr->content_offset);
-		ShmemLock = &ShmemAllocator->shmem_lock;
-	}
-	else
-	{
-		Size		offset;
-
-		/*
-		 * Allocations after this point should go through ShmemAlloc, which
-		 * expects to allocate everything on cache line boundaries.  Make sure
-		 * the first allocation begins on a cache line boundary.
-		 */
-		offset = CACHELINEALIGN(seghdr->content_offset + sizeof(ShmemAllocatorData));
-		if (offset > seghdr->totalsize)
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of shared memory (%zu bytes requested)",
-							offset)));
+	/*
+	 * Allocations after this point should go through ShmemAlloc, which
+	 * expects to allocate everything on cache line boundaries.  Make sure
+	 * the first allocation begins on a cache line boundary.
+	 */
+	offset = CACHELINEALIGN(seghdr->content_offset + sizeof(ShmemAllocatorData));
+	if (offset > seghdr->totalsize)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of shared memory (%zu bytes requested)",
+						offset)));
 
-		ShmemAllocator = (ShmemAllocatorData *) ((char *) seghdr + seghdr->content_offset);
+	ShmemAllocator = (ShmemAllocatorData *) ((char *) seghdr + seghdr->content_offset);
+	ShmemLock = &ShmemAllocator->shmem_lock;
 
+	if (!IsUnderPostmaster)
+	{
 		SpinLockInit(&ShmemAllocator->shmem_lock);
-		ShmemLock = &ShmemAllocator->shmem_lock;
 		ShmemAllocator->free_offset = offset;
-		/* ShmemIndex can't be set up yet (need LWLocks first) */
-		ShmemAllocator->index = NULL;
-		ShmemIndex = (HTAB *) NULL;
 	}
+
+	/*
+	 * Create (or attach to) the shared memory index of shmem areas.
+	 */
+	hash_size = num_registered_shmem_areas + SHMEM_INDEX_ADDITIONAL_SIZE;
+
+	info.keysize = SHMEM_INDEX_KEYSIZE;
+	info.entrysize = sizeof(ShmemIndexEnt);
+	info.dsize = info.max_dsize = hash_select_dirsize(hash_size);
+	info.alloc = ShmemAllocNoError;
+	hash_flags = HASH_ELEM | HASH_STRINGS | HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE;
+	if (!IsUnderPostmaster)
+	{
+		size = hash_get_shared_size(&info, hash_flags);
+		ShmemAllocator->index = (HASHHDR *) ShmemAlloc(size);
+	}
+	else
+		hash_flags |= HASH_ATTACH;
+	info.hctl = ShmemAllocator->index;
+	ShmemIndex = hash_create("ShmemIndex", hash_size, &info, hash_flags);
+	Assert(ShmemIndex != NULL);
 }
 
 /*
@@ -268,67 +646,18 @@ ShmemAddrIsValid(const void *addr)
 }
 
 /*
- *	InitShmemIndex() --- set up or attach to shmem index table.
- */
-void
-InitShmemIndex(void)
-{
-	HASHCTL		info;
-
-	/*
-	 * Create the shared memory shmem index.
-	 *
-	 * Since ShmemInitHash calls ShmemInitStruct, which expects the ShmemIndex
-	 * hashtable to exist already, we have a bit of a circularity problem in
-	 * initializing the ShmemIndex itself.  The special "ShmemIndex" hash
-	 * table name will tell ShmemInitStruct to fake it.
-	 */
-	info.keysize = SHMEM_INDEX_KEYSIZE;
-	info.entrysize = sizeof(ShmemIndexEnt);
-
-	ShmemIndex = ShmemInitHash("ShmemIndex",
-							   SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE,
-							   &info,
-							   HASH_ELEM | HASH_STRINGS);
-}
-
-/*
- * ShmemInitHash -- Create and initialize, or attach to, a
- *		shared memory hash table.
- *
- * We assume caller is doing some kind of synchronization
- * so that two processes don't try to create/initialize the same
- * table at once.  (In practice, all creations are done in the postmaster
- * process; child processes should always be attaching to existing tables.)
- *
- * max_size is the estimated maximum number of hashtable entries.  This is
- * not a hard limit, but the access efficiency will degrade if it is
- * exceeded substantially (since it's used to compute directory size and
- * the hash table buckets will get overfull).
- *
- * init_size is the number of hashtable entries to preallocate.  For a table
- * whose maximum size is certain, this should be equal to max_size; that
- * ensures that no run-time out-of-shared-memory failures can occur.
+ * ShmemRegisterHash -- Register a shared memory hash table.
  *
  * *infoP and hash_flags must specify at least the entry sizes and key
  * comparison semantics (see hash_create()).  Flag bits and values specific
  * to shared-memory hash tables are added here, except that callers may
  * choose to specify HASH_PARTITION and/or HASH_FIXED_SIZE.
- *
- * Note: before Postgres 9.0, this function returned NULL for some failure
- * cases.  Now, it always throws error instead, so callers need not check
- * for NULL.
  */
-HTAB *
-ShmemInitHash(const char *name,		/* table string name for shmem index */
-			  int64 init_size,	/* initial table size */
-			  int64 max_size,	/* max size of the table */
-			  HASHCTL *infoP,	/* info about key and bucket size */
-			  int hash_flags)	/* info about infoP */
+bool
+ShmemRegisterHash(ShmemHashDesc *desc,		/* configuration */
+				  HASHCTL *infoP,	/* info about key and bucket size */
+				  int hash_flags)	/* info about infoP */
 {
-	bool		found;
-	void	   *location;
-
 	/*
 	 * Hash tables allocated in shared memory have a fixed directory; it can't
 	 * grow or other backends wouldn't be able to find it. So, make sure we
@@ -336,145 +665,62 @@ ShmemInitHash(const char *name,		/* table string name for shmem index */
 	 *
 	 * The shared memory allocator must be specified too.
 	 */
-	infoP->dsize = infoP->max_dsize = hash_select_dirsize(max_size);
+	infoP->dsize = infoP->max_dsize = hash_select_dirsize(desc->max_size);
 	infoP->alloc = ShmemAllocNoError;
 	hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE;
 
 	/* look it up in the shmem index */
-	location = ShmemInitStruct(name,
-							   hash_get_shared_size(infoP, hash_flags),
-							   &found);
+	memset(&desc->base_desc, 0, sizeof(desc->base_desc));
+	desc->base_desc.name = desc->name;
+	desc->base_desc.size = hash_get_shared_size(infoP, hash_flags);
+	desc->base_desc.init_fn = shmem_hash_init;
+	desc->base_desc.init_fn_arg = desc;
+	desc->base_desc.attach_fn = shmem_hash_attach;
+	desc->base_desc.attach_fn_arg = desc;
 
 	/*
-	 * if it already exists, attach to it rather than allocate and initialize
-	 * new space
+	 * We need a stable pointer to hold the pointer to the shared memory. Use
+	 * the one passed in the descriptor now. It will be replaced with the hash
+	 * table header by init or attach function.
 	 */
-	if (found)
-		hash_flags |= HASH_ATTACH;
+	desc->base_desc.ptr = (void **) desc->ptr;
 
-	/* Pass location of hashtable header to hash_create */
-	infoP->hctl = (HASHHDR *) location;
+	desc->base_desc.extra_size = hash_estimate_size(desc->max_size, infoP->entrysize) - desc->base_desc.size;
+
+	desc->hash_flags = hash_flags;
+	desc->infoP = MemoryContextAlloc(TopMemoryContext, sizeof(HASHCTL));
+	memcpy(desc->infoP, infoP, sizeof(HASHCTL));
 
-	return hash_create(name, init_size, infoP, hash_flags);
+	return ShmemRegisterStruct(&desc->base_desc);
 }
 
-/*
- * ShmemInitStruct -- Create/attach to a structure in shared memory.
- *
- *		This is called during initialization to find or allocate
- *		a data structure in shared memory.  If no other process
- *		has created the structure, this routine allocates space
- *		for it.  If it exists already, a pointer to the existing
- *		structure is returned.
- *
- *	Returns: pointer to the object.  *foundPtr is set true if the object was
- *		already in the shmem index (hence, already initialized).
- *
- *	Note: before Postgres 9.0, this function returned NULL for some failure
- *	cases.  Now, it always throws error instead, so callers need not check
- *	for NULL.
- */
-void *
-ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+static void
+shmem_hash_init(void *arg)
 {
-	ShmemIndexEnt *result;
-	void	   *structPtr;
-
-	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+	ShmemHashDesc *desc = (ShmemHashDesc *) arg;
+	int			hash_flags = desc->hash_flags;
 
-	if (!ShmemIndex)
-	{
-		/* Must be trying to create/attach to ShmemIndex itself */
-		Assert(strcmp(name, "ShmemIndex") == 0);
-
-		if (IsUnderPostmaster)
-		{
-			/* Must be initializing a (non-standalone) backend */
-			Assert(ShmemAllocator->index != NULL);
-			structPtr = ShmemAllocator->index;
-			*foundPtr = true;
-		}
-		else
-		{
-			/*
-			 * If the shmem index doesn't exist, we are bootstrapping: we must
-			 * be trying to init the shmem index itself.
-			 *
-			 * Notice that the ShmemIndexLock is released before the shmem
-			 * index has been initialized.  This should be OK because no other
-			 * process can be accessing shared memory yet.
-			 */
-			Assert(ShmemAllocator->index == NULL);
-			structPtr = ShmemAlloc(size);
-			ShmemAllocator->index = structPtr;
-			*foundPtr = false;
-		}
-		LWLockRelease(ShmemIndexLock);
-		return structPtr;
-	}
-
-	/* look it up in the shmem index */
-	result = (ShmemIndexEnt *)
-		hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr);
-
-	if (!result)
-	{
-		LWLockRelease(ShmemIndexLock);
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
-						name)));
-	}
-
-	if (*foundPtr)
-	{
-		/*
-		 * Structure is in the shmem index so someone else has allocated it
-		 * already.  The size better be the same as the size we are trying to
-		 * initialize to, or there is a name conflict (or worse).
-		 */
-		if (result->size != size)
-		{
-			LWLockRelease(ShmemIndexLock);
-			ereport(ERROR,
-					(errmsg("ShmemIndex entry size is wrong for data structure"
-							" \"%s\": expected %zu, actual %zu",
-							name, size, result->size)));
-		}
-		structPtr = result->location;
-	}
-	else
-	{
-		Size		allocated_size;
+	/* Pass location of hashtable header to hash_create */
+	desc->infoP->hctl = (HASHHDR *) *desc->base_desc.ptr;
 
-		/* It isn't in the table yet. allocate and initialize it */
-		structPtr = ShmemAllocRaw(size, &allocated_size);
-		if (structPtr == NULL)
-		{
-			/* out of memory; remove the failed ShmemIndex entry */
-			hash_search(ShmemIndex, name, HASH_REMOVE, NULL);
-			LWLockRelease(ShmemIndexLock);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("not enough shared memory for data structure"
-							" \"%s\" (%zu bytes requested)",
-							name, size)));
-		}
-		result->size = size;
-		result->allocated_size = allocated_size;
-		result->location = structPtr;
-	}
+	*desc->ptr = hash_create(desc->name, desc->init_size, desc->infoP, hash_flags);
+}
 
-	LWLockRelease(ShmemIndexLock);
+static void
+shmem_hash_attach(void *arg)
+{
+	ShmemHashDesc *desc = (ShmemHashDesc *) arg;
+	int			hash_flags = desc->hash_flags;
 
-	Assert(ShmemAddrIsValid(structPtr));
+	/* attach to it rather than allocate and initialize new space */
+	hash_flags |= HASH_ATTACH;
 
-	Assert(structPtr == (void *) CACHELINEALIGN(structPtr));
+	/* Pass location of hashtable header to hash_create */
+	desc->infoP->hctl = (HASHHDR *) *desc->base_desc.ptr;
 
-	return structPtr;
+	*desc->ptr = hash_create(desc->name, desc->init_size, desc->infoP, hash_flags);
 }
 
-
 /*
  * Add two Size values, checking for overflow
  */
@@ -761,3 +1007,82 @@ pg_numa_available(PG_FUNCTION_ARGS)
 {
 	PG_RETURN_BOOL(pg_numa_init() != -1);
 }
+
+/*
+ * ShmemInitStruct -- Create/attach to a structure in shared memory.
+ *
+ *		This is called during initialization to find or allocate
+ *		a data structure in shared memory.  If no other process
+ *		has created the structure, this routine allocates space
+ *		for it.  If it exists already, a pointer to the existing
+ *		structure is returned.
+ *
+ *	Returns: pointer to the object.  *foundPtr is set true if the object was
+ *		already in the shmem index (hence, already initialized).
+ *
+ * Note: This is a legacy interface, kept for backwards compatibility with
+ * extensions.  Use ShmemRegisterStruct() in new code!
+ */
+void *
+ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+{
+	ShmemStructDesc *desc;
+
+	Assert(shmem_initialized);
+
+	desc = MemoryContextAllocZero(TopMemoryContext, sizeof(ShmemStructDesc) + sizeof(void *));
+	desc->name = name;
+	desc->size = size;
+	desc->ptr = (void *) (((char *) desc) + sizeof(ShmemStructDesc));
+
+	*foundPtr = ShmemRegisterStruct(desc);
+	Assert(*desc->ptr != NULL);
+	return *desc->ptr;
+}
+
+/*
+ * ShmemInitHash -- Create and initialize, or attach to, a
+ *		shared memory hash table.
+ *
+ * We assume caller is doing some kind of synchronization
+ * so that two processes don't try to create/initialize the same
+ * table at once.  (In practice, all creations are done in the postmaster
+ * process; child processes should always be attaching to existing tables.)
+ *
+ * max_size is the estimated maximum number of hashtable entries.  This is
+ * not a hard limit, but the access efficiency will degrade if it is
+ * exceeded substantially (since it's used to compute directory size and
+ * the hash table buckets will get overfull).
+ *
+ * init_size is the number of hashtable entries to preallocate.  For a table
+ * whose maximum size is certain, this should be equal to max_size; that
+ * ensures that no run-time out-of-shared-memory failures can occur.
+ *
+ * *infoP and hash_flags must specify at least the entry sizes and key
+ * comparison semantics (see hash_create()).  Flag bits and values specific
+ * to shared-memory hash tables are added here, except that callers may
+ * choose to specify HASH_PARTITION and/or HASH_FIXED_SIZE.
+ *
+ * Note: This is a legacy interface, kept for backwards compatibility with
+ * extensions.  Use ShmemRegisterHash() in new code!
+ */
+HTAB *
+ShmemInitHash(const char *name,		/* table string name for shmem index */
+			  int64 init_size,	/* initial table size */
+			  int64 max_size,	/* max size of the table */
+			  HASHCTL *infoP,	/* info about key and bucket size */
+			  int hash_flags)	/* info about infoP */
+{
+	ShmemHashDesc *desc;
+
+	Assert(shmem_initialized);
+
+	desc = MemoryContextAllocZero(TopMemoryContext, sizeof(ShmemHashDesc) + sizeof(HTAB *));
+	desc->name = name;
+	desc->init_size = init_size;
+	desc->max_size = max_size;
+	desc->ptr = (HTAB **) (((char *) desc) + sizeof(ShmemHashDesc));
+
+	ShmemRegisterHash(desc, infoP, hash_flags);
+	return *desc->ptr;
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d01a09dd0c4..fa074c419a8 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4162,6 +4162,9 @@ PostgresSingleUserMain(int argc, char *argv[],
 	/* Initialize size of fast-path lock cache. */
 	InitializeFastPathLocks();
 
+	/* Register the shared memory needs of all core subsystems. */
+	RegisterShmemStructs();
+
 	/*
 	 * Give preloaded libraries a chance to request additional shared memory.
 	 */
diff --git a/src/include/storage/ipc.h b/src/include/storage/ipc.h
index da32787ab51..8a3b71ad5d3 100644
--- a/src/include/storage/ipc.h
+++ b/src/include/storage/ipc.h
@@ -77,6 +77,7 @@ extern void check_on_shmem_exit_lists_are_empty(void);
 /* ipci.c */
 extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook;
 
+extern void RegisterShmemStructs(void);
 extern Size CalculateShmemSize(void);
 extern void CreateSharedMemoryAndSemaphores(void);
 #ifdef EXEC_BACKEND
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 89d45287c17..ea1884a8778 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -24,19 +24,138 @@
 #include "storage/spin.h"
 #include "utils/hsearch.h"
 
+typedef void (*ShmemInitCallback) (void *arg);
+typedef void (*ShmemAttachCallback) (void *arg);
+
+/*
+ * ShmemStructDesc describes a named area or struct in shared memory.
+ *
+ * Shared memory is reserved and allocated in a few phases at postmaster
+ * startup, and in EXEC_BACKEND mode, there's some extra work done to "attach"
+ * to them at backend startup. ShmemStructDesc contains all the information
+ * needed to manage the lifecycle.
+ *
+ * 'name', 'size' and the callback functions are filled in by the
+ * ShmemRegisterStruct() caller. After registration, the shmem machinery
+ * reserves the memory for the area, sets *ptr to point to the allocation, and
+ * calls the callbacks at the right moments.
+ */
+typedef struct ShmemStructDesc
+{
+	/* Name of the shared memory area. Must be unique across the system */
+	const char *name;
+
+	/* Size of the shared memory area */
+	size_t		size;
+
+	/*
+	 * Initialization callback function.  This is called when the shared
+	 * memory area is allocated, usually at postmaster startup.  'init_fn_arg'
+	 * is an opaque argument passed to the callback.
+	 */
+	ShmemInitCallback init_fn;
+	void	   *init_fn_arg;
+
+	/*
+	 * Attachment callback function.  In EXEC_BACKEND mode, this is called at
+	 * startup of each backend.  In !EXEC_BACKEND mode, this is only called if
+	 * the shared memory area is registered after postmaster startup.  We
+	 * never do that in core code, but extensions might.
+	 */
+	ShmemInitCallback attach_fn;
+	void	   *attach_fn_arg;
+
+	/*
+	 * Extra space to reserve for the shared memory segment, but it's not part
+	 * of the struct itself.  This is used for shared memory hash tables that
+	 * can grow beyond the initial size when more buckets are allocated.
+	 */
+	size_t		extra_size;
+
+	/*
+	 * When the shmem area is initialized or attached to, pointer to it is
+	 * stored in *ptr.  It usually points to a global variable, used to access
+	 * the shared memory area later.  *ptr is set before the init_fn or
+	 * attach_fn callback is called.
+	 */
+	void	   **ptr;
+} ShmemStructDesc;
+
+/*
+ * Descriptor for named shared memory hash table.
+ *
+ * Similar to ShmemStructDesc, but describes a shared memory hash table.  Each
+ * hash table is backed by an allocated area, described by 'base_desc', but if
+ * 'max_size' is greater than 'init_size', it can also grow beyond the initial
+ * allocated area by allocating more hash entries from the global unreserved
+ * space.
+ */
+typedef struct ShmemHashDesc
+{
+	/* Name of the shared memory area. Must be unique across the system */
+	const char *name;
+
+	/*
+	 * max_size is the estimated maximum number of hashtable entries.  This is
+	 * not a hard limit, but the access efficiency will degrade if it is
+	 * exceeded substantially (since it's used to compute directory size and
+	 * the hash table buckets will get overfull).
+	 */
+	size_t		max_size;
+
+	/*
+	 * init_size is the number of hashtable entries to preallocate.  For a table
+	 * whose maximum size is certain, this should be equal to max_size; that
+	 * ensures that no run-time out-of-shared-memory failures can occur.
+	 */
+	size_t		init_size;
+
+	/* Hash table options passed to hash_create() */
+	HASHCTL	   *infoP;
+	int			hash_flags;
+
+	/*
+	 * When the hash table is initialized or attached to, pointer to it is
+	 * stored in *ptr.  It usually points to a global variable, used to access
+	 * the shared hash table later.
+	 */
+	HTAB	   **ptr;
+
+	/*
+	 * Descriptor for the underlying "area".  Callers of ShmemRegisterHash()
+	 * do not need to touch this, it is filled in by ShmemRegisterHash() based
+	 * on the hash table parameters.
+	 */
+	ShmemStructDesc	base_desc;
+} ShmemHashDesc;
 
 /* shmem.c */
 extern PGDLLIMPORT slock_t *ShmemLock;
 typedef struct PGShmemHeader PGShmemHeader; /* avoid including
 											 * storage/pg_shmem.h here */
+extern void ResetShmemAllocator(void);
 extern void InitShmemAllocator(PGShmemHeader *seghdr);
+#ifdef EXEC_BACKEND
+extern void AttachShmemAllocator(PGShmemHeader *seghdr);
+#endif
 extern void *ShmemAlloc(Size size);
 extern void *ShmemAllocNoError(Size size);
 extern bool ShmemAddrIsValid(const void *addr);
-extern void InitShmemIndex(void);
+
+extern bool ShmemRegisterHash(ShmemHashDesc *desc, HASHCTL *infoP, int hash_flags);
+extern bool	ShmemRegisterStruct(ShmemStructDesc *desc);
+
+/* legacy shmem allocation functions */
 extern HTAB *ShmemInitHash(const char *name, int64 init_size, int64 max_size,
 						   HASHCTL *infoP, int hash_flags);
 extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr);
+
+extern size_t ShmemRegisteredSize(void);
+extern void ShmemInitRegistered(void);
+#ifdef EXEC_BACKEND
+extern void ShmemAttachRegistered(void);
+#endif
+
 extern Size add_size(Size s1, Size s2);
 extern Size mul_size(Size s1, Size s2);
 
@@ -45,19 +164,4 @@ extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
 /* ipci.c */
 extern void RequestAddinShmemSpace(Size size);
 
-/* size constants for the shmem index table */
- /* max size of data structure string name */
-#define SHMEM_INDEX_KEYSIZE		 (48)
- /* estimated size of the shmem index table (not a hard limit) */
-#define SHMEM_INDEX_SIZE		 (64)
-
-/* this is a hash bucket in the shmem index table */
-typedef struct
-{
-	char		key[SHMEM_INDEX_KEYSIZE];	/* string name */
-	void	   *location;		/* location in shared mem */
-	Size		size;			/* # bytes requested for the structure */
-	Size		allocated_size; /* # bytes actually allocated */
-} ShmemIndexEnt;
-
 #endif							/* SHMEM_H */
-- 
2.47.3

