blob: 4aefbeecad9d11551745b8691f2de1800a98ff9b [file] [log] [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_