Skip to content

Commit 04b96e5

Browse files
jasowangmstsirkin
authored andcommitted
vhost: lockless enqueuing
We use spinlock to synchronize the work list now which may cause unnecessary contentions. So this patch switch to use llist to remove this contention. Pktgen tests shows about 5% improvement: Before: ~1300000 pps After: ~1370000 pps Signed-off-by: Jason Wang <[email protected]> Reviewed-by: Michael S. Tsirkin <[email protected]> Signed-off-by: Michael S. Tsirkin <[email protected]>
1 parent 7235acd commit 04b96e5

File tree

2 files changed

+29
-30
lines changed

2 files changed

+29
-30
lines changed

drivers/vhost/vhost.c

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ static int vhost_poll_wakeup(wait_queue_t *wait, unsigned mode, int sync,
168168

169169
void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn)
170170
{
171-
INIT_LIST_HEAD(&work->node);
171+
clear_bit(VHOST_WORK_QUEUED, &work->flags);
172172
work->fn = fn;
173173
init_waitqueue_head(&work->done);
174174
}
@@ -246,23 +246,24 @@ EXPORT_SYMBOL_GPL(vhost_poll_flush);
246246

247247
void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work)
248248
{
249-
unsigned long flags;
249+
if (!dev->worker)
250+
return;
250251

251-
spin_lock_irqsave(&dev->work_lock, flags);
252-
if (list_empty(&work->node)) {
253-
list_add_tail(&work->node, &dev->work_list);
254-
spin_unlock_irqrestore(&dev->work_lock, flags);
252+
if (!test_and_set_bit(VHOST_WORK_QUEUED, &work->flags)) {
253+
/* We can only add the work to the list after we're
254+
* sure it was not in the list.
255+
*/
256+
smp_mb();
257+
llist_add(&work->node, &dev->work_list);
255258
wake_up_process(dev->worker);
256-
} else {
257-
spin_unlock_irqrestore(&dev->work_lock, flags);
258259
}
259260
}
260261
EXPORT_SYMBOL_GPL(vhost_work_queue);
261262

262263
/* A lockless hint for busy polling code to exit the loop */
263264
bool vhost_has_work(struct vhost_dev *dev)
264265
{
265-
return !list_empty(&dev->work_list);
266+
return !llist_empty(&dev->work_list);
266267
}
267268
EXPORT_SYMBOL_GPL(vhost_has_work);
268269

@@ -305,7 +306,8 @@ static void vhost_vq_reset(struct vhost_dev *dev,
305306
static int vhost_worker(void *data)
306307
{
307308
struct vhost_dev *dev = data;
308-
struct vhost_work *work = NULL;
309+
struct vhost_work *work, *work_next;
310+
struct llist_node *node;
309311
mm_segment_t oldfs = get_fs();
310312

311313
set_fs(USER_DS);
@@ -315,29 +317,25 @@ static int vhost_worker(void *data)
315317
/* mb paired w/ kthread_stop */
316318
set_current_state(TASK_INTERRUPTIBLE);
317319

318-
spin_lock_irq(&dev->work_lock);
319-
320320
if (kthread_should_stop()) {
321-
spin_unlock_irq(&dev->work_lock);
322321
__set_current_state(TASK_RUNNING);
323322
break;
324323
}
325-
if (!list_empty(&dev->work_list)) {
326-
work = list_first_entry(&dev->work_list,
327-
struct vhost_work, node);
328-
list_del_init(&work->node);
329-
} else
330-
work = NULL;
331-
spin_unlock_irq(&dev->work_lock);
332324

333-
if (work) {
325+
node = llist_del_all(&dev->work_list);
326+
if (!node)
327+
schedule();
328+
329+
node = llist_reverse_order(node);
330+
/* make sure flag is seen after deletion */
331+
smp_wmb();
332+
llist_for_each_entry_safe(work, work_next, node, node) {
333+
clear_bit(VHOST_WORK_QUEUED, &work->flags);
334334
__set_current_state(TASK_RUNNING);
335335
work->fn(work);
336336
if (need_resched())
337337
schedule();
338-
} else
339-
schedule();
340-
338+
}
341339
}
342340
unuse_mm(dev->mm);
343341
set_fs(oldfs);
@@ -398,9 +396,9 @@ void vhost_dev_init(struct vhost_dev *dev,
398396
dev->log_file = NULL;
399397
dev->memory = NULL;
400398
dev->mm = NULL;
401-
spin_lock_init(&dev->work_lock);
402-
INIT_LIST_HEAD(&dev->work_list);
403399
dev->worker = NULL;
400+
init_llist_head(&dev->work_list);
401+
404402

405403
for (i = 0; i < dev->nvqs; ++i) {
406404
vq = dev->vqs[i];
@@ -566,7 +564,7 @@ void vhost_dev_cleanup(struct vhost_dev *dev, bool locked)
566564
/* No one will access memory at this point */
567565
kvfree(dev->memory);
568566
dev->memory = NULL;
569-
WARN_ON(!list_empty(&dev->work_list));
567+
WARN_ON(!llist_empty(&dev->work_list));
570568
if (dev->worker) {
571569
kthread_stop(dev->worker);
572570
dev->worker = NULL;

drivers/vhost/vhost.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
struct vhost_work;
1616
typedef void (*vhost_work_fn_t)(struct vhost_work *work);
1717

18+
#define VHOST_WORK_QUEUED 1
1819
struct vhost_work {
19-
struct list_head node;
20+
struct llist_node node;
2021
vhost_work_fn_t fn;
2122
wait_queue_head_t done;
2223
int flushing;
2324
unsigned queue_seq;
2425
unsigned done_seq;
26+
unsigned long flags;
2527
};
2628

2729
/* Poll a file (eventfd or socket) */
@@ -126,8 +128,7 @@ struct vhost_dev {
126128
int nvqs;
127129
struct file *log_file;
128130
struct eventfd_ctx *log_ctx;
129-
spinlock_t work_lock;
130-
struct list_head work_list;
131+
struct llist_head work_list;
131132
struct task_struct *worker;
132133
};
133134

0 commit comments

Comments
 (0)