blob: 4aefbeecad9d11551745b8691f2de1800a98ff9b [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"
Tom Sepez04e98bf2024-10-25 18:19:3114#include "base/compiler_specific.h"
Joe Masonb305e962024-08-19 20:55:3915#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
21namespace 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>.
78template <typename T>
79class 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.
Peter Kastingdf00c9f2024-12-17 02:03:54116 T* WritablePtr() {
Joe Masonb305e962024-08-19 20:55:39117 CHECK(writable_mapping_.IsValid());
118 return writable_mapping_.GetMemoryAs<T>();
119 }
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.
Peter Kastingdf00c9f2024-12-17 02:03:54126 T& WritableRef() LIFETIME_BOUND {
Joe Masonb305e962024-08-19 20:55:39127 T* ptr = WritablePtr();
128 CHECK(ptr);
129 return *ptr;
130 }
Tom Sepez04e98bf2024-10-25 18:19:31131 const T& ReadOnlyRef() const LIFETIME_BOUND {
Joe Masonb305e962024-08-19 20:55:39132 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.
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.
148 ReadOnlySharedMemoryRegion DuplicateReadOnlyRegion() const {
149 CHECK(read_only_region_.IsValid());
150 return read_only_region_.Duplicate();
151 }
152
153 private:
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.
170template <typename T>
171class 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.
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.
Tom Sepez04e98bf2024-10-25 18:19:31186 const T& ReadOnlyRef() const LIFETIME_BOUND {
Joe Masonb305e962024-08-19 20:55:39187 const T* ptr = ReadOnlyPtr();
188 CHECK(ptr);
189 return *ptr;
190 }
191
192 private:
193 friend class StructuredSharedMemory<T>;
194
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.
203template <typename T>
204using AtomicSharedMemory = StructuredSharedMemory<std::atomic<T>>;
205
206// Implementation.
207
208namespace 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.
212template <typename T>
213 requires(subtle::AllowedOverSharedMemory<T>)
214void 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
226template <typename T>
227std::optional<StructuredSharedMemory<T>> StructuredSharedMemory<T>::Create() {
228 return CreateWithCustomMapper(nullptr);
229}
230
231// static
232template <typename T>
233template <typename U>
234std::optional<StructuredSharedMemory<T>> StructuredSharedMemory<T>::Create(
235 U&& initial_value) {
236 return CreateWithCustomMapper(std::forward<U>(initial_value), nullptr);
237}
238
239// static
240template <typename T>
241std::optional<StructuredSharedMemory<T>>
242StructuredSharedMemory<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
255template <typename T>
256template <typename U>
257std::optional<StructuredSharedMemory<T>>
258StructuredSharedMemory<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
272template <typename T>
273std::optional<typename StructuredSharedMemory<T>::ReadOnlyMapping>
274StructuredSharedMemory<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_