[v8,06/18] libcamera: shared_mem_object: Reorganize the code and document the SharedMemObject class
diff mbox series

Message ID 20240416091357.211951-7-mzamazal@redhat.com
State Accepted
Headers show
Series
  • libcamera: introduce Software ISP and Software IPA
Related show

Commit Message

Milan Zamazal April 16, 2024, 9:13 a.m. UTC
From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>

The SharedMemObject class template contains a fair amount of inline code
that does not depend on the template types T. To avoid duplicating it in
every template specialization, split that code to a separate base
SharedMem class.

We don't define copy semantics for the classes (we don't need one at the
moment) and we make them non-copyable since the default copy constructor
would lead to use-after-unmap.

Doxygen documentation by Dennis Bonke and Andrei Konovalov.

Reviewed-by: Pavel Machek <pavel@ucw.cz>
Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
Signed-off-by: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 .../libcamera/internal/shared_mem_object.h    | 103 ++++----
 src/libcamera/meson.build                     |   1 +
 src/libcamera/shared_mem_object.cpp           | 236 ++++++++++++++++++
 3 files changed, 290 insertions(+), 50 deletions(-)
 create mode 100644 src/libcamera/shared_mem_object.cpp

Patch
diff mbox series

diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
index a9970059..9b1d9393 100644
--- a/include/libcamera/internal/shared_mem_object.h
+++ b/include/libcamera/internal/shared_mem_object.h
@@ -1,85 +1,98 @@ 
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
- * Copyright (C) 2023, Raspberry Pi Ltd
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ * Copyright (C) 2024 Andrei Konovalov
+ * Copyright (C) 2024 Dennis Bonke
  *
- * shared_mem_object.h - Helper class for shared memory allocations
+ * shared_mem_object.h - Helpers for shared memory allocations
  */
 #pragma once
 
-#include <fcntl.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <string>
 #include <sys/mman.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include <type_traits>
 #include <utility>
 
 #include <libcamera/base/class.h>
 #include <libcamera/base/shared_fd.h>
+#include <libcamera/base/span.h>
 
 namespace libcamera {
 
-template<class T>
-class SharedMemObject
+class SharedMem
 {
 public:
-	static constexpr std::size_t kSize = sizeof(T);
+	SharedMem();
 
-	SharedMemObject()
-		: obj_(nullptr)
+	SharedMem(const std::string &name, std::size_t size);
+	SharedMem(SharedMem &&rhs);
+
+	virtual ~SharedMem();
+
+	SharedMem &operator=(SharedMem &&rhs);
+
+	const SharedFD &fd() const
 	{
+		return fd_;
 	}
 
-	template<class... Args>
-	SharedMemObject(const std::string &name, Args &&...args)
-		: name_(name), obj_(nullptr)
+	Span<uint8_t> mem() const
 	{
-		void *mem;
-		int ret;
+		return mem_;
+	}
 
-		ret = memfd_create(name_.c_str(), MFD_CLOEXEC);
-		if (ret < 0)
-			return;
+	explicit operator bool() const
+	{
+		return !mem_.empty();
+	}
 
-		fd_ = SharedFD(std::move(ret));
-		if (!fd_.isValid())
-			return;
+private:
+	LIBCAMERA_DISABLE_COPY(SharedMem)
 
-		ret = ftruncate(fd_.get(), kSize);
-		if (ret < 0)
-			return;
+	SharedFD fd_;
 
-		mem = mmap(nullptr, kSize, PROT_READ | PROT_WRITE, MAP_SHARED,
-			   fd_.get(), 0);
-		if (mem == MAP_FAILED)
+	Span<uint8_t> mem_;
+};
+
+template<class T, typename = std::enable_if_t<std::is_standard_layout<T>::value>>
+class SharedMemObject : public SharedMem
+{
+public:
+	static constexpr std::size_t kSize = sizeof(T);
+
+	SharedMemObject()
+		: SharedMem(), obj_(nullptr)
+	{
+	}
+
+	template<class... Args>
+	SharedMemObject(const std::string &name, Args &&...args)
+		: SharedMem(name, kSize), obj_(nullptr)
+	{
+		if (mem().empty())
 			return;
 
-		obj_ = new (mem) T(std::forward<Args>(args)...);
+		obj_ = new (mem().data()) T(std::forward<Args>(args)...);
 	}
 
 	SharedMemObject(SharedMemObject<T> &&rhs)
+		: SharedMem(std::move(rhs))
 	{
-		this->name_ = std::move(rhs.name_);
-		this->fd_ = std::move(rhs.fd_);
 		this->obj_ = rhs.obj_;
 		rhs.obj_ = nullptr;
 	}
 
 	~SharedMemObject()
 	{
-		if (obj_) {
+		if (obj_)
 			obj_->~T();
-			munmap(obj_, kSize);
-		}
 	}
 
-	/* Make SharedMemObject non-copyable for now. */
-	LIBCAMERA_DISABLE_COPY(SharedMemObject)
-
 	SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs)
 	{
-		this->name_ = std::move(rhs.name_);
-		this->fd_ = std::move(rhs.fd_);
+		SharedMem::operator=(std::move(rhs));
 		this->obj_ = rhs.obj_;
 		rhs.obj_ = nullptr;
 		return *this;
@@ -105,19 +118,9 @@  public:
 		return *obj_;
 	}
 
-	const SharedFD &fd() const
-	{
-		return fd_;
-	}
-
-	explicit operator bool() const
-	{
-		return !!obj_;
-	}
-
 private:
-	std::string name_;
-	SharedFD fd_;
+	LIBCAMERA_DISABLE_COPY(SharedMemObject)
+
 	T *obj_;
 };
 
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index dd8107fa..ce31180b 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -39,6 +39,7 @@  libcamera_sources = files([
     'process.cpp',
     'pub_key.cpp',
     'request.cpp',
+    'shared_mem_object.cpp',
     'source_paths.cpp',
     'stream.cpp',
     'sysfs.cpp',
diff --git a/src/libcamera/shared_mem_object.cpp b/src/libcamera/shared_mem_object.cpp
new file mode 100644
index 00000000..b018fb3b
--- /dev/null
+++ b/src/libcamera/shared_mem_object.cpp
@@ -0,0 +1,236 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ * Copyright (C) 2024 Andrei Konovalov
+ * Copyright (C) 2024 Dennis Bonke
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * shared_mem_object.cpp - Helpers for shared memory allocations
+ */
+
+#include "libcamera/internal/shared_mem_object.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/**
+ * \file shared_mem_object.cpp
+ * \brief Helpers for shared memory allocations
+ */
+
+namespace libcamera {
+
+/**
+ * \class SharedMem
+ * \brief Helper class to allocate and manage memory shareable between processes
+ *
+ * SharedMem manages memory suitable for sharing between processes. When an
+ * instance is constructed, it allocates a memory buffer of the requested size
+ * backed by an anonymous file, using the memfd API.
+ *
+ * The allocated memory is exposed by the mem() function. If memory allocation
+ * fails, the function returns an empty Span. This can be also checked using the
+ * bool() operator.
+ *
+ * The file descriptor for the backing file is exposed as a SharedFD by the fd()
+ * function. It can be shared with other processes across IPC boundaries, which
+ * can then map the memory with mmap().
+ *
+ * A single memfd is created for every SharedMem. If there is a need to allocate
+ * a large number of objects in shared memory, these objects should be grouped
+ * together and use the shared memory allocated by a single SharedMem object if
+ * possible. This will help to minimize the number of created memfd's.
+ */
+
+SharedMem::SharedMem() = default;
+
+/**
+ * \brief Construct a SharedMem with memory of the given \a size
+ * \param[in] name Name of the SharedMem
+ * \param[in] size Size of the shared memory to allocate and map
+ *
+ * The \a name is used for debugging purpose only. Multiple SharedMem instances
+ * can have the same name.
+ */
+SharedMem::SharedMem(const std::string &name, std::size_t size)
+{
+	int fd = memfd_create(name.c_str(), MFD_CLOEXEC);
+	if (fd < 0)
+		return;
+
+	fd_ = SharedFD(std::move(fd));
+	if (!fd_.isValid())
+		return;
+
+	if (ftruncate(fd_.get(), size) < 0) {
+		fd_ = SharedFD();
+		return;
+	}
+
+	void *mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
+			 fd_.get(), 0);
+	if (mem == MAP_FAILED) {
+		fd_ = SharedFD();
+		return;
+	}
+
+	mem_ = { static_cast<uint8_t *>(mem), size };
+}
+
+/**
+ * \brief Move constructor for SharedMem
+ * \param[in] rhs The object to move
+ */
+SharedMem::SharedMem(SharedMem &&rhs)
+{
+	this->fd_ = std::move(rhs.fd_);
+	this->mem_ = rhs.mem_;
+	rhs.mem_ = {};
+}
+
+/**
+ * \brief Destroy the SharedMem instance
+ *
+ * Destroying an instance invalidates the memory mapping exposed with mem().
+ * Other mappings of the backing file, created in this or other processes with
+ * mmap(), remain valid.
+ *
+ * Similarly, other references to the backing file descriptor created by copying
+ * the SharedFD returned by fd() remain valid. The underlying memory will be
+ * freed only when all file descriptors that reference the anonymous file get
+ * closed.
+ */
+SharedMem::~SharedMem()
+{
+	if (!mem_.empty())
+		munmap(mem_.data(), mem_.size_bytes());
+}
+
+/**
+ * \brief Move assignment operator for SharedMem
+ * \param[in] rhs The object to move
+ */
+SharedMem &SharedMem::operator=(SharedMem &&rhs)
+{
+	this->fd_ = std::move(rhs.fd_);
+	this->mem_ = rhs.mem_;
+	rhs.mem_ = {};
+	return *this;
+}
+
+/**
+ * \fn const SharedFD &SharedMem::fd() const
+ * \brief Retrieve the file descriptor for the underlying shared memory
+ * \return The file descriptor, or an invalid SharedFD if allocation failed
+ */
+
+/**
+ * \fn Span<uint8_t> SharedMem::mem() const
+ * \brief Retrieve the underlying shared memory
+ * \return The memory buffer, or an empty Span if allocation failed
+ */
+
+/**
+ * \fn SharedMem::operator bool()
+ * \brief Check if the shared memory allocation succeeded
+ * \return True if allocation of the shared memory succeeded, false otherwise
+ */
+
+/**
+ * \class SharedMemObject
+ * \brief Helper class to allocate an object in shareable memory
+ * \tparam The object type
+ *
+ * The SharedMemObject class is a specialization of the SharedMem class that
+ * wraps an object of type \a T and constructs it in shareable memory. It uses
+ * the same underlying memory allocation and sharing mechanism as the SharedMem
+ * class.
+ *
+ * The wrapped object is constructed at the same time as the SharedMemObject
+ * instance, by forwarding the arguments passed to the SharedMemObject
+ * constructor. The underlying memory allocation is sized to the object \a T
+ * size. The bool() operator should be used to check the allocation was
+ * successful. The object can be accessed using the dereference operators
+ * operator*() and operator->().
+ *
+ * While no restriction on the type \a T is enforced, not all types are suitable
+ * for sharing between multiple processes. Most notably, any object type that
+ * contains pointer or reference members will likely cause issues. Even if those
+ * members refer to other members of the same object, the shared memory will be
+ * mapped at different addresses in different processes, and the pointers will
+ * not be valid.
+ *
+ * A new anonymous file is created for every SharedMemObject instance. If there
+ * is a need to share a large number of small objects, these objects should be
+ * grouped into a single larger object to limit the number of file descriptors.
+ *
+ * To share the object with other processes, see the SharedMem documentation.
+ */
+
+/**
+ * \var SharedMemObject::kSize
+ * \brief The size of the object stored in shared memory
+ */
+
+/**
+ * \fn SharedMemObject::SharedMemObject(const std::string &name, Args &&...args)
+ * \brief Construct a SharedMemObject
+ * \param[in] name Name of the SharedMemObject
+ * \param[in] args Arguments to pass to the constructor of the object T
+ *
+ * The \a name is used for debugging purpose only. Multiple SharedMem instances
+ * can have the same name.
+ */
+
+/**
+ * \fn SharedMemObject::SharedMemObject(SharedMemObject<T> &&rhs)
+ * \brief Move constructor for SharedMemObject
+ * \param[in] rhs The object to move
+ */
+
+/**
+ * \fn SharedMemObject::~SharedMemObject()
+ * \brief Destroy the SharedMemObject instance
+ *
+ * Destroying a SharedMemObject calls the wrapped T object's destructor. While
+ * the underlying memory may not be freed immediately if other mappings have
+ * been created manually (see SharedMem::~SharedMem() for more information), the
+ * stored object may be modified. Depending on the ~T() destructor, accessing
+ * the object after destruction of the SharedMemObject causes undefined
+ * behaviour. It is the responsibility of the user of this class to synchronize
+ * with other users who have access to the shared object.
+ */
+
+/**
+ * \fn SharedMemObject::operator=(SharedMemObject<T> &&rhs)
+ * \brief Move assignment operator for SharedMemObject
+ * \param[in] rhs The SharedMemObject object to take the data from
+ *
+ * Moving a SharedMemObject does not affect the stored object.
+ */
+
+/**
+ * \fn SharedMemObject::operator->()
+ * \brief Dereference the stored object
+ * \return Pointer to the stored object
+ */
+
+/**
+ * \fn const T *SharedMemObject::operator->() const
+ * \copydoc SharedMemObject::operator->
+ */
+
+/**
+ * \fn SharedMemObject::operator*()
+ * \brief Dereference the stored object
+ * \return Reference to the stored object
+ */
+
+/**
+ * \fn const T &SharedMemObject::operator*() const
+ * \copydoc SharedMemObject::operator*
+ */
+
+} /* namespace libcamera */