Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * shm_toc.c
4 : : * shared memory segment table of contents
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * src/backend/storage/ipc/shm_toc.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : :
14 : : #include "postgres.h"
15 : :
16 : : #include "port/atomics.h"
17 : : #include "storage/shm_toc.h"
18 : : #include "storage/spin.h"
19 : :
20 : : typedef struct shm_toc_entry
21 : : {
22 : : uint64 key; /* Arbitrary identifier */
23 : : Size offset; /* Offset, in bytes, from TOC start */
24 : : } shm_toc_entry;
25 : :
26 : : struct shm_toc
27 : : {
28 : : uint64 toc_magic; /* Magic number identifying this TOC */
29 : : slock_t toc_mutex; /* Spinlock for mutual exclusion */
30 : : Size toc_total_bytes; /* Bytes managed by this TOC */
31 : : Size toc_allocated_bytes; /* Bytes allocated of those managed */
32 : : uint32 toc_nentry; /* Number of entries in TOC */
33 : : shm_toc_entry toc_entry[FLEXIBLE_ARRAY_MEMBER];
34 : : };
35 : :
36 : : /*
37 : : * Initialize a region of shared memory with a table of contents.
38 : : */
39 : : shm_toc *
40 : 815 : shm_toc_create(uint64 magic, void *address, Size nbytes)
41 : : {
42 : 815 : shm_toc *toc = (shm_toc *) address;
43 : :
44 : : Assert(nbytes > offsetof(shm_toc, toc_entry));
45 : 815 : toc->toc_magic = magic;
46 : 815 : SpinLockInit(&toc->toc_mutex);
47 : :
48 : : /*
49 : : * The alignment code in shm_toc_allocate() assumes that the starting
50 : : * value is buffer-aligned.
51 : : */
52 : 815 : toc->toc_total_bytes = BUFFERALIGN_DOWN(nbytes);
53 : 815 : toc->toc_allocated_bytes = 0;
54 : 815 : toc->toc_nentry = 0;
55 : :
56 : 815 : return toc;
57 : : }
58 : :
59 : : /*
60 : : * Attach to an existing table of contents. If the magic number found at
61 : : * the target address doesn't match our expectations, return NULL.
62 : : */
63 : : shm_toc *
64 : 4028 : shm_toc_attach(uint64 magic, void *address)
65 : : {
66 : 4028 : shm_toc *toc = (shm_toc *) address;
67 : :
68 [ - + ]: 4028 : if (toc->toc_magic != magic)
69 : 0 : return NULL;
70 : :
71 : : Assert(toc->toc_total_bytes >= toc->toc_allocated_bytes);
72 : : Assert(toc->toc_total_bytes > offsetof(shm_toc, toc_entry));
73 : :
74 : 4028 : return toc;
75 : : }
76 : :
77 : : /*
78 : : * Allocate shared memory from a segment managed by a table of contents.
79 : : *
80 : : * This is not a full-blown allocator; there's no way to free memory. It's
81 : : * just a way of dividing a single physical shared memory segment into logical
82 : : * chunks that may be used for different purposes.
83 : : *
84 : : * We allocate backwards from the end of the segment, so that the TOC entries
85 : : * can grow forward from the start of the segment.
86 : : */
87 : : void *
88 : 16719 : shm_toc_allocate(shm_toc *toc, Size nbytes)
89 : : {
90 : : Size total_bytes;
91 : : Size allocated_bytes;
92 : : Size nentry;
93 : : Size toc_bytes;
94 : :
95 : : /*
96 : : * Make sure request is well-aligned. XXX: MAXALIGN is not enough,
97 : : * because atomic ops might need a wider alignment. We don't have a
98 : : * proper definition for the minimum to make atomic ops safe, but
99 : : * BUFFERALIGN ought to be enough.
100 : : */
101 : 16719 : nbytes = BUFFERALIGN(nbytes);
102 : :
103 : 16719 : SpinLockAcquire(&toc->toc_mutex);
104 : :
105 : 16719 : total_bytes = toc->toc_total_bytes;
106 : 16719 : allocated_bytes = toc->toc_allocated_bytes;
107 : 16719 : nentry = toc->toc_nentry;
108 : 16719 : toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry)
109 : 16719 : + allocated_bytes;
110 : :
111 : : /* Check for memory exhaustion and overflow. */
112 [ + - - + ]: 16719 : if (toc_bytes + nbytes > total_bytes || toc_bytes + nbytes < toc_bytes)
113 : : {
114 : 0 : SpinLockRelease(&toc->toc_mutex);
115 [ # # ]: 0 : ereport(ERROR,
116 : : (errcode(ERRCODE_OUT_OF_MEMORY),
117 : : errmsg("out of shared memory")));
118 : : }
119 : 16719 : toc->toc_allocated_bytes += nbytes;
120 : :
121 : 16719 : SpinLockRelease(&toc->toc_mutex);
122 : :
123 : 16719 : return ((char *) toc) + (total_bytes - allocated_bytes - nbytes);
124 : : }
125 : :
126 : : /*
127 : : * Return the number of bytes that can still be allocated.
128 : : */
129 : : Size
130 : 0 : shm_toc_freespace(shm_toc *toc)
131 : : {
132 : : Size total_bytes;
133 : : Size allocated_bytes;
134 : : Size nentry;
135 : : Size toc_bytes;
136 : :
137 : 0 : SpinLockAcquire(&toc->toc_mutex);
138 : 0 : total_bytes = toc->toc_total_bytes;
139 : 0 : allocated_bytes = toc->toc_allocated_bytes;
140 : 0 : nentry = toc->toc_nentry;
141 : 0 : SpinLockRelease(&toc->toc_mutex);
142 : :
143 : 0 : toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry);
144 : : Assert(allocated_bytes + BUFFERALIGN(toc_bytes) <= total_bytes);
145 : 0 : return total_bytes - (allocated_bytes + BUFFERALIGN(toc_bytes));
146 : : }
147 : :
148 : : /*
149 : : * Insert a TOC entry.
150 : : *
151 : : * The idea here is that the process setting up the shared memory segment will
152 : : * register the addresses of data structures within the segment using this
153 : : * function. Each data structure will be identified using a 64-bit key, which
154 : : * is assumed to be a well-known or discoverable integer. Other processes
155 : : * accessing the shared memory segment can pass the same key to
156 : : * shm_toc_lookup() to discover the addresses of those data structures.
157 : : *
158 : : * Since the shared memory segment may be mapped at different addresses within
159 : : * different backends, we store relative rather than absolute pointers.
160 : : *
161 : : * This won't scale well to a large number of keys. Hopefully, that isn't
162 : : * necessary; if it proves to be, we might need to provide a more sophisticated
163 : : * data structure here. But the real idea here is just to give someone mapping
164 : : * a dynamic shared memory the ability to find the bare minimum number of
165 : : * pointers that they need to bootstrap. If you're storing a lot of stuff in
166 : : * the TOC, you're doing it wrong.
167 : : */
168 : : void
169 : 16719 : shm_toc_insert(shm_toc *toc, uint64 key, void *address)
170 : : {
171 : : Size total_bytes;
172 : : Size allocated_bytes;
173 : : Size nentry;
174 : : Size toc_bytes;
175 : : Size offset;
176 : :
177 : : /* Relativize pointer. */
178 : : Assert(address > (void *) toc);
179 : 16719 : offset = ((char *) address) - (char *) toc;
180 : :
181 : 16719 : SpinLockAcquire(&toc->toc_mutex);
182 : :
183 : 16719 : total_bytes = toc->toc_total_bytes;
184 : 16719 : allocated_bytes = toc->toc_allocated_bytes;
185 : 16719 : nentry = toc->toc_nentry;
186 : :
187 : : #ifdef USE_ASSERT_CHECKING
188 : : /* Verify no duplicate keys */
189 : : for (Size i = 0; i < nentry; i++)
190 : : Assert(toc->toc_entry[i].key != key);
191 : : #endif
192 : :
193 : 16719 : toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry)
194 : 16719 : + allocated_bytes;
195 : :
196 : : /* Check for memory exhaustion and overflow. */
197 [ + - + - ]: 16719 : if (toc_bytes + sizeof(shm_toc_entry) > total_bytes ||
198 [ - + ]: 16719 : toc_bytes + sizeof(shm_toc_entry) < toc_bytes ||
199 : : nentry >= PG_UINT32_MAX)
200 : : {
201 : 0 : SpinLockRelease(&toc->toc_mutex);
202 [ # # ]: 0 : ereport(ERROR,
203 : : (errcode(ERRCODE_OUT_OF_MEMORY),
204 : : errmsg("out of shared memory")));
205 : : }
206 : :
207 : : Assert(offset < total_bytes);
208 : 16719 : toc->toc_entry[nentry].key = key;
209 : 16719 : toc->toc_entry[nentry].offset = offset;
210 : :
211 : : /*
212 : : * By placing a write barrier after filling in the entry and before
213 : : * updating the number of entries, we make it safe to read the TOC
214 : : * unlocked.
215 : : */
216 : 16719 : pg_write_barrier();
217 : :
218 : 16719 : toc->toc_nentry++;
219 : :
220 : 16719 : SpinLockRelease(&toc->toc_mutex);
221 : 16719 : }
222 : :
223 : : /*
224 : : * Look up a TOC entry.
225 : : *
226 : : * If the key is not found, returns NULL if noError is true, otherwise
227 : : * throws elog(ERROR).
228 : : *
229 : : * Unlike the other functions in this file, this operation acquires no lock;
230 : : * it uses only barriers. It probably wouldn't hurt concurrency very much even
231 : : * if it did get a lock, but since it's reasonably likely that a group of
232 : : * worker processes could each read a series of entries from the same TOC
233 : : * right around the same time, there seems to be some value in avoiding it.
234 : : */
235 : : void *
236 : 60845 : shm_toc_lookup(shm_toc *toc, uint64 key, bool noError)
237 : : {
238 : : uint32 nentry;
239 : : uint32 i;
240 : :
241 : : /*
242 : : * Read the number of entries before we examine any entry. We assume that
243 : : * reading a uint32 is atomic.
244 : : */
245 : 60845 : nentry = toc->toc_nentry;
246 : 60845 : pg_read_barrier();
247 : :
248 : : /* Now search for a matching entry. */
249 [ + + ]: 874235 : for (i = 0; i < nentry; ++i)
250 : : {
251 [ + + ]: 867659 : if (toc->toc_entry[i].key == key)
252 : 54269 : return ((char *) toc) + toc->toc_entry[i].offset;
253 : : }
254 : :
255 : : /* No matching entry was found. */
256 [ - + ]: 6576 : if (!noError)
257 [ # # ]: 0 : elog(ERROR, "could not find key " UINT64_FORMAT " in shm TOC at %p",
258 : : key, toc);
259 : 6576 : return NULL;
260 : : }
261 : :
262 : : /*
263 : : * Estimate how much shared memory will be required to store a TOC and its
264 : : * dependent data structures.
265 : : */
266 : : Size
267 : 833 : shm_toc_estimate(shm_toc_estimator *e)
268 : : {
269 : : Size sz;
270 : :
271 : 833 : sz = offsetof(shm_toc, toc_entry);
272 : 833 : sz = add_size(sz, mul_size(e->number_of_keys, sizeof(shm_toc_entry)));
273 : 833 : sz = add_size(sz, e->space_for_chunks);
274 : :
275 : 833 : return BUFFERALIGN(sz);
276 : : }
|