Skip to content

Commit c1ecd8e

Browse files
mikechristiemstsirkin
authored andcommitted
vhost: allow userspace to create workers
For vhost-scsi with 3 vqs or more and a workload that tries to use them in parallel like: fio --filename=/dev/sdb --direct=1 --rw=randrw --bs=4k \ --ioengine=libaio --iodepth=128 --numjobs=3 the single vhost worker thread will become a bottlneck and we are stuck at around 500K IOPs no matter how many jobs, virtqueues, and CPUs are used. To better utilize virtqueues and available CPUs, this patch allows userspace to create workers and bind them to vqs. You can have N workers per dev and also share N workers with M vqs on that dev. This patch adds the interface related code and the next patch will hook vhost-scsi into it. The patches do not try to hook net and vsock into the interface because: 1. multiple workers don't seem to help vsock. The problem is that with only 2 virtqueues we never fully use the existing worker when doing bidirectional tests. This seems to match vhost-scsi where we don't see the worker as a bottleneck until 3 virtqueues are used. 2. net already has a way to use multiple workers. Signed-off-by: Mike Christie <michael.christie@oracle.com> Message-Id: <20230626232307.97930-16-michael.christie@oracle.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
1 parent 1cdaafa commit c1ecd8e

4 files changed

Lines changed: 193 additions & 1 deletion

File tree

drivers/vhost/vhost.c

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,76 @@ static struct vhost_worker *vhost_worker_create(struct vhost_dev *dev)
626626
return NULL;
627627
}
628628

629+
/* Caller must have device mutex */
630+
static void __vhost_vq_attach_worker(struct vhost_virtqueue *vq,
631+
struct vhost_worker *worker)
632+
{
633+
if (vq->worker)
634+
vq->worker->attachment_cnt--;
635+
worker->attachment_cnt++;
636+
vq->worker = worker;
637+
}
638+
639+
/* Caller must have device and virtqueue mutex */
640+
static int vhost_vq_attach_worker(struct vhost_virtqueue *vq,
641+
struct vhost_vring_worker *info)
642+
{
643+
unsigned long index = info->worker_id;
644+
struct vhost_dev *dev = vq->dev;
645+
struct vhost_worker *worker;
646+
647+
if (!dev->use_worker)
648+
return -EINVAL;
649+
650+
/*
651+
* We only allow userspace to set a virtqueue's worker if it's not
652+
* active and polling is not enabled. We also assume drivers
653+
* supporting this will not be internally queueing works directly or
654+
* via calls like vhost_dev_flush at this time.
655+
*/
656+
if (vhost_vq_get_backend(vq) || vq->kick)
657+
return -EBUSY;
658+
659+
worker = xa_find(&dev->worker_xa, &index, UINT_MAX, XA_PRESENT);
660+
if (!worker || worker->id != info->worker_id)
661+
return -ENODEV;
662+
663+
__vhost_vq_attach_worker(vq, worker);
664+
return 0;
665+
}
666+
667+
/* Caller must have device mutex */
668+
static int vhost_new_worker(struct vhost_dev *dev,
669+
struct vhost_worker_state *info)
670+
{
671+
struct vhost_worker *worker;
672+
673+
worker = vhost_worker_create(dev);
674+
if (!worker)
675+
return -ENOMEM;
676+
677+
info->worker_id = worker->id;
678+
return 0;
679+
}
680+
681+
/* Caller must have device mutex */
682+
static int vhost_free_worker(struct vhost_dev *dev,
683+
struct vhost_worker_state *info)
684+
{
685+
unsigned long index = info->worker_id;
686+
struct vhost_worker *worker;
687+
688+
worker = xa_find(&dev->worker_xa, &index, UINT_MAX, XA_PRESENT);
689+
if (!worker || worker->id != info->worker_id)
690+
return -ENODEV;
691+
692+
if (worker->attachment_cnt)
693+
return -EBUSY;
694+
695+
vhost_worker_destroy(dev, worker);
696+
return 0;
697+
}
698+
629699
static int vhost_get_vq_from_user(struct vhost_dev *dev, void __user *argp,
630700
struct vhost_virtqueue **vq, u32 *id)
631701
{
@@ -647,6 +717,76 @@ static int vhost_get_vq_from_user(struct vhost_dev *dev, void __user *argp,
647717
return 0;
648718
}
649719

720+
/* Caller must have device mutex */
721+
long vhost_worker_ioctl(struct vhost_dev *dev, unsigned int ioctl,
722+
void __user *argp)
723+
{
724+
struct vhost_vring_worker ring_worker;
725+
struct vhost_worker_state state;
726+
struct vhost_virtqueue *vq;
727+
long ret;
728+
u32 idx;
729+
730+
if (!dev->use_worker)
731+
return -EINVAL;
732+
733+
if (!vhost_dev_has_owner(dev))
734+
return -EINVAL;
735+
736+
ret = vhost_dev_check_owner(dev);
737+
if (ret)
738+
return ret;
739+
740+
switch (ioctl) {
741+
/* dev worker ioctls */
742+
case VHOST_NEW_WORKER:
743+
ret = vhost_new_worker(dev, &state);
744+
if (!ret && copy_to_user(argp, &state, sizeof(state)))
745+
ret = -EFAULT;
746+
return ret;
747+
case VHOST_FREE_WORKER:
748+
if (copy_from_user(&state, argp, sizeof(state)))
749+
return -EFAULT;
750+
return vhost_free_worker(dev, &state);
751+
/* vring worker ioctls */
752+
case VHOST_ATTACH_VRING_WORKER:
753+
case VHOST_GET_VRING_WORKER:
754+
break;
755+
default:
756+
return -ENOIOCTLCMD;
757+
}
758+
759+
ret = vhost_get_vq_from_user(dev, argp, &vq, &idx);
760+
if (ret)
761+
return ret;
762+
763+
mutex_lock(&vq->mutex);
764+
switch (ioctl) {
765+
case VHOST_ATTACH_VRING_WORKER:
766+
if (copy_from_user(&ring_worker, argp, sizeof(ring_worker))) {
767+
ret = -EFAULT;
768+
break;
769+
}
770+
771+
ret = vhost_vq_attach_worker(vq, &ring_worker);
772+
break;
773+
case VHOST_GET_VRING_WORKER:
774+
ring_worker.index = idx;
775+
ring_worker.worker_id = vq->worker->id;
776+
777+
if (copy_to_user(argp, &ring_worker, sizeof(ring_worker)))
778+
ret = -EFAULT;
779+
break;
780+
default:
781+
ret = -ENOIOCTLCMD;
782+
break;
783+
}
784+
785+
mutex_unlock(&vq->mutex);
786+
return ret;
787+
}
788+
EXPORT_SYMBOL_GPL(vhost_worker_ioctl);
789+
650790
/* Caller should have device mutex */
651791
long vhost_dev_set_owner(struct vhost_dev *dev)
652792
{
@@ -684,7 +824,7 @@ long vhost_dev_set_owner(struct vhost_dev *dev)
684824
smp_wmb();
685825

686826
for (i = 0; i < dev->nvqs; i++)
687-
dev->vqs[i]->worker = worker;
827+
__vhost_vq_attach_worker(dev->vqs[i], worker);
688828
}
689829

690830
return 0;

drivers/vhost/vhost.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct vhost_worker {
3131
struct llist_head work_list;
3232
u64 kcov_handle;
3333
u32 id;
34+
int attachment_cnt;
3435
};
3536

3637
/* Poll a file (eventfd or socket) */
@@ -190,6 +191,8 @@ void vhost_dev_cleanup(struct vhost_dev *);
190191
void vhost_dev_stop(struct vhost_dev *);
191192
long vhost_dev_ioctl(struct vhost_dev *, unsigned int ioctl, void __user *argp);
192193
long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp);
194+
long vhost_worker_ioctl(struct vhost_dev *dev, unsigned int ioctl,
195+
void __user *argp);
193196
bool vhost_vq_access_ok(struct vhost_virtqueue *vq);
194197
bool vhost_log_access_ok(struct vhost_dev *);
195198
void vhost_clear_msg(struct vhost_dev *dev);

include/uapi/linux/vhost.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@
4545
#define VHOST_SET_LOG_BASE _IOW(VHOST_VIRTIO, 0x04, __u64)
4646
/* Specify an eventfd file descriptor to signal on log write. */
4747
#define VHOST_SET_LOG_FD _IOW(VHOST_VIRTIO, 0x07, int)
48+
/* By default, a device gets one vhost_worker that its virtqueues share. This
49+
* command allows the owner of the device to create an additional vhost_worker
50+
* for the device. It can later be bound to 1 or more of its virtqueues using
51+
* the VHOST_ATTACH_VRING_WORKER command.
52+
*
53+
* This must be called after VHOST_SET_OWNER and the caller must be the owner
54+
* of the device. The new thread will inherit caller's cgroups and namespaces,
55+
* and will share the caller's memory space. The new thread will also be
56+
* counted against the caller's RLIMIT_NPROC value.
57+
*
58+
* The worker's ID used in other commands will be returned in
59+
* vhost_worker_state.
60+
*/
61+
#define VHOST_NEW_WORKER _IOR(VHOST_VIRTIO, 0x8, struct vhost_worker_state)
62+
/* Free a worker created with VHOST_NEW_WORKER if it's not attached to any
63+
* virtqueue. If userspace is not able to call this for workers its created,
64+
* the kernel will free all the device's workers when the device is closed.
65+
*/
66+
#define VHOST_FREE_WORKER _IOW(VHOST_VIRTIO, 0x9, struct vhost_worker_state)
4867

4968
/* Ring setup. */
5069
/* Set number of descriptors in ring. This parameter can not
@@ -70,6 +89,20 @@
7089
#define VHOST_VRING_BIG_ENDIAN 1
7190
#define VHOST_SET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x13, struct vhost_vring_state)
7291
#define VHOST_GET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x14, struct vhost_vring_state)
92+
/* Attach a vhost_worker created with VHOST_NEW_WORKER to one of the device's
93+
* virtqueues. This must be done before VHOST_SET_VRING_KICK and the driver
94+
* specific ioctl to activate the virtqueue (VHOST_SCSI_SET_ENDPOINT,
95+
* VHOST_NET_SET_BACKEND, VHOST_VSOCK_SET_RUNNING) has been run.
96+
*
97+
* This will replace the virtqueue's existing worker. If the replaced worker
98+
* is no longer attached to any virtqueues, it can be freed with
99+
* VHOST_FREE_WORKER.
100+
*/
101+
#define VHOST_ATTACH_VRING_WORKER _IOW(VHOST_VIRTIO, 0x15, \
102+
struct vhost_vring_worker)
103+
/* Return the vring worker's ID */
104+
#define VHOST_GET_VRING_WORKER _IOWR(VHOST_VIRTIO, 0x16, \
105+
struct vhost_vring_worker)
73106

74107
/* The following ioctls use eventfd file descriptors to signal and poll
75108
* for events. */

include/uapi/linux/vhost_types.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ struct vhost_vring_addr {
4747
__u64 log_guest_addr;
4848
};
4949

50+
struct vhost_worker_state {
51+
/*
52+
* For VHOST_NEW_WORKER the kernel will return the new vhost_worker id.
53+
* For VHOST_FREE_WORKER this must be set to the id of the vhost_worker
54+
* to free.
55+
*/
56+
unsigned int worker_id;
57+
};
58+
59+
struct vhost_vring_worker {
60+
/* vring index */
61+
unsigned int index;
62+
/* The id of the vhost_worker returned from VHOST_NEW_WORKER */
63+
unsigned int worker_id;
64+
};
65+
5066
/* no alignment requirement */
5167
struct vhost_iotlb_msg {
5268
__u64 iova;

0 commit comments

Comments
 (0)