• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_MEMORY_STRUCTURED_SHARED_MEMORY_H_
6 #define BASE_MEMORY_STRUCTURED_SHARED_MEMORY_H_
7 
8 #include <atomic>
9 #include <memory>
10 #include <optional>
11 #include <utility>
12 
13 #include "base/check.h"
14 #include "base/compiler_specific.h"
15 #include "base/containers/span.h"
16 #include "base/memory/read_only_shared_memory_region.h"
17 #include "base/memory/shared_memory_mapper.h"
18 #include "base/memory/shared_memory_mapping.h"
19 #include "base/memory/shared_memory_safety_checker.h"
20 
21 namespace base {
22 
23 // `StructuredSharedMemory` wraps a handle to a shared memory region, and a
24 // writable mapping of that region sized and aligned to hold a type `T`. Only
25 // the process that creates the memory region can write to it, but it can pass
26 // read-only handles to other processes for reading.
27 //
28 // The caller must ensure that reads from other processes are synchronized with
29 // writes to the memory, such as by using a shared lock or storing std::atomic
30 // types in the memory region. As a convenience, `AtomicSharedMemory<T>` is an
31 // alias for `StructuredSharedMemory<std::atomic<T>>`.
32 //
33 // If `T` is a struct, the caller should ensure that it has no padding that
34 // could leak information, and that each member is safe to use over shared
35 // memory. SharedMemorySafetyChecker is helpful for this.
36 //
37 // Example of use:
38 //
39 // In the browser process:
40 //
41 //   optional<AtomicSharedMemory<TimeTicks>> shared_timestamp_memory =
42 //       AtomicSharedMemory<TimeTicks>::Create(TimeTicks::Now());
43 //   if (!shared_timestamp_memory) {
44 //     HandleFailedToMapMemoryError();
45 //     return;
46 //   }
47 //   PassRegionHandleToChild(shared_timestamp_memory->TakeReadOnlyRegion());
48 //   ...
49 //   // When an event occurs:
50 //   shared_timestamp_memory->WritableRef().store(TimeTicks::Now(),
51 //       std::memory_order_relaxed);
52 //   ...
53 //   // Destroying the StructuredSharedMemory will unmap the memory from this
54 //   // process. The child will still have a mapping.
55 //   shared_timestamp_memory.reset();
56 //
57 // In the child process:
58 //
59 //   optional<AtomicSharedMemory<TimeTicks>::ReadOnlyMapping>
60 //       shared_timestamp_mapping =
61 //           AtomicSharedMemory<TimeTicks>::MapReadOnlyRegion(region_handle);
62 //   if (!shared_timestamp_mapping) {
63 //     HandleFailedToMapMemoryError();
64 //     return;
65 //   }
66 //   ...
67 //   // Periodically check the timestamp.
68 //   TimeTicks event_time = shared_timestamp_mapping->ReadOnlyRef().load(
69 //        std::memory_order_relaxed);
70 //   ...
71 //
72 // TODO(crbug.com/357945779): Find a way to automatically validate struct
73 // members, or find another way to safely store multiple types in the same
74 // region.
75 //
76 // TODO(crbug.com/357945779): Allow multiple copies of T, with accessors that
77 // return span<T>.
78 template <typename T>
79 class StructuredSharedMemory {
80  public:
81   class ReadOnlyMapping;
82 
83   // Move-only.
84   StructuredSharedMemory(const StructuredSharedMemory&) = delete;
85   StructuredSharedMemory& operator=(const StructuredSharedMemory&) = delete;
86   StructuredSharedMemory(StructuredSharedMemory&&) = default;
87   StructuredSharedMemory& operator=(StructuredSharedMemory&&) = default;
88 
89   // Creates and maps a default-initialized shared memory region. Returns
90   // nullopt if the region couldn't be created or mapped.
91   static std::optional<StructuredSharedMemory> Create();
92 
93   // Creates and maps a shared memory region initialized with `initial_value`.
94   // Returns nullopt if the region couldn't be created or mapped.
95   template <typename U>
96   static std::optional<StructuredSharedMemory> Create(U&& initial_value);
97 
98   // As Create(), but uses `mapper` to map and later unmap the region.
99   static std::optional<StructuredSharedMemory> CreateWithCustomMapper(
100       SharedMemoryMapper* mapper);
101 
102   // As Create<U>(), but uses `mapper` to map and later unmap the region.
103   template <typename U>
104   static std::optional<StructuredSharedMemory> CreateWithCustomMapper(
105       U&& initial_value,
106       SharedMemoryMapper* mapper);
107 
108   // Returns a read-only view of `region`, or nullopt if `region` couldn't be
109   // mapped or can't contain a T. `region` should be a handle returned by
110   // TakeReadOnlyRegion() or DuplicateReadOnlyRegion().
111   static std::optional<ReadOnlyMapping> MapReadOnlyRegion(
112       ReadOnlySharedMemoryRegion region,
113       SharedMemoryMapper* mapper = nullptr);
114 
115   // Returns a pointer to the object stored in the mapped region.
WritablePtr()116   T* WritablePtr() const {
117     CHECK(writable_mapping_.IsValid());
118     return writable_mapping_.GetMemoryAs<T>();
119   }
ReadOnlyPtr()120   const T* ReadOnlyPtr() const {
121     CHECK(writable_mapping_.IsValid());
122     return writable_mapping_.GetMemoryAs<const T>();
123   }
124 
125   // Returns a reference to the object stored in the mapped region.
WritableRef()126   T& WritableRef() const LIFETIME_BOUND {
127     T* ptr = WritablePtr();
128     CHECK(ptr);
129     return *ptr;
130   }
ReadOnlyRef()131   const T& ReadOnlyRef() const LIFETIME_BOUND {
132     const T* ptr = ReadOnlyPtr();
133     CHECK(ptr);
134     return *ptr;
135   }
136 
137   // Extracts and returns a read-only handle to the memory region that can be
138   // passed to other processes. After calling this, further calls to
139   // TakeReadOnlyRegion() or DuplicateReadOnlyRegion() will fail with a CHECK.
TakeReadOnlyRegion()140   ReadOnlySharedMemoryRegion TakeReadOnlyRegion() {
141     CHECK(read_only_region_.IsValid());
142     return std::move(read_only_region_);
143   }
144 
145   // Duplicates and returns a read-only handle to the memory region that can be
146   // passed to other processes. After calling this, further calls to
147   // TakeReadOnlyRegion() or DuplicateReadOnlyRegion() will succeed.
DuplicateReadOnlyRegion()148   ReadOnlySharedMemoryRegion DuplicateReadOnlyRegion() const {
149     CHECK(read_only_region_.IsValid());
150     return read_only_region_.Duplicate();
151   }
152 
153  private:
StructuredSharedMemory(MappedReadOnlyRegion mapped_region)154   explicit StructuredSharedMemory(MappedReadOnlyRegion mapped_region)
155       : read_only_region_(std::move(mapped_region.region)),
156         writable_mapping_(std::move(mapped_region.mapping)) {}
157 
158   ReadOnlySharedMemoryRegion read_only_region_;
159   WritableSharedMemoryMapping writable_mapping_;
160 };
161 
162 // A read-only mapping of a shared memory region, sized and aligned to hold a
163 // list types `T`. This is intended for use with a ReadOnlySharedMemoryRegion
164 // created by StructuredSharedMemory<T>.
165 //
166 // Although this view of the memory is read-only, the memory can be modified by
167 // the process holding the StructuredSharedMemory at any time. So all reads must
168 // be synchronized with the writes, such as by using a shared lock or storing
169 // std::atomic types in the memory region.
170 template <typename T>
171 class StructuredSharedMemory<T>::ReadOnlyMapping {
172  public:
173   // Move-only.
174   ReadOnlyMapping(const ReadOnlyMapping&) = delete;
175   ReadOnlyMapping& operator=(const ReadOnlyMapping&) = delete;
176   ReadOnlyMapping(ReadOnlyMapping&&) = default;
177   ReadOnlyMapping& operator=(ReadOnlyMapping&&) = default;
178 
179   // Returns a pointer to the object stored in the mapped region.
ReadOnlyPtr()180   const T* ReadOnlyPtr() const {
181     CHECK(read_only_mapping_.IsValid());
182     return read_only_mapping_.GetMemoryAs<T>();
183   }
184 
185   // Returns a reference to the object stored in the mapped region.
ReadOnlyRef()186   const T& ReadOnlyRef() const LIFETIME_BOUND {
187     const T* ptr = ReadOnlyPtr();
188     CHECK(ptr);
189     return *ptr;
190   }
191 
192  private:
193   friend class StructuredSharedMemory<T>;
194 
ReadOnlyMapping(ReadOnlySharedMemoryMapping read_only_mapping)195   explicit ReadOnlyMapping(ReadOnlySharedMemoryMapping read_only_mapping)
196       : read_only_mapping_(std::move(read_only_mapping)) {}
197 
198   ReadOnlySharedMemoryMapping read_only_mapping_;
199 };
200 
201 // Convenience alias for a StructuredSharedMemory region containing a
202 // std::atomic type.
203 template <typename T>
204 using AtomicSharedMemory = StructuredSharedMemory<std::atomic<T>>;
205 
206 // Implementation.
207 
208 namespace internal {
209 
210 // CHECK's that a mapping of length `size` located at `ptr` is aligned correctly
211 // and large enough to hold a T, and that T is safe to use over shared memory.
212 template <typename T>
requires(subtle::AllowedOverSharedMemory<T>)213   requires(subtle::AllowedOverSharedMemory<T>)
214 void AssertSafeToMap(base::span<const uint8_t> mapped_span) {
215   // If the mapping is too small, align() returns null.
216   void* aligned_ptr = const_cast<uint8_t*>(mapped_span.data());
217   size_t size = mapped_span.size_bytes();
218   CHECK(std::align(alignof(T), sizeof(T), aligned_ptr, size));
219   // align() modifies `aligned_ptr` - if it's already aligned it won't change.
220   CHECK(aligned_ptr == mapped_span.data());
221 }
222 
223 }  // namespace internal
224 
225 // static
226 template <typename T>
Create()227 std::optional<StructuredSharedMemory<T>> StructuredSharedMemory<T>::Create() {
228   return CreateWithCustomMapper(nullptr);
229 }
230 
231 // static
232 template <typename T>
233 template <typename U>
Create(U && initial_value)234 std::optional<StructuredSharedMemory<T>> StructuredSharedMemory<T>::Create(
235     U&& initial_value) {
236   return CreateWithCustomMapper(std::forward<U>(initial_value), nullptr);
237 }
238 
239 // static
240 template <typename T>
241 std::optional<StructuredSharedMemory<T>>
CreateWithCustomMapper(SharedMemoryMapper * mapper)242 StructuredSharedMemory<T>::CreateWithCustomMapper(SharedMemoryMapper* mapper) {
243   MappedReadOnlyRegion mapped_region =
244       ReadOnlySharedMemoryRegion::Create(sizeof(T), mapper);
245   if (!mapped_region.IsValid()) {
246     return std::nullopt;
247   }
248   internal::AssertSafeToMap<T>(mapped_region.mapping);
249   // Placement new to initialize the structured memory contents in place.
250   new (mapped_region.mapping.memory()) T;
251   return StructuredSharedMemory<T>(std::move(mapped_region));
252 }
253 
254 // static
255 template <typename T>
256 template <typename U>
257 std::optional<StructuredSharedMemory<T>>
CreateWithCustomMapper(U && initial_value,SharedMemoryMapper * mapper)258 StructuredSharedMemory<T>::CreateWithCustomMapper(U&& initial_value,
259                                                   SharedMemoryMapper* mapper) {
260   MappedReadOnlyRegion mapped_region =
261       ReadOnlySharedMemoryRegion::Create(sizeof(T), mapper);
262   if (!mapped_region.IsValid()) {
263     return std::nullopt;
264   }
265   internal::AssertSafeToMap<T>(mapped_region.mapping);
266   // Placement new to initialize the structured memory contents in place.
267   new (mapped_region.mapping.memory()) T(std::forward<U>(initial_value));
268   return StructuredSharedMemory<T>(std::move(mapped_region));
269 }
270 
271 // static
272 template <typename T>
273 std::optional<typename StructuredSharedMemory<T>::ReadOnlyMapping>
MapReadOnlyRegion(ReadOnlySharedMemoryRegion region,SharedMemoryMapper * mapper)274 StructuredSharedMemory<T>::MapReadOnlyRegion(ReadOnlySharedMemoryRegion region,
275                                              SharedMemoryMapper* mapper) {
276   ReadOnlySharedMemoryMapping mapping = region.Map(mapper);
277   if (!mapping.IsValid()) {
278     return std::nullopt;
279   }
280   internal::AssertSafeToMap<T>(mapping);
281   return ReadOnlyMapping(std::move(mapping));
282 }
283 
284 }  // namespace base
285 
286 #endif  // BASE_MEMORY_STRUCTURED_SHARED_MEMORY_H_
287