Skip to contentSkip to conversation

VSOCK: support fill data to mergeable rx buffer in host

PATCH In review jiangyiwen 4 participants 26 messages Dec 12, 2018 – Dec 14, 2018

Subsystems (6)
lore
Share & tools
lore

jiangyiwen

author
#0 lore raw

PATCH v2 2/5 VSOCK: support fill data to mergeable rx buffer in host

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1
#1 lore raw
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Quoted 217 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1
author
#2 lore raw
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Thanks,
Yiwen.

Quoted 220 lines
Quoted 217 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1

.

#3 lore raw
Quoted 38 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Quoted 2 lines

Thanks,
Yiwen.

What I mean is that at least some of the gain here is because larger skbs are passed up the stack.

You do not need larger packets to build larger skbs though. Just check that the addresses match and you can combine multiple fragments in a single skb.

Quoted 221 lines
Quoted 220 lines
Quoted 217 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1

.

author
#4 lore raw
Quoted 39 lines · Michael S. Tsirkin
Quoted 38 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Quoted 6 lines
Quoted 2 lines

Thanks,
Yiwen.

What I mean is that at least some of the gain here is because larger skbs are passed up the stack.

Yes, the main gain is from larger skbs.

Quoted 5 lines

You do not need larger packets to build larger skbs though. Just check that the addresses match and you can combine multiple fragments in a single skb.

I understand what you mean, if use batching send that the performance also can be improved, I can test the real performance result only use batching.

Thanks,
Yiwen.

Quoted 225 lines
Quoted 207 lines
Quoted 207 lines
Quoted 207 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +0 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
Quoted 15 lines
Quoted 5 lines
Quoted 5 lines
include/uapi/linux/virtio_vsock.h +5 -0
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
Quoted 9 lines
Quoted 6 lines
include/uapi/linux/virtio_vsock.h +0 -0
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1

.

.

author
#5 lore raw
In reply to Michael S. Tsirkin
Quoted 39 lines · Michael S. Tsirkin
Quoted 38 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Quoted 6 lines
Quoted 2 lines

Thanks,
Yiwen.

What I mean is that at least some of the gain here is because larger skbs are passed up the stack.

Yes, the main gain is from larger skbs.

Quoted 5 lines

You do not need larger packets to build larger skbs though. Just check that the addresses match and you can combine multiple fragments in a single skb.

I understand what you mean, if use batching send that the performance also can be improved, I can test the real performance result only use batching.

Thanks,
Yiwen.

Quoted 225 lines
Quoted 207 lines
Quoted 207 lines
Quoted 207 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +0 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
Quoted 15 lines
Quoted 5 lines
Quoted 5 lines
include/uapi/linux/virtio_vsock.h +5 -0
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
Quoted 9 lines
Quoted 6 lines
include/uapi/linux/virtio_vsock.h +0 -0
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1

.

.

#6 lore raw
In reply to jiangyiwen
Quoted 38 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Quoted 2 lines

Thanks,
Yiwen.

What I mean is that at least some of the gain here is because larger skbs are passed up the stack.

You do not need larger packets to build larger skbs though. Just check that the addresses match and you can combine multiple fragments in a single skb.

Quoted 221 lines
Quoted 220 lines
Quoted 217 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1

.

maintainer
#7 lore raw
In reply to jiangyiwen
Quoted 29 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

Quoted 8 lines

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

I like the idea of making optimizations in small steps and measuring the effect of each step. This way we'll know which aspect caused the differences in benchmark results.

Stefan

author
#8 lore raw
Quoted 39 lines · Stefan Hajnoczi
Quoted 29 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

Quoted 8 lines

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Quoted 6 lines

I like the idea of making optimizations in small steps and measuring the effect of each step. This way we'll know which aspect caused the differences in benchmark results.

Stefan

Yes, now I also focus on other project, but I will use some extra time to measure it.

Thanks,
Yiwen.

author
#9 lore raw
In reply to Stefan Hajnoczi
Quoted 39 lines · Stefan Hajnoczi
Quoted 29 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

Quoted 8 lines

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

Quoted 6 lines

I like the idea of making optimizations in small steps and measuring the effect of each step. This way we'll know which aspect caused the differences in benchmark results.

Stefan

Yes, now I also focus on other project, but I will use some extra time to measure it.

Thanks,
Yiwen.

maintainer
#10 lore raw
In reply to jiangyiwen
Quoted 29 lines · jiangyiwen
Quoted 12 lines · Michael S. Tsirkin
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

Quoted 9 lines

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Hi Michael,

I don't fully understand what you mean, do you want to see a performance comparison that before performance and only use batching?

Quoted 8 lines

In my opinion, guest don't fill big buffer in rx vq because the balance performance and guest memory pressure, add mergeable feature can improve big packets performance, as for small packets, I try to find out the reason, may be the fluctuation of test results, or in mergeable mode, when Host send a 4k packet to Guest, we should call vhost_get_vq_desc() twice in host(hdr + 4k data), and in guest we also should call virtqueue_get_buf() twice.

I like the idea of making optimizations in small steps and measuring the effect of each step. This way we'll know which aspect caused the differences in benchmark results.

Stefan

_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization

#11 lore raw
In reply to jiangyiwen
Quoted 4 lines · jiangyiwen

When vhost support VIRTIO_VSOCK_F_MRG_RXBUF feature, it will merge big packet into rx vq.

I feel this approach jumps into making interface changes for optimizations too quickly. For example, what prevents us from taking a big buffer, prepending each chunk with the header and writing it out without host/guest interface changes?

This should allow optimizations such as vhost_add_used_n batching.

I realize a header in each packet does have a cost, but it also has advantages such as improved robustness, I'd like to see more of an apples to apples comparison of the performance gain from skipping them.

Quoted 217 lines
---
 drivers/vhost/vsock.c             | 111 ++++++++++++++++++++++++++++++--------
 include/linux/virtio_vsock.h      |   1 +
 include/uapi/linux/virtio_vsock.h |   5 ++
 3 files changed, 94 insertions(+), 23 deletions(-)
drivers/vhost/vsock.c +88 -23
index 34bc3ab..dc52b0f 100644
@@ -22,7 +22,8 @@
 #define VHOST_VSOCK_DEFAULT_HOST_CID	2
 
 enum {
-	VHOST_VSOCK_FEATURES = VHOST_FEATURES,
+	VHOST_VSOCK_FEATURES = VHOST_FEATURES |
+			(1ULL << VIRTIO_VSOCK_F_MRG_RXBUF),
 };
 
 /* Used to track all the vhost_vsock instances on the system. */
@@ -80,6 +81,69 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	return vsock;
 }
 
+/* This segment of codes are copied from drivers/vhost/net.c */
+static int get_rx_bufs(struct vhost_virtqueue *vq,
+		struct vring_used_elem *heads, int datalen,
+		unsigned *iovcount, unsigned int quota)
+{
+	unsigned int out, in;
+	int seg = 0;
+	int headcount = 0;
+	unsigned d;
+	int ret;
+	/*
+	 * len is always initialized before use since we are always called with
+	 * datalen > 0.
+	 */
+	u32 uninitialized_var(len);
+
+	while (datalen > 0 && headcount < quota) {
+		if (unlikely(seg >= UIO_MAXIOV)) {
+			ret = -ENOBUFS;
+			goto err;
+		}
+
+		ret = vhost_get_vq_desc(vq, vq->iov + seg,
+				ARRAY_SIZE(vq->iov) - seg, &out,
+				&in, NULL, NULL);
+		if (unlikely(ret < 0))
+			goto err;
+
+		d = ret;
+		if (d == vq->num) {
+			ret = 0;
+			goto err;
+		}
+
+		if (unlikely(out || in <= 0)) {
+			vq_err(vq, "unexpected descriptor format for RX: "
+					"out %d, in %d\n", out, in);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		heads[headcount].id = cpu_to_vhost32(vq, d);
+		len = iov_length(vq->iov + seg, in);
+		heads[headcount].len = cpu_to_vhost32(vq, len);
+		datalen -= len;
+		++headcount;
+		seg += in;
+	}
+
+	heads[headcount - 1].len = cpu_to_vhost32(vq, len + datalen);
+	*iovcount = seg;
+
+	/* Detect overrun */
+	if (unlikely(datalen > 0)) {
+		ret = UIO_MAXIOV + 1;
+		goto err;
+	}
+	return headcount;
+err:
+	vhost_discard_vq_desc(vq, headcount);
+	return ret;
+}
+
 static void
 vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 			    struct vhost_virtqueue *vq)
@@ -87,22 +151,34 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 	struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
 	bool added = false;
 	bool restart_tx = false;
+	int mergeable;
+	size_t vsock_hlen;
 
 	mutex_lock(&vq->mutex);
 
 	if (!vq->private_data)
 		goto out;
 
+	mergeable = vhost_has_feature(vq, VIRTIO_VSOCK_F_MRG_RXBUF);
+	/*
+	 * Guest fill page for rx vq in mergeable case, so it will not
+	 * allocate pkt structure, we should reserve size of pkt in advance.
+	 */
+	if (likely(mergeable))
+		vsock_hlen = sizeof(struct virtio_vsock_pkt);
+	else
+		vsock_hlen = sizeof(struct virtio_vsock_hdr);
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
 	for (;;) {
 		struct virtio_vsock_pkt *pkt;
 		struct iov_iter iov_iter;
-		unsigned out, in;
+		unsigned out = 0, in = 0;
 		size_t nbytes;
 		size_t len;
-		int head;
+		s16 headcount;
 
 		spin_lock_bh(&vsock->send_pkt_list_lock);
 		if (list_empty(&vsock->send_pkt_list)) {
@@ -116,16 +192,9 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 		list_del_init(&pkt->list);
 		spin_unlock_bh(&vsock->send_pkt_list_lock);
 
-		head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov),
-					 &out, &in, NULL, NULL);
-		if (head < 0) {
-			spin_lock_bh(&vsock->send_pkt_list_lock);
-			list_add(&pkt->list, &vsock->send_pkt_list);
-			spin_unlock_bh(&vsock->send_pkt_list_lock);
-			break;
-		}
-
-		if (head == vq->num) {
+		headcount = get_rx_bufs(vq, vq->heads, vsock_hlen + pkt->len,
+				&in, likely(mergeable) ? UIO_MAXIOV : 1);
+		if (headcount <= 0) {
 			spin_lock_bh(&vsock->send_pkt_list_lock);
 			list_add(&pkt->list, &vsock->send_pkt_list);
 			spin_unlock_bh(&vsock->send_pkt_list_lock);
@@ -133,24 +202,20 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
-			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
+			if (!headcount && unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
 			}
 			break;
 		}
 
-		if (out) {
-			virtio_transport_free_pkt(pkt);
-			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
-			break;
-		}
-
 		len = iov_length(&vq->iov[out], in);
 		iov_iter_init(&iov_iter, READ, &vq->iov[out], in, len);
 
-		nbytes = copy_to_iter(&pkt->hdr, sizeof(pkt->hdr), &iov_iter);
-		if (nbytes != sizeof(pkt->hdr)) {
+		if (likely(mergeable))
+			pkt->mrg_rxbuf_hdr.num_buffers = cpu_to_le16(headcount);
+		nbytes = copy_to_iter(&pkt->hdr, vsock_hlen, &iov_iter);
+		if (nbytes != vsock_hlen) {
 			virtio_transport_free_pkt(pkt);
 			vq_err(vq, "Faulted on copying pkt hdr\n");
 			break;
@@ -163,7 +228,7 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid)
 			break;
 		}
 
-		vhost_add_used(vq, head, sizeof(pkt->hdr) + pkt->len);
+		vhost_add_used_n(vq, vq->heads, headcount);
 		added = true;
 
 		if (pkt->reply) {
include/linux/virtio_vsock.h +1 -0
index bf84418..da9e1fe 100644
@@ -50,6 +50,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt {
 	struct virtio_vsock_hdr	hdr;
+	struct virtio_vsock_mrg_rxbuf_hdr mrg_rxbuf_hdr;
 	struct work_struct work;
 	struct list_head list;
 	/* socket refcnt not held, only use for cancellation */
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 };
Signature · 1 line
1.8.3.1
maintainer
#12 lore raw
In reply to jiangyiwen

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

author
#13 lore raw
Quoted 21 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

Quoted 2 lines

.

Hi David,

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

Thanks,
Yiwen.

maintainer
#14 lore raw

From: jiangyiwen <[email protected]>
Date: Thu, 13 Dec 2018 11:11:48 +0800

Quoted 3 lines

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

The question is if this actully matters.

Do you know?

If the obejct this is embeeded inside of is at least 2 byte aligned, you are marking it packed for nothing.

There are only %100 downsides to using the packed attribute.

Simply define your datastructures properly, with fixed sized types, and all padding defined explicitly.

author
#15 lore raw
Quoted 14 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Thu, 13 Dec 2018 11:11:48 +0800

Quoted 3 lines

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

The question is if this actully matters.

Do you know?

If the obejct this is embeeded inside of is at least 2 byte aligned, you are marking it packed for nothing.

Quoted 7 lines

There are only %100 downsides to using the packed attribute.

Simply define your datastructures properly, with fixed sized types, and all padding defined explicitly.

.

Hi David,

Thanks a lot, I need to send number buffers from Host to Guest, so I think we need to keep the structure size the same between host and guest. But after your reminder, I feel my code may exist a serious problem, that in mergeable mode, I send the total structure virtio_vsock_pkt from Host to Guest, however, this structure size may be different under different compilers (Guest and Host are different). Then, Guest may parse the wrong packet length.

David, I want to ask if there is such a problem?

In addition, why I send total virtio_vsock_pkt structure from Host to Guest?
- In order to avoid to allocate virtio_vsock_pkt memory when receiving packets, in case of insufficient memory, it may have some advantages, and we may keep consistent with old version.

Thanks again,
Yiwen.

maintainer
#16 lore raw
Quoted 33 lines · jiangyiwen
Quoted 14 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Thu, 13 Dec 2018 11:11:48 +0800

Quoted 3 lines

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

The question is if this actully matters.

Do you know?

If the obejct this is embeeded inside of is at least 2 byte aligned, you are marking it packed for nothing.

Quoted 7 lines

There are only %100 downsides to using the packed attribute.

Simply define your datastructures properly, with fixed sized types, and all padding defined explicitly.

.

Hi David,

Thanks a lot, I need to send number buffers from Host to Guest, so I think we need to keep the structure size the same between host and guest. But after your reminder, I feel my code may exist a serious problem, that in mergeable mode, I send the total structure virtio_vsock_pkt from Host to Guest, however, this structure size may be different under different compilers (Guest and Host are different). Then, Guest may parse the wrong packet length.

Quoted 6 lines

David, I want to ask if there is such a problem?

In addition, why I send total virtio_vsock_pkt structure from Host to Guest?
- In order to avoid to allocate virtio_vsock_pkt memory when receiving packets, in case of insufficient memory, it may have some advantages, and we may keep consistent with old version.

Yes, virtio_vsock_pkt is internal driver state and should not be part of the host<->guest interface (also for security reasons it's not good to expose internal state structs across the interface).

Stefan

maintainer
#17 lore raw
In reply to jiangyiwen
Quoted 33 lines · jiangyiwen
Quoted 14 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Thu, 13 Dec 2018 11:11:48 +0800

Quoted 3 lines

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

The question is if this actully matters.

Do you know?

If the obejct this is embeeded inside of is at least 2 byte aligned, you are marking it packed for nothing.

Quoted 7 lines

There are only %100 downsides to using the packed attribute.

Simply define your datastructures properly, with fixed sized types, and all padding defined explicitly.

.

Hi David,

Thanks a lot, I need to send number buffers from Host to Guest, so I think we need to keep the structure size the same between host and guest. But after your reminder, I feel my code may exist a serious problem, that in mergeable mode, I send the total structure virtio_vsock_pkt from Host to Guest, however, this structure size may be different under different compilers (Guest and Host are different). Then, Guest may parse the wrong packet length.

Quoted 6 lines

David, I want to ask if there is such a problem?

In addition, why I send total virtio_vsock_pkt structure from Host to Guest?
- In order to avoid to allocate virtio_vsock_pkt memory when receiving packets, in case of insufficient memory, it may have some advantages, and we may keep consistent with old version.

Yes, virtio_vsock_pkt is internal driver state and should not be part of the host<->guest interface (also for security reasons it's not good to expose internal state structs across the interface).

Stefan

_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/virtualization

author
#18 lore raw
In reply to David Miller
Quoted 14 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Thu, 13 Dec 2018 11:11:48 +0800

Quoted 3 lines

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

The question is if this actully matters.

Do you know?

If the obejct this is embeeded inside of is at least 2 byte aligned, you are marking it packed for nothing.

Quoted 7 lines

There are only %100 downsides to using the packed attribute.

Simply define your datastructures properly, with fixed sized types, and all padding defined explicitly.

.

Hi David,

Thanks a lot, I need to send number buffers from Host to Guest, so I think we need to keep the structure size the same between host and guest. But after your reminder, I feel my code may exist a serious problem, that in mergeable mode, I send the total structure virtio_vsock_pkt from Host to Guest, however, this structure size may be different under different compilers (Guest and Host are different). Then, Guest may parse the wrong packet length.

David, I want to ask if there is such a problem?

In addition, why I send total virtio_vsock_pkt structure from Host to Guest?
- In order to avoid to allocate virtio_vsock_pkt memory when receiving packets, in case of insufficient memory, it may have some advantages, and we may keep consistent with old version.

Thanks again,
Yiwen.

maintainer
#19 lore raw
In reply to jiangyiwen

From: jiangyiwen <[email protected]>
Date: Thu, 13 Dec 2018 11:11:48 +0800

Quoted 3 lines

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

The question is if this actully matters.

Do you know?

If the obejct this is embeeded inside of is at least 2 byte aligned, you are marking it packed for nothing.

There are only %100 downsides to using the packed attribute.

Simply define your datastructures properly, with fixed sized types, and all padding defined explicitly.

#20 lore raw
In reply to jiangyiwen
Quoted 25 lines · jiangyiwen
Quoted 21 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

Quoted 2 lines

.

Quoted 8 lines

Hi David,

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

Thanks,
Yiwen.

It doesn't work like this now though, does it? Buffers are preallocated and they are always aligned. So I do not see the point.

Signature · 1 line
MST
author
#21 lore raw
Quoted 32 lines · Michael S. Tsirkin
Quoted 31 lines · jiangyiwen
Quoted 21 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

Quoted 2 lines

.

Hi David,

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

Quoted 7 lines
Quoted 2 lines

Thanks,
Yiwen.

It doesn't work like this now though, does it? Buffers are preallocated and they are always aligned. So I do not see the point.

Hi Michael,

Now my patch has a serious problem, I use virtio_vsock_pkt as the transport header from host to guest, it will cause guest parse the wrong packet length. Because this structure size may be different under different compilers (guest and host are different). I will solve the problem in later version.

Thanks,
Yiwen.

author
#22 lore raw
In reply to Michael S. Tsirkin
Quoted 32 lines · Michael S. Tsirkin
Quoted 31 lines · jiangyiwen
Quoted 21 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

Quoted 2 lines

.

Hi David,

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

Quoted 7 lines
Quoted 2 lines

Thanks,
Yiwen.

It doesn't work like this now though, does it? Buffers are preallocated and they are always aligned. So I do not see the point.

Hi Michael,

Now my patch has a serious problem, I use virtio_vsock_pkt as the transport header from host to guest, it will cause guest parse the wrong packet length. Because this structure size may be different under different compilers (guest and host are different). I will solve the problem in later version.

Thanks,
Yiwen.

#23 lore raw
In reply to jiangyiwen
Quoted 25 lines · jiangyiwen
Quoted 21 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

Quoted 2 lines

.

Quoted 8 lines

Hi David,

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

Thanks,
Yiwen.

It doesn't work like this now though, does it? Buffers are preallocated and they are always aligned. So I do not see the point.

Signature · 1 line
MST
author
#24 lore raw
In reply to David Miller
Quoted 21 lines · David Miller

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.

Quoted 2 lines

.

Hi David,

I hope Host can fill fewer bytes into rx virtqueue, so I keep structure virtio_vsock_mrg_rxbuf_hdr one byte alignment.

Thanks,
Yiwen.

maintainer
#25 lore raw
In reply to jiangyiwen

From: jiangyiwen <[email protected]>
Date: Wed, 12 Dec 2018 17:29:31 +0800

include/uapi/linux/virtio_vsock.h struct virtio_vsock_hdr { · line 70

Quoted 13 lines
include/uapi/linux/virtio_vsock.h +5 -0
index 1d57ed3..2292f30 100644
@@ -63,6 +63,11 @@ struct virtio_vsock_hdr {
 	__le32	fwd_cnt;
 } __attribute__((packed));
 
+/* It add mergeable rx buffers feature */
+struct virtio_vsock_mrg_rxbuf_hdr {
+	__le16  num_buffers;    /* number of mergeable rx buffers */
+} __attribute__((packed));
+

I know the rest of this file uses 'packed' but this attribute should only be used if absolutely necessary as it incurs a non-trivial performance penalty for some architectures.