Joe Mason | b305e96 | 2024-08-19 20:55:39 | [diff] [blame] | 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" |
Tom Sepez | 04e98bf | 2024-10-25 18:19:31 | [diff] [blame] | 14 | #include "base/compiler_specific.h" |
Joe Mason | b305e96 | 2024-08-19 20:55:39 | [diff] [blame] | 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. |
Peter Kasting | df00c9f | 2024-12-17 02:03:54 | [diff] [blame] | 116 | T* WritablePtr() { |
Joe Mason | b305e96 | 2024-08-19 20:55:39 | [diff] [blame] | 117 | 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 Kasting | df00c9f | 2024-12-17 02:03:54 | [diff] [blame] | 126 | T& WritableRef() LIFETIME_BOUND { |
Joe Mason | b305e96 | 2024-08-19 20:55:39 | [diff] [blame] | 127 | T* ptr = WritablePtr(); |
| 128 | CHECK(ptr); |
| 129 | return *ptr; |
| 130 | } |
Tom Sepez | 04e98bf | 2024-10-25 18:19:31 | [diff] [blame] | 131 | const T& ReadOnlyRef() const LIFETIME_BOUND { |
Joe Mason | b305e96 | 2024-08-19 20:55:39 | [diff] [blame] | 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. |
| 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. |
| 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. |
| 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 Sepez | 04e98bf | 2024-10-25 18:19:31 | [diff] [blame] | 186 | const T& ReadOnlyRef() const LIFETIME_BOUND { |
Joe Mason | b305e96 | 2024-08-19 20:55:39 | [diff] [blame] | 187 | 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. |
| 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> |
| 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> |
| 227 | std::optional<StructuredSharedMemory<T>> StructuredSharedMemory<T>::Create() { |
| 228 | return CreateWithCustomMapper(nullptr); |
| 229 | } |
| 230 | |
| 231 | // static |
| 232 | template <typename T> |
| 233 | template <typename U> |
| 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>> |
| 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>> |
| 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> |
| 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_ |