[libcamera-devel] [PATCH v2 2/3] libcamera: ipc: unix: Add a IPC mechanism based on Unix sockets
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Mon Jul 1 01:55:57 CEST 2019
Hi Niklas,
Thank you for the patch.
On Sun, Jun 30, 2019 at 06:25:13PM +0200, Niklas Söderlund wrote:
> To be able to isolate an IPA component in a separate process an IPC
> mechanism is needed to communicate with it. Add an IPC mechanism based
> on Unix sockets which allows users to pass both data and file descriptors
> to and from the IPA process.
>
> The implementation allows users to send both data and file descriptors
> in the same message. This allows users to more easily implement
> serialization and deserialization of objects as all elements belonging
> to an object can be sent in one message.
>
> Signed-off-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
> ---
> src/libcamera/include/ipc_unixsocket.h | 57 +++++
> src/libcamera/ipc_unixsocket.cpp | 312 +++++++++++++++++++++++++
> src/libcamera/meson.build | 2 +
> 3 files changed, 371 insertions(+)
> create mode 100644 src/libcamera/include/ipc_unixsocket.h
> create mode 100644 src/libcamera/ipc_unixsocket.cpp
>
> diff --git a/src/libcamera/include/ipc_unixsocket.h b/src/libcamera/include/ipc_unixsocket.h
> new file mode 100644
> index 0000000000000000..84d786e8424ccf54
> --- /dev/null
> +++ b/src/libcamera/include/ipc_unixsocket.h
> @@ -0,0 +1,57 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * ipc_unixsocket.h - IPC mechanism based on Unix sockets
> + */
> +
> +#ifndef __LIBCAMERA_IPC_UNIXSOCKET_H__
> +#define __LIBCAMERA_IPC_UNIXSOCKET_H__
> +
> +#include <cstdint>
> +#include <sys/types.h>
> +#include <vector>
> +
> +#include <libcamera/event_notifier.h>
> +
> +namespace libcamera {
> +
> +class IPCUnixSocket
> +{
> +public:
> + struct Payload {
> + std::vector<uint8_t> data;
> + std::vector<int32_t> fds;
> + };
> +
> + IPCUnixSocket();
> + ~IPCUnixSocket();
> +
> + int create();
> + int bind(int fd);
> + void close();
> + bool isBound();
bool isBound() const;
> +
> + int send(const Payload &payload);
> + int receive(Payload *payload);
> +
> + Signal<IPCUnixSocket *> readyRead;
> +
> +private:
> + struct Header {
> + uint32_t data;
> + uint8_t fds;
> + };
> +
> + int sendData(const void *buffer, size_t length, const int32_t *fds, unsigned int num);
> + int recvData(void *buffer, size_t length, int32_t *fds, unsigned int num);
> +
> + void dataNotifier(EventNotifier *notifier);
> +
> + int fd_;
> + EventNotifier *notifier_;
> +};
> +
> +} /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_IPC_UNIXSOCKET_H__ */
> diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp
> new file mode 100644
> index 0000000000000000..f9ef4dafa4db0652
> --- /dev/null
> +++ b/src/libcamera/ipc_unixsocket.cpp
> @@ -0,0 +1,312 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * ipc_unixsocket.cpp - IPC mechanism based on Unix sockets
> + */
> +
> +#include "ipc_unixsocket.h"
> +
> +#include <string.h>
> +#include <sys/socket.h>
> +#include <unistd.h>
> +
> +#include "log.h"
> +
> +/**
> + * \file ipc_unixsocket.h
> + * \brief IPC mechanism based on Unix sockets
> + */
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(IPCUnixSocket)
> +
> +/**
> + * \struct IPCUnixSocket::Payload
> + * \brief Container for an IPC payload
> + *
> + * Holds an array of bytes and an array of file descriptors that can be
> + * transported across a IPC boundary.
> + */
> +
> +/**
> + * \var IPCUnixSocket::Payload::data
> + * \brief Array of bytes to cross IPC boundary
> + */
> +
> +/**
> + * \var IPCUnixSocket::Payload::fds
> + * \brief Array of file descriptors to cross IPC boundary
> + */
> +
> +/**
> + * \class IPCUnixSocket
> + * \brief IPC mechanism based on Unix sockets
> + *
> + * The Unix socket IPC allows bidirectional communication between two processes
> + * through unnamed Unix sockets. It implements datagram-based communication,
> + * transporting entire payloads with guaranteed ordering.
> + *
> + * The IPC design is asynchronous, a message is queued to a receiver which gets
> + * notified that a message is ready to be consumed by a signal. The queuer of
> + * the message gets no notification when a message is delivered nor processed.
> + * If such interactions are needed a protocol specific to the users use-case
> + * should be implemented on top of the IPC objects.
> + *
> + * The IPC design can transmit messages in any direction and the only difference
> + * from a master and slave operation is how they are created and bound to one
> + * another. After the two parts are setup the operation to send and receive
> + * messages are the same for both.
I think you can drop the above paragraph as the next one contains the
same information and more.
> + * Establishment of an IPC channel is asymmetrical. The side that initiates
> + * communication first instantiates a local side socket and creates the channel
> + * with create(). The method returns a file descriptor for the remote side of
> + * the channel, which is passed to the remote process through an out-of-band
> + * communication method. The remote side then instantiates a socket, and binds
> + * it to the other side by passing the file descriptor to bind(). At that point
> + * the channel is operation and communication is bidirectional and symmmetrical.
> + */
> +
> +IPCUnixSocket::IPCUnixSocket()
> + : fd_(-1), notifier_(nullptr)
> +{
> +}
> +
> +IPCUnixSocket::~IPCUnixSocket()
> +{
> + close();
> +}
> +
> +/**
> + * \brief Create an new IPC channel
> + *
> + * This method creates a new IPC channel. The socket instance is bound to the
> + * local side of the channel, and the method returns a file descriptor bound to
> + * the remote side. The caller is responsible for passing the file descriptor to
> + * the remote process, where it can be used with IPCUnixSocket::bind() to bind
> + * the remote side socket.
> + *
> + * \return A file descriptor on success, negative error code on failure
> + */
> +int IPCUnixSocket::create()
> +{
> + int sockets[2];
> + int ret;
> +
> + ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets);
> + if (ret) {
> + ret = -errno;
> + LOG(IPCUnixSocket, Error)
> + << "Failed to create socket pair: " << strerror(-ret);
> + return ret;
> + }
> +
> + ret = bind(sockets[0]);
> + if (ret)
> + return ret;
> +
> + return sockets[1];
> +}
> +
> +/**
> + * \brief Bind to an existing IPC channel
> + * \param[in] fd File descriptor
> + *
> + * This method binds the socket instance to an existing IPC channel identified
> + * by the file descriptor \a fd. The file descriptor is obtained from the
> + * IPCUnixSocket::create() method.
> + *
> + * \return 0 on success or a negative error code otherwise
> + */
> +int IPCUnixSocket::bind(int fd)
> +{
> + if (isBound())
> + return -EINVAL;
> +
> + fd_ = fd;
> + notifier_ = new EventNotifier(fd_, EventNotifier::Read);
> + notifier_->activated.connect(this, &IPCUnixSocket::dataNotifier);
> +
> + return 0;
> +}
> +
> +/**
> + * \brief Close the IPC channel
> + *
> + * No communication is possible after close() has been called.
> + */
> +void IPCUnixSocket::close()
> +{
> + if (!isBound())
> + return;
> +
> + delete notifier_;
> + notifier_ = nullptr;
> +
> + ::close(fd_);
> +
> + fd_ = -1;
> +}
> +
> +/**
> + * \brief Check if the IPC channel is bound
> + * \return True if the IPC channel is bound, false otherwise
> + */
> +bool IPCUnixSocket::isBound()
> +{
> + return fd_ != -1;
> +}
> +
> +/**
> + * \brief Send a message payload
> + * \param[in] payload Message payload to send
> + *
> + * This method queues the message payload for transmission to the other end of
> + * the IPC channel. This method returns immediately, before the message is
s/This method/It/
> + * delivered to the remote side.
> + *
> + * \return 0 on success or a negative error code otherwise
> + */
> +int IPCUnixSocket::send(const Payload &payload)
> +{
> + Header hdr;
You can declare this variable just before using it.
> + int ret;
> +
> + if (!isBound())
> + return -ENOTCONN;
> +
> + hdr.data = payload.data.size();
> + hdr.fds = payload.fds.size();
> +
> + ret = ::send(fd_, &hdr, sizeof(hdr), 0);
> + if (ret < 0) {
> + ret = -errno;
> + LOG(IPCUnixSocket, Error)
> + << "Failed to send: " << strerror(-ret);
> + return ret;
> + }
> +
> + return sendData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds);
> +}
> +
> +/**
> + * \brief Receive a message payload
> + * \param[out] payload Payload where to write the received message
> + *
> + * This method receives the message payload from the IPC channel and writes it
> + * to the \a payload. This method blocks until one message is received, if an
s/This method/It/
> + * asynchronous behavior is desired this method should be called when the
> + * readyRead signal is emitted.
Hmmm... We'll have a problem here, if the remove side sends us a header
but no payload, we'll block forever. I think we'll have to implement a
state machine to fix this. It could possibly be done on top of this
patch series to avoid blocking development that depends on IPC, but then
you should record a \todo item.
> + *
> + * \return 0 on success or a negative error code otherwise
> + */
> +int IPCUnixSocket::receive(Payload *payload)
> +{
> + Header hdr;
> + int ret;
> +
> + if (!isBound())
> + return -ENOTCONN;
> +
> + if (!payload)
> + return -EINVAL;
> +
> + ret = ::recv(fd_, &hdr, sizeof(hdr), 0);
> + if (ret < 0) {
> + ret = -errno;
> + LOG(IPCUnixSocket, Error)
> + << "Failed to recv header: " << strerror(-ret);
> + return ret;
> + }
> +
> + payload->data.resize(hdr.data);
> + payload->fds.resize(hdr.fds);
> +
> + ret = recvData(payload->data.data(), hdr.data, payload->fds.data(), hdr.fds);
> + if (ret)
> + return ret;
> +
> + return 0;
Just
return recvData(...);
> +}
> +
> +/**
> + * \var IPCUnixSocket::readyRead
> + * \brief A Signal emitted when a message is ready to be read
> + */
> +
> +int IPCUnixSocket::sendData(const void *buffer, size_t length, const int32_t *fds, unsigned int num)
> +{
> + struct iovec iov[1];
> + iov[0].iov_base = const_cast<void *>(buffer);
> + iov[0].iov_len = length;
> +
> + char buf[CMSG_SPACE(num * sizeof(uint32_t))];
> + memset(buf, 0, sizeof(buf));
> +
> + struct cmsghdr *cmsg = (struct cmsghdr *)buf;
> + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
> + cmsg->cmsg_level = SOL_SOCKET;
> + cmsg->cmsg_type = SCM_RIGHTS;
> +
> + struct msghdr msg;
> + msg.msg_name = nullptr;
> + msg.msg_namelen = 0;
> + msg.msg_iov = iov;
> + msg.msg_iovlen = 1;
> + msg.msg_control = cmsg;
> + msg.msg_controllen = cmsg->cmsg_len;
> + msg.msg_flags = 0;
> + memcpy(CMSG_DATA(cmsg), fds, num * sizeof(uint32_t));
> +
> + if (sendmsg(fd_, &msg, 0) < 0) {
> + int ret = -errno;
> + LOG(IPCUnixSocket, Error)
> + << "Failed to sendmsg: " << strerror(-ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +int IPCUnixSocket::recvData(void *buffer, size_t length, int32_t *fds, unsigned int num)
> +{
> + struct iovec iov[1];
> + iov[0].iov_base = buffer;
> + iov[0].iov_len = length;
> +
> + char buf[CMSG_SPACE(num * sizeof(uint32_t))];
> + memset(buf, 0, sizeof(buf));
> +
> + struct cmsghdr *cmsg = (struct cmsghdr *)buf;
> + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
> + cmsg->cmsg_level = SOL_SOCKET;
> + cmsg->cmsg_type = SCM_RIGHTS;
> +
> + struct msghdr msg;
> + msg.msg_name = nullptr;
> + msg.msg_namelen = 0;
> + msg.msg_iov = iov;
> + msg.msg_iovlen = 1;
> + msg.msg_control = cmsg;
> + msg.msg_controllen = cmsg->cmsg_len;
> + msg.msg_flags = 0;
> +
> + if (recvmsg(fd_, &msg, 0) < 0) {
> + int ret = -errno;
> + LOG(IPCUnixSocket, Error)
> + << "Failed to recvmsg: " << strerror(-ret);
> + return ret;
> + }
> +
> + memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t));
> +
> + return 0;
> +}
> +
> +void IPCUnixSocket::dataNotifier(EventNotifier *notifier)
> +{
> + readyRead.emit(this);
I should implement support for connecting signals to signals with the
need for an intermediate slot.
> +}
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index 985aa7e8ab0eb6ce..45bd9d1793aa0b19 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -13,6 +13,7 @@ libcamera_sources = files([
> 'ipa_interface.cpp',
> 'ipa_manager.cpp',
> 'ipa_module.cpp',
> + 'ipc_unixsocket.cpp',
> 'log.cpp',
> 'media_device.cpp',
> 'media_object.cpp',
> @@ -38,6 +39,7 @@ libcamera_headers = files([
> 'include/formats.h',
> 'include/ipa_manager.h',
> 'include/ipa_module.h',
> + 'include/ipc_unixsocket.h',
> 'include/log.h',
> 'include/media_device.h',
> 'include/media_object.h',
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list