[v6,05/18] libcamera: shared_mem_object: reorganize the code and document the SharedMemObject class
diff mbox series

Message ID 20240319123622.675599-6-mzamazal@redhat.com
State Superseded
Headers show
Series
  • libcamera: introduce Software ISP and Software IPA
Related show

Commit Message

Milan Zamazal March 19, 2024, 12:35 p.m. UTC
From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>

Split the parts which doesn't otherwise depend on the type T or
arguments Args out of the SharedMemObject class into a new
SharedMem class.

Doxygen documentation by Dennis Bonke and Andrei Konovalov.

Reviewed-by: Pavel Machek <pavel@ucw.cz>
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>
Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
---
 .../libcamera/internal/shared_mem_object.h    | 101 ++++++----
 src/libcamera/meson.build                     |   1 +
 src/libcamera/shared_mem_object.cpp           | 190 ++++++++++++++++++
 3 files changed, 253 insertions(+), 39 deletions(-)
 create mode 100644 src/libcamera/shared_mem_object.cpp

Comments

Laurent Pinchart March 27, 2024, 12:57 p.m. UTC | #1
Hi Milan and Andrey,

Thank you for the patch.

On Tue, Mar 19, 2024 at 01:35:52PM +0100, Milan Zamazal wrote:
> From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
> 
> Split the parts which doesn't otherwise depend on the type T or
> arguments Args out of the SharedMemObject class into a new
> SharedMem class.

The commit message should explain why.

> Doxygen documentation by Dennis Bonke and Andrei Konovalov.
> 
> Reviewed-by: Pavel Machek <pavel@ucw.cz>
> 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>
> Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>  .../libcamera/internal/shared_mem_object.h    | 101 ++++++----
>  src/libcamera/meson.build                     |   1 +
>  src/libcamera/shared_mem_object.cpp           | 190 ++++++++++++++++++
>  3 files changed, 253 insertions(+), 39 deletions(-)
>  create mode 100644 src/libcamera/shared_mem_object.cpp
> 
> diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
> index 98636b44..43b07c9d 100644
> --- a/include/libcamera/internal/shared_mem_object.h
> +++ b/include/libcamera/internal/shared_mem_object.h
> @@ -6,12 +6,9 @@
>   */
>  #pragma once
>  
> -#include <fcntl.h>
>  #include <stddef.h>
>  #include <string>
>  #include <sys/mman.h>
> -#include <sys/stat.h>
> -#include <unistd.h>
>  #include <utility>
>  
>  #include <libcamera/base/class.h>
> @@ -19,58 +16,92 @@
>  
>  namespace libcamera {
>  
> +class SharedMem
> +{
> +public:
> +	SharedMem()
> +		: mem_(nullptr)
> +	{
> +	}
> +
> +	SharedMem(const std::string &name, std::size_t size);
> +
> +	SharedMem(SharedMem &&rhs)
> +	{
> +		this->name_ = std::move(rhs.name_);
> +		this->fd_ = std::move(rhs.fd_);
> +		this->mem_ = rhs.mem_;
> +		rhs.mem_ = nullptr;
> +	}
> +
> +	virtual ~SharedMem()
> +	{
> +		if (mem_)
> +			munmap(mem_, size_);
> +	}

I think neither of these need to be inline. Same for operator=().

> +
> +	/* Make SharedMem non-copyable for now. */

Record the reason in the commit message, and drop this comment.

> +	LIBCAMERA_DISABLE_COPY(SharedMem)

This goes in the private: section.

> +
> +	SharedMem &operator=(SharedMem &&rhs)
> +	{
> +		this->name_ = std::move(rhs.name_);
> +		this->fd_ = std::move(rhs.fd_);
> +		this->mem_ = rhs.mem_;
> +		rhs.mem_ = nullptr;
> +		return *this;
> +	}
> +
> +	const SharedFD &fd() const
> +	{
> +		return fd_;
> +	}
> +
> +	void *mem() const

Make this return a Span<uint8_t>. Naked pointers lead to out-of-bound
access.

> +	{
> +		return mem_;
> +	}
> +
> +private:
> +	std::string name_;

name_ is set in the constructor and then never used. I would drop it.

> +	SharedFD fd_;
> +	size_t size_;

Missing blank line.

> +protected:
> +	void *mem_;

We put protected before private, but it seems mem_ can be private as
it's exposed by the mem() function, and classes that inherit SharedMem
shouldn't have a need to set mem_. You can then store mem_ and size_ as
a Span instead of separate members.

> +};
> +
>  template<class T>

Should we limit this to standard-layout types
(https://en.cppreference.com/w/cpp/types/is_standard_layout) ? Ideally
we should forbid usage of types that contain pointers, as they can't be
shared between processes, but there's no corresponding C++ type trait as
far as I can tell.

> -class SharedMemObject
> +class SharedMemObject : public SharedMem
>  {
>  public:
>  	static constexpr std::size_t SIZE = sizeof(T);

s/SIZE/kSize/

It is probably best done in a separate patch (before or after this one,
probably before is better).

>  
>  	SharedMemObject()
> -		: obj_(nullptr)
> +		: SharedMem(), obj_(nullptr)
>  	{
>  	}
>  
>  	template<class... Args>
>  	SharedMemObject(const std::string &name, Args &&...args)
> -		: name_(name), obj_(nullptr)
> +		: SharedMem(name, SIZE), obj_(nullptr)
>  	{
> -		void *mem;
> -		int ret;
> -
> -		ret = memfd_create(name_.c_str(), MFD_CLOEXEC);
> -		if (ret < 0)
> -			return;
> -
> -		fd_ = SharedFD(std::move(ret));
> -		if (!fd_.isValid())
> -			return;
> -
> -		ret = ftruncate(fd_.get(), SIZE);
> -		if (ret < 0)
> +		if (mem_ == nullptr)
>  			return;
>  
> -		mem = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
> -			   fd_.get(), 0);
> -		if (mem == MAP_FAILED)
> -			return;
> -
> -		obj_ = new (mem) T(std::forward<Args>(args)...);
> +		obj_ = new (mem_) 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_, SIZE);
> -		}
>  	}
>  
>  	/* Make SharedMemObject non-copyable for now. */

This also goes to the private: section.

> @@ -78,8 +109,7 @@ public:
>  
>  	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 +135,12 @@ public:
>  		return *obj_;
>  	}
>  
> -	const SharedFD &fd() const
> -	{
> -		return fd_;
> -	}
> -
>  	explicit operator bool() const

This seems to be a candidate for the base class, it doesn't depend on
the type T.

>  	{
>  		return !!obj_;
>  	}
>  
>  private:
> -	std::string name_;
> -	SharedFD fd_;
>  	T *obj_;

Do we need to store obj_, or could we always cast the 

>  };
>  
> 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..44fe74c2
> --- /dev/null
> +++ b/src/libcamera/shared_mem_object.cpp
> @@ -0,0 +1,190 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Raspberry Pi Ltd

The documentation is Dennis and Andrei's work, I don't think this should
be owned by Raspberry Pi.

> + *
> + * shared_mem_object.cpp - Helper class for shared memory allocations
> + */
> +
> +#include "libcamera/internal/shared_mem_object.h"
> +
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +/**
> + * \file shared_mem_object.cpp
> + * \brief Helper class for shared memory allocations

s/class/classes/, or maybe better, just "Helpers for shared memory
allocations".

> + */
> +
> +namespace libcamera {
> +
> +/**
> + * \class SharedMem
> + * \brief Helper class for allocating shared memory

 * \brief Helper class to allocate and manage memory shareable between processes

> + *
> + * Memory is allocated and exposed as a SharedFD for use across IPC boundaries.
> + *
> + * SharedMem allocates the shared memory of the given size and maps it.
> + * To check that the shared memory was allocated and mapped successfully, one
> + * needs to verify that the pointer to the shared memory returned by SharedMem::mem()
> + * is not nullptr.
> + *
> + * To access the shared memory from another process the SharedFD should be passed
> + * to that process, and then the shared memory should be mapped into that process
> + * address space by calling 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.

memfd comes a bit out of the blue here. Let me try to improve the
documentation:

 * 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.
 *
 * 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.

> + */
> +
> +/**
> + * \fn SharedMem::SharedMem(const std::string &name, std::size_t size)
> + * \brief Constructor for the SharedMem

 * \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.

> + */
> +
> +/**
> + * \fn SharedMem::SharedMem(SharedMem &&rhs)
> + * \brief Move constructor for SharedMem
> + * \param[in] rhs The object to move
> + */
> +
> +/**
> + * \fn SharedMem::~SharedMem()
> + * \brief SharedMem destructor
> + *
> + * Unmaps the allocated shared memory. Decrements the shared memory descriptor use
> + * count.

/**
 * \fn SharedMem::~SharedMem()
 * \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.
 */

> + */
> +
> +/**
> + * \fn SharedMem &SharedMem::operator=(SharedMem &&rhs)
> + * \brief Move constructor for SharedMem

s/constructor/assignment operator/

> + * \param[in] rhs The object to move
> + */
> +
> +/**
> + * \fn const SharedFD &SharedMem::fd() const
> + * \brief Gets the file descriptor for the underlying shared memory

s/Gets/Retrieve/

> + * \return The file descriptor

 * \return The file descriptor, or an invalid SharedFD if allocation failed

> + */
> +
> +/**
> + * \fn void *SharedMem::mem() const
> + * \brief Gets the pointer to the underlying shared memory
> + * \return The pointer to the shared memory

 * \fn Span<uint8_t> SharedMem::mem() const
 * \brief Retrieve the underlying shared memory
 * \return The memory buffer, or an empty span if allocation failed

> + */
> +
> +SharedMem::SharedMem(const std::string &name, std::size_t size)
> +	: name_(name), size_(size), mem_(nullptr)

This should come right after the corresponding documentation block, and
you can then delete the \fn line of that block.

> +{
> +	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)
> +		return;

Should you clear fd_ here ? It seems pointless to keep it open, and the
class would expose consistent information.

> +

Should we set the GROW and SHRINK seals (in a separate patch) ?

> +	mem_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
> +		    fd_.get(), 0);
> +	if (mem_ == MAP_FAILED)
> +		mem_ = nullptr;

Same here, should you clear fd_ ?

> +}
> +
> +/**
> + * \var SharedMem::mem_
> + * \brief Pointer to the shared memory allocated
> + */
> +
> +/**
> + * \class SharedMemObject
> + * \brief Helper class for allocating objects in shared memory
> + *
> + * Memory is allocated and exposed as a SharedFD for use across IPC boundaries.
> + *
> + * Given the type of the object to be created in shared memory and the arguments
> + * to pass to this object's constructor, SharedMemObject allocates the shared memory
> + * of the size of the object and constructs the object in this memory. To ensure
> + * that the SharedMemObject was created successfully, one needs to verify that the
> + * overloaded bool() operator returns true. The object created in the shared memory

The part about the bool() operator should be moved to SharedMem.

> + * can be accessed using the SharedMemObject::operator*() indirection operator. Its
> + * members can be accessed with the SharedMemObject::operator->() member of pointer
> + * operator.
> + *
> + * To access the object from another process the SharedFD should be passed to that
> + * process, and the shared memory should be mapped by calling mmap().
> + *
> + * A single memfd is created for every SharedMemObject. If there is a need to allocate
> + * a large number of objects in shared memory, these objects should be grouped into a
> + * single large object to keep the number of created memfd's reasonably small.

 * \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 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::SIZE
> + * \brief The size of the object that is going to be stored here

 * \brief The size of the object stored in shared memory

> + */
> +
> +/**
> + * \fn SharedMemObject< T >::SharedMemObject(const std::string &name, Args &&...args)

I think you can drop < T > here (or it's missing everywhere below).

> + * \brief Constructor for the SharedMemObject

 * \brief Construct a SharedMemObject

> + * \param[in] name Name of the SharedMemObject
> + * \param[in] args Args to pass to the constructor of the object in shared memory

s/Args/Arguments/
s/in shared memory/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 SharedMemObject destructor
> + *
> + * Destroys the object created in the shared memory and then unmaps the shared memory.
> + * Decrements the shared memory descriptor use count.
> + */

/**
 * \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 Operator= for SharedMemObject

 * \brief Move assignment operator for SharedMemObject

> + * \param[in] rhs The SharedMemObject object to take the data from

 *
 * Moving a SharedMemObject does not affect in stored object.

> + */
> +
> +/**
> + * \fn SharedMemObject::operator->()
> + * \brief Operator-> for SharedMemObject
> + * \return The pointer to the object

 * \brief Dereference the stored object
 * \return Pointer to the stored object

> + */
> +
> +/**
> + * \fn const T *SharedMemObject::operator->() const
> + * \brief Operator-> for SharedMemObject
> + * \return The pointer to the const object

Same here. You use \copydoc to avoid duplicating the documentation,
there are a few examples in the code base. You may need to use copydoc
in the non-const version instead of here though, no sure.

> + */
> +
> +/**
> + * \fn SharedMemObject::operator*()
> + * \brief Operator* for SharedMemObject
> + * \return The reference to the object

 * \brief Dereference the stored object
 * \return Reference to the stored object

Same comment regarding copydoc.

> + */
> +
> +/**
> + * \fn const T &SharedMemObject::operator*() const
> + * \brief Operator* for SharedMemObject
> + * \return Const reference to the object
> + */
> +
> +/**
> + * \fn SharedMemObject::operator bool()
> + * \brief Operator bool() for SharedMemObject
> + * \return True if the object was created OK in the shared memory, false otherwise

Assuming you'll move this to SharedMem,

 * \brief Check if the shared memory allocation succeeded
 * \return True if allocation of the shared memorysucceeded, false otherwise

> + */
> +
> +} // namespace libcamera

/* namespace libcamera */
Milan Zamazal March 28, 2024, 9:43 a.m. UTC | #2
Andrei, Dennis,

could you please help me with the points below?

Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:

Thank you, Laurent, for the review and all the suggestions regarding docstring
improvements (very nice and educative!) etc.

>> Split the parts which doesn't otherwise depend on the type T or
>> arguments Args out of the SharedMemObject class into a new
>> SharedMem class.
>
> The commit message should explain why.

Do you have a suggestion what to write there?

>> +
>> +	/* Make SharedMem non-copyable for now. */
>
> Record the reason in the commit message, and drop this comment.

The same here.

>> --- /dev/null
>> +++ b/src/libcamera/shared_mem_object.cpp
>> @@ -0,0 +1,190 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2023, Raspberry Pi Ltd
>
> The documentation is Dennis and Andrei's work, I don't think this should
> be owned by Raspberry Pi.

Should I put you there as private persons or are there institutions behind you
that should own the copyright?

>> +{
>> +	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)
>> +		return;
>
> Should you clear fd_ here ? It seems pointless to keep it open, and the
> class would expose consistent information.
>
>> +
>
> Should we set the GROW and SHRINK seals (in a separate patch) ?
>
>> +	mem_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
>> +		    fd_.get(), 0);
>> +	if (mem_ == MAP_FAILED)
>> +		mem_ = nullptr;
>
> Same here, should you clear fd_ ?

Do you have answers for these questions?

Thanks,
Milan
Andrei Konovalov March 28, 2024, 11:08 a.m. UTC | #3
Hi Milan,

On 28.03.2024 12:43, Milan Zamazal wrote:
> Andrei, Dennis,
> 
> could you please help me with the points below?
> 
> Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:
> 
> Thank you, Laurent, for the review and all the suggestions regarding docstring
> improvements (very nice and educative!) etc.
> 
>>> Split the parts which doesn't otherwise depend on the type T or
>>> arguments Args out of the SharedMemObject class into a new
>>> SharedMem class.
>>
>> The commit message should explain why.
> 
> Do you have a suggestion what to write there?

This split was suggested by Laurent in his review of the v2 patch:
https://lists.libcamera.org/pipermail/libcamera-devel/2024-January/040344.html

My understanding was that we shouldn't create an extra copy of the same code
per each type T.

>>> +
>>> +	/* Make SharedMem non-copyable for now. */
>>
>> Record the reason in the commit message, and drop this comment.
> 
> The same here.

This comment comes from the original code by RaspberryPi.

>>> --- /dev/null
>>> +++ b/src/libcamera/shared_mem_object.cpp
>>> @@ -0,0 +1,190 @@
>>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>>> +/*
>>> + * Copyright (C) 2023, Raspberry Pi Ltd
>>
>> The documentation is Dennis and Andrei's work, I don't think this should
>> be owned by Raspberry Pi.
> 
> Should I put you there as private persons or are there institutions behind you
> that should own the copyright?

If the author of the patch is "Andrei Konovalov <andrey.konovalov.ynk@gmail.com>"
then please put me as a private person.

If the author of the patch is "Andrey Konovalov <andrey.konovalov@linaro.org>"
then please use "Copyright (C) <year>, Linaro Ltd"

>>> +{
>>> +	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)
>>> +		return;
>>
>> Should you clear fd_ here ? It seems pointless to keep it open, and the
>> class would expose consistent information.

Yes, this makes sense.

>>> +
>>
>> Should we set the GROW and SHRINK seals (in a separate patch) ?

Yes, this can be done.
Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch
some potential errors related to improper access to the shared memory allocated by
the SharedMemObject.

>>> +	mem_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
>>> +		    fd_.get(), 0);
>>> +	if (mem_ == MAP_FAILED)
>>> +		mem_ = nullptr;
>>
>> Same here, should you clear fd_ ?

I think so.

Thanks,
Andrei

> Do you have answers for these questions?
> 
> Thanks,
> Milan
>
Milan Zamazal April 2, 2024, 7:02 p.m. UTC | #4
Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:

> On Tue, Mar 19, 2024 at 01:35:52PM +0100, Milan Zamazal wrote:
>> From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
>>
>> +class SharedMemObject : public SharedMem
>>  {

[...]

>>  private:
>> -	std::string name_;
>> -	SharedFD fd_;
>>  	T *obj_;
>
> Do we need to store obj_, or could we always cast the 

IIRC something similar has already been suggested, with the conclusion it'd
better not to touch this now.  If there is a good idea how to improve this, it
can be done later.
Milan Zamazal April 2, 2024, 7:12 p.m. UTC | #5
Hi Andrei,

thank you for clarification.

Andrei Konovalov <andrey.konovalov.ynk@gmail.com> writes:

> Hi Milan,
>
> On 28.03.2024 12:43, Milan Zamazal wrote:
>> Andrei, Dennis,
>> could you please help me with the points below?
>> Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:
>> Thank you, Laurent, for the review and all the suggestions regarding docstring
>> improvements (very nice and educative!) etc.
>> 
>>>> Split the parts which doesn't otherwise depend on the type T or
>>>> arguments Args out of the SharedMemObject class into a new
>>>> SharedMem class.
>>>
>>> The commit message should explain why.
>> Do you have a suggestion what to write there?
>
> This split was suggested by Laurent in his review of the v2 patch:
> https://lists.libcamera.org/pipermail/libcamera-devel/2024-January/040344.html
>
> My understanding was that we shouldn't create an extra copy of the same code
> per each type T.

In such a case, I don't have any idea what smart to add there, it looks saying
basically the thing.

>>>> +
>>>> +	/* Make SharedMem non-copyable for now. */
>>>
>>> Record the reason in the commit message, and drop this comment.
>> The same here.
>
> This comment comes from the original code by RaspberryPi.

I couldn't find the reason, so I left this unchanged.

[...]

>>>> +	if (ftruncate(fd_.get(), size_) < 0)
>>>> +		return;
>>>
>>> Should we set the GROW and SHRINK seals (in a separate patch) ?
>
> Yes, this can be done.
> Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch
> some potential errors related to improper access to the shared memory allocated by
> the SharedMemObject.

Added to TODO.
Kieran Bingham April 2, 2024, 7:35 p.m. UTC | #6
Quoting Milan Zamazal (2024-04-02 20:12:51)
> Hi Andrei,
> 
> thank you for clarification.
> 
> Andrei Konovalov <andrey.konovalov.ynk@gmail.com> writes:
> 
> > Hi Milan,
> >
> > On 28.03.2024 12:43, Milan Zamazal wrote:
> >> Andrei, Dennis,
> >> could you please help me with the points below?
> >> Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:
> >> Thank you, Laurent, for the review and all the suggestions regarding docstring
> >> improvements (very nice and educative!) etc.
> >> 
> >>>> Split the parts which doesn't otherwise depend on the type T or
> >>>> arguments Args out of the SharedMemObject class into a new
> >>>> SharedMem class.
> >>>
> >>> The commit message should explain why.
> >> Do you have a suggestion what to write there?
> >
> > This split was suggested by Laurent in his review of the v2 patch:
> > https://lists.libcamera.org/pipermail/libcamera-devel/2024-January/040344.html
> >
> > My understanding was that we shouldn't create an extra copy of the same code
> > per each type T.
> 
> In such a case, I don't have any idea what smart to add there, it looks saying
> basically the thing.
> 
> >>>> +
> >>>> +  /* Make SharedMem non-copyable for now. */
> >>>
> >>> Record the reason in the commit message, and drop this comment.
> >> The same here.
> >
> > This comment comes from the original code by RaspberryPi.
> 
> I couldn't find the reason, so I left this unchanged.

What would it mean to copy a SharedMem object? Would the copy simply be
a lightweight copy of the SharedMem class with an increased reference?
(like copying a shared pointer) Or does it make a clean copy of the
underlying SharedMem?

I suspect answering that question answers the why it's non-copyable. Or
maybe because that question isn't answered is why it's set as
non-copyable....

> [...]
> 
> >>>> +  if (ftruncate(fd_.get(), size_) < 0)
> >>>> +          return;
> >>>
> >>> Should we set the GROW and SHRINK seals (in a separate patch) ?
> >
> > Yes, this can be done.
> > Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch
> > some potential errors related to improper access to the shared memory allocated by
> > the SharedMemObject.
> 
> Added to TODO.
>
Milan Zamazal April 2, 2024, 7:50 p.m. UTC | #7
Hi Laurent,

the changes in this patch were not completely trivial so I'd suggest briefly
reviewing the updated version as a whole again, once it is posted.

Thanks,
Milan
Milan Zamazal April 3, 2024, 10:02 a.m. UTC | #8
Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:

> On Tue, Apr 02, 2024 at 08:35:26PM +0100, Kieran Bingham wrote:
>> Quoting Milan Zamazal (2024-04-02 20:12:51)
>> > Hi Andrei,
>
>> > 
>> > thank you for clarification.
>> > 
>> > Andrei Konovalov <andrey.konovalov.ynk@gmail.com> writes:
>> > 
>> > > Hi Milan,
>> > >
>> > > On 28.03.2024 12:43, Milan Zamazal wrote:
>> > >> Andrei, Dennis,
>> > >> could you please help me with the points below?
>> > >> Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:
>> > >> Thank you, Laurent, for the review and all the suggestions regarding docstring
>> > >> improvements (very nice and educative!) etc.
>> > >> 
>> > >>>> Split the parts which doesn't otherwise depend on the type T or
>> > >>>> arguments Args out of the SharedMemObject class into a new
>> > >>>> SharedMem class.
>> > >>>
>> > >>> The commit message should explain why.
>> > >>
>> > >> Do you have a suggestion what to write there?
>> > >
>> > > This split was suggested by Laurent in his review of the v2 patch:
>> > > https://lists.libcamera.org/pipermail/libcamera-devel/2024-January/040344.html
>> > >
>> > > My understanding was that we shouldn't create an extra copy of the same code
>> > > per each type T.
>> > 
>> > In such a case, I don't have any idea what smart to add there, it looks saying
>> > basically the thing.
>
> How about
>
> 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.
>
>> > >>>> +
>> > >>>> +  /* Make SharedMem non-copyable for now. */
>> > >>>
>> > >>> Record the reason in the commit message, and drop this comment.
>> > >>
>> > >> The same here.
>> > >
>> > > This comment comes from the original code by RaspberryPi.
>> > 
>> > I couldn't find the reason, so I left this unchanged.
>> 
>> What would it mean to copy a SharedMem object? Would the copy simply be
>> a lightweight copy of the SharedMem class with an increased reference?
>> (like copying a shared pointer) Or does it make a clean copy of the
>> underlying SharedMem?
>> 
>> I suspect answering that question answers the why it's non-copyable. Or
>> maybe because that question isn't answered is why it's set as
>> non-copyable....
>
> :-)
>
> We could define copy semantics, but because we haven't, the class should
> be non-copyable given that it owns the mapped memory resource. The
> default copy constructor would lead to use-after-free (or rather
> use-after-mmap).

Thanks, added the explanations to the commit message.

Patch
diff mbox series

diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
index 98636b44..43b07c9d 100644
--- a/include/libcamera/internal/shared_mem_object.h
+++ b/include/libcamera/internal/shared_mem_object.h
@@ -6,12 +6,9 @@ 
  */
 #pragma once
 
-#include <fcntl.h>
 #include <stddef.h>
 #include <string>
 #include <sys/mman.h>
-#include <sys/stat.h>
-#include <unistd.h>
 #include <utility>
 
 #include <libcamera/base/class.h>
@@ -19,58 +16,92 @@ 
 
 namespace libcamera {
 
+class SharedMem
+{
+public:
+	SharedMem()
+		: mem_(nullptr)
+	{
+	}
+
+	SharedMem(const std::string &name, std::size_t size);
+
+	SharedMem(SharedMem &&rhs)
+	{
+		this->name_ = std::move(rhs.name_);
+		this->fd_ = std::move(rhs.fd_);
+		this->mem_ = rhs.mem_;
+		rhs.mem_ = nullptr;
+	}
+
+	virtual ~SharedMem()
+	{
+		if (mem_)
+			munmap(mem_, size_);
+	}
+
+	/* Make SharedMem non-copyable for now. */
+	LIBCAMERA_DISABLE_COPY(SharedMem)
+
+	SharedMem &operator=(SharedMem &&rhs)
+	{
+		this->name_ = std::move(rhs.name_);
+		this->fd_ = std::move(rhs.fd_);
+		this->mem_ = rhs.mem_;
+		rhs.mem_ = nullptr;
+		return *this;
+	}
+
+	const SharedFD &fd() const
+	{
+		return fd_;
+	}
+
+	void *mem() const
+	{
+		return mem_;
+	}
+
+private:
+	std::string name_;
+	SharedFD fd_;
+	size_t size_;
+protected:
+	void *mem_;
+};
+
 template<class T>
-class SharedMemObject
+class SharedMemObject : public SharedMem
 {
 public:
 	static constexpr std::size_t SIZE = sizeof(T);
 
 	SharedMemObject()
-		: obj_(nullptr)
+		: SharedMem(), obj_(nullptr)
 	{
 	}
 
 	template<class... Args>
 	SharedMemObject(const std::string &name, Args &&...args)
-		: name_(name), obj_(nullptr)
+		: SharedMem(name, SIZE), obj_(nullptr)
 	{
-		void *mem;
-		int ret;
-
-		ret = memfd_create(name_.c_str(), MFD_CLOEXEC);
-		if (ret < 0)
-			return;
-
-		fd_ = SharedFD(std::move(ret));
-		if (!fd_.isValid())
-			return;
-
-		ret = ftruncate(fd_.get(), SIZE);
-		if (ret < 0)
+		if (mem_ == nullptr)
 			return;
 
-		mem = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
-			   fd_.get(), 0);
-		if (mem == MAP_FAILED)
-			return;
-
-		obj_ = new (mem) T(std::forward<Args>(args)...);
+		obj_ = new (mem_) 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_, SIZE);
-		}
 	}
 
 	/* Make SharedMemObject non-copyable for now. */
@@ -78,8 +109,7 @@  public:
 
 	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 +135,12 @@  public:
 		return *obj_;
 	}
 
-	const SharedFD &fd() const
-	{
-		return fd_;
-	}
-
 	explicit operator bool() const
 	{
 		return !!obj_;
 	}
 
 private:
-	std::string name_;
-	SharedFD fd_;
 	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..44fe74c2
--- /dev/null
+++ b/src/libcamera/shared_mem_object.cpp
@@ -0,0 +1,190 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * shared_mem_object.cpp - Helper class for shared memory allocations
+ */
+
+#include "libcamera/internal/shared_mem_object.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+/**
+ * \file shared_mem_object.cpp
+ * \brief Helper class for shared memory allocations
+ */
+
+namespace libcamera {
+
+/**
+ * \class SharedMem
+ * \brief Helper class for allocating shared memory
+ *
+ * Memory is allocated and exposed as a SharedFD for use across IPC boundaries.
+ *
+ * SharedMem allocates the shared memory of the given size and maps it.
+ * To check that the shared memory was allocated and mapped successfully, one
+ * needs to verify that the pointer to the shared memory returned by SharedMem::mem()
+ * is not nullptr.
+ *
+ * To access the shared memory from another process the SharedFD should be passed
+ * to that process, and then the shared memory should be mapped into that process
+ * address space by calling 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.
+ */
+
+/**
+ * \fn SharedMem::SharedMem(const std::string &name, std::size_t size)
+ * \brief Constructor for the SharedMem
+ * \param[in] name Name of the SharedMem
+ * \param[in] size Size of the shared memory to allocate and map
+ */
+
+/**
+ * \fn SharedMem::SharedMem(SharedMem &&rhs)
+ * \brief Move constructor for SharedMem
+ * \param[in] rhs The object to move
+ */
+
+/**
+ * \fn SharedMem::~SharedMem()
+ * \brief SharedMem destructor
+ *
+ * Unmaps the allocated shared memory. Decrements the shared memory descriptor use
+ * count.
+ */
+
+/**
+ * \fn SharedMem &SharedMem::operator=(SharedMem &&rhs)
+ * \brief Move constructor for SharedMem
+ * \param[in] rhs The object to move
+ */
+
+/**
+ * \fn const SharedFD &SharedMem::fd() const
+ * \brief Gets the file descriptor for the underlying shared memory
+ * \return The file descriptor
+ */
+
+/**
+ * \fn void *SharedMem::mem() const
+ * \brief Gets the pointer to the underlying shared memory
+ * \return The pointer to the shared memory
+ */
+
+SharedMem::SharedMem(const std::string &name, std::size_t size)
+	: name_(name), size_(size), mem_(nullptr)
+{
+	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)
+		return;
+
+	mem_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
+		    fd_.get(), 0);
+	if (mem_ == MAP_FAILED)
+		mem_ = nullptr;
+}
+
+/**
+ * \var SharedMem::mem_
+ * \brief Pointer to the shared memory allocated
+ */
+
+/**
+ * \class SharedMemObject
+ * \brief Helper class for allocating objects in shared memory
+ *
+ * Memory is allocated and exposed as a SharedFD for use across IPC boundaries.
+ *
+ * Given the type of the object to be created in shared memory and the arguments
+ * to pass to this object's constructor, SharedMemObject allocates the shared memory
+ * of the size of the object and constructs the object in this memory. To ensure
+ * that the SharedMemObject was created successfully, one needs to verify that the
+ * overloaded bool() operator returns true. The object created in the shared memory
+ * can be accessed using the SharedMemObject::operator*() indirection operator. Its
+ * members can be accessed with the SharedMemObject::operator->() member of pointer
+ * operator.
+ *
+ * To access the object from another process the SharedFD should be passed to that
+ * process, and the shared memory should be mapped by calling mmap().
+ *
+ * A single memfd is created for every SharedMemObject. If there is a need to allocate
+ * a large number of objects in shared memory, these objects should be grouped into a
+ * single large object to keep the number of created memfd's reasonably small.
+ */
+
+/**
+ * \var SharedMemObject::SIZE
+ * \brief The size of the object that is going to be stored here
+ */
+
+/**
+ * \fn SharedMemObject< T >::SharedMemObject(const std::string &name, Args &&...args)
+ * \brief Constructor for the SharedMemObject
+ * \param[in] name Name of the SharedMemObject
+ * \param[in] args Args to pass to the constructor of the object in shared memory
+ */
+
+/**
+ * \fn SharedMemObject::SharedMemObject(SharedMemObject<T> &&rhs)
+ * \brief Move constructor for SharedMemObject
+ * \param[in] rhs The object to move
+ */
+
+/**
+ * \fn SharedMemObject::~SharedMemObject()
+ * \brief SharedMemObject destructor
+ *
+ * Destroys the object created in the shared memory and then unmaps the shared memory.
+ * Decrements the shared memory descriptor use count.
+ */
+
+/**
+ * \fn SharedMemObject::operator=(SharedMemObject<T> &&rhs)
+ * \brief Operator= for SharedMemObject
+ * \param[in] rhs The SharedMemObject object to take the data from
+ */
+
+/**
+ * \fn SharedMemObject::operator->()
+ * \brief Operator-> for SharedMemObject
+ * \return The pointer to the object
+ */
+
+/**
+ * \fn const T *SharedMemObject::operator->() const
+ * \brief Operator-> for SharedMemObject
+ * \return The pointer to the const object
+ */
+
+/**
+ * \fn SharedMemObject::operator*()
+ * \brief Operator* for SharedMemObject
+ * \return The reference to the object
+ */
+
+/**
+ * \fn const T &SharedMemObject::operator*() const
+ * \brief Operator* for SharedMemObject
+ * \return Const reference to the object
+ */
+
+/**
+ * \fn SharedMemObject::operator bool()
+ * \brief Operator bool() for SharedMemObject
+ * \return True if the object was created OK in the shared memory, false otherwise
+ */
+
+} // namespace libcamera