|
|
586cba |
From 4aeba0365d30dabe2e70dc172683f0878a4a9621 Mon Sep 17 00:00:00 2001
|
|
|
586cba |
From: Leonardo Bras <leobras@redhat.com>
|
|
|
586cba |
Date: Fri, 13 May 2022 03:28:32 -0300
|
|
|
586cba |
Subject: [PATCH 09/18] QIOChannelSocket: Implement io_writev zero copy flag &
|
|
|
586cba |
io_flush for CONFIG_LINUX
|
|
|
586cba |
MIME-Version: 1.0
|
|
|
586cba |
Content-Type: text/plain; charset=UTF-8
|
|
|
586cba |
Content-Transfer-Encoding: 8bit
|
|
|
586cba |
|
|
|
586cba |
RH-Author: Leonardo Brás <leobras@redhat.com>
|
|
|
586cba |
RH-MergeRequest: 95: MSG_ZEROCOPY + Multifd
|
|
|
586cba |
RH-Commit: [3/11] 9afeac1f5ac7675624660a0281726c09c8321180 (LeoBras/centos-qemu-kvm)
|
|
|
586cba |
RH-Bugzilla: 1968509
|
|
|
586cba |
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
586cba |
RH-Acked-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
|
|
|
586cba |
RH-Acked-by: Peter Xu <peterx@redhat.com>
|
|
|
586cba |
|
|
|
586cba |
For CONFIG_LINUX, implement the new zero copy flag and the optional callback
|
|
|
586cba |
io_flush on QIOChannelSocket, but enables it only when MSG_ZEROCOPY
|
|
|
586cba |
feature is available in the host kernel, which is checked on
|
|
|
586cba |
qio_channel_socket_connect_sync()
|
|
|
586cba |
|
|
|
586cba |
qio_channel_socket_flush() was implemented by counting how many times
|
|
|
586cba |
sendmsg(...,MSG_ZEROCOPY) was successfully called, and then reading the
|
|
|
586cba |
socket's error queue, in order to find how many of them finished sending.
|
|
|
586cba |
Flush will loop until those counters are the same, or until some error occurs.
|
|
|
586cba |
|
|
|
586cba |
Notes on using writev() with QIO_CHANNEL_WRITE_FLAG_ZERO_COPY:
|
|
|
586cba |
1: Buffer
|
|
|
586cba |
- As MSG_ZEROCOPY tells the kernel to use the same user buffer to avoid copying,
|
|
|
586cba |
some caution is necessary to avoid overwriting any buffer before it's sent.
|
|
|
586cba |
If something like this happen, a newer version of the buffer may be sent instead.
|
|
|
586cba |
- If this is a problem, it's recommended to call qio_channel_flush() before freeing
|
|
|
586cba |
or re-using the buffer.
|
|
|
586cba |
|
|
|
586cba |
2: Locked memory
|
|
|
586cba |
- When using MSG_ZERCOCOPY, the buffer memory will be locked after queued, and
|
|
|
586cba |
unlocked after it's sent.
|
|
|
586cba |
- Depending on the size of each buffer, and how often it's sent, it may require
|
|
|
586cba |
a larger amount of locked memory than usually available to non-root user.
|
|
|
586cba |
- If the required amount of locked memory is not available, writev_zero_copy
|
|
|
586cba |
will return an error, which can abort an operation like migration,
|
|
|
586cba |
- Because of this, when an user code wants to add zero copy as a feature, it
|
|
|
586cba |
requires a mechanism to disable it, so it can still be accessible to less
|
|
|
586cba |
privileged users.
|
|
|
586cba |
|
|
|
586cba |
Signed-off-by: Leonardo Bras <leobras@redhat.com>
|
|
|
586cba |
Reviewed-by: Peter Xu <peterx@redhat.com>
|
|
|
586cba |
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
|
|
|
586cba |
Reviewed-by: Juan Quintela <quintela@redhat.com>
|
|
|
586cba |
Message-Id: <20220513062836.965425-4-leobras@redhat.com>
|
|
|
586cba |
Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
|
|
|
586cba |
(cherry picked from commit 2bc58ffc2926a4efdd03edfb5909861fefc68c3d)
|
|
|
586cba |
Signed-off-by: Leonardo Bras <leobras@redhat.com>
|
|
|
586cba |
---
|
|
|
586cba |
include/io/channel-socket.h | 2 +
|
|
|
586cba |
io/channel-socket.c | 116 ++++++++++++++++++++++++++++++++++--
|
|
|
586cba |
2 files changed, 114 insertions(+), 4 deletions(-)
|
|
|
586cba |
|
|
|
586cba |
diff --git a/include/io/channel-socket.h b/include/io/channel-socket.h
|
|
|
586cba |
index e747e63514..513c428fe4 100644
|
|
|
586cba |
--- a/include/io/channel-socket.h
|
|
|
586cba |
+++ b/include/io/channel-socket.h
|
|
|
586cba |
@@ -47,6 +47,8 @@ struct QIOChannelSocket {
|
|
|
586cba |
socklen_t localAddrLen;
|
|
|
586cba |
struct sockaddr_storage remoteAddr;
|
|
|
586cba |
socklen_t remoteAddrLen;
|
|
|
586cba |
+ ssize_t zero_copy_queued;
|
|
|
586cba |
+ ssize_t zero_copy_sent;
|
|
|
586cba |
};
|
|
|
586cba |
|
|
|
586cba |
|
|
|
586cba |
diff --git a/io/channel-socket.c b/io/channel-socket.c
|
|
|
586cba |
index a1be2197ca..fbd2214d20 100644
|
|
|
586cba |
--- a/io/channel-socket.c
|
|
|
586cba |
+++ b/io/channel-socket.c
|
|
|
586cba |
@@ -26,6 +26,14 @@
|
|
|
586cba |
#include "io/channel-watch.h"
|
|
|
586cba |
#include "trace.h"
|
|
|
586cba |
#include "qapi/clone-visitor.h"
|
|
|
586cba |
+#ifdef CONFIG_LINUX
|
|
|
586cba |
+#include <linux/errqueue.h>
|
|
|
586cba |
+#include <sys/socket.h>
|
|
|
586cba |
+
|
|
|
586cba |
+#if (defined(MSG_ZEROCOPY) && defined(SO_ZEROCOPY))
|
|
|
586cba |
+#define QEMU_MSG_ZEROCOPY
|
|
|
586cba |
+#endif
|
|
|
586cba |
+#endif
|
|
|
586cba |
|
|
|
586cba |
#define SOCKET_MAX_FDS 16
|
|
|
586cba |
|
|
|
586cba |
@@ -55,6 +63,8 @@ qio_channel_socket_new(void)
|
|
|
586cba |
|
|
|
586cba |
sioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
|
|
|
586cba |
sioc->fd = -1;
|
|
|
586cba |
+ sioc->zero_copy_queued = 0;
|
|
|
586cba |
+ sioc->zero_copy_sent = 0;
|
|
|
586cba |
|
|
|
586cba |
ioc = QIO_CHANNEL(sioc);
|
|
|
586cba |
qio_channel_set_feature(ioc, QIO_CHANNEL_FEATURE_SHUTDOWN);
|
|
|
586cba |
@@ -154,6 +164,16 @@ int qio_channel_socket_connect_sync(QIOChannelSocket *ioc,
|
|
|
586cba |
return -1;
|
|
|
586cba |
}
|
|
|
586cba |
|
|
|
586cba |
+#ifdef QEMU_MSG_ZEROCOPY
|
|
|
586cba |
+ int ret, v = 1;
|
|
|
586cba |
+ ret = setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &v, sizeof(v));
|
|
|
586cba |
+ if (ret == 0) {
|
|
|
586cba |
+ /* Zero copy available on host */
|
|
|
586cba |
+ qio_channel_set_feature(QIO_CHANNEL(ioc),
|
|
|
586cba |
+ QIO_CHANNEL_FEATURE_WRITE_ZERO_COPY);
|
|
|
586cba |
+ }
|
|
|
586cba |
+#endif
|
|
|
586cba |
+
|
|
|
586cba |
return 0;
|
|
|
586cba |
}
|
|
|
586cba |
|
|
|
586cba |
@@ -534,6 +554,7 @@ static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
|
|
|
586cba |
char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
|
|
|
586cba |
size_t fdsize = sizeof(int) * nfds;
|
|
|
586cba |
struct cmsghdr *cmsg;
|
|
|
586cba |
+ int sflags = 0;
|
|
|
586cba |
|
|
|
586cba |
memset(control, 0, CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS));
|
|
|
586cba |
|
|
|
586cba |
@@ -558,15 +579,31 @@ static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
|
|
|
586cba |
memcpy(CMSG_DATA(cmsg), fds, fdsize);
|
|
|
586cba |
}
|
|
|
586cba |
|
|
|
586cba |
+#ifdef QEMU_MSG_ZEROCOPY
|
|
|
586cba |
+ if (flags & QIO_CHANNEL_WRITE_FLAG_ZERO_COPY) {
|
|
|
586cba |
+ sflags = MSG_ZEROCOPY;
|
|
|
586cba |
+ }
|
|
|
586cba |
+#endif
|
|
|
586cba |
+
|
|
|
586cba |
retry:
|
|
|
586cba |
- ret = sendmsg(sioc->fd, &msg, 0);
|
|
|
586cba |
+ ret = sendmsg(sioc->fd, &msg, sflags);
|
|
|
586cba |
if (ret <= 0) {
|
|
|
586cba |
- if (errno == EAGAIN) {
|
|
|
586cba |
+ switch (errno) {
|
|
|
586cba |
+ case EAGAIN:
|
|
|
586cba |
return QIO_CHANNEL_ERR_BLOCK;
|
|
|
586cba |
- }
|
|
|
586cba |
- if (errno == EINTR) {
|
|
|
586cba |
+ case EINTR:
|
|
|
586cba |
goto retry;
|
|
|
586cba |
+#ifdef QEMU_MSG_ZEROCOPY
|
|
|
586cba |
+ case ENOBUFS:
|
|
|
586cba |
+ if (sflags & MSG_ZEROCOPY) {
|
|
|
586cba |
+ error_setg_errno(errp, errno,
|
|
|
586cba |
+ "Process can't lock enough memory for using MSG_ZEROCOPY");
|
|
|
586cba |
+ return -1;
|
|
|
586cba |
+ }
|
|
|
586cba |
+ break;
|
|
|
586cba |
+#endif
|
|
|
586cba |
}
|
|
|
586cba |
+
|
|
|
586cba |
error_setg_errno(errp, errno,
|
|
|
586cba |
"Unable to write to socket");
|
|
|
586cba |
return -1;
|
|
|
586cba |
@@ -660,6 +697,74 @@ static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
|
|
|
586cba |
}
|
|
|
586cba |
#endif /* WIN32 */
|
|
|
586cba |
|
|
|
586cba |
+
|
|
|
586cba |
+#ifdef QEMU_MSG_ZEROCOPY
|
|
|
586cba |
+static int qio_channel_socket_flush(QIOChannel *ioc,
|
|
|
586cba |
+ Error **errp)
|
|
|
586cba |
+{
|
|
|
586cba |
+ QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
|
|
586cba |
+ struct msghdr msg = {};
|
|
|
586cba |
+ struct sock_extended_err *serr;
|
|
|
586cba |
+ struct cmsghdr *cm;
|
|
|
586cba |
+ char control[CMSG_SPACE(sizeof(*serr))];
|
|
|
586cba |
+ int received;
|
|
|
586cba |
+ int ret = 1;
|
|
|
586cba |
+
|
|
|
586cba |
+ msg.msg_control = control;
|
|
|
586cba |
+ msg.msg_controllen = sizeof(control);
|
|
|
586cba |
+ memset(control, 0, sizeof(control));
|
|
|
586cba |
+
|
|
|
586cba |
+ while (sioc->zero_copy_sent < sioc->zero_copy_queued) {
|
|
|
586cba |
+ received = recvmsg(sioc->fd, &msg, MSG_ERRQUEUE);
|
|
|
586cba |
+ if (received < 0) {
|
|
|
586cba |
+ switch (errno) {
|
|
|
586cba |
+ case EAGAIN:
|
|
|
586cba |
+ /* Nothing on errqueue, wait until something is available */
|
|
|
586cba |
+ qio_channel_wait(ioc, G_IO_ERR);
|
|
|
586cba |
+ continue;
|
|
|
586cba |
+ case EINTR:
|
|
|
586cba |
+ continue;
|
|
|
586cba |
+ default:
|
|
|
586cba |
+ error_setg_errno(errp, errno,
|
|
|
586cba |
+ "Unable to read errqueue");
|
|
|
586cba |
+ return -1;
|
|
|
586cba |
+ }
|
|
|
586cba |
+ }
|
|
|
586cba |
+
|
|
|
586cba |
+ cm = CMSG_FIRSTHDR(&msg;;
|
|
|
586cba |
+ if (cm->cmsg_level != SOL_IP &&
|
|
|
586cba |
+ cm->cmsg_type != IP_RECVERR) {
|
|
|
586cba |
+ error_setg_errno(errp, EPROTOTYPE,
|
|
|
586cba |
+ "Wrong cmsg in errqueue");
|
|
|
586cba |
+ return -1;
|
|
|
586cba |
+ }
|
|
|
586cba |
+
|
|
|
586cba |
+ serr = (void *) CMSG_DATA(cm);
|
|
|
586cba |
+ if (serr->ee_errno != SO_EE_ORIGIN_NONE) {
|
|
|
586cba |
+ error_setg_errno(errp, serr->ee_errno,
|
|
|
586cba |
+ "Error on socket");
|
|
|
586cba |
+ return -1;
|
|
|
586cba |
+ }
|
|
|
586cba |
+ if (serr->ee_origin != SO_EE_ORIGIN_ZEROCOPY) {
|
|
|
586cba |
+ error_setg_errno(errp, serr->ee_origin,
|
|
|
586cba |
+ "Error not from zero copy");
|
|
|
586cba |
+ return -1;
|
|
|
586cba |
+ }
|
|
|
586cba |
+
|
|
|
586cba |
+ /* No errors, count successfully finished sendmsg()*/
|
|
|
586cba |
+ sioc->zero_copy_sent += serr->ee_data - serr->ee_info + 1;
|
|
|
586cba |
+
|
|
|
586cba |
+ /* If any sendmsg() succeeded using zero copy, return 0 at the end */
|
|
|
586cba |
+ if (serr->ee_code != SO_EE_CODE_ZEROCOPY_COPIED) {
|
|
|
586cba |
+ ret = 0;
|
|
|
586cba |
+ }
|
|
|
586cba |
+ }
|
|
|
586cba |
+
|
|
|
586cba |
+ return ret;
|
|
|
586cba |
+}
|
|
|
586cba |
+
|
|
|
586cba |
+#endif /* QEMU_MSG_ZEROCOPY */
|
|
|
586cba |
+
|
|
|
586cba |
static int
|
|
|
586cba |
qio_channel_socket_set_blocking(QIOChannel *ioc,
|
|
|
586cba |
bool enabled,
|
|
|
586cba |
@@ -790,6 +895,9 @@ static void qio_channel_socket_class_init(ObjectClass *klass,
|
|
|
586cba |
ioc_klass->io_set_delay = qio_channel_socket_set_delay;
|
|
|
586cba |
ioc_klass->io_create_watch = qio_channel_socket_create_watch;
|
|
|
586cba |
ioc_klass->io_set_aio_fd_handler = qio_channel_socket_set_aio_fd_handler;
|
|
|
586cba |
+#ifdef QEMU_MSG_ZEROCOPY
|
|
|
586cba |
+ ioc_klass->io_flush = qio_channel_socket_flush;
|
|
|
586cba |
+#endif
|
|
|
586cba |
}
|
|
|
586cba |
|
|
|
586cba |
static const TypeInfo qio_channel_socket_info = {
|
|
|
586cba |
--
|
|
|
586cba |
2.35.3
|
|
|
586cba |
|