// 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 #include #include #include #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` is an // alias for `StructuredSharedMemory>`. // // 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> shared_timestamp_memory = // AtomicSharedMemory::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::ReadOnlyMapping> // shared_timestamp_mapping = // AtomicSharedMemory::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. template 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 Create(); // Creates and maps a shared memory region initialized with `initial_value`. // Returns nullopt if the region couldn't be created or mapped. template static std::optional Create(U&& initial_value); // As Create(), but uses `mapper` to map and later unmap the region. static std::optional CreateWithCustomMapper( SharedMemoryMapper* mapper); // As Create(), but uses `mapper` to map and later unmap the region. template static std::optional 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 MapReadOnlyRegion( ReadOnlySharedMemoryRegion region, SharedMemoryMapper* mapper = nullptr); // Returns a pointer to the object stored in the mapped region. T* WritablePtr() const { CHECK(writable_mapping_.IsValid()); return writable_mapping_.GetMemoryAs(); } const T* ReadOnlyPtr() const { CHECK(writable_mapping_.IsValid()); return writable_mapping_.GetMemoryAs(); } // Returns a reference to the object stored in the mapped region. T& WritableRef() const 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. // // 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 class StructuredSharedMemory::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(); } // 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; 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 using AtomicSharedMemory = StructuredSharedMemory>; // 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 requires(subtle::AllowedOverSharedMemory) void AssertSafeToMap(base::span mapped_span) { // If the mapping is too small, align() returns null. void* aligned_ptr = const_cast(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 std::optional> StructuredSharedMemory::Create() { return CreateWithCustomMapper(nullptr); } // static template template std::optional> StructuredSharedMemory::Create( U&& initial_value) { return CreateWithCustomMapper(std::forward(initial_value), nullptr); } // static template std::optional> StructuredSharedMemory::CreateWithCustomMapper(SharedMemoryMapper* mapper) { MappedReadOnlyRegion mapped_region = ReadOnlySharedMemoryRegion::Create(sizeof(T), mapper); if (!mapped_region.IsValid()) { return std::nullopt; } internal::AssertSafeToMap(mapped_region.mapping); // Placement new to initialize the structured memory contents in place. new (mapped_region.mapping.memory()) T; return StructuredSharedMemory(std::move(mapped_region)); } // static template template std::optional> StructuredSharedMemory::CreateWithCustomMapper(U&& initial_value, SharedMemoryMapper* mapper) { MappedReadOnlyRegion mapped_region = ReadOnlySharedMemoryRegion::Create(sizeof(T), mapper); if (!mapped_region.IsValid()) { return std::nullopt; } internal::AssertSafeToMap(mapped_region.mapping); // Placement new to initialize the structured memory contents in place. new (mapped_region.mapping.memory()) T(std::forward(initial_value)); return StructuredSharedMemory(std::move(mapped_region)); } // static template std::optional::ReadOnlyMapping> StructuredSharedMemory::MapReadOnlyRegion(ReadOnlySharedMemoryRegion region, SharedMemoryMapper* mapper) { ReadOnlySharedMemoryMapping mapping = region.Map(mapper); if (!mapping.IsValid()) { return std::nullopt; } internal::AssertSafeToMap(mapping); return ReadOnlyMapping(std::move(mapping)); } } // namespace base #endif // BASE_MEMORY_STRUCTURED_SHARED_MEMORY_H_