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