Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
sparse_managed_shm.hpp
Go to the documentation of this file.
1/* Flow-IPC: Shared Memory
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include "ipc/shm/shm_fwd.hpp"
24#include "ipc/util/util_fwd.hpp"
25#include <flow/util/util.hpp>
26#include <boost/interprocess/managed_external_buffer.hpp>
27#include <boost/interprocess/detail/managed_open_or_create_impl.hpp>
28#include <boost/noncopyable.hpp>
29#include <algorithm>
30#include <cassert>
31#include <cstdint>
32
33namespace ipc::shm::bipc_ext
34{
35
36// Types.
37
38/**
39 * Similar to bipc's `basic_managed_shared_memory`, except that if a ctor created the SHM-pool in the file-system,
40 * then it will not have unconditionally taken the pool's entire specified size from RAM; while the added commit()
41 * method is available to do so at any time.
42 *
43 * ### Background ###
44 * @see doc header for nearby Sparse_shm_object first. It explains the situation, as summarized just above, in
45 * detail.
46 *
47 * Given that, we continue. Typically (not necessarily) if one uses bipc for SHM, one tends to use
48 * `basic_managed_shared_memory` (or common-use alias `managed_shared_memory`) as opposed to `shared_memory_object`.
49 * It will also map, internally, a `mapped_region` and place a segment-manager along with the `Mem_algo` (or use
50 * those if already placed there). One can then use an allocation API, an object index, and related goodies.
51 * Therefore the ability to use a sparsely-committed SHM-pool -- and only optionally commit the entire thing at
52 * will -- in a `basic_managed_shared_memory` is often even more desirable than it is for a mere
53 * `shared_memory_object`.
54 *
55 * The problem one faces is that, as of this writing and for a long time now, it is not possible to just slot-in
56 * a sparseness-capable thing like Sparse_shm_object instead of the potentially-not-sparseness-capable
57 * `shared_memory_object`. I.e., `basic_managed_shared_memory` hard-codes the use of `shared_memory_object`,
58 * so there's nothing one can readily do to achieve that "even more desirable" thing.
59 *
60 * Therefore in the present Sparse_managed_shm we provide that alternative to `basic_managed_shared_memory`.
61 *
62 * ### How to use this class ###
63 * As it stands, Sparse_managed_shm is not *quite* a direct replacement for `basic_managed_shared_memory`,
64 * meaning the API is slightly different. There are roughly the following parts to its API:
65 * - The constructors. These are basically similar. (Semantically, though, if the ctor created the
66 * SHM-pool in file-system, then it will *not* have been RAM-committed and remains sparse, to the extent
67 * that the underlying `Mem_algo` allows. In addition, for purposes of a possible subsequent commit(), the
68 * OS handle/FD pointing at the SHM-pool is not dropped, as it would in a `basic_managed_shared_memory`.
69 * We mention this as handles/FDs are themselves a resource.)
70 * - The added method commit(). This will RAM-commit the entire pool-size after all. (For some applications
71 * it may be desirable or runtime-conditionally desirable.)
72 * - The added method close_shm_object_handle(). This will close the aforementioned SHM-pool handle, returning
73 * that resource to the system (while a `*this` remains otherwise operational, except that commit() shall no
74 * longer work).
75 * - The main non-static API. *This is a key API difference.* To access the bulk of the expected API of
76 * a bipc `managed_shared_memory` use core() to access a certain object; then use the API off `core()->`.
77 * (For example: `core()->construct<T>()`.)
78 * - The main static API. *This is a key API difference.* To access expected useful member aliases (e.g.,
79 * `memory_algorithm`, `mutex_family`) and perhaps `static` methods of a bipc `managed_shared_memory` use
80 * the alias #Segment to get at a certain type; the stuff you want is available off that.
81 * (For example: `Segment::memory_algorithm`.)
82 * - The added methods address() and size(). While these generally correspond to `core()->get_address()` and
83 * `core()->get_size()`, respectively, they are subtly different. Hence if certain code expects
84 * `this->core()->get_address()` (ditto `get_size()`) to equal what one would get via
85 * `B->get_address()` (ditto `get_size()`), where `B` is a `basic_managed_shared_memory`, that code will
86 * be potentially buggy. Instead it should likely use the direct methods address() and size() which were
87 * added for that purpose.
88 * - `this->core()->get_address()` is the address of the #Segment which begins not quite at the base of the
89 * full SHM-pool but rather after a certain small #S_SEGMENT_OFFSET from that base. So to get the
90 * expected SHM-pool base vaddr, use address() which will properly subtract #S_SEGMENT_OFFSET.
91 * - Accordingly `this->core()->get_size()` is the size from the start of #Segment to the end of the pool.
92 * So to get the SHM-pool's full size, use size() which properly adds #S_SEGMENT_OFFSET.
93 *
94 * `shrink_to_fit()` and `grow()` are not provided. For now there are also no move semantics (though this would
95 * likely be easy to add).
96 *
97 * @todo bipc_ext::Sparse_managed_shm is currently in detail/ (formally not user-facing) and, as explained above
98 * this to-do in the code, provides access to a very similar API but is not identical; the to-do would be
99 * to (1) make it (close to) identical and (2) move it out of detail/. For details of (1) consult the
100 * nearby comparison of the two APIs. If (1) proves impractical -- e.g., if core() proves difficult to eliminate --
101 * then (2) alone, with some quality-of-life tweaks such as adding move semantics, would still be quite nice. /
102 * As of this writing it hasn't been done for reasons of expediency only. This move would
103 * fit well with doing a similar to-do on Sparse_shm_object; an example of "synergy" is that the
104 * latter is used by Sparse_managed_shm. In terms of priority: `basic_managed_shared_memory` is a crown jewel of
105 * usefulness in bipc, and among power users the lack of sparse/on-demand RAM-commits in later Boosts can be
106 * surprising and unpleasant. So while Flow-IPC's overall mission does not center on bipc per se, this particular
107 * utility in and of itself may be quite useful indeed to a power user of bipc. I.e., it'd be an easy sizable win
108 * despite, at the moment, being something Flow-IPC uses only internally.
109 *
110 * ### Impl ###
111 * While the code is not long, and we comment all the pieces and logic appropriately, in my (ygoldfel) experience:
112 * - The task, conceptually, is pretty straightforward and follows what a direct user of POSIX-SHM usually
113 * faces at the start: get SHM-handle/possibly create SHM-pool (`shm_open()`), size it in the latter case
114 * (`ftruncate()`), map the vaddr region (`mmap()`). Then plop a bipc `segment_manager` of the desired type
115 * (with desired memory-algorithm and object-index if any) at (near) the start of the SHM-pool. (If opening
116 * an existing SHM-pool, then ensure the plopping has already fully occurred.) Done.
117 * - Unfortunately the way bipc achieves this is not straightforward; or rather it is not straightforwardly
118 * changeable. Partially because the internal bipc machinery that achieves the
119 * plop-`segment_manager`-or-wait-until-plopped part -- in atomic fashion as promised -- is also (in addition to
120 * SHM) applied to other memory-mapped resources (files, XSI, pre-existing vaddr buffers), that machinery
121 * is somewhat impenetrable to immediate understanding.
122 * - More to the point, impenetrable or otherwise (which is subjective), it is *internal*:
123 * It (unlike `shared_memory_object` and `mapped_region`) is under Boost's detail/ and in an `ipcdetail::`
124 * namespace.
125 *
126 * More specifically the crux of it is `ipcdetail::managed_open_or_create_impl` (our #Region). More or less
127 * it could be described as the combination of `shared_memory_object` (in our case Sparse_shm_object),
128 * `mapped_region` (does `mmap()` or equivalent + stores resulting vaddr + eventually `munmap()`/equivalent),
129 * and the ctor-invoked algorithm such that:
130 * - the pool-creating-guy (a) sizes ("truncates") the SHM-pool, (b) plops (constructs) the `segment_manager`
131 * near the start of the `mapped_region`, and (c) marks this as having been achieved; while
132 * - a pool-opening-guy waits for (a) and (b) to be completed -- they might be happening concurrently! --
133 * and returns once done.
134 *
135 * The `shared_memory_object` and `mapped_region` parts are readily available, in and of themselves, but that
136 * algorithm is centered around this `ipcdetail::managed_open_or_create_impl`, with tricky hooks into other
137 * objects.
138 *
139 * There are therefore two options as to how to achieve what we want.
140 * -# We could reuse `ipcdetail::managed_open_or_create_impl` (#Region), plugging in the essential different
141 * part, Sparse_shm_object instead of `shared_memory_object`.
142 * - Pro: It is not hard (in terms of lines of code).
143 * - Con: It uses at least one non-trivial detail/ thing; it could change with a future Boost version.
144 * -# We could reimplement the algorithm.
145 * - Pro: No more reliance on detail/ stuff.
146 * - Con: More lines of code; no choice but to reimplement what #Region implemented.
147 *
148 * For the moment we felt that (1) is worthwhile despite the "con." (2) remains a desirable improvement:
149 *
150 * @todo Avoid relying on boost.ipc internals for the impl of Sparse_managed_shm; reimplement
151 * Sparse_managed_shm::Region ourselves. The algorithm is somewhat hairy, but it's not that long, and we can
152 * skip some inapplicable complications/knobs, as we're targeting only POSIX-SHM specifically: not XSI, not
153 * files, etc. Suggest carrying this out before attempting the to-do above wherein we'd make
154 * Sparse_managed_shm user-facing.
155 *
156 * @tparam Mem_algo
157 * See `basic_managed_shared_memory`.
158 * @tparam Index
159 * See `basic_managed_shared_memory`.
160 */
161template<typename Mem_algo, template<typename> class Index>
163 private boost::noncopyable // For expediency for now. Probably very easy to make it movable.
164{
165public:
166 // Types.
167
168 /**
169 * The type through which (and through method core() which returns one of these) one accesses the main
170 * managed-memory API (`allocate()`, `construct<>()`, `find_or_construct<>()`, `get_segment_manager()`,
171 * `memory_algorithm` alias, etc. etc.).
172 *
173 * A key point: While aliases and most methods (through core()) behave just as one would expect
174 * from a SHM-pool-encapsulating `managed_shared_memory`, this #Segment represents not *quite* the full
175 * SHM-pool. To wit: the #Segment begins not at the pool-base but rather at offset #S_SEGMENT_OFFSET.
176 * Therefore `Segment::get_address()` and `Segment::get_size()` return slightly different values. For
177 * convenience direct methods address() and size() are available to compensate for the offset.
178 */
179 using Segment = bipc::basic_managed_external_buffer<char, Mem_algo, Index>;
180
181 /**
182 * Short-hand for `Segment::segment_manager`. As with `basic_managed_shared_memory` this is an object
183 * placed in SHM at pool-base + `S_SEGMENT_OFFSET`. (The `Mem_algo` is part of it; the actual object's
184 * byte 0 is at byte 0 of `Segment_manager`.)
185 */
186 using Segment_manager = typename Segment::segment_manager;
187
188private:
189 // Constants.
190
191 /**
192 * Alignment of Segment_manager placement: the stricter of: whatever it itself needs; mem-algo's
193 * configured/default. In any case it is computed here exactly as `basic_managed_shared_memory` does.
194 *
195 * Typically it comes out to `flow::util::max_align_sz()` (for Linux x86-64: 16).
196 */
197 static constexpr size_t S_SEGMENT_ALIGNMENT
198 = std::max<size_t>(alignof(Segment_manager),
199 Segment_manager::memory_algorithm::Alignment);
200
201 // Types.
202
203 /**
204 * bipc's startup engine behind its `basic_managed_shared_memory`, parameterized in such a way as to
205 * support initial sparse-mapping and subsequent *optional* full-RAM-commit on-demand.
206 *
207 * @see Class doc header Impl section which gives useful background about this guy, including a short
208 * outline of what it holds and, crucially, does in its ctor.
209 *
210 * We configure it differently so as to get what we want. To wit:
211 * - The `Device` type is `Sparse_shm_object` whose eponymous `truncate()` truncates/sizes as always -- but
212 * sparsely (does not RAM-commit it).
213 * - Because: That's our main thing; we need it to act this way unlike `shared_memory_object` (possibly) does.
214 * - The `StoreDevice` flag is chosen to be `true`. As a result, once the `Region` ctor maps the
215 * vaddr region to the SHM-pool's extent, it keeps the opened Sparse_shm_object (data-wise, the
216 * SHM-object handle/FD stored therein) instead of destroying it (=> closing the handle/FD; this does
217 * not harm the by-then-established mapping; and the SHM-pool lives at least as long as the mapping does).
218 * - Because: The handle/FD is internally required for commit() which, while now optional/on-demand,
219 * continues to be available.
220 *
221 * @see also close_shm_object_handle(): It will release the handle/FD after all (may be desirable, as handles/FDs
222 * are themselves a resource). commit() subsequently would only no-op/return failure.
223 *
224 * (The middle flag `FileBased` is `true` as normal; means it's a thing that can/must be `truncate()`d -- sized.)
225 */
226 using Region = bipc::ipcdetail::managed_open_or_create_impl<Sparse_shm_object, S_SEGMENT_ALIGNMENT, true, true>;
227
228public:
229 // Constants (continued).
230
231 /**
232 * Offset from pool-base at which #Segment_manager is placed. The preceding bytes belong to #Region; as of
233 * this writing it happens to be a 4-byte spin-accessed create-vs-open handshake word, rounded
234 * up to a multiple of #S_SEGMENT_ALIGNMENT. (Usually comes out to, for Linux x86-64, 16 as of this writing.)
235 */
236 static constexpr size_t S_SEGMENT_OFFSET = Region::ManagedOpenOrCreateUserOffset;
237
238 // Constructors/destructor.
239
240 /**
241 * Equivalent of the create-only `basic_managed_shared_memory` ctor. Same error-emission semantics.
242 *
243 * @param mode_tag
244 * API-choosing tag util::CREATE_ONLY.
245 * @param pool_name
246 * Absolute name at which the persistent SHM pool lives.
247 * @param pool_sz
248 * What size() shall return.
249 * @param perms
250 * Permissions to use for creation.
251 */
252 explicit Sparse_managed_shm(util::Create_only mode_tag, const Shared_name& pool_name, size_t pool_sz,
253 const util::Permissions& perms = {});
254
255 /**
256 * Equivalent of the open-only `basic_managed_shared_memory`. Same error-emission semantics.
257 *
258 * @param mode_tag
259 * API-choosing tag util::OPEN_ONLY.
260 * @param pool_name
261 * Absolute name at which the persistent SHM pool lives.
262 * @param read_only
263 * If and only if `true` the calling process will be prevented by the OS from writing into the pages
264 * mapped by `*this` subsequently. Such attempts will lead to undefined behavior.
265 */
266 explicit Sparse_managed_shm(util::Open_only mode_tag, const Shared_name& pool_name, bool read_only = false);
267
268 /**
269 * Equivalent of the atomic-create-or-open `basic_managed_shared_memory` ctor. Same error-emission semantics.
270 *
271 * Concurrent open-or-creates (and opens) of the same name are (still) allowed and (still) race safely.
272 *
273 * @param mode_tag
274 * API-choosing tag util::OPEN_OR_CREATE.
275 * @param pool_name
276 * Absolute name at which the persistent SHM pool lives.
277 * @param pool_sz
278 * What size() shall return (ignored unless creation occurs).
279 * @param perms_on_create
280 * Permissions to use for creation.
281 */
282 explicit Sparse_managed_shm(util::Open_or_create mode_tag, const Shared_name& pool_name, size_t pool_sz,
283 const util::Permissions& perms_on_create = {});
284
285 // Methods.
286
287 /**
288 * Commit (take RAM for) every page of the SHM-pool: forwards to Sparse_shm_object::commit() and returns
289 * `true` normally. However if invoked after close_shm_object_handle() it will no-op and return `false`.
290 *
291 * Before close_shm_object_handle(): it is usable at any time and any number of times. Each call commits whichever
292 * pages are not committed at that point; so a repeat call matters only if pages were decommitted since (holes
293 * punched, e.g., via Linux's `madvise(MADV_REMOVE)` on the mapping); otherwise it changes nothing.
294 *
295 * @warning See Sparse_shm_object::commit() doc header for key error-emission semantics and especially the
296 * suggestion to be ready for exceeding a RAM-use limit.
297 *
298 * @return `true` if called before close_shm_object_handle() (and didn't throw error, obviously); else `false`.
299 */
300 bool commit();
301
302 /**
303 * Close the SHM-pool handle, if any is still open, returning that resource to the system (while a `*this` remains
304 * otherwise operational, except that commit() shall subsequently no-op/return `false`). Idempotent.
305 *
306 * The SHM-pool handle/FD is kept open, by default, only to enable commit(). Once you know you won't
307 * commit() (ever or subsequently), it is prudent to call this.
308 */
310
311 /**
312 * Access to the (mutating) managed-memory API.
313 *
314 * @see doc header for #Segment. Please read this before using.
315 *
316 * @return See above.
317 */
318 Segment* core();
319
320 /**
321 * Access to the (non-mutating) managed-memory API.
322 *
323 * @see doc header for #Segment. Please read this before using.
324 *
325 * @return See above.
326 */
327 const Segment* core() const;
328
329 /**
330 * Equivalent of `basic_managed_shared_memory::get_address()`, this equals `core()->get_address()`
331 * minus #S_SEGMENT_OFFSET.
332 *
333 * @see doc header for #Segment which explains/gives context to these semantics.
334 *
335 * @return See above.
336 */
337 void* address() const;
338
339 /**
340 * Equivalent of `basic_managed_shared_memory::get_size()`, this equals `core()->get_size()`
341 * plus #S_SEGMENT_OFFSET.
342 *
343 * @see doc header for #Segment which explains/gives context to these semantics.
344 *
345 * @return See above.
346 */
347 size_t size() const;
348
349private:
350 // Types.
351
352 /**
353 * What #Region invokes, inside its create-vs-open handshake, once the pool is mapped.
354 *
355 * Namely its `()` is invoked (assuming happy path) once during ctor:
356 * - with `created=true` by the one ctor invocation that created the SHM-pool in file system;
357 * - with `created=false` by each of the other ctor invocations.
358 *
359 * Each ctor has a comment outlining the steps involved including where and how the `Construct_func{}`
360 * is used by it. In short, the creating-guy will call with `created=true`, and we'll construct-in-place
361 * the #Segment_manager; the others will call with `false`, and we'll no-op.
362 */
364 {
365 // Methods.
366
367 // Region requires it. `addr` = pool-base + S_SEGMENT_OFFSET; `size` = pool size minus S_SEGMENT_OFFSET.
368
369 /**
370 * The functor action as required/invoked by #Region in its ctor. The Construct_func header above covers it.
371 *
372 * Throws if and only if Segment_manager ctor throws (propagates that): same as the vanilla impl.
373 * The #Region ctor will deal with it in a reasonable way.
374 *
375 * @param addr
376 * Equals `core()->get_address()`: pool-base plus #S_SEGMENT_OFFSET.
377 * @param sz
378 * Equals `core()->get_size()`; pool-size minus #S_SEGMENT_OFFSET.
379 * @param created
380 * `true` if the creating-guy is calling us; else `false`. `true` => we'll try to construct
381 * the #Segment_manager at `addr`.
382 * @return `true`. As of this writing Region ignores it anyway.
383 */
384 bool operator()(void* addr, size_t sz, bool created) const;
385
386 /**
387 * Required by #Region: returns `Segment_manager::get_min_size()`.
388 * @return See above.
389 */
390 static size_t get_min_size();
391 }; // struct Construct_func
392
393 // Data.
394
395 /**
396 * See #Region. The `Device` -- a Sparse_shm_object in our case -- is accessible by-reference via
397 * `m_region.get_device()` which is required to impl commit() and close_shm_object_handle().
398 *
399 * Order -- this going before #m_segment -- matters for the pithiest ctor impl, wherein both members
400 * are fully prepared pre-`{}`:
401 * -# First #Region ctor does its whole open/truncate/map/init-`Segment` -or-
402 * open/map/wait-until-`Segment`-ready procedure.
403 * -# Then #Segment can be safely place-constructed in *its* open mode, returning to indicate
404 * `*this` is safe to immediately use.
405 */
407
408 /**
409 * Stores the handle to the Segment_manager inside the mapped vaddr extent, it is accessed via core().
410 *
411 * See #Segment, core(), and #m_region doc headers.
412 */
414}; // class Sparse_managed_shm
415
416// Template implementations.
417
418template<typename Mem_algo, template<typename> class Index>
420 const util::Permissions& perms) :
421 /* In this part, m_region ctor does this (happy path):
422 * -# Create Sparse_shm_object in Create_only mode which shm_open(pool_name, create)s and stores returned handle.
423 * (This act creates the actual named SHM-pool if file system.)
424 * -# Call its Sparse_shm_object::truncate() (which sizes it but keeps it sparse/not committed).
425 * -# Through our Construct_func{} construct-in-place a new Segment_manager at offset S_SEGMENT_OFFSET.
426 * -# In the pre-S_SEGMENT_OFFSET little area mark the completion of init. */
427 m_region(util::CREATE_ONLY, pool_name.native_str(), pool_sz, bipc::read_write, nullptr, Construct_func{}, perms),
428 /* Then this simply saves the address at offset S_SEGMENT_OFFSET as where Segment_manager begins.
429 * Note: Not CREATE_ONLY; Construct_func{} did already what CREATE_ONLY would do. Clean. */
430 m_segment(util::OPEN_ONLY, m_region.get_user_address(), m_region.get_user_size())
431{
432 // Done.
433}
434
435template<typename Mem_algo, template<typename> class Index>
437 /* In this part, m_region ctor does this (happy path):
438 * -# Create Sparse_shm_object in Open_only mode which shm_open(pool_name, open)s and stores returned handle.
439 * (The actual named SHM-pool therefore was already in file system.)
440 * -# Spin until the completion of init has been marked (see above) in the pre-S_SEGMENT_OFFSET little area.
441 * -# (Our Construct_func{} (since it is the opened-did-not-created path -- init already done) does nothing.) */
442 m_region(util::OPEN_ONLY, pool_name.native_str(), read_only ? bipc::read_only : bipc::read_write, nullptr,
444 // Same as above: This simply saves the address at offset S_SEGMENT_OFFSET as where Segment_manager begins.
445 m_segment(util::OPEN_ONLY, m_region.get_user_address(), m_region.get_user_size())
446{
447 // Done.
448}
449
450template<typename Mem_algo, template<typename> class Index>
452 size_t pool_sz, const util::Permissions& perms_on_create) :
453 /* In this part, m_region does a combo of what the other two ctors have it do. Essentially depending on
454 * whether it had to create the SHM-pool in file-system, or merely open it (first step), it then does the remaining
455 * steps from either the Create_only or Open_only ctor. */
456 m_region(util::OPEN_OR_CREATE, pool_name.native_str(), pool_sz, bipc::read_write, nullptr, Construct_func{},
457 perms_on_create),
458 // Again: same as in each of the other 2 ctors.
459 m_segment(util::OPEN_ONLY, m_region.get_user_address(), m_region.get_user_size())
460{
461 // Done.
462}
463
464template<typename Mem_algo, template<typename> class Index>
466{
467 return Segment_manager::get_min_size();
468 // As of this writing Boost's equivalent has some paranoid checking, but we basically skip it as overmuch.
469}
470
471template<typename Mem_algo, template<typename> class Index>
472bool Sparse_managed_shm<Mem_algo, Index>::Construct_func::operator()(void* addr, size_t sz, bool created) const
473{
474 // using flow::util::construct_at; // (In C++20 can clash with the std:: counterpart.)
475
476 if (created)
477 {
478 // Region placed us at pool-base + S_SEGMENT_OFFSET which must be, and is, aligned at least to Segment_manager.
479 assert((reinterpret_cast<uintptr_t>(addr) % alignof(Segment_manager)) == 0);
480
481 /* basic_managed_shared_memory does some redundant checking here too; we just get on with it.
482 * This can throw (as we advertised). */
483 flow::util::construct_at(static_cast<Segment_manager*>(addr), sz);
484 }
485 /* else:
486 * Opener. Nothing to do in SHM. (m_segment attaches to `addr` after Region finishes, for creator and
487 * opener alike; so this functor stays stateless and does only the thing that *must* happen inside the
488 * handshake.) Note bipc's own class does the same in substance: its open_impl() merely stores the pointer.
489 * Then we create the new Segment m_segment, whose constructor open_impl()s after all.
490 *
491 * Why we didn't just copy the way bipc::basic_managed_shared_memory did it -- with its integrated Segment-like
492 * object's interplay with m_region's construction? It is explained in our doc header in Impl section. Restated:
493 * (1) it's more segregated chronologically (first make thing A and wait as needed; once ready: make thing B),
494 * and (2) we suspect it'll be easier to later replace the formally bipc-internal
495 * ipcdetail::managed_open_or_create_impl (= Region) with our own reimpl that avoids bipc internals. */
496
497 return true; // Ignored by Region anyway.
498} // Sparse_managed_shm::Construct_func::operator()()
499
500template<typename Mem_algo, template<typename> class Index>
502{
503 return &m_segment;
504}
505
506template<typename Mem_algo, template<typename> class Index>
508{
509 return &m_segment;
510}
511
512template<typename Mem_algo, template<typename> class Index>
514{
515 return m_region.get_mapped_region().get_address();
516}
517
518template<typename Mem_algo, template<typename> class Index>
520{
521 return m_region.get_real_size(); // The mapping's size; as opposed to get_user_size() = minus S_SEGMENT_OFFSET.
522}
523
524template<typename Mem_algo, template<typename> class Index>
526{
527 Sparse_shm_object& shm_object = m_region.get_device();
528
529 // A default-cted Sparse_shm_object (see close_shm_object_handle()) holds no handle.
530 if (shm_object.get_mapping_handle().handle == -1)
531 {
532 return false;
533 }
534 // else
535
536 shm_object.commit(); // Can throw, especially on no-space-left.
537 return true;
538}
539
540template<typename Mem_algo, template<typename> class Index>
542{
543 /* Overwriting closes held handle/FD. The overwritten (in 1st call to us) such Sparse_shm_object was placed
544 * inside m_region, when we initialized m_region in our ctor. After this commit() will no-op. */
545 [[maybe_unused]] auto& shm_object = m_region.get_device() = Sparse_shm_object{};
546
547 assert((shm_object.get_mapping_handle().handle == -1) && "commit() check relies on this.");
548}
549
550} // namespace ipc::shm::bipc_ext
Similar to bipc's basic_managed_shared_memory, except that if a ctor created the SHM-pool in the file...
static constexpr size_t S_SEGMENT_OFFSET
Offset from pool-base at which Segment_manager is placed.
Sparse_managed_shm(util::Create_only mode_tag, const Shared_name &pool_name, size_t pool_sz, const util::Permissions &perms={})
Equivalent of the create-only basic_managed_shared_memory ctor.
Segment * core()
Access to the (mutating) managed-memory API.
static constexpr size_t S_SEGMENT_ALIGNMENT
Alignment of Segment_manager placement: the stricter of: whatever it itself needs; mem-algo's configu...
void * address() const
Equivalent of basic_managed_shared_memory::get_address(), this equals core()->get_address() minus S_S...
typename Segment::segment_manager Segment_manager
Short-hand for Segment::segment_manager.
bipc::ipcdetail::managed_open_or_create_impl< Sparse_shm_object, S_SEGMENT_ALIGNMENT, true, true > Region
bipc's startup engine behind its basic_managed_shared_memory, parameterized in such a way as to suppo...
size_t size() const
Equivalent of basic_managed_shared_memory::get_size(), this equals core()->get_size() plus S_SEGMENT_...
bipc::basic_managed_external_buffer< char, Mem_algo, Index > Segment
The type through which (and through method core() which returns one of these) one accesses the main m...
bool commit()
Commit (take RAM for) every page of the SHM-pool: forwards to Sparse_shm_object::commit() and returns...
void close_shm_object_handle()
Close the SHM-pool handle, if any is still open, returning that resource to the system (while a *this...
Segment m_segment
Stores the handle to the Segment_manager inside the mapped vaddr extent, it is accessed via core().
Similar to bipc's shared_memory_object, but truncate() will not unconditionally commit the entire spe...
void commit()
Commit (take RAM for) every page of the SHM-object's size as determined by the last truncate().
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
Extensions, informal or otherwise, of boost.ipc (boost::interprocess, bipc) SHM-related facilities,...
bipc::permissions Permissions
Short-hand for Unix (POSIX) permissions class.
Definition: util_fwd.hpp:158
bipc::open_only_t Open_only
Tag type indicating an ideally-atomic open-if-exists-else-fail operation.
Definition: util_fwd.hpp:152
const Open_or_create OPEN_OR_CREATE
Tag value indicating an open-if-exists-else-create operation.
Definition: util.cpp:36
const Open_only OPEN_ONLY
Tag value indicating an atomic open-if-exists-else-fail operation.
Definition: util.cpp:37
bipc::open_or_create_t Open_or_create
Tag type indicating an atomic open-if-exists-else-create operation.
Definition: util_fwd.hpp:149
bipc::create_only_t Create_only
Tag type indicating a create-unless-exists-else-fail operation.
Definition: util_fwd.hpp:155
const Create_only CREATE_ONLY
Tag value indicating an atomic create-unless-exists-else-fail operation.
Definition: util.cpp:38
What Region invokes, inside its create-vs-open handshake, once the pool is mapped.
static size_t get_min_size()
Required by Region: returns Segment_manager::get_min_size().
bool operator()(void *addr, size_t sz, bool created) const
The functor action as required/invoked by Region in its ctor.