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