Skip to contentSkip to conversation

workqueue: Add an API to create a singlethread workqueue attached to the current task's cgroup

PATCH In review Sridhar Samudrala 7 participants 115 messages May 19, 2010 – Aug 4, 2010

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

PATCH 2/3 workqueue: Add an API to create a singlethread workqueue attached to the current task's cgroup

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

include/linux/workqueue.h +1 -0
index 9466e86..6d6f301 100644
@@ -211,6 +211,7 @@ __create_workqueue_key(const char *name, int singlethread,
 #define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1, 0)
 #define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0, 0)
 
+extern struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name); 
 extern void destroy_workqueue(struct workqueue_struct *wq);
 
 extern int queue_work(struct workqueue_struct *wq, struct work_struct *work);
kernel/workqueue.c +40 -0
index 5bfb213..6ba226e 100644
@@ -35,6 +35,7 @@
 #include <linux/lockdep.h>
 #define CREATE_TRACE_POINTS
 #include <trace/events/workqueue.h>
+#include <linux/cgroup.h>
 
 /*
  * The per-CPU workqueue (if single thread, we always use the first
@@ -193,6 +194,45 @@ static const struct cpumask *cpu_singlethread_map __read_mostly;
  */
 static cpumask_var_t cpu_populated_map __read_mostly;
 
+static struct task_struct *get_singlethread_wq_task(struct workqueue_struct *wq)
+{
+	return (per_cpu_ptr(wq->cpu_wq, singlethread_cpu))->thread;
+}
+
+/* Create a singlethread workqueue and attach it's task to the current task's
+ * cgroup and set it's cpumask to the current task's cpumask.
+ */
+struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
+{
+	struct workqueue_struct *wq;
+	struct task_struct *task;
+	cpumask_var_t mask;
+
+	wq = create_singlethread_workqueue(name);
+	if (!wq)
+		goto out;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto err;
+			
+	if (sched_getaffinity(current->pid, mask))
+		goto err;
+
+	task = get_singlethread_wq_task(wq);
+	if (sched_setaffinity(task->pid, mask))
+		goto err;
+
+	if (cgroup_attach_task_current_cg(task))
+		goto err;
+out:	
+	return wq;
+err:
+	destroy_workqueue(wq);
+	wq = NULL;
+	goto out;
+}
+EXPORT_SYMBOL_GPL(create_singlethread_workqueue_in_current_cg);
+
 /* If it's single threaded, it isn't in the list of workqueues. */
 static inline int is_wq_single_threaded(struct workqueue_struct *wq)
 {
#1 lore raw
Quoted 4 lines · Sridhar Samudrala

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

Could someone familiar with workqueue code please comment on whether this patch is suitable for 2.6.35?

It is needed to fix the case where vhost user might cause a kernel thread to consume more CPU than allowed by the cgroup. Should I merge it through the vhost tree?
Ack for this?

Thanks!

Quoted 71 lines
include/linux/workqueue.h +1 -0
index 9466e86..6d6f301 100644
@@ -211,6 +211,7 @@ __create_workqueue_key(const char *name, int singlethread,
 #define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1, 0)
 #define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0, 0)
 
+extern struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name); 
 extern void destroy_workqueue(struct workqueue_struct *wq);
 
 extern int queue_work(struct workqueue_struct *wq, struct work_struct *work);
kernel/workqueue.c +40 -0
index 5bfb213..6ba226e 100644
@@ -35,6 +35,7 @@
 #include <linux/lockdep.h>
 #define CREATE_TRACE_POINTS
 #include <trace/events/workqueue.h>
+#include <linux/cgroup.h>
 
 /*
  * The per-CPU workqueue (if single thread, we always use the first
@@ -193,6 +194,45 @@ static const struct cpumask *cpu_singlethread_map __read_mostly;
  */
 static cpumask_var_t cpu_populated_map __read_mostly;
 
+static struct task_struct *get_singlethread_wq_task(struct workqueue_struct *wq)
+{
+	return (per_cpu_ptr(wq->cpu_wq, singlethread_cpu))->thread;
+}
+
+/* Create a singlethread workqueue and attach it's task to the current task's
+ * cgroup and set it's cpumask to the current task's cpumask.
+ */
+struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
+{
+	struct workqueue_struct *wq;
+	struct task_struct *task;
+	cpumask_var_t mask;
+
+	wq = create_singlethread_workqueue(name);
+	if (!wq)
+		goto out;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto err;
+			
+	if (sched_getaffinity(current->pid, mask))
+		goto err;
+
+	task = get_singlethread_wq_task(wq);
+	if (sched_setaffinity(task->pid, mask))
+		goto err;
+
+	if (cgroup_attach_task_current_cg(task))
+		goto err;
+out:	
+	return wq;
+err:
+	destroy_workqueue(wq);
+	wq = NULL;
+	goto out;
+}
+EXPORT_SYMBOL_GPL(create_singlethread_workqueue_in_current_cg);
+
 /* If it's single threaded, it isn't in the list of workqueues. */
 static inline int is_wq_single_threaded(struct workqueue_struct *wq)
 {
#2 lore raw
Quoted 7 lines · Michael S. Tsirkin
Quoted 4 lines · Sridhar Samudrala

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

Quoted 7 lines

Could someone familiar with workqueue code please comment on whether this patch is suitable for 2.6.35?

It is needed to fix the case where vhost user might cause a kernel thread to consume more CPU than allowed by the cgroup. Should I merge it through the vhost tree?
Ack for this?

I don't understand the reasons for this patch, but this doesn't matter.

I don't really see any need to change workqueue.c,

Quoted 4 lines

kernel/workqueue.c static const struct cpumask *cpu_singlethread_map __read_mostly; · line 200

Quoted 4 lines
kernel/workqueue.c +4 -0
+static struct task_struct *get_singlethread_wq_task(struct workqueue_struct *wq)
+{
+	return (per_cpu_ptr(wq->cpu_wq, singlethread_cpu))->thread;
+}

(Not sure this trivial static helper with the single caller makes sense, but
see below)

Quoted 24 lines
Quoted 24 lines
kernel/workqueue.c +24 -0
+/* Create a singlethread workqueue and attach it's task to the current task's
+ * cgroup and set it's cpumask to the current task's cpumask.
+ */
+struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
+{
+	struct workqueue_struct *wq;
+	struct task_struct *task;
+	cpumask_var_t mask;
+
+	wq = create_singlethread_workqueue(name);
+	if (!wq)
+		goto out;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto err;
+			
+	if (sched_getaffinity(current->pid, mask))
+		goto err;
+
+	task = get_singlethread_wq_task(wq);
+	if (sched_setaffinity(task->pid, mask))
+		goto err;
+
+	if (cgroup_attach_task_current_cg(task))
Quoted 8 lines

kernel/workqueue.c static const struct cpumask *cpu_singlethread_map __read_mostly; · line 233

Quoted 8 lines
kernel/workqueue.c +8 -0
+		goto err;
+out:	
+	return wq;
+err:
+	destroy_workqueue(wq);
+	wq = NULL;
+	goto out;
+}

Instead, cgroup.c (or whoever needs this) can do

	struct move_struct {
		struct work_struct work;
		int ret;
	};

	static void move_func(struct work_struct *work)
	{
		struct move_struct *move = container_of(...);

		if (cgroup_attach_task_current_cg(current))
			ret = -EANY;
	}

	static struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
	{
		struct workqueue_struct *wq;
		struct move_struct move = {
			.work = __WORK_INITIALIZER(move_func);
		};

		wq = create_singlethread_workqueue(name);
		if (!wq)
			return NULL;

		queue_work(&move.work);
		flush_work(&move.work);

		if (move.ret) {
			destroy_workqueue(wq);
			wq = NULL;
		}

		return wq;
	}

Or. Just export wq_per_cpu() from workqueue.c (probably with a better name) and use it like the patch does.

But, imho, create_singlethread_workqueue_in_current_cg() does not belong to workqueue.c.

Oleg.

#3 lore raw
Quoted 11 lines · Oleg Nesterov
Quoted 10 lines · Michael S. Tsirkin
Quoted 4 lines · Sridhar Samudrala

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

Could someone familiar with workqueue code please comment on whether this patch is suitable for 2.6.35?

Quoted 6 lines
Quoted 4 lines

It is needed to fix the case where vhost user might cause a kernel thread to consume more CPU than allowed by the cgroup. Should I merge it through the vhost tree?
Ack for this?

I don't understand the reasons for this patch, but this doesn't matter.

Depending on userspace application, driver can create a lot of work for a workqueue to handle. By making the workqueue thread belong in a cgroup, we make it possible to the CPU and other resources thus consumed.

Signature · 1 line
MST
#4 lore raw
Quoted 18 lines · Michael S. Tsirkin
Quoted 16 lines · Oleg Nesterov
Quoted 14 lines · Michael S. Tsirkin
Quoted 4 lines · Sridhar Samudrala

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

Could someone familiar with workqueue code please comment on whether this patch is suitable for 2.6.35?

It is needed to fix the case where vhost user might cause a kernel thread to consume more CPU than allowed by the cgroup. Should I merge it through the vhost tree?
Ack for this?

Quoted 6 lines
Quoted 1 line

I don't understand the reasons for this patch, but this doesn't matter.

Depending on userspace application, driver can create a lot of work for a workqueue to handle. By making the workqueue thread belong in a cgroup, we make it possible to the CPU and other resources thus consumed.

OK, I see, thanks for your explanation.

in case I wasn't clear... I didn't mean I dislike this idea, only the the implementation of create_singlethread_workqueue_in_current_cg(), it doesn't belong to workqueue.c imho.

Oleg.

maintainer
#5 lore raw
In reply to Michael S. Tsirkin

Hello,

Quoted 6 lines · Michael S. Tsirkin
Quoted 1 line

I don't understand the reasons for this patch, but this doesn't matter.

Depending on userspace application, driver can create a lot of work for a workqueue to handle. By making the workqueue thread belong in a cgroup, we make it possible to the CPU and other resources thus consumed.

Hmmm.... I don't really get it. The unit of scheduling in workqueue is a work. Unless you're gonna convert every driver to use this special kind of workqueue (and what happens when multiple tasks from different cgroups share the driver?), I can't see how this is gonna be useful. If you really wanna impose cgroup control on workqueue items, you'll have to do it per work item which might lead to the problem of priority inversion. Can you please describe what you're trying to do in more detail?

Thank you.

Signature · 1 line
tejun
#6 lore raw
Quoted 5 lines · Tejun Heo

Hello,

Quoted 2 lines · Michael S. Tsirkin
Quoted 1 line

I don't understand the reasons for this patch, but this doesn't matter.

Quoted 7 lines
Quoted 4 lines

Depending on userspace application, driver can create a lot of work for a workqueue to handle. By making the workqueue thread belong in a cgroup, we make it possible to the CPU and other resources thus consumed.

Hmmm.... I don't really get it. The unit of scheduling in workqueue is a work.

Yes. However, we use cgroups to limit when the workqueue itself is scheduled. This affects all of work done on this workqueue, so it's a bit
of a blunt intrument. Thus we are not trying to apply this
to all drivers, we intend to start with vhost-net.

Quoted 3 lines

Unless you're gonna convert every driver to use this special kind of workqueue (and what happens when multiple tasks from different cgroups share the driver?),

We'll then create a workqueue per task. Each workqueue will have the right cgroup. But we are not trying to selve the problem for every driver.

Quoted 4 lines

I can't see how this is gonna be
useful. If you really wanna impose cgroup control on workqueue items, you'll have to do it per work item which might lead to the problem of priority inversion.

Exactly. cgroup is per-workqueue not per work item. If driver wants to let administrators control priority for different kinds of items separately, driver will have to submit them to separate workqueues.

Quoted 4 lines

Can you please describe what you're trying to do
in more detail?

Thank you.

vhost-net driver is under control from userspace,
it queues potentially a lot of work into the workqueue, which might load the system beyond the cgroup limits.
And staying within cgroups limits is important for virtualization where vhost is used.

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#7 lore raw

Hello,

Quoted 7 lines · Michael S. Tsirkin
Quoted 3 lines

Unless you're gonna convert every driver to use this special kind of workqueue (and what happens when multiple tasks from different cgroups share the driver?),

We'll then create a workqueue per task. Each workqueue will have the right cgroup. But we are not trying to selve the problem for every driver.

Ah... I see. You're gonna use multiple workqueues. Once concern that I have is that this is abuse of workqueue interface to certain level and depends on the implementation detail of workqueue rather than its intended usage model. stop_machine() was a similar case and in the end it was better served by a different mechanism built on kthread directly (cpu_stop). Wouldn't it be cleaner to use kthread directly for your case too? You're basically trying to use workqueue as a frontend to kthread, so...

Thanks.

Signature · 1 line
tejun
#8 lore raw
Quoted 7 lines · Tejun Heo

Hello,

Quoted 4 lines · Michael S. Tsirkin
Quoted 3 lines

Unless you're gonna convert every driver to use this special kind of workqueue (and what happens when multiple tasks from different cgroups share the driver?),

Quoted 8 lines
Quoted 3 lines

We'll then create a workqueue per task. Each workqueue will have the right cgroup. But we are not trying to selve the problem for every driver.

Ah... I see. You're gonna use multiple workqueues. Once concern that I have is that this is abuse of workqueue interface to certain level and depends on the implementation detail of workqueue rather than its intended usage model.

Well, this is why I proposed adding a new API for creating workqueue within workqueue.c, rather than exposing the task and attaching it to cgroups in our driver: so that workqueue maintainers can fix the implementation if it ever changes.

And after all, it's an internal API, we can always change it later if we need.

Quoted 7 lines

stop_machine() was a similar case and in the
end it was better served by a different mechanism built on kthread directly (cpu_stop). Wouldn't it be cleaner to use kthread directly for your case too? You're basically trying to use workqueue as a frontend to kthread, so...

Thanks.

Well, yes but we are using APIs like flush_work etc. These are very handy. It seems much easier than rolling our own queue on top of kthread.

Makes sense?

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#9 lore raw

Hello, Michael.

Quoted 7 lines · Michael S. Tsirkin

Well, this is why I proposed adding a new API for creating workqueue within workqueue.c, rather than exposing the task and attaching it to cgroups in our driver: so that workqueue maintainers can fix the implementation if it ever changes.

And after all, it's an internal API, we can always change it later if we need.

...

Quoted 2 lines

Well, yes but we are using APIs like flush_work etc. These are very handy. It seems much easier than rolling our own queue on top of kthread.

The thing is that this kind of one-off usage becomes problemetic when you're trying to change the implementation detail. All current workqueue users don't care which thread they run on and they shouldn't as each work owns the context only for the duration the work is executing. If this sort of fundamental guidelines are followed, the implementation can be improved in pretty much transparent way but when you start depending on specific implementation details, things become messy pretty quickly.

If this type of usage were more common, adding proper way to account work usage according to cgroups would make sense but that's not the case here and I removed the only exception case recently while trying to implement cmwq and if this is added. So, this would be the only one which makes such extra assumptions in the whole kernel. One way or the other, workqueue needs to be improved and I don't really think adding the single exception at this point is a good idea.

The thing I realized after stop_machine conversion was that there was no reason to use workqueue there at all. There already are more than enough not-too-difficult synchronization constructs and if you're using a thread for dedicated purposes, code complexity isn't that different either way. Plus, it would also be clearer that dedicated threads are required there for what reason. So, I strongly suggest using a kthread. If there are issues which are noticeably difficult to solve with kthread, we can definitely talk about that and think about things again.

Thank you.

Signature · 1 line
tejun
#10 lore raw
Quoted 41 lines · Tejun Heo

Hello, Michael.

Quoted 7 lines · Michael S. Tsirkin

Well, this is why I proposed adding a new API for creating workqueue within workqueue.c, rather than exposing the task and attaching it to cgroups in our driver: so that workqueue maintainers can fix the implementation if it ever changes.

And after all, it's an internal API, we can always change it later if we need.

...

Quoted 2 lines

Well, yes but we are using APIs like flush_work etc. These are very handy. It seems much easier than rolling our own queue on top of kthread.

The thing is that this kind of one-off usage becomes problemetic when you're trying to change the implementation detail. All current workqueue users don't care which thread they run on and they shouldn't as each work owns the context only for the duration the work is executing. If this sort of fundamental guidelines are followed, the implementation can be improved in pretty much transparent way but when you start depending on specific implementation details, things become messy pretty quickly.

If this type of usage were more common, adding proper way to account work usage according to cgroups would make sense but that's not the case here and I removed the only exception case recently while trying to implement cmwq and if this is added. So, this would be the only one which makes such extra assumptions in the whole kernel. One way or the other, workqueue needs to be improved and I don't really think adding the single exception at this point is a good idea.

The thing I realized after stop_machine conversion was that there was no reason to use workqueue there at all. There already are more than enough not-too-difficult synchronization constructs and if you're using a thread for dedicated purposes, code complexity isn't that different either way. Plus, it would also be clearer that dedicated threads are required there for what reason. So, I strongly suggest using a kthread. If there are issues which are noticeably difficult to solve with kthread, we can definitely talk about that and think about things again.

Quoted 1 line

Thank you.

Well, we have create_singlethread_workqueue, right? This is not very different ... is it?

Just copying structures and code from workqueue.c,
adding vhost_ in front of it will definitely work:
there is nothing magic about the workqueue library.
But this just involves cut and paste which might be best avoided. One final idea before we go the cut and paste way: how about 'create_workqueue_from_task' that would get a thread and have workqueue run there?

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#11 lore raw

Hello,

Quoted 5 lines · Michael S. Tsirkin

Well, we have create_singlethread_workqueue, right? This is not very different ... is it?

Just copying structures and code from workqueue.c, adding vhost_ in front of it will definitely work:

Sure it will, but you'll probably be able to get away with much less.

Quoted 2 lines

there is nothing magic about the workqueue library.
But this just involves cut and paste which might be best avoided.

What I'm saying is that some magic needs to be added to workqueue and if you add this single(!) exception, it will have to be backed out pretty soon, so it would be better to do it properly now.

Quoted 3 lines

One final idea before we go the cut and paste way: how about 'create_workqueue_from_task' that would get a thread and have workqueue run there?

You can currently depend on that implementation detail but it's not the workqueue interface is meant to do. The single threadedness is there as execution ordering and concurrency specification and it doesn't (or rather won't) necessarily mean that a specific single thread is bound to certain workqueue.

Can you please direct me to have a look at the code. I'll be happy to do the conversion for you.

Thanks.

Signature · 1 line
tejun
#12 lore raw
Quoted 22 lines · Tejun Heo

Hello,

Quoted 5 lines · Michael S. Tsirkin

Well, we have create_singlethread_workqueue, right? This is not very different ... is it?

Just copying structures and code from workqueue.c, adding vhost_ in front of it will definitely work:

Sure it will, but you'll probably be able to get away with much less.

Quoted 2 lines

there is nothing magic about the workqueue library.
But this just involves cut and paste which might be best avoided.

What I'm saying is that some magic needs to be added to workqueue and if you add this single(!) exception, it will have to be backed out pretty soon, so it would be better to do it properly now.

Quoted 3 lines

One final idea before we go the cut and paste way: how about 'create_workqueue_from_task' that would get a thread and have workqueue run there?

Quoted 8 lines

You can currently depend on that implementation detail but it's not the workqueue interface is meant to do. The single threadedness is there as execution ordering and concurrency specification and it doesn't (or rather won't) necessarily mean that a specific single thread is bound to certain workqueue.

Can you please direct me to have a look at the code. I'll be happy to do the conversion for you.

Great, thanks! The code in question is in drivers/vhost/vhost.c It is used from drivers/vhost/net.c

On top of this, we have patchset from Sridhar Samudrala, titled '0/3 Make vhost multi-threaded and associate each thread to its guest's cgroup':

cgroups: Add an API to attach a task to current task's cgroup
workqueue: Add an API to create a singlethread workqueue attached to the current task's cgroup
vhost: make it more scalable by creating a vhost thread per device

I have bounced the last three your way.

Quoted 4 lines

Thanks.

Signature · 1 line
tejun
maintainer
#13 lore raw

PATCH 1/3 vhost: replace vhost_workqueue with per-vhost kthread

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_poll *, there's no visible change to vhost_poll_*() interface.

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>

---
Okay, here is three patch series to convert vhost to use per-vhost kthread, add cgroup_attach_task_current_cg() and apply it to the vhost kthreads. The conversion is mostly straight forward although flush is slightly tricky.

The problem is that I have no idea how to test this. It builds fine and I read it several times but it's entirely possible / likely that I missed something. Please proceed with caution (so, no sign off yet).

Thanks.

 drivers/vhost/net.c   |   58 +++++++++++++----------------
 drivers/vhost/vhost.c |   99 ++++++++++++++++++++++++++++++++++++--------------
 drivers/vhost/vhost.h |   32 +++++++++-------
 3 files changed, 117 insertions(+), 72 deletions(-)
drivers/vhost/net.c +26 -32
Index: work/drivers/vhost/net.c
===================================================================
@@ -294,54 +294,60 @@ static void handle_rx(struct vhost_net *
 	unuse_mm(net->dev.mm);
 }
 
-static void handle_tx_kick(struct work_struct *work)
+static void handle_tx_kick(struct vhost_poll *poll)
 {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq =
+		container_of(poll, struct vhost_virtqueue, poll);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
 	handle_tx(net);
 }
 
-static void handle_rx_kick(struct work_struct *work)
+static void handle_rx_kick(struct vhost_poll *poll)
 {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq =
+		container_of(poll, struct vhost_virtqueue, poll);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
 	handle_rx(net);
 }
 
-static void handle_tx_net(struct work_struct *work)
+static void handle_tx_net(struct vhost_poll *poll)
 {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_TX].work);
+	struct vhost_net *net =
+		container_of(poll, struct vhost_net, poll[VHOST_NET_VQ_TX]);
+
 	handle_tx(net);
 }
 
-static void handle_rx_net(struct work_struct *work)
+static void handle_rx_net(struct vhost_poll *poll)
 {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_RX].work);
+	struct vhost_net *net =
+		container_of(poll, struct vhost_net, poll[VHOST_NET_VQ_RX]);
+
 	handle_rx(net);
 }
 
 static int vhost_net_open(struct inode *inode, struct file *f)
 {
 	struct vhost_net *n = kmalloc(sizeof *n, GFP_KERNEL);
+	struct vhost_dev *dev;
 	int r;
+
 	if (!n)
 		return -ENOMEM;
+
+	dev = &n->dev;
 	n->vqs[VHOST_NET_VQ_TX].handle_kick = handle_tx_kick;
 	n->vqs[VHOST_NET_VQ_RX].handle_kick = handle_rx_kick;
-	r = vhost_dev_init(&n->dev, n->vqs, VHOST_NET_VQ_MAX);
+	r = vhost_dev_init(dev, n->vqs, VHOST_NET_VQ_MAX);
 	if (r < 0) {
 		kfree(n);
 		return r;
 	}
 
-	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT);
-	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev);
 	n->tx_poll_state = VHOST_NET_POLL_DISABLED;
 
 	f->private_data = n;
@@ -644,25 +650,13 @@ static struct miscdevice vhost_net_misc
 
 static int vhost_net_init(void)
 {
-	int r = vhost_init();
-	if (r)
-		goto err_init;
-	r = misc_register(&vhost_net_misc);
-	if (r)
-		goto err_reg;
-	return 0;
-err_reg:
-	vhost_cleanup();
-err_init:
-	return r;
-
+	return misc_register(&vhost_net_misc);
 }
 module_init(vhost_net_init);
 
 static void vhost_net_exit(void)
 {
 	misc_deregister(&vhost_net_misc);
-	vhost_cleanup();
 }
 module_exit(vhost_net_exit);
 
drivers/vhost/vhost.c +72 -27

177 lines not on this page · raw message

drivers/vhost/vhost.h +19 -13
Index: work/drivers/vhost/vhost.h
===================================================================
@@ -5,7 +5,6 @@
 #include <linux/vhost.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
-#include <linux/workqueue.h>
 #include <linux/poll.h>
 #include <linux/file.h>
 #include <linux/skbuff.h>
@@ -20,19 +19,26 @@ enum {
 	VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2,
 };
 
+struct vhost_poll;
+typedef void (*vhost_poll_fn_t)(struct vhost_poll *poll);
+
 /* Poll a file (eventfd or socket) */
 /* Note: there's nothing vhost specific about this structure. */
 struct vhost_poll {
+	vhost_poll_fn_t		  fn;
 	poll_table                table;
 	wait_queue_head_t        *wqh;
 	wait_queue_t              wait;
-	/* struct which will handle all actual work. */
-	struct work_struct        work;
+	struct list_head	  node;
+	wait_queue_head_t	  done;
 	unsigned long		  mask;
+	struct vhost_dev	 *dev;
+	int			  queue_seq;
+	int			  done_seq;
 };
 
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask);
+void vhost_poll_init(struct vhost_poll *poll, vhost_poll_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev);
 void vhost_poll_start(struct vhost_poll *poll, struct file *file);
 void vhost_poll_stop(struct vhost_poll *poll);
 void vhost_poll_flush(struct vhost_poll *poll);
@@ -63,7 +69,7 @@ struct vhost_virtqueue {
 	struct vhost_poll poll;
 
 	/* The routine to call when the Guest pings us, or timeout. */
-	work_func_t handle_kick;
+	vhost_poll_fn_t handle_kick;
 
 	/* Last available index we saw. */
 	u16 last_avail_idx;
@@ -86,11 +92,11 @@ struct vhost_virtqueue {
 	struct iovec hdr[VHOST_NET_MAX_SG];
 	size_t hdr_size;
 	/* We use a kind of RCU to access private pointer.
-	 * All readers access it from workqueue, which makes it possible to
-	 * flush the workqueue instead of synchronize_rcu. Therefore readers do
+	 * All readers access it from poller, which makes it possible to
+	 * flush the vhost_poll instead of synchronize_rcu. Therefore readers do
 	 * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
-	 * work item execution acts instead of rcu_read_lock() and the end of
-	 * work item execution acts instead of rcu_read_lock().
+	 * vhost_poll execution acts instead of rcu_read_lock() and the end of
+	 * vhost_poll execution acts instead of rcu_read_lock().
 	 * Writers use virtqueue mutex. */
 	void *private_data;
 	/* Log write descriptors */
@@ -110,6 +116,9 @@ struct vhost_dev {
 	int nvqs;
 	struct file *log_file;
 	struct eventfd_ctx *log_ctx;
+	spinlock_t poller_lock;
+	struct list_head poll_list;
+	struct task_struct *poller;
 };
 
 long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs);
@@ -136,9 +145,6 @@ bool vhost_enable_notify(struct vhost_vi
 int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
 		    unsigned int log_num, u64 len);
 
-int vhost_init(void);
-void vhost_cleanup(void);
-
 #define vq_err(vq, fmt, ...) do {                                  \
 		pr_debug(pr_fmt(fmt), ##__VA_ARGS__);       \
 		if ((vq)->error_ctx)                               \
#14 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 3 lines · Tejun Heo

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Personally, I agree. I think This is better than play with workqueue thread.

A couple of simple questions after the quick glance at the unapplied patch...

drivers/vhost/vhost.c void vhost_poll_stop(struct vhost_poll * · line 100

Quoted 7 lines

void vhost_poll_flush(struct vhost_poll *poll)
{

drivers/vhost/vhost.c +4 -1
-	flush_work(&poll->work);
+	int seq = poll->queue_seq;
+
+	if (seq - poll->done_seq > 0)
+		wait_event(poll->done, seq - poll->done_seq <= 0);

The check before wait_event() is not needed, please note that wait_event() checks the condition before __wait_event().

What I can't understand is why we do have ->queue_seq and ->done_seq.

Isn't the single "bool poll->active" enough? vhost_poll_queue() sets ->active == T, vhost_poller() clears it before wake_up_all(poll->done).

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 148

Quoted 7 lines
drivers/vhost/vhost.c +7 -0
+static int vhost_poller(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_poll *poll;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */

I don't understand the comment... why do we need this barrier?

Quoted 15 lines
drivers/vhost/vhost.c +15 -0
+	if (kthread_should_stop()) {
+		__set_current_state(TASK_RUNNING);
+		return 0;
+	}
+
+	poll = NULL;
+	spin_lock(&dev->poller_lock);
+	if (!list_empty(&dev->poll_list)) {
+		poll = list_first_entry(&dev->poll_list,
+					struct vhost_poll, node);
+		list_del_init(&poll->node);
+	}
+	spin_unlock(&dev->poller_lock);
+
+	if (poll) {

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 174

Quoted 10 lines
drivers/vhost/vhost.c +10 -0
+		__set_current_state(TASK_RUNNING);
+		poll->fn(poll);
+		smp_wmb();	/* paired with rmb in vhost_poll_flush() */
+		poll->done_seq = poll->queue_seq;
+		wake_up_all(&poll->done);
+	} else
+		schedule();
+
+	goto repeat;
+}

Given that vhost_poll_queue() does list_add() and wake_up_process() under ->poller_lock, I don't think we need any barriers to change ->state.

IOW, can't vhost_poller() simply do

	while(!kthread_should_stop()) {

		poll = NULL;
		spin_lock(&dev->poller_lock);
		if (!list_empty(&dev->poll_list)) {
			...
		} else
			 __set_current_state(TASK_INTERRUPTIBLE);
		spin_unlock(&dev->poller_lock);

		if (poll) {
			...
		} else
			schedule();
	}

?

Oleg.

maintainer
#15 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 6 lines · Oleg Nesterov
Quoted 3 lines · Tejun Heo

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Personally, I agree. I think This is better than play with workqueue thread.

Yeap, I think so too. In vhost's case tho, as it exports a lot of workqueue characteristics to vhost users, it's a bit more complex than I wish it were. It can probably be simplified more if someone who knows the code better takes a look or maybe we need to make this kind of things easier by providing a generic helpers if more cases like this spring up, but if that happens probably the RTTD would be somehow teaching workqueue how to deal with cgroups. As this is the first case, I guess open coding is okay for now.

Quoted 12 lines

A couple of simple questions after the quick glance at the unapplied patch...

drivers/vhost/vhost.c void vhost_poll_stop(struct vhost_poll * · line 100

Quoted 7 lines

void vhost_poll_flush(struct vhost_poll *poll)
{

drivers/vhost/vhost.c +4 -1
-	flush_work(&poll->work);
+	int seq = poll->queue_seq;
+
+	if (seq - poll->done_seq > 0)
+		wait_event(poll->done, seq - poll->done_seq <= 0);

The check before wait_event() is not needed, please note that wait_event() checks the condition before __wait_event().

Heh... right, I was looking at __wait_event() and thinking "ooh... we can skip lock in the fast path". :-)

Quoted 4 lines

What I can't understand is why we do have ->queue_seq and ->done_seq.

Isn't the single "bool poll->active" enough? vhost_poll_queue() sets ->active == T, vhost_poller() clears it before wake_up_all(poll->done).

I might have slightly over engineered this part not knowing the expected workload. ->queue_seq/->done_seq pair is to guarantee that flushers never get starved. Without sequencing queueings and executions, flushers should wait for !pending && !active which can take some time to come if the poll in question is very busy.

Quoted 9 lines

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 148

Quoted 7 lines
drivers/vhost/vhost.c +7 -0
+static int vhost_poller(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_poll *poll;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */

I don't understand the comment... why do we need this barrier?

So that either kthread_stop()'s should_stop = 1 in kthread_stop() is visible to kthread_should_stop() or task state is set to RUNNING.

Quoted 46 lines
Quoted 15 lines
drivers/vhost/vhost.c +15 -0
+	if (kthread_should_stop()) {
+		__set_current_state(TASK_RUNNING);
+		return 0;
+	}
+
+	poll = NULL;
+	spin_lock(&dev->poller_lock);
+	if (!list_empty(&dev->poll_list)) {
+		poll = list_first_entry(&dev->poll_list,
+					struct vhost_poll, node);
+		list_del_init(&poll->node);
+	}
+	spin_unlock(&dev->poller_lock);
+
+	if (poll) {

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 174

Quoted 10 lines
drivers/vhost/vhost.c +10 -0
+		__set_current_state(TASK_RUNNING);
+		poll->fn(poll);
+		smp_wmb();	/* paired with rmb in vhost_poll_flush() */
+		poll->done_seq = poll->queue_seq;
+		wake_up_all(&poll->done);
+	} else
+		schedule();
+
+	goto repeat;
+}

Given that vhost_poll_queue() does list_add() and wake_up_process() under ->poller_lock, I don't think we need any barriers to change ->state.

IOW, can't vhost_poller() simply do

	while(!kthread_should_stop()) {

		poll = NULL;
		spin_lock(&dev->poller_lock);
		if (!list_empty(&dev->poll_list)) {
			...
		} else
			 __set_current_state(TASK_INTERRUPTIBLE);
		spin_unlock(&dev->poller_lock);

		if (poll) {
			...
		} else
			schedule();
	}
Quoted 1 line

?

But then kthread_stop() can happen between kthread_should_stop() and __set_current_state(TASK_INTERRUPTIBLE) and poller can just sleep in schedule() not knowing that.

Thank you.

Signature · 1 line
tejun
#16 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 11 lines · Tejun Heo
Quoted 4 lines · Oleg Nesterov

What I can't understand is why we do have ->queue_seq and ->done_seq.

Isn't the single "bool poll->active" enough? vhost_poll_queue() sets ->active == T, vhost_poller() clears it before wake_up_all(poll->done).

I might have slightly over engineered this part not knowing the expected workload. ->queue_seq/->done_seq pair is to guarantee that flushers never get starved.

Ah, indeed.

Well, afaics we do not need 2 counters anyway, both vhost_poll_queue() and vhost_poller() could increment the single counter and the flusher can take bit 0 into account. But I agree 2 counters are much more clean.

Quoted 10 lines
Quoted 9 lines

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 148

Quoted 7 lines
drivers/vhost/vhost.c +7 -0
+static int vhost_poller(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_poll *poll;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */

I don't understand the comment... why do we need this barrier?

Quoted 2 lines

So that either kthread_stop()'s should_stop = 1 in kthread_stop() is visible to kthread_should_stop() or task state is set to RUNNING.

Of course, you are right. I am really surprized I asked this question ;)

Thanks,

Oleg.

maintainer
#17 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 9 lines · Oleg Nesterov
Quoted 3 lines

I might have slightly over engineered this part not knowing the expected workload. ->queue_seq/->done_seq pair is to guarantee that flushers never get starved.

Ah, indeed.

Well, afaics we do not need 2 counters anyway, both vhost_poll_queue() and vhost_poller() could increment the single counter and the flusher can take bit 0 into account. But I agree 2 counters are much more clean.

Right, we can do that too. Cool. :-)

Quoted 10 lines
Quoted 10 lines
Quoted 9 lines

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 148

Quoted 7 lines
drivers/vhost/vhost.c +7 -0
+static int vhost_poller(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_poll *poll;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */

I don't understand the comment... why do we need this barrier?

Quoted 4 lines
Quoted 2 lines

So that either kthread_stop()'s should_stop = 1 in kthread_stop() is visible to kthread_should_stop() or task state is set to RUNNING.

Of course, you are right. I am really surprized I asked this question ;)

This part is always a bit tricky tho. Maybe it would be a good idea to make kthread_stop() do periodic wakeups. It's already injecting one rather unexpected wake up into the mix anyway so there isn't much point in avoiding multiple and it would make designing kthread loops easier. Or maybe what we need is something like kthread_idle() which encapsulates the check and sleep part.

Thanks.

Signature · 1 line
tejun
#18 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 3 lines · Tejun Heo

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_poll *, there's no visible change to vhost_poll_*() interface.

I would prefer a substructure vhost_work, even just to make the code easier to review and compare to workqueue.c.

Quoted 7 lines

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Quoted 7 lines

---
Okay, here is three patch series to convert vhost to use per-vhost kthread, add cgroup_attach_task_current_cg() and apply it to the vhost kthreads. The conversion is mostly straight forward although flush is slightly tricky.

The problem is that I have no idea how to test this.

It's a 3 step process:

1.
Install qemu-kvm under fc13, or build recent one from source, get it from here:
git://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git

2. install guest under it:
qemu-img create -f qcow2 disk.qcow2 100G
Now get some image (e.g. fedora 13 in fc13.iso)
and install guest:
qemu-kvm -enable-kvm -m 1G -cdrom fc13.iso -drive file=disk.qcow2

3. set up networking. I usually simply do host to guest on a special subnet for testing purposes:

Set up a bridge named mstbr0:

ifconfig mstbr0 down
brctl delbr mstbr0
brctl addbr mstbr0
brctl setfd mstbr0 0
ifconfig mstbr0 11.0.0.1

cat > ifup << EOF
#!/bin/sh -x
/sbin/ifconfig msttap0 0.0.0.0 up
brctl addif mstbr0 msttap0
EOF

qemu-kvm -enable-kvm -m 1G -cdrom fc13.iso -drive file=disk.qcow2
-net nic,model=virtio,netdev=foo -netdev
tap,id=foo,ifname=msttap0,script=/home/mst/ifup,downscript=no,vhost=on

after you set up the guest, log into it and
ifconfig eth0 11.0.0.2

You should now be able to ping guest to host and back. Use something like netperf to stress the connection. Close qemu with kill -9 and unload module to test flushing code.

Quoted 4 lines

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +0 -0

...

Quoted 22 lines
drivers/vhost/vhost.c +18 -0
@@ -125,10 +139,50 @@ static void vhost_vq_reset(struct vhost_
 	vq->log_ctx = NULL;
 }
 
+static int vhost_poller(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_poll *poll;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
+
+	if (kthread_should_stop()) {
+		__set_current_state(TASK_RUNNING);
+		return 0;
+	}
+
+	poll = NULL;
+	spin_lock(&dev->poller_lock);
+	if (!list_empty(&dev->poll_list)) {
+		poll = list_first_entry(&dev->poll_list,
+					struct vhost_poll, node);

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 169

Quoted 10 lines
drivers/vhost/vhost.c +10 -0
+		list_del_init(&poll->node);
+	}
+	spin_unlock(&dev->poller_lock);
+
+	if (poll) {
+		__set_current_state(TASK_RUNNING);
+		poll->fn(poll);
+		smp_wmb();	/* paired with rmb in vhost_poll_flush() */
+		poll->done_seq = poll->queue_seq;
+		wake_up_all(&poll->done);

This seems to add wakeups on data path, which uses spinlocks etc. OTOH workqueue.c adds a special barrier
entry which only does a wakeup when needed.
Right?

Quoted 54 lines
drivers/vhost/vhost.c +18 -4
+	} else
+		schedule();
+
+	goto repeat;
+}
+
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
+	struct task_struct *poller;
 	int i;
+
+	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
+	if (IS_ERR(poller))
+		return PTR_ERR(poller);
+
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
 	mutex_init(&dev->mutex);
@@ -136,6 +190,9 @@ long vhost_dev_init(struct vhost_dev *de
 	dev->log_file = NULL;
 	dev->memory = NULL;
 	dev->mm = NULL;
+	spin_lock_init(&dev->poller_lock);
+	INIT_LIST_HEAD(&dev->poll_list);
+	dev->poller = poller;
 
 	for (i = 0; i < dev->nvqs; ++i) {
 		dev->vqs[i].dev = dev;
@@ -143,8 +200,7 @@ long vhost_dev_init(struct vhost_dev *de
 		vhost_vq_reset(dev, dev->vqs + i);
 		if (dev->vqs[i].handle_kick)
 			vhost_poll_init(&dev->vqs[i].poll,
-					dev->vqs[i].handle_kick,
-					POLLIN);
+					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
 	return 0;
 }
@@ -217,6 +273,8 @@ void vhost_dev_cleanup(struct vhost_dev
 	if (dev->mm)
 		mmput(dev->mm);
 	dev->mm = NULL;
+
+	kthread_stop(dev->poller);
 }
 
 static int log_access_ok(void __user *log_base, u64 addr, unsigned long sz)
@@ -1113,16 +1171,3 @@ void vhost_disable_notify(struct vhost_v
 		vq_err(vq, "Failed to enable notification at %p: %d\n",
 		       &vq->used->flags, r);
 }
-
-int vhost_init(void)

drivers/vhost/vhost.c void vhost_disable_notify(struct vhost_v · line 1127

Quoted 10 lines
drivers/vhost/vhost.c +0 -10
-{
-	vhost_workqueue = create_singlethread_workqueue("vhost");
-	if (!vhost_workqueue)
-		return -ENOMEM;
-	return 0;
-}
-
-void vhost_cleanup(void)
-{
-	destroy_workqueue(vhost_workqueue);

I note that destroy_workqueue does a flush, kthread_stop doesn't. Right? Sure we don't need to check nothing is in one of the lists? Maybe add a BUG_ON?

Quoted 89 lines
drivers/vhost/vhost.c +0 -1
-}

Index: work/drivers/vhost/vhost.h
===================================================================

drivers/vhost/vhost.h +19 -13
@@ -5,7 +5,6 @@
 #include <linux/vhost.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
-#include <linux/workqueue.h>
 #include <linux/poll.h>
 #include <linux/file.h>
 #include <linux/skbuff.h>
@@ -20,19 +19,26 @@ enum {
 	VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2,
 };
 
+struct vhost_poll;
+typedef void (*vhost_poll_fn_t)(struct vhost_poll *poll);
+
 /* Poll a file (eventfd or socket) */
 /* Note: there's nothing vhost specific about this structure. */
 struct vhost_poll {
+	vhost_poll_fn_t		  fn;
 	poll_table                table;
 	wait_queue_head_t        *wqh;
 	wait_queue_t              wait;
-	/* struct which will handle all actual work. */
-	struct work_struct        work;
+	struct list_head	  node;
+	wait_queue_head_t	  done;
 	unsigned long		  mask;
+	struct vhost_dev	 *dev;
+	int			  queue_seq;
+	int			  done_seq;
 };
 
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask);
+void vhost_poll_init(struct vhost_poll *poll, vhost_poll_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev);
 void vhost_poll_start(struct vhost_poll *poll, struct file *file);
 void vhost_poll_stop(struct vhost_poll *poll);
 void vhost_poll_flush(struct vhost_poll *poll);
@@ -63,7 +69,7 @@ struct vhost_virtqueue {
 	struct vhost_poll poll;
 
 	/* The routine to call when the Guest pings us, or timeout. */
-	work_func_t handle_kick;
+	vhost_poll_fn_t handle_kick;
 
 	/* Last available index we saw. */
 	u16 last_avail_idx;
@@ -86,11 +92,11 @@ struct vhost_virtqueue {
 	struct iovec hdr[VHOST_NET_MAX_SG];
 	size_t hdr_size;
 	/* We use a kind of RCU to access private pointer.
-	 * All readers access it from workqueue, which makes it possible to
-	 * flush the workqueue instead of synchronize_rcu. Therefore readers do
+	 * All readers access it from poller, which makes it possible to
+	 * flush the vhost_poll instead of synchronize_rcu. Therefore readers do
 	 * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
-	 * work item execution acts instead of rcu_read_lock() and the end of
-	 * work item execution acts instead of rcu_read_lock().
+	 * vhost_poll execution acts instead of rcu_read_lock() and the end of
+	 * vhost_poll execution acts instead of rcu_read_lock().
 	 * Writers use virtqueue mutex. */
 	void *private_data;
 	/* Log write descriptors */
@@ -110,6 +116,9 @@ struct vhost_dev {
 	int nvqs;
 	struct file *log_file;
 	struct eventfd_ctx *log_ctx;
+	spinlock_t poller_lock;
+	struct list_head poll_list;
+	struct task_struct *poller;
 };
 
 long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs);
@@ -136,9 +145,6 @@ bool vhost_enable_notify(struct vhost_vi
 int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
 		    unsigned int log_num, u64 len);
 
-int vhost_init(void);
-void vhost_cleanup(void);
-
 #define vq_err(vq, fmt, ...) do {                                  \
 		pr_debug(pr_fmt(fmt), ##__VA_ARGS__);       \
 		if ((vq)->error_ctx)                               \
maintainer
#19 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 7 lines · Michael S. Tsirkin
Quoted 3 lines · Tejun Heo

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_poll *, there's no visible change to vhost_poll_*() interface.

I would prefer a substructure vhost_work, even just to make the code easier to review and compare to workqueue.c.

Yeap, sure.

Quoted 3 lines
Quoted 1 line

The problem is that I have no idea how to test this.

It's a 3 step process:

...

Quoted 3 lines

You should now be able to ping guest to host and back. Use something like netperf to stress the connection. Close qemu with kill -9 and unload module to test flushing code.

Thanks for the instruction. I'll see if there's a way to do it without building qemu myself on opensuse. But please feel free to go ahead and test it. It might just work! :-)

Quoted 11 lines

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 169

Quoted 6 lines
drivers/vhost/vhost.c +6 -0
+	if (poll) {
+		__set_current_state(TASK_RUNNING);
+		poll->fn(poll);
+		smp_wmb();	/* paired with rmb in vhost_poll_flush() */
+		poll->done_seq = poll->queue_seq;
+		wake_up_all(&poll->done);

This seems to add wakeups on data path, which uses spinlocks etc. OTOH workqueue.c adds a special barrier entry which only does a wakeup when needed. Right?

Yeah, well, if it's a really hot path sure we can avoid wake_up_all() in most cases. Do you think that would be necessary?

Quoted 7 lines

drivers/vhost/vhost.c void vhost_disable_notify(struct vhost_v · line 1127

Quoted 3 lines
drivers/vhost/vhost.c +0 -3
-void vhost_cleanup(void)
-{
-	destroy_workqueue(vhost_workqueue);

I note that destroy_workqueue does a flush, kthread_stop doesn't. Right? Sure we don't need to check nothing is in one of the lists? Maybe add a BUG_ON?

There were a bunch of flushes before kthread_stop() and they seemed to stop and flush everything. Aren't they enough? We can definitely add BUG_ON() after kthread_should_stop() check succeeds either way tho.

Thanks.

Signature · 1 line
tejun
#20 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 15 lines · Tejun Heo

Hello,

Quoted 7 lines · Michael S. Tsirkin
Quoted 3 lines · Tejun Heo

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_poll *, there's no visible change to vhost_poll_*() interface.

I would prefer a substructure vhost_work, even just to make the code easier to review and compare to workqueue.c.

Yeap, sure.

Quoted 2 lines
Quoted 1 line

The problem is that I have no idea how to test this.

Quoted 8 lines
Quoted 1 line

It's a 3 step process:

...

Quoted 3 lines

You should now be able to ping guest to host and back. Use something like netperf to stress the connection. Close qemu with kill -9 and unload module to test flushing code.

Thanks for the instruction. I'll see if there's a way to do it without building qemu myself on opensuse.

My guess is no, there was no stable qemu release with vhost net support yet. Building it is mostly configure/make/make install,
as far as I remember you only need devel versions of X libraries, SDL and curses installed.

Quoted 11 lines

But please feel free to go
ahead and test it. It might just work! :-)

Quoted 7 lines
Quoted 6 lines
drivers/vhost/vhost.c +6 -0
+	if (poll) {
+		__set_current_state(TASK_RUNNING);
+		poll->fn(poll);
+		smp_wmb();	/* paired with rmb in vhost_poll_flush() */
+		poll->done_seq = poll->queue_seq;
+		wake_up_all(&poll->done);
Quoted 6 lines
Quoted 3 lines

This seems to add wakeups on data path, which uses spinlocks etc. OTOH workqueue.c adds a special barrier entry which only does a wakeup when needed. Right?

Yeah, well, if it's a really hot path sure we can avoid wake_up_all() in most cases. Do you think that would be necessary?

My guess is yes. This is really very hot path in code, and we are close to 100% CPU in some benchmarks.

Quoted 10 lines
Quoted 7 lines

drivers/vhost/vhost.c void vhost_disable_notify(struct vhost_v · line 1127

Quoted 3 lines
drivers/vhost/vhost.c +0 -3
-void vhost_cleanup(void)
-{
-	destroy_workqueue(vhost_workqueue);

I note that destroy_workqueue does a flush, kthread_stop doesn't. Right? Sure we don't need to check nothing is in one of the lists? Maybe add a BUG_ON?

There were a bunch of flushes before kthread_stop() and they seemed to stop and flush everything. Aren't they enough?

I was just asking, I'll need to review the code in depth.

Quoted 7 lines

We can definitely add
BUG_ON() after kthread_should_stop() check succeeds either way tho.

Thanks.

Signature · 1 line
tejun
maintainer
#21 lore raw

PATCH 1/3 vhost: replace vhost_workqueue with per-vhost kthread

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_work *, there's no visible change to vhost_poll_*() interface.

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

* Updated to use sub structure vhost_work instead of directly using vhost_poll at Michael's suggestion.

* Added flusher wake_up() optimization at Michael's suggestion.

NOT_SIGNED_OFF_YET
Cc: Michael S. Tsirkin <[email protected]>
Cc: Sridhar Samudrala <[email protected]>
---
Okay, here's the updated version. I'm still in the process of setting up the testing environment. I'll be traveling from this afternoon till tomorrow so I don't think I'll be able to test it before that. If you can give it a shot, it would be great.

Thanks.

 drivers/vhost/net.c   |   56 ++++++++++---------------
 drivers/vhost/vhost.c |  110 ++++++++++++++++++++++++++++++++++++++------------
 drivers/vhost/vhost.h |   38 +++++++++++------
 3 files changed, 133 insertions(+), 71 deletions(-)
drivers/vhost/net.c +24 -32
Index: work/drivers/vhost/net.c
===================================================================
@@ -294,54 +294,58 @@ static void handle_rx(struct vhost_net *
 	unuse_mm(net->dev.mm);
 }
 
-static void handle_tx_kick(struct work_struct *work)
+static void handle_tx_kick(struct vhost_work *work)
 {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
+						  poll.work);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
 	handle_tx(net);
 }
 
-static void handle_rx_kick(struct work_struct *work)
+static void handle_rx_kick(struct vhost_work *work)
 {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
+						  poll.work);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
 	handle_rx(net);
 }
 
-static void handle_tx_net(struct work_struct *work)
+static void handle_tx_net(struct vhost_work *work)
 {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_TX].work);
+	struct vhost_net *net = container_of(work, struct vhost_net,
+					     poll[VHOST_NET_VQ_TX].work);
 	handle_tx(net);
 }
 
-static void handle_rx_net(struct work_struct *work)
+static void handle_rx_net(struct vhost_work *work)
 {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_RX].work);
+	struct vhost_net *net = container_of(work, struct vhost_net,
+					     poll[VHOST_NET_VQ_RX].work);
 	handle_rx(net);
 }
 
 static int vhost_net_open(struct inode *inode, struct file *f)
 {
 	struct vhost_net *n = kmalloc(sizeof *n, GFP_KERNEL);
+	struct vhost_dev *dev;
 	int r;
+
 	if (!n)
 		return -ENOMEM;
+
+	dev = &n->dev;
 	n->vqs[VHOST_NET_VQ_TX].handle_kick = handle_tx_kick;
 	n->vqs[VHOST_NET_VQ_RX].handle_kick = handle_rx_kick;
-	r = vhost_dev_init(&n->dev, n->vqs, VHOST_NET_VQ_MAX);
+	r = vhost_dev_init(dev, n->vqs, VHOST_NET_VQ_MAX);
 	if (r < 0) {
 		kfree(n);
 		return r;
 	}
 
-	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT);
-	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev);
 	n->tx_poll_state = VHOST_NET_POLL_DISABLED;
 
 	f->private_data = n;
@@ -644,25 +648,13 @@ static struct miscdevice vhost_net_misc
 
 static int vhost_net_init(void)
 {
-	int r = vhost_init();
-	if (r)
-		goto err_init;
-	r = misc_register(&vhost_net_misc);
-	if (r)
-		goto err_reg;
-	return 0;
-err_reg:
-	vhost_cleanup();
-err_init:
-	return r;
-
+	return misc_register(&vhost_net_misc);
 }
 module_init(vhost_net_init);
 
 static void vhost_net_exit(void)
 {
 	misc_deregister(&vhost_net_misc);
-	vhost_cleanup();
 }
 module_exit(vhost_net_exit);
 
drivers/vhost/vhost.c +84 -26

186 lines not on this page · raw message

drivers/vhost/vhost.h +25 -13
Index: work/drivers/vhost/vhost.h
===================================================================
@@ -5,13 +5,13 @@
 #include <linux/vhost.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
-#include <linux/workqueue.h>
 #include <linux/poll.h>
 #include <linux/file.h>
 #include <linux/skbuff.h>
 #include <linux/uio.h>
 #include <linux/virtio_config.h>
 #include <linux/virtio_ring.h>
+#include <asm/atomic.h>
 
 struct vhost_device;
 
@@ -20,19 +20,31 @@ enum {
 	VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2,
 };
 
+struct vhost_work;
+typedef void (*vhost_work_fn_t)(struct vhost_work *work);
+
+struct vhost_work {
+	struct list_head	  node;
+	vhost_work_fn_t		  fn;
+	wait_queue_head_t	  done;
+	atomic_t		  flushing;
+	int			  queue_seq;
+	int			  done_seq;
+};
+
 /* Poll a file (eventfd or socket) */
 /* Note: there's nothing vhost specific about this structure. */
 struct vhost_poll {
 	poll_table                table;
 	wait_queue_head_t        *wqh;
 	wait_queue_t              wait;
-	/* struct which will handle all actual work. */
-	struct work_struct        work;
+	struct vhost_work	  work;
 	unsigned long		  mask;
+	struct vhost_dev	 *dev;
 };
 
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask);
+void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev);
 void vhost_poll_start(struct vhost_poll *poll, struct file *file);
 void vhost_poll_stop(struct vhost_poll *poll);
 void vhost_poll_flush(struct vhost_poll *poll);
@@ -63,7 +75,7 @@ struct vhost_virtqueue {
 	struct vhost_poll poll;
 
 	/* The routine to call when the Guest pings us, or timeout. */
-	work_func_t handle_kick;
+	vhost_work_fn_t handle_kick;
 
 	/* Last available index we saw. */
 	u16 last_avail_idx;
@@ -86,11 +98,11 @@ struct vhost_virtqueue {
 	struct iovec hdr[VHOST_NET_MAX_SG];
 	size_t hdr_size;
 	/* We use a kind of RCU to access private pointer.
-	 * All readers access it from workqueue, which makes it possible to
-	 * flush the workqueue instead of synchronize_rcu. Therefore readers do
+	 * All readers access it from worker, which makes it possible to
+	 * flush the vhost_work instead of synchronize_rcu. Therefore readers do
 	 * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
-	 * work item execution acts instead of rcu_read_lock() and the end of
-	 * work item execution acts instead of rcu_read_lock().
+	 * vhost_work execution acts instead of rcu_read_lock() and the end of
+	 * vhost_work execution acts instead of rcu_read_lock().
 	 * Writers use virtqueue mutex. */
 	void *private_data;
 	/* Log write descriptors */
@@ -110,6 +122,9 @@ struct vhost_dev {
 	int nvqs;
 	struct file *log_file;
 	struct eventfd_ctx *log_ctx;
+	spinlock_t work_lock;
+	struct list_head work_list;
+	struct task_struct *worker;
 };
 
 long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs);
@@ -136,9 +151,6 @@ bool vhost_enable_notify(struct vhost_vi
 int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
 		    unsigned int log_num, u64 len);
 
-int vhost_init(void);
-void vhost_cleanup(void);
-
 #define vq_err(vq, fmt, ...) do {                                  \
 		pr_debug(pr_fmt(fmt), ##__VA_ARGS__);       \
 		if ((vq)->error_ctx)                               \
maintainer
#22 lore raw

PATCH 1/3 vhost: replace vhost_workqueue with per-vhost kthread

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_work *, there's no visible change to vhost_poll_*() interface.

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

* Updated to use sub structure vhost_work instead of directly using vhost_poll at Michael's suggestion.

* Added flusher wake_up() optimization at Michael's suggestion.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>

---
Okay, just tested it. dev->work_lock had to be updated to use irq operations but other than that it worked just fine. Copied a large file using scp and it seems to perform pretty well although I don't have any reference of comparison. So, here's the updated version with the sign off.

Thanks.

 drivers/vhost/net.c   |   56 ++++++++++---------------
 drivers/vhost/vhost.c |  111 ++++++++++++++++++++++++++++++++++++++------------
 drivers/vhost/vhost.h |   38 +++++++++++------
 3 files changed, 134 insertions(+), 71 deletions(-)
drivers/vhost/net.c +24 -32
Index: work/drivers/vhost/net.c
===================================================================
@@ -294,54 +294,58 @@ static void handle_rx(struct vhost_net *
 	unuse_mm(net->dev.mm);
 }
 
-static void handle_tx_kick(struct work_struct *work)
+static void handle_tx_kick(struct vhost_work *work)
 {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
+						  poll.work);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
 	handle_tx(net);
 }
 
-static void handle_rx_kick(struct work_struct *work)
+static void handle_rx_kick(struct vhost_work *work)
 {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
+						  poll.work);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
 	handle_rx(net);
 }
 
-static void handle_tx_net(struct work_struct *work)
+static void handle_tx_net(struct vhost_work *work)
 {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_TX].work);
+	struct vhost_net *net = container_of(work, struct vhost_net,
+					     poll[VHOST_NET_VQ_TX].work);
 	handle_tx(net);
 }
 
-static void handle_rx_net(struct work_struct *work)
+static void handle_rx_net(struct vhost_work *work)
 {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_RX].work);
+	struct vhost_net *net = container_of(work, struct vhost_net,
+					     poll[VHOST_NET_VQ_RX].work);
 	handle_rx(net);
 }
 
 static int vhost_net_open(struct inode *inode, struct file *f)
 {
 	struct vhost_net *n = kmalloc(sizeof *n, GFP_KERNEL);
+	struct vhost_dev *dev;
 	int r;
+
 	if (!n)
 		return -ENOMEM;
+
+	dev = &n->dev;
 	n->vqs[VHOST_NET_VQ_TX].handle_kick = handle_tx_kick;
 	n->vqs[VHOST_NET_VQ_RX].handle_kick = handle_rx_kick;
-	r = vhost_dev_init(&n->dev, n->vqs, VHOST_NET_VQ_MAX);
+	r = vhost_dev_init(dev, n->vqs, VHOST_NET_VQ_MAX);
 	if (r < 0) {
 		kfree(n);
 		return r;
 	}
 
-	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT);
-	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev);
 	n->tx_poll_state = VHOST_NET_POLL_DISABLED;
 
 	f->private_data = n;
@@ -644,25 +648,13 @@ static struct miscdevice vhost_net_misc
 
 static int vhost_net_init(void)
 {
-	int r = vhost_init();
-	if (r)
-		goto err_init;
-	r = misc_register(&vhost_net_misc);
-	if (r)
-		goto err_reg;
-	return 0;
-err_reg:
-	vhost_cleanup();
-err_init:
-	return r;
-
+	return misc_register(&vhost_net_misc);
 }
 module_init(vhost_net_init);
 
 static void vhost_net_exit(void)
 {
 	misc_deregister(&vhost_net_misc);
-	vhost_cleanup();
 }
 module_exit(vhost_net_exit);
 
drivers/vhost/vhost.c +85 -26

187 lines not on this page · raw message

drivers/vhost/vhost.h +25 -13
Index: work/drivers/vhost/vhost.h
===================================================================
@@ -5,13 +5,13 @@
 #include <linux/vhost.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
-#include <linux/workqueue.h>
 #include <linux/poll.h>
 #include <linux/file.h>
 #include <linux/skbuff.h>
 #include <linux/uio.h>
 #include <linux/virtio_config.h>
 #include <linux/virtio_ring.h>
+#include <asm/atomic.h>
 
 struct vhost_device;
 
@@ -20,19 +20,31 @@ enum {
 	VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2,
 };
 
+struct vhost_work;
+typedef void (*vhost_work_fn_t)(struct vhost_work *work);
+
+struct vhost_work {
+	struct list_head	  node;
+	vhost_work_fn_t		  fn;
+	wait_queue_head_t	  done;
+	atomic_t		  flushing;
+	int			  queue_seq;
+	int			  done_seq;
+};
+
 /* Poll a file (eventfd or socket) */
 /* Note: there's nothing vhost specific about this structure. */
 struct vhost_poll {
 	poll_table                table;
 	wait_queue_head_t        *wqh;
 	wait_queue_t              wait;
-	/* struct which will handle all actual work. */
-	struct work_struct        work;
+	struct vhost_work	  work;
 	unsigned long		  mask;
+	struct vhost_dev	 *dev;
 };
 
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask);
+void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev);
 void vhost_poll_start(struct vhost_poll *poll, struct file *file);
 void vhost_poll_stop(struct vhost_poll *poll);
 void vhost_poll_flush(struct vhost_poll *poll);
@@ -63,7 +75,7 @@ struct vhost_virtqueue {
 	struct vhost_poll poll;
 
 	/* The routine to call when the Guest pings us, or timeout. */
-	work_func_t handle_kick;
+	vhost_work_fn_t handle_kick;
 
 	/* Last available index we saw. */
 	u16 last_avail_idx;
@@ -86,11 +98,11 @@ struct vhost_virtqueue {
 	struct iovec hdr[VHOST_NET_MAX_SG];
 	size_t hdr_size;
 	/* We use a kind of RCU to access private pointer.
-	 * All readers access it from workqueue, which makes it possible to
-	 * flush the workqueue instead of synchronize_rcu. Therefore readers do
+	 * All readers access it from worker, which makes it possible to
+	 * flush the vhost_work instead of synchronize_rcu. Therefore readers do
 	 * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
-	 * work item execution acts instead of rcu_read_lock() and the end of
-	 * work item execution acts instead of rcu_read_lock().
+	 * vhost_work execution acts instead of rcu_read_lock() and the end of
+	 * vhost_work execution acts instead of rcu_read_lock().
 	 * Writers use virtqueue mutex. */
 	void *private_data;
 	/* Log write descriptors */
@@ -110,6 +122,9 @@ struct vhost_dev {
 	int nvqs;
 	struct file *log_file;
 	struct eventfd_ctx *log_ctx;
+	spinlock_t work_lock;
+	struct list_head work_list;
+	struct task_struct *worker;
 };
 
 long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs);
@@ -136,9 +151,6 @@ bool vhost_enable_notify(struct vhost_vi
 int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
 		    unsigned int log_num, u64 len);
 
-int vhost_init(void);
-void vhost_cleanup(void);
-
 #define vq_err(vq, fmt, ...) do {                                  \
 		pr_debug(pr_fmt(fmt), ##__VA_ARGS__);       \
 		if ((vq)->error_ctx)                               \
#23 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 17 lines · Tejun Heo

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_work *, there's no visible change to vhost_poll_*() interface.

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

* Updated to use sub structure vhost_work instead of directly using vhost_poll at Michael's suggestion.

* Added flusher wake_up() optimization at Michael's suggestion.

Cc Michael S. Tsirkin<[email protected]>
Cc Sridhar Samudrala<[email protected]>
Quoted 7 lines

---
Okay, just tested it. dev->work_lock had to be updated to use irq operations but other than that it worked just fine. Copied a large file using scp and it seems to perform pretty well although I don't have any reference of comparison. So, here's the updated version with the sign off.

I tested this with 4 VMs running netperf TCP stream tests from guest to host and i am seeing similar level of scalability in throughput i saw with the multi-thread workqueue patch.
11200Mb/s - default (host cpu utilization: 40%)
21600Mb/s - multi-thread (host cpu utilization: 86%)

Thanks
Sridhar

Quoted 417 lines

Thanks.

  drivers/vhost/net.c   |   56 ++++++++++---------------
  drivers/vhost/vhost.c |  111 ++++++++++++++++++++++++++++++++++++++------------
  drivers/vhost/vhost.h |   38 +++++++++++------
  3 files changed, 134 insertions(+), 71 deletions(-)

Index: work/drivers/vhost/net.c
===================================================================

drivers/vhost/net.c +24 -32
@@ -294,54 +294,58 @@ static void handle_rx(struct vhost_net *
  	unuse_mm(net->dev.mm);
  }
 
-static void handle_tx_kick(struct work_struct *work)
+static void handle_tx_kick(struct vhost_work *work)
  {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
+						  poll.work);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
  	handle_tx(net);
  }
 
-static void handle_rx_kick(struct work_struct *work)
+static void handle_rx_kick(struct vhost_work *work)
  {
-	struct vhost_virtqueue *vq;
-	struct vhost_net *net;
-	vq = container_of(work, struct vhost_virtqueue, poll.work);
-	net = container_of(vq->dev, struct vhost_net, dev);
+	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
+						  poll.work);
+	struct vhost_net *net = container_of(vq->dev, struct vhost_net, dev);
+
  	handle_rx(net);
  }
 
-static void handle_tx_net(struct work_struct *work)
+static void handle_tx_net(struct vhost_work *work)
  {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_TX].work);
+	struct vhost_net *net = container_of(work, struct vhost_net,
+					     poll[VHOST_NET_VQ_TX].work);
  	handle_tx(net);
  }
 
-static void handle_rx_net(struct work_struct *work)
+static void handle_rx_net(struct vhost_work *work)
  {
-	struct vhost_net *net;
-	net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_RX].work);
+	struct vhost_net *net = container_of(work, struct vhost_net,
+					     poll[VHOST_NET_VQ_RX].work);
  	handle_rx(net);
  }
 
  static int vhost_net_open(struct inode *inode, struct file *f)
  {
  	struct vhost_net *n = kmalloc(sizeof *n, GFP_KERNEL);
+	struct vhost_dev *dev;
  	int r;
+
  	if (!n)
  		return -ENOMEM;
+
+	dev =&n->dev;
  	n->vqs[VHOST_NET_VQ_TX].handle_kick = handle_tx_kick;
  	n->vqs[VHOST_NET_VQ_RX].handle_kick = handle_rx_kick;
-	r = vhost_dev_init(&n->dev, n->vqs, VHOST_NET_VQ_MAX);
+	r = vhost_dev_init(dev, n->vqs, VHOST_NET_VQ_MAX);
  	if (r<  0) {
  		kfree(n);
  		return r;
  	}
 
-	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT);
-	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev);
+	vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev);
  	n->tx_poll_state = VHOST_NET_POLL_DISABLED;
 
  	f->private_data = n;
@@ -644,25 +648,13 @@ static struct miscdevice vhost_net_misc
 
  static int vhost_net_init(void)
  {
-	int r = vhost_init();
-	if (r)
-		goto err_init;
-	r = misc_register(&vhost_net_misc);
-	if (r)
-		goto err_reg;
-	return 0;
-err_reg:
-	vhost_cleanup();
-err_init:
-	return r;
-
+	return misc_register(&vhost_net_misc);
  }
  module_init(vhost_net_init);
 
  static void vhost_net_exit(void)
  {
  	misc_deregister(&vhost_net_misc);
-	vhost_cleanup();
  }
  module_exit(vhost_net_exit);
 

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +85 -26
@@ -17,12 +17,12 @@
  #include<linux/mm.h>
  #include<linux/miscdevice.h>
  #include<linux/mutex.h>
-#include<linux/workqueue.h>
  #include<linux/rcupdate.h>
  #include<linux/poll.h>
  #include<linux/file.h>
  #include<linux/highmem.h>
  #include<linux/slab.h>
+#include<linux/kthread.h>
 
  #include<linux/net.h>
  #include<linux/if_packet.h>
@@ -37,8 +37,6 @@ enum {
  	VHOST_MEMORY_F_LOG = 0x1,
  };
 
-static struct workqueue_struct *vhost_workqueue;
-
  static void vhost_poll_func(struct file *file, wait_queue_head_t *wqh,
  			    poll_table *pt)
  {
@@ -52,23 +50,31 @@ static void vhost_poll_func(struct file
  static int vhost_poll_wakeup(wait_queue_t *wait, unsigned mode, int sync,
  			     void *key)
  {
-	struct vhost_poll *poll;
-	poll = container_of(wait, struct vhost_poll, wait);
+	struct vhost_poll *poll = container_of(wait, struct vhost_poll, wait);
+
  	if (!((unsigned long)key&  poll->mask))
  		return 0;
 
-	queue_work(vhost_workqueue,&poll->work);
+	vhost_poll_queue(poll);
  	return 0;
  }
 
  /* Init poll structure */
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask)
+void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev)
  {
-	INIT_WORK(&poll->work, func);
+	struct vhost_work *work =&poll->work;
+
  	init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup);
  	init_poll_funcptr(&poll->table, vhost_poll_func);
  	poll->mask = mask;
+	poll->dev = dev;
+
+	INIT_LIST_HEAD(&work->node);
+	work->fn = fn;
+	init_waitqueue_head(&work->done);
+	atomic_set(&work->flushing, 0);
+	work->queue_seq = work->done_seq = 0;
  }
 
  /* Start polling a file. We add ourselves to file's wait queue. The caller must
@@ -92,12 +98,29 @@ void vhost_poll_stop(struct vhost_poll *
   * locks that are also used by the callback. */
  void vhost_poll_flush(struct vhost_poll *poll)
  {
-	flush_work(&poll->work);
+	struct vhost_work *work =&poll->work;
+	int seq = work->queue_seq;
+
+	atomic_inc(&work->flushing);
+	smp_mb__after_atomic_inc();	/* mb flush-b0 paired with worker-b1 */
+	wait_event(work->done, seq - work->done_seq<= 0);
+	atomic_dec(&work->flushing);
+	smp_mb__after_atomic_dec();	/* rmb flush-b1 paired with worker-b0 */
  }
 
  void vhost_poll_queue(struct vhost_poll *poll)
  {
-	queue_work(vhost_workqueue,&poll->work);
+	struct vhost_dev *dev = poll->dev;
+	struct vhost_work *work =&poll->work;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->work_lock, flags);
+	if (list_empty(&work->node)) {
+		list_add_tail(&work->node,&dev->work_list);
+		work->queue_seq++;
+		wake_up_process(dev->worker);
+	}
+	spin_unlock_irqrestore(&dev->work_lock, flags);
  }
 
  static void vhost_vq_reset(struct vhost_dev *dev,
@@ -125,10 +148,52 @@ static void vhost_vq_reset(struct vhost_
  	vq->log_ctx = NULL;
  }
 
+static int vhost_worker(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_work *work;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
+
+	if (kthread_should_stop()) {
+		__set_current_state(TASK_RUNNING);
+		return 0;
+	}
+
+	work = NULL;
+	spin_lock_irq(&dev->work_lock);
+	if (!list_empty(&dev->work_list)) {
+		work = list_first_entry(&dev->work_list,
+					struct vhost_work, node);
+		list_del_init(&work->node);
+	}
+	spin_unlock_irq(&dev->work_lock);
+
+	if (work) {
+		__set_current_state(TASK_RUNNING);
+		work->fn(work);
+		smp_wmb();	/* wmb worker-b0 paired with flush-b1 */
+		work->done_seq = work->queue_seq;
+		smp_mb();	/* mb worker-b1 paired with flush-b0 */
+		if (atomic_read(&work->flushing))
+			wake_up_all(&work->done);
+	} else
+		schedule();
+
+	goto repeat;
+}
+
  long vhost_dev_init(struct vhost_dev *dev,
  		    struct vhost_virtqueue *vqs, int nvqs)
  {
+	struct task_struct *worker;
  	int i;
+
+	worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
+	if (IS_ERR(worker))
+		return PTR_ERR(worker);
+
  	dev->vqs = vqs;
  	dev->nvqs = nvqs;
  	mutex_init(&dev->mutex);
@@ -136,6 +201,9 @@ long vhost_dev_init(struct vhost_dev *de
  	dev->log_file = NULL;
  	dev->memory = NULL;
  	dev->mm = NULL;
+	spin_lock_init(&dev->work_lock);
+	INIT_LIST_HEAD(&dev->work_list);
+	dev->worker = worker;
 
  	for (i = 0; i<  dev->nvqs; ++i) {
  		dev->vqs[i].dev = dev;
@@ -143,9 +211,10 @@ long vhost_dev_init(struct vhost_dev *de
  		vhost_vq_reset(dev, dev->vqs + i);
  		if (dev->vqs[i].handle_kick)
  			vhost_poll_init(&dev->vqs[i].poll,
-					dev->vqs[i].handle_kick,
-					POLLIN);
+					dev->vqs[i].handle_kick, POLLIN, dev);
  	}
+
+	wake_up_process(worker);	/* avoid contributing to loadavg */
  	return 0;
  }
 
@@ -217,6 +286,9 @@ void vhost_dev_cleanup(struct vhost_dev
  	if (dev->mm)
  		mmput(dev->mm);
  	dev->mm = NULL;
+
+	WARN_ON(!list_empty(&dev->work_list));
+	kthread_stop(dev->worker);
  }
 
  static int log_access_ok(void __user *log_base, u64 addr, unsigned long sz)
@@ -1113,16 +1185,3 @@ void vhost_disable_notify(struct vhost_v
  		vq_err(vq, "Failed to enable notification at %p: %d\n",
  		&vq->used->flags, r);
  }
-
-int vhost_init(void)
-{
-	vhost_workqueue = create_singlethread_workqueue("vhost");
-	if (!vhost_workqueue)
-		return -ENOMEM;
-	return 0;
-}
-
-void vhost_cleanup(void)
-{
-	destroy_workqueue(vhost_workqueue);
-}

Index: work/drivers/vhost/vhost.h
===================================================================

drivers/vhost/vhost.h +25 -13
@@ -5,13 +5,13 @@
  #include<linux/vhost.h>
  #include<linux/mm.h>
  #include<linux/mutex.h>
-#include<linux/workqueue.h>
  #include<linux/poll.h>
  #include<linux/file.h>
  #include<linux/skbuff.h>
  #include<linux/uio.h>
  #include<linux/virtio_config.h>
  #include<linux/virtio_ring.h>
+#include<asm/atomic.h>
 
  struct vhost_device;
 
@@ -20,19 +20,31 @@ enum {
  	VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2,
  };
 
+struct vhost_work;
+typedef void (*vhost_work_fn_t)(struct vhost_work *work);
+
+struct vhost_work {
+	struct list_head	  node;
+	vhost_work_fn_t		  fn;
+	wait_queue_head_t	  done;
+	atomic_t		  flushing;
+	int			  queue_seq;
+	int			  done_seq;
+};
+
  /* Poll a file (eventfd or socket) */
  /* Note: there's nothing vhost specific about this structure. */
  struct vhost_poll {
  	poll_table                table;
  	wait_queue_head_t        *wqh;
  	wait_queue_t              wait;
-	/* struct which will handle all actual work. */
-	struct work_struct        work;
+	struct vhost_work	  work;
  	unsigned long		  mask;
+	struct vhost_dev	 *dev;
  };
 
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask);
+void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev);
  void vhost_poll_start(struct vhost_poll *poll, struct file *file);
  void vhost_poll_stop(struct vhost_poll *poll);
  void vhost_poll_flush(struct vhost_poll *poll);
@@ -63,7 +75,7 @@ struct vhost_virtqueue {
  	struct vhost_poll poll;
 
  	/* The routine to call when the Guest pings us, or timeout. */
-	work_func_t handle_kick;
+	vhost_work_fn_t handle_kick;
 
  	/* Last available index we saw. */
  	u16 last_avail_idx;
@@ -86,11 +98,11 @@ struct vhost_virtqueue {
  	struct iovec hdr[VHOST_NET_MAX_SG];
  	size_t hdr_size;
  	/* We use a kind of RCU to access private pointer.
-	 * All readers access it from workqueue, which makes it possible to
-	 * flush the workqueue instead of synchronize_rcu. Therefore readers do
+	 * All readers access it from worker, which makes it possible to
+	 * flush the vhost_work instead of synchronize_rcu. Therefore readers do
  	 * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
-	 * work item execution acts instead of rcu_read_lock() and the end of
-	 * work item execution acts instead of rcu_read_lock().
+	 * vhost_work execution acts instead of rcu_read_lock() and the end of
+	 * vhost_work execution acts instead of rcu_read_lock().
  	 * Writers use virtqueue mutex. */
  	void *private_data;
  	/* Log write descriptors */
@@ -110,6 +122,9 @@ struct vhost_dev {
  	int nvqs;
  	struct file *log_file;
  	struct eventfd_ctx *log_ctx;
+	spinlock_t work_lock;
+	struct list_head work_list;
+	struct task_struct *worker;
  };
 
  long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs);
@@ -136,9 +151,6 @@ bool vhost_enable_notify(struct vhost_vi
  int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
  		    unsigned int log_num, u64 len);
 
-int vhost_init(void);
-void vhost_cleanup(void);
-
  #define vq_err(vq, fmt, ...) do {                                  \
  		pr_debug(pr_fmt(fmt), ##__VA_ARGS__);       \
  		if ((vq)->error_ctx)                               \
#24 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 9 lines · Tejun Heo

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_work *, there's no visible change to vhost_poll_*() interface.

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

Quoted 8 lines

* Updated to use sub structure vhost_work instead of directly using vhost_poll at Michael's suggestion.

* Added flusher wake_up() optimization at Michael's suggestion.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>

All the tricky barrier pairing made me uncomfortable. So I came up with this on top (untested): if we do all operations under the spinlock, we can get by without barriers and atomics. And since we need the lock for list operations anyway, this should have no paerformance impact.

What do you think?

drivers/vhost/vhost.c +44 -34
index 0c6b533..7730a30 100644
@@ -73,7 +73,7 @@ void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
 	INIT_LIST_HEAD(&work->node);
 	work->fn = fn;
 	init_waitqueue_head(&work->done);
-	atomic_set(&work->flushing, 0);
+	work->flushing = 0;
 	work->queue_seq = work->done_seq = 0;
 }
 
@@ -99,13 +99,23 @@ void vhost_poll_stop(struct vhost_poll *poll)
 void vhost_poll_flush(struct vhost_poll *poll)
 {
 	struct vhost_work *work = &poll->work;
-	int seq = work->queue_seq;
+	unsigned seq, left;
+	int flushing;
 
-	atomic_inc(&work->flushing);
-	smp_mb__after_atomic_inc();	/* mb flush-b0 paired with worker-b1 */
-	wait_event(work->done, seq - work->done_seq <= 0);
-	atomic_dec(&work->flushing);
-	smp_mb__after_atomic_dec();	/* rmb flush-b1 paired with worker-b0 */
+	spin_lock_irq(&dev->work_lock);
+	seq = work->queue_seq;
+	work->flushing++;
+	spin_unlock_irq(&dev->work_lock);
+	wait_event(work->done, {
+		   spin_lock_irq(&dev->work_lock);
+		   left = work->done_seq - seq;
+		   spin_unlock_irq(&dev->work_lock);
+		   left < UINT_MAX / 2;
+	});
+	spin_lock_irq(&dev->work_lock);
+	flushing = --work->flushing;
+	spin_unlock_irq(&dev->work_lock);
+	BUG_ON(flushing < 0);
 }
 
 void vhost_poll_queue(struct vhost_poll *poll)
@@ -151,37 +161,37 @@ static void vhost_vq_reset(struct vhost_dev *dev,
 static int vhost_worker(void *data)
 {
 	struct vhost_dev *dev = data;
-	struct vhost_work *work;
+	struct vhost_work *work = NULL;
 
-repeat:
-	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
 
-	if (kthread_should_stop()) {
-		__set_current_state(TASK_RUNNING);
-		return 0;
-	}
+		if (kthread_should_stop()) {
+			__set_current_state(TASK_RUNNING);
+			return 0;
+		}
 
-	work = NULL;
-	spin_lock_irq(&dev->work_lock);
-	if (!list_empty(&dev->work_list)) {
-		work = list_first_entry(&dev->work_list,
-					struct vhost_work, node);
-		list_del_init(&work->node);
-	}
-	spin_unlock_irq(&dev->work_lock);
+		spin_lock_irq(&dev->work_lock);
+		if (work) {
+			work->done_seq = work->queue_seq;
+			if (work->flushing)
+				wake_up_all(&work->done);
+		}
+		if (!list_empty(&dev->work_list)) {
+			work = list_first_entry(&dev->work_list,
+						struct vhost_work, node);
+			list_del_init(&work->node);
+		} else
+			work = NULL;
+		spin_unlock_irq(&dev->work_lock);
+
+		if (work) {
+			__set_current_state(TASK_RUNNING);
+			work->fn(work);
+		} else
+			schedule();
 
-	if (work) {
-		__set_current_state(TASK_RUNNING);
-		work->fn(work);
-		smp_wmb();	/* wmb worker-b0 paired with flush-b1 */
-		work->done_seq = work->queue_seq;
-		smp_mb();	/* mb worker-b1 paired with flush-b0 */
-		if (atomic_read(&work->flushing))
-			wake_up_all(&work->done);
-	} else
-		schedule();
-
-	goto repeat;
+	}
 }
 
 long vhost_dev_init(struct vhost_dev *dev,
drivers/vhost/vhost.h +3 -3
index 0e63091..3693327 100644
@@ -27,9 +27,9 @@ struct vhost_work {
 	struct list_head	  node;
 	vhost_work_fn_t		  fn;
 	wait_queue_head_t	  done;
-	atomic_t		  flushing;
-	int			  queue_seq;
-	int			  done_seq;
+	int			  flushing;
+	unsigned		  queue_seq;
+	unsigned		  done_seq;
 };
 
 /* Poll a file (eventfd or socket) */
maintainer
#25 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 6 lines · Michael S. Tsirkin

All the tricky barrier pairing made me uncomfortable. So I came up with this on top (untested): if we do all operations under the spinlock, we can get by without barriers and atomics. And since we need the lock for list operations anyway, this should have no paerformance impact.

What do you think?

I've created kthread_worker in wq#for-next tree and already converted ivtv to use it. Once this lands in mainline, I think converting vhost to use it would be better choice. kthread worker code uses basically the same logic used in the vhost_workqueue code but is better organized and documented. So, I think it would be better to stick with the original implementation, as otherwise we're likely to just decrease test coverage without much gain.

http://git.kernel.org/?p=linux/kernel/git/tj/wq.git;a=commitdiff;h=b56c0d8937e665a27d90517ee7a746d0aa05af46;hp=53c5f5ba42c194cb13dd3083ed425f2c5b1ec439

Quoted 24 lines
drivers/vhost/vhost.c +7 -10
@@ -151,37 +161,37 @@ static void vhost_vq_reset(struct vhost_dev *dev,
 static int vhost_worker(void *data)
 {
 	struct vhost_dev *dev = data;
-	struct vhost_work *work;
+	struct vhost_work *work = NULL;
 
-repeat:
-	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
 
-	if (kthread_should_stop()) {
-		__set_current_state(TASK_RUNNING);
-		return 0;
-	}
+		if (kthread_should_stop()) {
+			__set_current_state(TASK_RUNNING);
+			return 0;
+		}
 
-	work = NULL;
-	spin_lock_irq(&dev->work_lock);
-	if (!list_empty(&dev->work_list)) {

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_dev *dev, · line 178

Quoted 10 lines
drivers/vhost/vhost.c +5 -5
-		work = list_first_entry(&dev->work_list,
-					struct vhost_work, node);
-		list_del_init(&work->node);
-	}
-	spin_unlock_irq(&dev->work_lock);
+		spin_lock_irq(&dev->work_lock);
+		if (work) {
+			work->done_seq = work->queue_seq;
+			if (work->flushing)
+				wake_up_all(&work->done);

I don't think doing this before executing the function is correct, so you'll have to release the lock, execute the function, regrab the lock and then do the flush processing.

Thanks.

Signature · 1 line
tejun
#26 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 18 lines · Tejun Heo

Hello,

Quoted 6 lines · Michael S. Tsirkin

All the tricky barrier pairing made me uncomfortable. So I came up with this on top (untested): if we do all operations under the spinlock, we can get by without barriers and atomics. And since we need the lock for list operations anyway, this should have no paerformance impact.

What do you think?

I've created kthread_worker in wq#for-next tree and already converted ivtv to use it. Once this lands in mainline, I think converting vhost to use it would be better choice. kthread worker code uses basically the same logic used in the vhost_workqueue code but is better organized and documented. So, I think it would be better to stick with the original implementation, as otherwise we're likely to just decrease test coverage without much gain.

Quoted 1 line

http://git.kernel.org/?p=linux/kernel/git/tj/wq.git;a=commitdiff;h=b56c0d8937e665a27d90517ee7a746d0aa05af46;hp=53c5f5ba42c194cb13dd3083ed425f2c5b1ec439

Sure, if we keep using workqueue. But I'd like to investigate this direction a bit more because there's discussion to switching from kthread to regular threads altogether.

Quoted 21 lines
Quoted 21 lines
drivers/vhost/vhost.c +7 -7
@@ -151,37 +161,37 @@ static void vhost_vq_reset(struct vhost_dev *dev,
 static int vhost_worker(void *data)
 {
 	struct vhost_dev *dev = data;
-	struct vhost_work *work;
+	struct vhost_work *work = NULL;
 
-repeat:
-	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
 
-	if (kthread_should_stop()) {
-		__set_current_state(TASK_RUNNING);
-		return 0;
-	}
+		if (kthread_should_stop()) {
+			__set_current_state(TASK_RUNNING);
+			return 0;
+		}
 
Quoted 15 lines

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_dev *dev, · line 178

Quoted 13 lines
drivers/vhost/vhost.c +5 -8
-	work = NULL;
-	spin_lock_irq(&dev->work_lock);
-	if (!list_empty(&dev->work_list)) {
-		work = list_first_entry(&dev->work_list,
-					struct vhost_work, node);
-		list_del_init(&work->node);
-	}
-	spin_unlock_irq(&dev->work_lock);
+		spin_lock_irq(&dev->work_lock);
+		if (work) {
+			work->done_seq = work->queue_seq;
+			if (work->flushing)
+				wake_up_all(&work->done);

I don't think doing this before executing the function is correct,

Well, before I execute the function work is NULL, so this is skipped. Correct?

Quoted 5 lines

so
you'll have to release the lock, execute the function, regrab the lock and then do the flush processing.

Thanks.

It's done in the loop, so I thought we can reuse the locking done for the sake of processing the next work item. Makes sense?

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#27 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 8 lines · Michael S. Tsirkin
Quoted 8 lines

I've created kthread_worker in wq#for-next tree and already converted ivtv to use it. Once this lands in mainline, I think converting vhost to use it would be better choice. kthread worker code uses basically the same logic used in the vhost_workqueue code but is better organized and documented. So, I think it would be better to stick with the original implementation, as otherwise we're likely to just decrease test coverage without much gain.

Quoted 5 lines
Quoted 1 line

http://git.kernel.org/?p=linux/kernel/git/tj/wq.git;a=commitdiff;h=b56c0d8937e665a27d90517ee7a746d0aa05af46;hp=53c5f5ba42c194cb13dd3083ed425f2c5b1ec439

Sure, if we keep using workqueue. But I'd like to investigate this direction a bit more because there's discussion to switching from kthread to regular threads altogether.

Hmmm? It doesn't have much to do with workqueue. kthread_worker is a simple wrapper around kthread. It now assumes kthread but changing it to be useable with any thread shouldn't be too hard. Wouldn't that be better?

Quoted 9 lines
Quoted 1 line

I don't think doing this before executing the function is correct,

Well, before I execute the function work is NULL, so this is skipped. Correct?

Quoted 4 lines

so
you'll have to release the lock, execute the function, regrab the lock and then do the flush processing.

Quoted 5 lines
Quoted 1 line

Thanks.

It's done in the loop, so I thought we can reuse the locking done for the sake of processing the next work item. Makes sense?

Yeap, right. I think it would make much more sense to use common code when it becomes available but if you think the posted change is necessary till then, please feel free to go ahead.

Thanks.

Signature · 1 line
tejun
#28 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 13 lines · Tejun Heo

Hello,

Quoted 10 lines · Michael S. Tsirkin
Quoted 9 lines

I've created kthread_worker in wq#for-next tree and already converted ivtv to use it. Once this lands in mainline, I think converting vhost to use it would be better choice. kthread worker code uses basically the same logic used in the vhost_workqueue code but is better organized and documented. So, I think it would be better to stick with the original implementation, as otherwise we're likely to just decrease test coverage without much gain.

http://git.kernel.org/?p=linux/kernel/git/tj/wq.git;a=commitdiff;h=b56c0d8937e665a27d90517ee7a746d0aa05af46;hp=53c5f5ba42c194cb13dd3083ed425f2c5b1ec439

Quoted 8 lines
Quoted 3 lines

Sure, if we keep using workqueue. But I'd like to investigate this direction a bit more because there's discussion to switching from kthread to regular threads altogether.

Hmmm? It doesn't have much to do with workqueue. kthread_worker is a simple wrapper around kthread. It now assumes kthread but changing it to be useable with any thread shouldn't be too hard. Wouldn't that be better?

Yes, of course, when common code becomes available we should switch to that.

Quoted 23 lines
Quoted 9 lines
Quoted 1 line

I don't think doing this before executing the function is correct,

Well, before I execute the function work is NULL, so this is skipped. Correct?

Quoted 4 lines

so
you'll have to release the lock, execute the function, regrab the lock and then do the flush processing.

Quoted 5 lines
Quoted 1 line

Thanks.

It's done in the loop, so I thought we can reuse the locking done for the sake of processing the next work item. Makes sense?

Yeap, right. I think it would make much more sense to use common code when it becomes available but if you think the posted change is necessary till then, please feel free to go ahead.

Thanks.

Signature · 1 line
tejun
#29 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 13 lines · Tejun Heo

Hello,

Quoted 10 lines · Michael S. Tsirkin
Quoted 9 lines

I've created kthread_worker in wq#for-next tree and already converted ivtv to use it. Once this lands in mainline, I think converting vhost to use it would be better choice. kthread worker code uses basically the same logic used in the vhost_workqueue code but is better organized and documented. So, I think it would be better to stick with the original implementation, as otherwise we're likely to just decrease test coverage without much gain.

http://git.kernel.org/?p=linux/kernel/git/tj/wq.git;a=commitdiff;h=b56c0d8937e665a27d90517ee7a746d0aa05af46;hp=53c5f5ba42c194cb13dd3083ed425f2c5b1ec439

Quoted 8 lines
Quoted 3 lines

Sure, if we keep using workqueue. But I'd like to investigate this direction a bit more because there's discussion to switching from kthread to regular threads altogether.

Hmmm? It doesn't have much to do with workqueue. kthread_worker is a simple wrapper around kthread. It now assumes kthread but changing it to be useable with any thread shouldn't be too hard. Wouldn't that be better?

BTW, kthread_worker would benefit from the optimization I implemented here as well.

Signature · 1 line
MST
maintainer
#30 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 2 lines · Michael S. Tsirkin

BTW, kthread_worker would benefit from the optimization I implemented here as well.

Hmmm... I'm not quite sure whether it's an optimization. I thought the patch was due to feeling uncomfortable about using barriers? Is it an optimization?

Thanks.

Signature · 1 line
tejun
maintainer
#31 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 9 lines · Tejun Heo

Hello,

Quoted 2 lines · Michael S. Tsirkin

BTW, kthread_worker would benefit from the optimization I implemented here as well.

Hmmm... I'm not quite sure whether it's an optimization. I thought the patch was due to feeling uncomfortable about using barriers? Is it an optimization?

Yeah, one less smp_mb() in execution path. The lock dancing in flush() is ugly but then again mucking with barriers could be harder to understand. Care to send a patch against wq#for-next tree?

Thanks.

Signature · 1 line
tejun
#32 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 11 lines · Tejun Heo
Quoted 9 lines · Tejun Heo

Hello,

Quoted 2 lines · Michael S. Tsirkin

BTW, kthread_worker would benefit from the optimization I implemented here as well.

Hmmm... I'm not quite sure whether it's an optimization. I thought the patch was due to feeling uncomfortable about using barriers? Is it an optimization?

Quoted 5 lines

Yeah, one less smp_mb() in execution path. The lock dancing in flush() is ugly but then again mucking with barriers could be harder to understand. Care to send a patch against wq#for-next tree?

Thanks.

Sure. Where's that, exactly?

Quoted 2 lines
Signature · 1 line
tejun
#33 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 8 lines · Tejun Heo

Hello,

Quoted 2 lines · Michael S. Tsirkin

BTW, kthread_worker would benefit from the optimization I implemented here as well.

Hmmm... I'm not quite sure whether it's an optimization. I thought the patch was due to feeling uncomfortable about using barriers?

Oh yes. But getting rid of barriers is what motivated me originally.

Quoted 3 lines

Is it an optimization?

Thanks.

Yes, sure. This removes atomic read and 2 barrier operations on data path. And it does not add any new synchronization: instead, we reuse the lock that we take anyway. The relevant part is:

+ if (work) {
+ __set_current_state(TASK_RUNNING);
+ work->fn(work);
+ } else
+ schedule();

- if (work) {

-               __set_current_state(TASK_RUNNING);
-               work->fn(work);
-               smp_wmb();      /* wmb worker-b0 paired with flush-b1 */
-               work->done_seq = work->queue_seq;
-               smp_mb();       /* mb worker-b1 paired with flush-b0 */
-               if (atomic_read(&work->flushing))

- wake_up_all(&work->done);
- } else
- schedule();
-
- goto repeat;

Is there a git tree with kthread_worker applied?
I might do this just for fun ...

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#34 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines

Hmmm... I'm not quite sure whether it's an optimization. I thought the patch was due to feeling uncomfortable about using barriers?

Oh yes. But getting rid of barriers is what motivated me originally.

Yeah, getting rid of barriers is always good. :-)

Quoted 2 lines

Is there a git tree with kthread_worker applied?
I might do this just for fun ...

git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-next

For the original implementaiton, please take a look at commit b56c0d8937e665a27d90517ee7a746d0aa05af46.

* Can you please keep the outer goto repeat loop? I just don't like outermost for (;;).

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

* I think A - B <= 0 test would be more familiar. At least time_before/after() are implemented that way.

Thanks.

Signature · 1 line
tejun
maintainer
#35 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Just one more thing.

Quoted 2 lines · Tejun Heo

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

* Similar issue exists for kthread_stop(). The kthread shouldn't exit while there's a work to flush (please note that kthread_worker interface allows detaching / attaching worker kthread during operation, so it should remain in consistent state with regard to flushing).

Thanks.

Signature · 1 line
tejun
#36 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 5 lines · Tejun Heo

Just one more thing.

Quoted 2 lines · Tejun Heo

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

BTW why is this important?
We could always get another work and flush right after try_to_freeze, and then flush would block for a long time.

BTW the vhost patch you sent does not do this at all. I am guessing it is because our thread is not freezable?

Quoted 7 lines

* Similar issue exists for kthread_stop(). The kthread shouldn't exit while there's a work to flush (please note that kthread_worker interface allows detaching / attaching worker kthread during operation, so it should remain in consistent state with regard to flushing).

Thanks.

Not sure I agree here. Users must synchronise flush and stop calls. Otherwise a work might get queued after stop is called, and you won't be able to flush it.

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#37 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 10 lines · Michael S. Tsirkin
Quoted 3 lines · Tejun Heo
Quoted 2 lines

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

BTW why is this important?
We could always get another work and flush right after try_to_freeze, and then flush would block for a long time.

BTW the vhost patch you sent does not do this at all. I am guessing it is because our thread is not freezable?

Yeap, I think so.

Quoted 9 lines
Quoted 5 lines

* Similar issue exists for kthread_stop(). The kthread shouldn't exit while there's a work to flush (please note that kthread_worker interface allows detaching / attaching worker kthread during operation, so it should remain in consistent state with regard to flushing).

Not sure I agree here. Users must synchronise flush and stop calls. Otherwise a work might get queued after stop is called, and you won't be able to flush it.

For freeze, it probably is okay but for stop, I think it's better to keep the semantics straight forward. It may be okay to do otherwise but having such oddity in generic interface is nasty and may lead to surprises which can be pretty difficult to track down later on. It's just a bit more of annoyance while writing the generic code, so...

Thanks.

Signature · 1 line
tejun
#38 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 22 lines · Tejun Heo

Hello,

Quoted 10 lines · Michael S. Tsirkin
Quoted 3 lines · Tejun Heo
Quoted 2 lines

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

BTW why is this important?
We could always get another work and flush right after try_to_freeze, and then flush would block for a long time.

BTW the vhost patch you sent does not do this at all. I am guessing it is because our thread is not freezable?

Yeap, I think so.

Quoted 6 lines
Quoted 5 lines

* Similar issue exists for kthread_stop(). The kthread shouldn't exit while there's a work to flush (please note that kthread_worker interface allows detaching / attaching worker kthread during operation, so it should remain in consistent state with regard to flushing).

Quoted 6 lines
Quoted 3 lines

Not sure I agree here. Users must synchronise flush and stop calls. Otherwise a work might get queued after stop is called, and you won't be able to flush it.

For freeze, it probably is okay but for stop, I think it's better to keep the semantics straight forward.

What are the semantics then? What do we want stop followed by queue and flush to do?

Quoted 9 lines

It may be okay to do otherwise
but having such oddity in generic interface is nasty and may lead to surprises which can be pretty difficult to track down later on. It's just a bit more of annoyance while writing the generic code, so...

Thanks.

Signature · 1 line
tejun
maintainer
#39 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 5 lines · Michael S. Tsirkin
Quoted 2 lines

For freeze, it probably is okay but for stop, I think it's better to keep the semantics straight forward.

What are the semantics then? What do we want stop followed by queue and flush to do?

One scenario I can think of is the following.

kthread_worker allows kthreads to be attached and stopped anytime, so if the caller stops the current worker while flushing is pending and attaches a new worker, the flushing which was pending will never happen.

But, in general, it's nasty to allow execution and its completion to be separated. Things like that are likely to bite us back in obscure ways. I think it would be silly to have such oddity in generic code when it can be avoided without too much trouble.

Thanks.

Signature · 1 line
tejun
#40 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 1 line · Tejun Heo

Just one more thing.

I noticed that with vhost, flush_work was getting the worker pointer as well. Can we live with this API change?

Signature · 1 line
MST
maintainer
#41 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 5 lines · Michael S. Tsirkin
Quoted 1 line · Tejun Heo

Just one more thing.

I noticed that with vhost, flush_work was getting the worker pointer as well. Can we live with this API change?

Yeah, the flushing mechanism wouldn't work reliably if the work is queued to a different worker without flushing, so yeah passing in @worker might actually be better.

Thanks.

Signature · 1 line
tejun
maintainer
#42 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 7 lines · Tejun Heo
Quoted 2 lines · Michael S. Tsirkin

I noticed that with vhost, flush_work was getting the worker pointer as well. Can we live with this API change?

Yeah, the flushing mechanism wouldn't work reliably if the work is queued to a different worker without flushing, so yeah passing in @worker might actually be better.

Thinking a bit more about it, it kind of sucks that queueing to another worker from worker->func() breaks flush. Maybe the right thing to do there is using atomic_t for done_seq? It pays a bit more overhead but maybe that's justifiable to keep the API saner? It would be great if it can be fixed somehow even if it means that the work has to be separately flushed for each worker it has been on before being destroyed.

Or, if flushing has to be associated with a specific worker anyway, maybe it would be better to move the sequence counter to kthread_worker and do it similarly with the original workqueue so that work can be destroyed once execution starts? Then, it can at least remain semantically identical to the original workqueue.

Thanks.

Signature · 1 line
tejun
#43 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 19 lines · Tejun Heo

Hello,

Quoted 7 lines · Tejun Heo
Quoted 2 lines · Michael S. Tsirkin

I noticed that with vhost, flush_work was getting the worker pointer as well. Can we live with this API change?

Yeah, the flushing mechanism wouldn't work reliably if the work is queued to a different worker without flushing, so yeah passing in @worker might actually be better.

Thinking a bit more about it, it kind of sucks that queueing to another worker from worker->func() breaks flush. Maybe the right thing to do there is using atomic_t for done_seq? It pays a bit more overhead but maybe that's justifiable to keep the API saner? It would be great if it can be fixed somehow even if it means that the work has to be separately flushed for each worker it has been on before being destroyed.

Quoted 7 lines

Or, if flushing has to be associated with a specific worker anyway, maybe it would be better to move the sequence counter to kthread_worker and do it similarly with the original workqueue so that work can be destroyed once execution starts? Then, it can at least remain semantically identical to the original workqueue.

Thanks.

This last sounds sane: in fact I didn't know there is any difference.

Quoted 2 lines
Signature · 1 line
tejun
#44 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 7 lines · Tejun Heo

Hello,

Quoted 4 lines · Tejun Heo
Quoted 2 lines · Michael S. Tsirkin

I noticed that with vhost, flush_work was getting the worker pointer as well. Can we live with this API change?

Quoted 7 lines
Quoted 3 lines

Yeah, the flushing mechanism wouldn't work reliably if the work is queued to a different worker without flushing, so yeah passing in @worker might actually be better.

Thinking a bit more about it, it kind of sucks that queueing to another worker from worker->func() breaks flush. Maybe the right thing to do there is using atomic_t for done_seq?

I don't believe it will help: we might have:

worker1 runs work
work requeues itself queued index = 1 worker1 reads queued index = 1 worker2 runs work
work requeues itself queued index = 2 worker2 runs work
worker2 reads queued index = 2 worker2 writes done index = 2 worker1 writes done index = 1

As you see, done index got moved back.

Quoted 16 lines

It pays a bit more
overhead but maybe that's justifiable to keep the API saner? It would be great if it can be fixed somehow even if it means that the work has to be separately flushed for each worker it has been on before being destroyed.

Or, if flushing has to be associated with a specific worker anyway, maybe it would be better to move the sequence counter to kthread_worker and do it similarly with the original workqueue so that work can be destroyed once execution starts? Then, it can at least remain semantically identical to the original workqueue.

Thanks.

Signature · 1 line
tejun
maintainer
#45 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 16 lines · Michael S. Tsirkin
Quoted 3 lines

Thinking a bit more about it, it kind of sucks that queueing to another worker from worker->func() breaks flush. Maybe the right thing to do there is using atomic_t for done_seq?

I don't believe it will help: we might have:

worker1 runs work
work requeues itself queued index = 1 worker1 reads queued index = 1 worker2 runs work
work requeues itself queued index = 2 worker2 runs work
worker2 reads queued index = 2 worker2 writes done index = 2 worker1 writes done index = 1

Quoted 1 line

As you see, done index got moved back.

Yeah, I think the flushing logic should be moved to the worker. Are you interested in doing it w/ your change?

Thanks.

Signature · 1 line
tejun
#46 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 17 lines · Tejun Heo
Quoted 16 lines · Michael S. Tsirkin
Quoted 3 lines

Thinking a bit more about it, it kind of sucks that queueing to another worker from worker->func() breaks flush. Maybe the right thing to do there is using atomic_t for done_seq?

I don't believe it will help: we might have:

worker1 runs work
work requeues itself queued index = 1 worker1 reads queued index = 1 worker2 runs work
work requeues itself queued index = 2 worker2 runs work
worker2 reads queued index = 2 worker2 writes done index = 2 worker1 writes done index = 1

Quoted 6 lines
Quoted 1 line

As you see, done index got moved back.

Yeah, I think the flushing logic should be moved to the worker. Are you interested in doing it w/ your change?

Thanks.

I'm unsure how flush_work operates under these conditions. E.g. in workqueue.c, this seems to work by keeping a pointer to current workqueue in the work. But what prevents us from destroying the workqueue when work might not be running?

Is this currently broken if you use multiple workqueues for the same work? If yes, I propose we do as I did, making flush_work get worker pointer, and only flushing on that worker.

Quoted 2 lines
Signature · 1 line
tejun
maintainer
#47 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 4 lines · Michael S. Tsirkin

I'm unsure how flush_work operates under these conditions. E.g. in workqueue.c, this seems to work by keeping a pointer to current workqueue in the work. But what prevents us from destroying the workqueue when work might not be running?

In cmwq, work points to the gcwq it was on, which keeps track of all the works in progress, so flushing work which is on a destroyed workqueue should be fine, but in the original implementation, it would end up accessing freed memory.

Quoted 4 lines

Is this currently broken if you use multiple workqueues for the same work? If yes, I propose we do as I did, making flush_work get worker pointer, and only flushing on that worker.

The original semantics of workqueue is that flush_work() guarantees that the work has finished executing on the workqueue it was last queued on. Adding @worker to flush_work() is okay, I think.

Thanks.

Signature · 1 line
tejun
#48 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo

Here's an untested patch forward-ported from vhost (works fine for vhost).

kthread_worker: replace barriers+atomics with a lock

We can save some cycles and make code simpler by reusing worker lock for flush, instead of atomics. flush_kthread_work needs to get worker pointer for this to work.

---

include/linux/kthread.h +4 -3
index 685ea65..19ae9f2 100644
@@ -58,7 +58,7 @@ struct kthread_work {
 	struct list_head	node;
 	kthread_work_func_t	func;
 	wait_queue_head_t	done;
-	atomic_t		flushing;
+	int			flushing;
 	int			queue_seq;
 	int			done_seq;
 };
@@ -72,7 +72,7 @@ struct kthread_work {
 	.node = LIST_HEAD_INIT((work).node),				\
 	.func = (fn),							\
 	.done = __WAIT_QUEUE_HEAD_INITIALIZER((work).done),		\
-	.flushing = ATOMIC_INIT(0),					\
+	.flushing = 0,							\
 	}
 
 #define DEFINE_KTHREAD_WORKER(worker)					\
@@ -96,7 +96,8 @@ int kthread_worker_fn(void *worker_ptr);
 
 bool queue_kthread_work(struct kthread_worker *worker,
 			struct kthread_work *work);
-void flush_kthread_work(struct kthread_work *work);
+void flush_kthread_work(struct kthread_worker *worker,
+			struct kthread_work *work);
 void flush_kthread_worker(struct kthread_worker *worker);
 
 #endif /* _LINUX_KTHREAD_H */
kernel/kthread.c +29 -25

93 lines not on this page · raw message

#49 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Tejun Heo
Quoted 13 lines · Tejun Heo

Hello,

Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines

Hmmm... I'm not quite sure whether it's an optimization. I thought the patch was due to feeling uncomfortable about using barriers?

Oh yes. But getting rid of barriers is what motivated me originally.

Yeah, getting rid of barriers is always good. :-)

Quoted 2 lines

Is there a git tree with kthread_worker applied?
I might do this just for fun ...

Quoted 7 lines

git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-next

For the original implementaiton, please take a look at commit b56c0d8937e665a27d90517ee7a746d0aa05af46.

* Can you please keep the outer goto repeat loop? I just don't like outermost for (;;).

Okay ... can we put the code in a {} scope to make it clear where does the loop starts and ends?

Quoted 2 lines

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

It currently seems to be executed when there is work to flush. Is this wrong?

Quoted 2 lines

* I think A - B <= 0 test would be more familiar. At least time_before/after() are implemented that way.

I am concerned that this overflows a signed integer - which I seem to remeber that C99 disallows.
timer macros are on data path so might be worth the risk there, but flush is slow path so better be safe?

Quoted 4 lines

Thanks.

Signature · 1 line
tejun
maintainer
#50 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 5 lines · Michael S. Tsirkin
Quoted 2 lines

* Can you please keep the outer goto repeat loop? I just don't like outermost for (;;).

Okay ... can we put the code in a {} scope to make it clear where does the loop starts and ends?

If we're gonna do that, it would be better to put it inside a loop construct. The reason why I don't like it is that loops like that don't really help read/writeability much while indenting the whole logic unnecessarily and look more like a result of obsession against goto rather than any practical reason. It's just a cosmetic preference and I might as well be the weirdo here, so if you feel strong about it, please feel free to put everything in a loop.

Quoted 5 lines
Quoted 2 lines

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

It currently seems to be executed when there is work to flush. Is this wrong?

Oh, does it? As I wrote in the other mail, things like that wouldn't necessarily break correctness but I think it would be better to avoid surprises in the generic code if not too difficult.

Quoted 5 lines
Quoted 2 lines

* I think A - B <= 0 test would be more familiar. At least time_before/after() are implemented that way.

I am concerned that this overflows a signed integer - which I seem to remeber that C99 disallows.

Really? Overflows of pointer isn't expected and that's why we have weird RELOC_HIDE() macro for such calculations but integers not expected to overflow is a news to me. Are you sure? That basically means time_before/after() aren't safe either.

Quoted 2 lines

timer macros are on data path so might be worth the risk there, but flush is slow path so better be safe?

I don't think performance matters much here. I just think the sign test is clearer / more familiar for the logic.

Thanks.

Signature · 1 line
tejun
#51 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Quoted 18 lines · Tejun Heo
Quoted 5 lines · Michael S. Tsirkin
Quoted 2 lines

* Can you please keep the outer goto repeat loop? I just don't like outermost for (;;).

Okay ... can we put the code in a {} scope to make it clear where does the loop starts and ends?

If we're gonna do that, it would be better to put it inside a loop construct. The reason why I don't like it is that loops like that don't really help read/writeability much while indenting the whole logic unnecessarily and look more like a result of obsession against goto rather than any practical reason. It's just a cosmetic preference and I might as well be the weirdo here, so if you feel strong about it, please feel free to put everything in a loop.

Quoted 3 lines
Quoted 2 lines

* Placing try_to_freeze() could be a bit annoying. It shouldn't be executed when there's a work to flush.

Quoted 6 lines
Quoted 2 lines

It currently seems to be executed when there is work to flush. Is this wrong?

Oh, does it? As I wrote in the other mail, things like that wouldn't necessarily break correctness but I think it would be better to avoid surprises in the generic code if not too difficult.

Let's try to define what do we want to achieve then. Do you want code that flushes workers not to block when workers are frozen? How will we handle work submitted when worker is frozen?

Quoted 10 lines
Quoted 5 lines
Quoted 2 lines

* I think A - B <= 0 test would be more familiar. At least time_before/after() are implemented that way.

I am concerned that this overflows a signed integer - which I seem to remeber that C99 disallows.

Really? Overflows of pointer isn't expected and that's why we have weird RELOC_HIDE() macro for such calculations but integers not expected to overflow is a news to me. Are you sure? That basically means time_before/after() aren't safe either.

As I said, in C99.
However, the kernel is built with -fno-strict-overflow, so it will work.

Quoted 10 lines
Quoted 2 lines

timer macros are on data path so might be worth the risk there, but flush is slow path so better be safe?

I don't think performance matters much here. I just think the sign test is clearer / more familiar for the logic.

Thanks.

Signature · 1 line
tejun
maintainer
#52 lore raw

Re: [PATCH UPDATED 1/3] vhost: replace vhost_workqueue with per-vhost kthread

Hello,

Quoted 3 lines · Michael S. Tsirkin

Let's try to define what do we want to achieve then. Do you want code that flushes workers not to block when workers are frozen? How will we handle work submitted when worker is frozen?

As I wrote earlier, it's not necessarily about correctness but rather avoiding unnecessary surprises and of course flushing can and should stall if the queue is frozen but let's not separate execution of a work and its completion with something which can take undeterminate amount of time.

Thanks.

Signature · 1 line
tejun
maintainer
#53 lore raw

PATCH 2/3 cgroups: Add an API to attach a task to current task's cgroup

In reply to Michael S. Tsirkin

From: Sridhar Samudrala <[email protected]>

Add a new kernel API to attach a task to current task's cgroup in all the active hierarchies.

---
 include/linux/cgroup.h |    1 +
 kernel/cgroup.c        |   23 +++++++++++++++++++++++
 2 files changed, 24 insertions(+)
include/linux/cgroup.h +1 -0
Index: work/include/linux/cgroup.h
===================================================================
@@ -570,6 +570,7 @@ struct task_struct *cgroup_iter_next(str
 void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it);
 int cgroup_scan_tasks(struct cgroup_scanner *scan);
 int cgroup_attach_task(struct cgroup *, struct task_struct *);
+int cgroup_attach_task_current_cg(struct task_struct *);
 
 /*
  * CSS ID is ID for cgroup_subsys_state structs under subsys. This only works
kernel/cgroup.c +23 -0
Index: work/kernel/cgroup.c
===================================================================
@@ -1788,6 +1788,29 @@ out:
 	return retval;
 }
 
+/**
+ * cgroup_attach_task_current_cg - attach task 'tsk' to current task's cgroup
+ * @tsk: the task to be attached
+ */
+int cgroup_attach_task_current_cg(struct task_struct *tsk)
+{
+	struct cgroupfs_root *root;
+	struct cgroup *cur_cg;
+	int retval = 0;
+
+	cgroup_lock();
+	for_each_active_root(root) {
+		cur_cg = task_cgroup_from_root(current, root);
+		retval = cgroup_attach_task(cur_cg, tsk);
+		if (retval)
+			break;
+	}
+	cgroup_unlock();
+
+	return retval;
+}
+EXPORT_SYMBOL_GPL(cgroup_attach_task_current_cg);
+
 /*
  * Attach task with pid 'pid' to cgroup 'cgrp'. Call with cgroup_mutex
  * held. May take task_lock of task
maintainer
#54 lore raw

PATCH 3/3 vhost: apply cpumask and cgroup to vhost workers

In reply to Michael S. Tsirkin

Apply the cpumask and cgroup of the initializing task to the created vhost worker.

Based on Sridhar Samudrala's patch. Li Zefan spotted a bug in error path (twice), fixed (twice).

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Cc Li Zefan <[email protected]>
---
 drivers/vhost/vhost.c |   34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)
drivers/vhost/vhost.c +30 -4
Index: work/drivers/vhost/vhost.c
===================================================================
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -187,11 +188,29 @@ long vhost_dev_init(struct vhost_dev *de
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
 	struct task_struct *worker;
-	int i;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out_free_mask;
 
 	worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
-	if (IS_ERR(worker))
-		return PTR_ERR(worker);
+	if (IS_ERR(worker)) {
+		ret = PTR_ERR(worker);
+		goto out_free_mask;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = sched_setaffinity(worker->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = cgroup_attach_task_current_cg(worker);
+	if (ret)
+		goto out_stop_worker;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -214,7 +233,14 @@ long vhost_dev_init(struct vhost_dev *de
 	}
 
 	wake_up_process(worker);	/* avoid contributing to loadavg */
-	return 0;
+	ret = 0;
+	goto out_free_mask;
+
+out_stop_worker:
+	kthread_stop(worker);
+out_free_mask:
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
#55 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost workers

Quoted 10 lines · Tejun Heo

Apply the cpumask and cgroup of the initializing task to the created vhost worker.

Based on Sridhar Samudrala's patch. Li Zefan spotted a bug in error path (twice), fixed (twice).

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Cc Li Zefan <[email protected]>

Something that I wanted to figure out - what happens if the CPU mask limits us to a certain CPU that subsequently goes offline? Will e.g. flush block forever or until that CPU comes back? Also, does singlethreaded workqueue behave in the same way?

Quoted 65 lines
---
 drivers/vhost/vhost.c |   34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +30 -4
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -187,11 +188,29 @@ long vhost_dev_init(struct vhost_dev *de
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
 	struct task_struct *worker;
-	int i;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out_free_mask;
 
 	worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
-	if (IS_ERR(worker))
-		return PTR_ERR(worker);
+	if (IS_ERR(worker)) {
+		ret = PTR_ERR(worker);
+		goto out_free_mask;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = sched_setaffinity(worker->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = cgroup_attach_task_current_cg(worker);
+	if (ret)
+		goto out_stop_worker;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -214,7 +233,14 @@ long vhost_dev_init(struct vhost_dev *de
 	}
 
 	wake_up_process(worker);	/* avoid contributing to loadavg */
-	return 0;
+	ret = 0;
+	goto out_free_mask;
+
+out_stop_worker:
+	kthread_stop(worker);
+out_free_mask:
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
maintainer
#56 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost workers

Hello,

Quoted 2 lines · Michael S. Tsirkin

Something that I wanted to figure out - what happens if the CPU mask limits us to a certain CPU that subsequently goes offline?

The thread gets unbound during the last steps of cpu offlining.

Quoted 2 lines

Will e.g. flush block forever or until that CPU comes back? Also, does singlethreaded workqueue behave in the same way?

So, things will proceed as usual although the thread will lose its affinity. Singlethread wqs don't bind their workers (and they shouldn't! :-). MT ones explicitly manage workers according to cpu up/down events.

Thanks.

Signature · 1 line
tejun
#57 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost workers

In reply to Tejun Heo
Quoted 27 lines · Tejun Heo

Apply the cpumask and cgroup of the initializing task to the created vhost worker.

Based on Sridhar Samudrala's patch. Li Zefan spotted a bug in error path (twice), fixed (twice).

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Cc Li Zefan <[email protected]>
---
 drivers/vhost/vhost.c |   34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +1 -0
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>

drivers/vhost/vhost.c long vhost_dev_init(struct vhost_dev *de · line 195

Quoted 9 lines
drivers/vhost/vhost.c +5 -1
@@ -187,11 +188,29 @@ long vhost_dev_init(struct vhost_dev *de
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
 	struct task_struct *worker;
-	int i;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out_free_mask;

I think this is another bug in the error path. You should simply do a return instead of a goto here when aloc_cpu_mask fails.

Thanks
Sridhar

Quoted 43 lines
drivers/vhost/vhost.c +24 -4
 	worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
-	if (IS_ERR(worker))
-		return PTR_ERR(worker);
+	if (IS_ERR(worker)) {
+		ret = PTR_ERR(worker);
+		goto out_free_mask;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = sched_setaffinity(worker->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = cgroup_attach_task_current_cg(worker);
+	if (ret)
+		goto out_stop_worker;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -214,7 +233,14 @@ long vhost_dev_init(struct vhost_dev *de
 	}
 
 	wake_up_process(worker);	/* avoid contributing to loadavg */
-	return 0;
+	ret = 0;
+	goto out_free_mask;
+
+out_stop_worker:
+	kthread_stop(worker);
+out_free_mask:
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
--

To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

maintainer
#58 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost workers

Quoted 9 lines · Sridhar Samudrala

drivers/vhost/vhost.c long vhost_dev_init(struct vhost_dev *de · line 195

Quoted 6 lines
drivers/vhost/vhost.c +5 -1
-	int i;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out_free_mask;

I think this is another bug in the error path. You should simply do a return instead of a goto here when aloc_cpu_mask fails.

Oh... it's always safe to call free_cpumask_var() after failed alloc_cpumask_var(), so that part isn't broken.

Thanks.

Signature · 1 line
tejun
#59 lore raw

Re: [PATCH 1/3] vhost: replace vhost_workqueue with per-vhost kthread

In reply to Michael S. Tsirkin
Quoted 7 lines · Michael S. Tsirkin
Quoted 3 lines · Tejun Heo

Replace vhost_workqueue with per-vhost kthread. Other than callback argument change from struct work_struct * to struct vhost_poll *, there's no visible change to vhost_poll_*() interface.

I would prefer a substructure vhost_work, even just to make the code easier to review and compare to workqueue.c.

Either way this plays out, the rcu_dereference_check() calls will need to be adjusted to reflect the change.

Thanx, Paul

Quoted 269 lines
Quoted 7 lines

This conversion is to make each vhost use a dedicated kthread so that resource control via cgroup can be applied.

Partially based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Quoted 7 lines

---
Okay, here is three patch series to convert vhost to use per-vhost kthread, add cgroup_attach_task_current_cg() and apply it to the vhost kthreads. The conversion is mostly straight forward although flush is slightly tricky.

The problem is that I have no idea how to test this.

It's a 3 step process:

1.
Install qemu-kvm under fc13, or build recent one from source, get it from here:
git://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git

2. install guest under it:
qemu-img create -f qcow2 disk.qcow2 100G
Now get some image (e.g. fedora 13 in fc13.iso)
and install guest:
qemu-kvm -enable-kvm -m 1G -cdrom fc13.iso -drive file=disk.qcow2

3. set up networking. I usually simply do host to guest on a special subnet for testing purposes:

Set up a bridge named mstbr0:

ifconfig mstbr0 down
brctl delbr mstbr0
brctl addbr mstbr0
brctl setfd mstbr0 0
ifconfig mstbr0 11.0.0.1

cat > ifup << EOF
#!/bin/sh -x
/sbin/ifconfig msttap0 0.0.0.0 up
brctl addif mstbr0 msttap0
EOF

qemu-kvm -enable-kvm -m 1G -cdrom fc13.iso -drive file=disk.qcow2
-net nic,model=virtio,netdev=foo -netdev
tap,id=foo,ifname=msttap0,script=/home/mst/ifup,downscript=no,vhost=on

after you set up the guest, log into it and
ifconfig eth0 11.0.0.2

You should now be able to ping guest to host and back. Use something like netperf to stress the connection. Close qemu with kill -9 and unload module to test flushing code.

Quoted 4 lines

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +0 -0

...

Quoted 22 lines
drivers/vhost/vhost.c +18 -0
@@ -125,10 +139,50 @@ static void vhost_vq_reset(struct vhost_
 	vq->log_ctx = NULL;
 }
 
+static int vhost_poller(void *data)
+{
+	struct vhost_dev *dev = data;
+	struct vhost_poll *poll;
+
+repeat:
+	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */
+
+	if (kthread_should_stop()) {
+		__set_current_state(TASK_RUNNING);
+		return 0;
+	}
+
+	poll = NULL;
+	spin_lock(&dev->poller_lock);
+	if (!list_empty(&dev->poll_list)) {
+		poll = list_first_entry(&dev->poll_list,
+					struct vhost_poll, node);

drivers/vhost/vhost.c static void vhost_vq_reset(struct vhost_ · line 169

Quoted 10 lines
drivers/vhost/vhost.c +10 -0
+		list_del_init(&poll->node);
+	}
+	spin_unlock(&dev->poller_lock);
+
+	if (poll) {
+		__set_current_state(TASK_RUNNING);
+		poll->fn(poll);
+		smp_wmb();	/* paired with rmb in vhost_poll_flush() */
+		poll->done_seq = poll->queue_seq;
+		wake_up_all(&poll->done);

This seems to add wakeups on data path, which uses spinlocks etc. OTOH workqueue.c adds a special barrier
entry which only does a wakeup when needed.
Right?

Quoted 54 lines
drivers/vhost/vhost.c +18 -4
+	} else
+		schedule();
+
+	goto repeat;
+}
+
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
+	struct task_struct *poller;
 	int i;
+
+	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
+	if (IS_ERR(poller))
+		return PTR_ERR(poller);
+
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
 	mutex_init(&dev->mutex);
@@ -136,6 +190,9 @@ long vhost_dev_init(struct vhost_dev *de
 	dev->log_file = NULL;
 	dev->memory = NULL;
 	dev->mm = NULL;
+	spin_lock_init(&dev->poller_lock);
+	INIT_LIST_HEAD(&dev->poll_list);
+	dev->poller = poller;
 
 	for (i = 0; i < dev->nvqs; ++i) {
 		dev->vqs[i].dev = dev;
@@ -143,8 +200,7 @@ long vhost_dev_init(struct vhost_dev *de
 		vhost_vq_reset(dev, dev->vqs + i);
 		if (dev->vqs[i].handle_kick)
 			vhost_poll_init(&dev->vqs[i].poll,
-					dev->vqs[i].handle_kick,
-					POLLIN);
+					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
 	return 0;
 }
@@ -217,6 +273,8 @@ void vhost_dev_cleanup(struct vhost_dev
 	if (dev->mm)
 		mmput(dev->mm);
 	dev->mm = NULL;
+
+	kthread_stop(dev->poller);
 }
 
 static int log_access_ok(void __user *log_base, u64 addr, unsigned long sz)
@@ -1113,16 +1171,3 @@ void vhost_disable_notify(struct vhost_v
 		vq_err(vq, "Failed to enable notification at %p: %d\n",
 		       &vq->used->flags, r);
 }
-
-int vhost_init(void)

drivers/vhost/vhost.c void vhost_disable_notify(struct vhost_v · line 1127

Quoted 10 lines
drivers/vhost/vhost.c +0 -10
-{
-	vhost_workqueue = create_singlethread_workqueue("vhost");
-	if (!vhost_workqueue)
-		return -ENOMEM;
-	return 0;
-}
-
-void vhost_cleanup(void)
-{
-	destroy_workqueue(vhost_workqueue);

I note that destroy_workqueue does a flush, kthread_stop doesn't. Right? Sure we don't need to check nothing is in one of the lists? Maybe add a BUG_ON?

Quoted 80 lines
drivers/vhost/vhost.c +0 -1
-}

Index: work/drivers/vhost/vhost.h
===================================================================

drivers/vhost/vhost.h +19 -10
@@ -5,7 +5,6 @@
 #include <linux/vhost.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
-#include <linux/workqueue.h>
 #include <linux/poll.h>
 #include <linux/file.h>
 #include <linux/skbuff.h>
@@ -20,19 +19,26 @@ enum {
 	VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2,
 };
 
+struct vhost_poll;
+typedef void (*vhost_poll_fn_t)(struct vhost_poll *poll);
+
 /* Poll a file (eventfd or socket) */
 /* Note: there's nothing vhost specific about this structure. */
 struct vhost_poll {
+	vhost_poll_fn_t		  fn;
 	poll_table                table;
 	wait_queue_head_t        *wqh;
 	wait_queue_t              wait;
-	/* struct which will handle all actual work. */
-	struct work_struct        work;
+	struct list_head	  node;
+	wait_queue_head_t	  done;
 	unsigned long		  mask;
+	struct vhost_dev	 *dev;
+	int			  queue_seq;
+	int			  done_seq;
 };
 
-void vhost_poll_init(struct vhost_poll *poll, work_func_t func,
-		     unsigned long mask);
+void vhost_poll_init(struct vhost_poll *poll, vhost_poll_fn_t fn,
+		     unsigned long mask, struct vhost_dev *dev);
 void vhost_poll_start(struct vhost_poll *poll, struct file *file);
 void vhost_poll_stop(struct vhost_poll *poll);
 void vhost_poll_flush(struct vhost_poll *poll);
@@ -63,7 +69,7 @@ struct vhost_virtqueue {
 	struct vhost_poll poll;
 
 	/* The routine to call when the Guest pings us, or timeout. */
-	work_func_t handle_kick;
+	vhost_poll_fn_t handle_kick;
 
 	/* Last available index we saw. */
 	u16 last_avail_idx;
@@ -86,11 +92,11 @@ struct vhost_virtqueue {
 	struct iovec hdr[VHOST_NET_MAX_SG];
 	size_t hdr_size;
 	/* We use a kind of RCU to access private pointer.
-	 * All readers access it from workqueue, which makes it possible to
-	 * flush the workqueue instead of synchronize_rcu. Therefore readers do
+	 * All readers access it from poller, which makes it possible to
+	 * flush the vhost_poll instead of synchronize_rcu. Therefore readers do
 	 * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
-	 * work item execution acts instead of rcu_read_lock() and the end of
-	 * work item execution acts instead of rcu_read_lock().
+	 * vhost_poll execution acts instead of rcu_read_lock() and the end of
+	 * vhost_poll execution acts instead of rcu_read_lock().
 	 * Writers use virtqueue mutex. */
 	void *private_data;
 	/* Log write descriptors */
@@ -110,6 +116,9 @@ struct vhost_dev {
 	int nvqs;
 	struct file *log_file;
 	struct eventfd_ctx *log_ctx;
+	spinlock_t poller_lock;
+	struct list_head poll_list;
+	struct task_struct *poller;
 };
 
 long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs);

drivers/vhost/vhost.h bool vhost_enable_notify(struct vhost_vi · line 150

Quoted 9 lines
drivers/vhost/vhost.h +0 -3
@@ -136,9 +145,6 @@ bool vhost_enable_notify(struct vhost_vi
 int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
 		    unsigned int log_num, u64 len);
 
-int vhost_init(void);
-void vhost_cleanup(void);
-
 #define vq_err(vq, fmt, ...) do {                                  \
 		pr_debug(pr_fmt(fmt), ##__VA_ARGS__);       \
 		if ((vq)->error_ctx)                               \

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

maintainer
#60 lore raw

PATCH 2/3 cgroups: Add an API to attach a task to current task's cgroup

In reply to Michael S. Tsirkin

From: Sridhar Samudrala <[email protected]>

Add a new kernel API to attach a task to current task's cgroup in all the active hierarchies.

---
 include/linux/cgroup.h |    1 +
 kernel/cgroup.c        |   23 +++++++++++++++++++++++
 2 files changed, 24 insertions(+)
include/linux/cgroup.h +1 -0
Index: work/include/linux/cgroup.h
===================================================================
@@ -570,6 +570,7 @@ struct task_struct *cgroup_iter_next(str
 void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it);
 int cgroup_scan_tasks(struct cgroup_scanner *scan);
 int cgroup_attach_task(struct cgroup *, struct task_struct *);
+int cgroup_attach_task_current_cg(struct task_struct *);
 
 /*
  * CSS ID is ID for cgroup_subsys_state structs under subsys. This only works
kernel/cgroup.c +23 -0
Index: work/kernel/cgroup.c
===================================================================
@@ -1788,6 +1788,29 @@ out:
 	return retval;
 }
 
+/**
+ * cgroup_attach_task_current_cg - attach task 'tsk' to current task's cgroup
+ * @tsk: the task to be attached
+ */
+int cgroup_attach_task_current_cg(struct task_struct *tsk)
+{
+	struct cgroupfs_root *root;
+	struct cgroup *cur_cg;
+	int retval = 0;
+
+	cgroup_lock();
+	for_each_active_root(root) {
+		cur_cg = task_cgroup_from_root(current, root);
+		retval = cgroup_attach_task(cur_cg, tsk);
+		if (retval)
+			break;
+	}
+	cgroup_unlock();
+
+	return retval;
+}
+EXPORT_SYMBOL_GPL(cgroup_attach_task_current_cg);
+
 /*
  * Attach task with pid 'pid' to cgroup 'cgrp'. Call with cgroup_mutex
  * held. May take task_lock of task
maintainer
#62 lore raw

Re: [PATCH 2/3] cgroups: Add an API to attach a task to current task's cgroup

Quoted 11 lines · Li Zefan
Quoted 6 lines · Tejun Heo

From: Sridhar Samudrala <[email protected]>

Add a new kernel API to attach a task to current task's cgroup in all the active hierarchies.

btw: you lost the reviewed-by tag given by Paul Menage.

I only got bounced the original posting. Michael, can you please add it if/when you commit these?

Thank you.

Signature · 1 line
tejun
maintainer
#63 lore raw

PATCH 3/3 vhost: apply cpumask and cgroup to vhost pollers

In reply to Michael S. Tsirkin

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
---
 drivers/vhost/vhost.c |   36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)
drivers/vhost/vhost.c +31 -5
Index: work/drivers/vhost/vhost.c
===================================================================
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -176,12 +177,30 @@ repeat:
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
-	struct task_struct *poller;
-	int i;
+	struct task_struct *poller = NULL;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out;
 
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret)
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
#64 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost pollers

Quoted 26 lines · Tejun Heo

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
---
 drivers/vhost/vhost.c |   36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +1 -0
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -176,12 +177,30 @@ repeat:
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)

drivers/vhost/vhost.c repeat: · line 186

Quoted 10 lines
drivers/vhost/vhost.c +6 -2
 {
-	struct task_struct *poller;
-	int i;
+	struct task_struct *poller = NULL;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out;
 

If we "goto out", we will end up calling kthread_stop(poller), but seems kthread_stop() requires the task_struct pointer != NULL.

Quoted 38 lines
drivers/vhost/vhost.c +24 -3
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret)
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
maintainer
#65 lore raw

PATCH 3/3 vhost: apply cpumask and cgroup to vhost pollers

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch. Li Zefan spotted a bug in error path, fixed.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Cc Li Zefan <[email protected]>

---
Updated accordingly. Thanks.

 drivers/vhost/vhost.c |   36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)
drivers/vhost/vhost.c +31 -5
Index: work/drivers/vhost/vhost.c
===================================================================
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -176,12 +177,30 @@ repeat:
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
-	struct task_struct *poller;
-	int i;
+	struct task_struct *poller = NULL;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out;
 
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret && poller)
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
#66 lore raw

Re: [PATCH UPDATED 3/3] vhost: apply cpumask and cgroup to vhost pollers

drivers/vhost/vhost.c repeat: · line 190

Quoted 6 lines
drivers/vhost/vhost.c +3 -2
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;

here...

Quoted 17 lines
drivers/vhost/vhost.c +13 -0
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;

drivers/vhost/vhost.c long vhost_dev_init(struct vhost_dev *de · line 228

Quoted 9 lines
drivers/vhost/vhost.c +5 -1
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret && poller)

It's still buggy..

Quoted 8 lines
drivers/vhost/vhost.c +3 -0
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
maintainer
#67 lore raw

Re: [PATCH UPDATED2 3/3] vhost: apply cpumask and cgroup to vhost pollers

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch. Li Zefan spotted a bug in error path (twice), fixed (twice).

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Cc Li Zefan <[email protected]>

---
Heh... that's embarrassing. Let's see if I can get it right the third time.

Thank you.

 drivers/vhost/vhost.c |   36 ++++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)
drivers/vhost/vhost.c +32 -4
Index: work/drivers/vhost/vhost.c
===================================================================
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -177,11 +178,29 @@ long vhost_dev_init(struct vhost_dev *de
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
 	struct task_struct *poller;
-	int i;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out_free_mask;
 
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out_free_mask;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out_stop_poller;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out_stop_poller;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out_stop_poller;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,16 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+	goto out_free_mask;
+
+out_stop_poller:
+	kthread_stop(poller);
+out_free_mask:
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
#68 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost pollers

In reply to Tejun Heo
Quoted 7 lines · Tejun Heo

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>

I wanted to apply this, but modpost fails:
ERROR: "sched_setaffinity" [drivers/vhost/vhost_net.ko] undefined!
ERROR: "sched_getaffinity" [drivers/vhost/vhost_net.ko] undefined!

Did you try building as a module?

Quoted 67 lines
---
 drivers/vhost/vhost.c |   36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +31 -5
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -176,12 +177,30 @@ repeat:
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
-	struct task_struct *poller;
-	int i;
+	struct task_struct *poller = NULL;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out;
 
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret)
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */
#69 lore raw

Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost pollers

Quoted 10 lines · Michael S. Tsirkin
Quoted 7 lines · Tejun Heo

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>
Quoted 5 lines

I wanted to apply this, but modpost fails:
ERROR: "sched_setaffinity" [drivers/vhost/vhost_net.ko] undefined!
ERROR: "sched_getaffinity" [drivers/vhost/vhost_net.ko] undefined!

Did you try building as a module?

In my original implementation, i had these calls in workqueue.c. Now that these are moved to vhost.c which can be built as a module, these symbols need to be exported.
The following patch fixes the build issue with vhost as a module.

kernel/sched.c +1 -0
index 3c2a54f..15a0c6f 100644
@@ -4837,6 +4837,7 @@ out_put_task:
 	put_online_cpus();
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_setaffinity);
 
 static int get_user_cpu_mask(unsigned long __user *user_mask_ptr,

unsigned len,
struct cpumask *new_mask)
@@ -4900,6 +4901,7 @@ out_unlock:

return retval;
}
+EXPORT_SYMBOL_GPL(sched_getaffinity);

/**
* sys_sched_getaffinity - get the cpu affinity of a process

Quoted 71 lines
Quoted 57 lines
---
 drivers/vhost/vhost.c |   36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +24 -5
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -176,12 +177,30 @@ repeat:
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
-	struct task_struct *poller;
-	int i;
+	struct task_struct *poller = NULL;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out;
 
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+

drivers/vhost/vhost.c long vhost_dev_init(struct vhost_dev *de · line 234

Quoted 10 lines
drivers/vhost/vhost.c +7 -0
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret)
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */

--
To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#70 lore raw

PATCH 1/1 sched: export sched_set/getaffinity (was Re: [PATCH 3/3] vhost: apply cpumask and cgroup to vhost pollers)

Quoted 15 lines · Sridhar Samudrala
Quoted 14 lines · Michael S. Tsirkin
Quoted 7 lines · Tejun Heo

Apply the cpumask and cgroup of the initializing task to the created vhost poller.

Based on Sridhar Samudrala's patch.

Cc Michael S. Tsirkin <[email protected]>
Cc Sridhar Samudrala <[email protected]>

I wanted to apply this, but modpost fails:
ERROR: "sched_setaffinity" [drivers/vhost/vhost_net.ko] undefined!
ERROR: "sched_getaffinity" [drivers/vhost/vhost_net.ko] undefined!

Quoted 8 lines
Quoted 1 line

Did you try building as a module?

In my original implementation, i had these calls in workqueue.c. Now that these are moved to vhost.c which can be built as a module, these symbols need to be exported.
The following patch fixes the build issue with vhost as a module.

Works for me. To simplify dependencies, I'd like to queue this together with the chost patches through net-next. Ack to this?

Quoted 94 lines
kernel/sched.c +1 -0
index 3c2a54f..15a0c6f 100644
@@ -4837,6 +4837,7 @@ out_put_task:
 	put_online_cpus();
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_setaffinity);
 
 static int get_user_cpu_mask(unsigned long __user *user_mask_ptr,
 unsigned len,
kernel/sched.c +1 -0
 			     struct cpumask *new_mask)
@@ -4900,6 +4901,7 @@ out_unlock:
 
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_getaffinity);
 
 /**
  * sys_sched_getaffinity - get the cpu affinity of a process
Quoted 73 lines
Quoted 59 lines

---
 drivers/vhost/vhost.c |   36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

Index: work/drivers/vhost/vhost.c
===================================================================

drivers/vhost/vhost.c +24 -5
@@ -23,6 +23,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/kthread.h>
+#include <linux/cgroup.h>
 
 #include <linux/net.h>
 #include <linux/if_packet.h>
@@ -176,12 +177,30 @@ repeat:
 long vhost_dev_init(struct vhost_dev *dev,
 		    struct vhost_virtqueue *vqs, int nvqs)
 {
-	struct task_struct *poller;
-	int i;
+	struct task_struct *poller = NULL;
+	cpumask_var_t mask;
+	int i, ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out;
 
 	poller = kthread_create(vhost_poller, dev, "vhost-%d", current->pid);
-	if (IS_ERR(poller))
-		return PTR_ERR(poller);
+	if (IS_ERR(poller)) {
+		ret = PTR_ERR(poller);
+		goto out;
+	}
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = sched_setaffinity(poller->pid, mask);
+	if (ret)
+		goto out;
+
+	ret = cgroup_attach_task_current_cg(poller);
+	if (ret)
+		goto out;
 
 	dev->vqs = vqs;
 	dev->nvqs = nvqs;
@@ -202,7 +221,14 @@ long vhost_dev_init(struct vhost_dev *de
 			vhost_poll_init(&dev->vqs[i].poll,
 					dev->vqs[i].handle_kick, POLLIN, dev);
 	}
-	return 0;
+

drivers/vhost/vhost.c long vhost_dev_init(struct vhost_dev *de · line 234

Quoted 10 lines
drivers/vhost/vhost.c +7 -0
+	wake_up_process(poller);	/* avoid contributing to loadavg */
+	ret = 0;
+out:
+	if (ret)
+		kthread_stop(poller);
+	free_cpumask_var(mask);
+	return ret;
 }
 
 /* Caller should have device mutex */

--
To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#71 lore raw

PATCH 1/1 sched: export sched_set/getaffinity to modules

Author: Sridhar Samudrala <[email protected]>

sched: export sched_set/getaffinity to modules

vhost-net driver wants to copy the affinity from the owner thread to thread it creates. Export sched_set/get affinity to modules to make this possible when vhost is built as a module.

---

I'm not sure the previous time made it clear what exactly is the proposed change, so reposting. Info, Peter, could you ack merging the following through the net-next tree please?

kernel/sched.c +2 -0
index d484081..3759391 100644
@@ -4744,6 +4744,7 @@ out_put_task:
 	put_online_cpus();
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_setaffinity);
 
 static int get_user_cpu_mask(unsigned long __user *user_mask_ptr, unsigned len,
 			     struct cpumask *new_mask)
@@ -4807,6 +4808,7 @@ out_unlock:
 
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_getaffinity);
 
 /**
  * sys_sched_getaffinity - get the cpu affinity of a process
#72 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 31 lines · Michael S. Tsirkin

Author: Sridhar Samudrala <[email protected]>

sched: export sched_set/getaffinity to modules

vhost-net driver wants to copy the affinity from the owner thread to thread it creates. Export sched_set/get affinity to modules to make this possible when vhost is built as a module.

---

I'm not sure the previous time made it clear what exactly is the proposed change, so reposting. Info, Peter, could you ack merging the following through the net-next tree please?

kernel/sched.c +1 -0
index d484081..3759391 100644
@@ -4744,6 +4744,7 @@ out_put_task:
 	put_online_cpus();
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_setaffinity);
 
 static int get_user_cpu_mask(unsigned long __user *user_mask_ptr, unsigned len,
 			     struct cpumask *new_mask)

kernel/sched.c out_unlock: · line 4814

Quoted 7 lines
kernel/sched.c +1 -0
@@ -4807,6 +4808,7 @@ out_unlock:
 
 	return retval;
 }
+EXPORT_SYMBOL_GPL(sched_getaffinity);
 
 /**
  * sys_sched_getaffinity - get the cpu affinity of a process

Urgh,.. so why again is that a good idea?

#73 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 11 lines · Peter Zijlstra
Quoted 8 lines · Michael S. Tsirkin

Author: Sridhar Samudrala <[email protected]>

sched: export sched_set/getaffinity to modules

vhost-net driver wants to copy the affinity from the owner thread to thread it creates. Export sched_set/get affinity to modules to make this possible when vhost is built as a module.

Urgh,.. so why again is that a good idea?

In particular:
- who sets the affinity of the task?
- why can't it set the kernel thread's affinity too?
- what happens if someone changes the tasks' affinity?

So no, I don't think this is a sensible thing to do at all.

#74 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 11 lines · Peter Zijlstra
Quoted 9 lines · Peter Zijlstra
Quoted 8 lines · Michael S. Tsirkin

Author: Sridhar Samudrala <[email protected]>

sched: export sched_set/getaffinity to modules

vhost-net driver wants to copy the affinity from the owner thread to thread it creates. Export sched_set/get affinity to modules to make this possible when vhost is built as a module.

Quoted 4 lines
Quoted 1 line

Urgh,.. so why again is that a good idea?

In particular:
- who sets the affinity of the task?

management tools do this when they start qemu.

Quoted 1 line

- why can't it set the kernel thread's affinity too?

It can. However: the threads are started internally by the driver when qemu does an ioctl. What we want to do is give it a sensible default affinity. management tool can later tweak it if it wants to.

Quoted 1 line

- what happens if someone changes the tasks' affinity?

We would normally create a cgroup including all internal tasks, making it easy to find and change affinity for them all if necessary.

Quoted 1 line

So no, I don't think this is a sensible thing to do at all.

#75 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 25 lines · Michael S. Tsirkin
Quoted 11 lines · Peter Zijlstra
Quoted 9 lines · Peter Zijlstra
Quoted 8 lines · Michael S. Tsirkin

Author: Sridhar Samudrala <[email protected]>

sched: export sched_set/getaffinity to modules

vhost-net driver wants to copy the affinity from the owner thread to thread it creates. Export sched_set/get affinity to modules to make this possible when vhost is built as a module.

Quoted 4 lines
Quoted 1 line

Urgh,.. so why again is that a good idea?

In particular:
- who sets the affinity of the task?

management tools do this when they start qemu.

Quoted 1 line

- why can't it set the kernel thread's affinity too?

It can. However: the threads are started internally by the driver when qemu does an ioctl. What we want to do is give it a sensible default affinity. management tool can later tweak it if it wants to.

Quoted 7 lines
Quoted 1 line

- what happens if someone changes the tasks' affinity?

We would normally create a cgroup including all internal tasks, making it easy to find and change affinity for them all if necessary.

Quoted 1 line

So no, I don't think this is a sensible thing to do at all.

The patch using this is here:
http://www.mail-archive.com/[email protected]/msg35411.html

It simply copies the affinity from the parent when thread is created.

Signature · 1 line
MST
#77 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 8 lines · Peter Zijlstra
Quoted 5 lines · Michael S. Tsirkin

The patch using this is here:
http://www.mail-archive.com/[email protected]/msg35411.html

It simply copies the affinity from the parent when thread is created.

Sounds like policy, not something the kernel should do..

The alternative would be using clone() instead of thread_create() and inherit everything from the creating task. Inheriting from kthreadd and then undoing some aspects just sounds like daft policy that really ought to be in userspace.

#78 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 6 lines · Peter Zijlstra
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Michael S. Tsirkin

The patch using this is here:
http://www.mail-archive.com/[email protected]/msg35411.html

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

It simply copies the affinity from the parent when thread is created.

Sounds like policy, not something the kernel should do..

The alternative would be using clone() instead of thread_create() and inherit everything from the creating task.
Inheriting from kthreadd and then undoing some aspects just sounds like daft policy that really ought to be in userspace.

Yes, that's basically what this patchset is trying to do:
create a workqueue inheriting everything from the creating task. Sridhar started with an API to do exactly this:
http://linux.derkeiler.com/Mailing-Lists/Kernel/2010-05/msg07478.html

Then we switched to raw kthread to avoid stepping on cwq toes. Maybe it makes sense to add kthread_clone (in addition to kthread_create) that would do what you suggest?
If yes, any hints on an implementation?

Signature · 1 line
MST
#79 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 21 lines · Michael S. Tsirkin
Quoted 6 lines · Peter Zijlstra
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Michael S. Tsirkin

The patch using this is here:
http://www.mail-archive.com/[email protected]/msg35411.html

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

It simply copies the affinity from the parent when thread is created.

Sounds like policy, not something the kernel should do..

The alternative would be using clone() instead of thread_create() and inherit everything from the creating task.
Inheriting from kthreadd and then undoing some aspects just sounds like daft policy that really ought to be in userspace.

Yes, that's basically what this patchset is trying to do:
create a workqueue inheriting everything from the creating task. Sridhar started with an API to do exactly this:
http://linux.derkeiler.com/Mailing-Lists/Kernel/2010-05/msg07478.html

Quoted 4 lines

Then we switched to raw kthread to avoid stepping on cwq toes. Maybe it makes sense to add kthread_clone (in addition to kthread_create) that would do what you suggest?
If yes, any hints on an implementation?

I think that's called kernel_thread() see
kernel/kthread.c:create_kthread().

Doing the whole kthreadd dance and then copying bits and pieces back sounds very fragile, so yeah, something like that should work.

The other issue to consider is the thread group status of these things, I think it would be best if these threads were still considered part of the process that spawned them so that they would die nicely when the process gets whacked.

At which point one could wonder if the kthread interface makes any sense, why not let userspace fork tasks and let them call into the kernel to perform work...

#80 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 27 lines · Peter Zijlstra
Quoted 21 lines · Michael S. Tsirkin
Quoted 6 lines · Peter Zijlstra
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Michael S. Tsirkin

The patch using this is here:
http://www.mail-archive.com/[email protected]/msg35411.html

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

It simply copies the affinity from the parent when thread is created.

Sounds like policy, not something the kernel should do..

The alternative would be using clone() instead of thread_create() and inherit everything from the creating task.
Inheriting from kthreadd and then undoing some aspects just sounds like daft policy that really ought to be in userspace.

Yes, that's basically what this patchset is trying to do:
create a workqueue inheriting everything from the creating task. Sridhar started with an API to do exactly this:
http://linux.derkeiler.com/Mailing-Lists/Kernel/2010-05/msg07478.html

Quoted 4 lines

Then we switched to raw kthread to avoid stepping on cwq toes. Maybe it makes sense to add kthread_clone (in addition to kthread_create) that would do what you suggest?
If yes, any hints on an implementation?

Quoted 5 lines

I think that's called kernel_thread() see
kernel/kthread.c:create_kthread().

Doing the whole kthreadd dance and then copying bits and pieces back sounds very fragile, so yeah, something like that should work.

Thanks!
Sridhar, Tejun, have the time to look into this approach?

Quoted 4 lines

The other issue to consider is the thread group status of these things, I think it would be best if these threads were still considered part of the process that spawned them so that they would die nicely when the process gets whacked.

The proposed patch kills the thread when the fd is closed, so I think this already works without making it part of the process.

Quoted 3 lines

At which point one could wonder if the kthread interface makes any sense, why not let userspace fork tasks and let them call into the kernel to perform work...

One thing I wanted to avoid is letting userspace know just how many threads are there. We are using a single one now, but we used to have threads per-cpu, and we might switch to a thread per virtqueue in the future. IMO all this should ideally be transparent to userspace.

Signature · 1 line
MST
#81 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 4 lines · Michael S. Tsirkin

The proposed patch kills the thread when the fd is closed, so I think this already works without making it part of the process.

OK, fd bound resources are fine.

maintainer
#82 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Michael S. Tsirkin

Hello,

Quoted 8 lines · Michael S. Tsirkin
Quoted 5 lines

I think that's called kernel_thread() see
kernel/kthread.c:create_kthread().

Doing the whole kthreadd dance and then copying bits and pieces back sounds very fragile, so yeah, something like that should work.

Thanks!
Sridhar, Tejun, have the time to look into this approach?

All that's necessary is shortcutting indirection through kthreadd. ie. An exported function which looks like the following,

struct kthread_clone_or_whatever(int (*threadfn).....)

 {
	struct kthread_create_info create;
	int pid;

	INIT create;

	pid = kernel_thread(kthread, &create, CLONE_FS...);
	if (pid < 0)
		return ERROR;
	wait_for_completion(&create.done);

	if (!IS_ERR(create.result))
		SET NAME;
	return create.result;

}

It might be a good idea to make the function take extra clone flags but anyways once created cloned task can be treated the same way as other kthreads, so nothing else needs to be changed.

Thanks.

Signature · 1 line
tejun
#83 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 19 lines · Tejun Heo

All that's necessary is shortcutting indirection through kthreadd. ie. An exported function which looks like the following,

struct kthread_clone_or_whatever(int (*threadfn).....)

 {
	struct kthread_create_info create;
	int pid;

	INIT create;

	pid = kernel_thread(kthread, &create, CLONE_FS...);
	if (pid < 0)
		return ERROR;
	wait_for_completion(&create.done);

	if (!IS_ERR(create.result))
		SET NAME;
	return create.result;
Quoted 5 lines

}

It might be a good idea to make the function take extra clone flags but anyways once created cloned task can be treated the same way as other kthreads, so nothing else needs to be changed.

This makes kthread_stop() work. Otherwise the new thread is just the CLONE_VM child of the caller, and the caller is the user-mode task doing ioctl() ?

Oleg.

maintainer
#84 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Hello,

Quoted 7 lines · Oleg Nesterov
Quoted 3 lines

It might be a good idea to make the function take extra clone flags but anyways once created cloned task can be treated the same way as other kthreads, so nothing else needs to be changed.

This makes kthread_stop() work. Otherwise the new thread is just the CLONE_VM child of the caller, and the caller is the user-mode task doing ioctl() ?

Hmmm, indeed. It makes the attribute inheritance work but circumvents the whole reason there is kthreadd.

Thanks.

Signature · 1 line
tejun
#85 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 7 lines · Tejun Heo

Hello,

Quoted 4 lines · Oleg Nesterov
Quoted 3 lines

It might be a good idea to make the function take extra clone flags but anyways once created cloned task can be treated the same way as other kthreads, so nothing else needs to be changed.

Quoted 6 lines
Quoted 3 lines

This makes kthread_stop() work. Otherwise the new thread is just the CLONE_VM child of the caller, and the caller is the user-mode task doing ioctl() ?

Hmmm, indeed. It makes the attribute inheritance work but circumvents the whole reason there is kthreadd.

I thought the whole reason there was threadd was to avoid the inheritance? So avoiding the avoiding of inheritance seems like the goal here, no?

#86 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 14 lines · Peter Zijlstra
Quoted 12 lines · Tejun Heo

Hello,

Quoted 8 lines · Oleg Nesterov
Quoted 4 lines

It might be a good idea to make the function take extra clone flags but anyways once created cloned task can be treated the same way as other kthreads, so nothing else needs to be changed.

This makes kthread_stop() work. Otherwise the new thread is just the CLONE_VM child of the caller, and the caller is the user-mode task doing ioctl() ?

Quoted 7 lines
Quoted 3 lines

Hmmm, indeed. It makes the attribute inheritance work but circumvents the whole reason there is kthreadd.

I thought the whole reason there was threadd was to avoid the inheritance? So avoiding the avoiding of inheritance seems like the goal here, no?

I think so. Does it (Tejun's kthread_clone() patch) also inherit the cgroup of the caller? or do we still need the explicit call to attach the thread to the current task's cgroup?

I am on vacation next week and cannot look into this until Jul 12. Hope this will be resoved by then. If not, i will look into after i am back.

Thanks
Sridhar

#87 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 3 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

#88 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Oleg.

#89 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 21 lines · Oleg Nesterov
Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Quoted 4 lines

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Oleg.

Right. Doing this might break things like flush. The signal and exit behaviour needs to be examined carefully. I am also unsure whether using such threads might be more expensive than inheriting kthreadd.

Signature · 1 line
MST
#90 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 26 lines · Michael S. Tsirkin
Quoted 24 lines · Oleg Nesterov
Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Quoted 8 lines
Quoted 2 lines

Oleg.

Right. Doing this might break things like flush. The signal and exit behaviour needs to be examined carefully. I am also unsure whether using such threads might be more expensive than inheriting kthreadd.

Should we just leave it to the userspace to set the cgroup/cpumask after qemu starts the guest and the vhost threads?

Thanks
Sridhar

#91 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 28 lines · Sridhar Samudrala
Quoted 18 lines · Michael S. Tsirkin
Quoted 17 lines · Oleg Nesterov
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Quoted 9 lines
Quoted 4 lines

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Oleg.

Right. Doing this might break things like flush. The signal and exit behaviour needs to be examined carefully. I am also unsure whether using such threads might be more expensive than inheriting kthreadd.

Quoted 6 lines

Should we just leave it to the userspace to set the cgroup/cpumask after qemu starts the guest and
the vhost threads?

Thanks
Sridhar

Yes but we can't trust userspace to do this. It's important to do it on thread creation: if we don't, malicious userspace can create large amount of work exceeding the cgroup limits.

And the same applies so the affinity: if the qemu process is limited to a set of CPUs, it's important to make the kernel thread that does work our behalf limited to the same set of CPUs.

This is not unique to vhost, it's just that virt scenarious are affected by this more: people seem to run untrusted applications and expect the damage to be contained.

Signature · 1 line
MST
#92 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 40 lines · Michael S. Tsirkin
Quoted 28 lines · Sridhar Samudrala
Quoted 18 lines · Michael S. Tsirkin
Quoted 17 lines · Oleg Nesterov
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Quoted 9 lines
Quoted 4 lines

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Oleg.

Right. Doing this might break things like flush. The signal and exit behaviour needs to be examined carefully. I am also unsure whether using such threads might be more expensive than inheriting kthreadd.

Quoted 6 lines

Should we just leave it to the userspace to set the cgroup/cpumask after qemu starts the guest and
the vhost threads?

Thanks
Sridhar

Yes but we can't trust userspace to do this. It's important to do it on thread creation: if we don't, malicious userspace can create large amount of work exceeding the cgroup limits.

Quoted 8 lines

And the same applies so the affinity: if the qemu process is limited to a set of CPUs, it's important to make the kernel thread that does work our behalf limited to the same set of CPUs.

This is not unique to vhost, it's just that virt scenarious are affected by this more: people seem to run untrusted applications and expect the damage to be contained.

OK. So we want to create a thread that is a child of kthreadd, but inherits the cgroup/cpumask from the caller. How about an exported kthread function kthread_create_in_current_cg() that does this?

include/linux/kthread.h +3 -0
index aabc8a1..e0616f0 100644
@@ -9,6 +9,9 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 				   const char namefmt[], ...)
 	__attribute__((format(printf, 3, 4)));
 
+struct task_struct *kthread_create_in_current_cg(int (*threadfn)(void *data),
+						 void *data, char *name);
+
 /**
  * kthread_run - create and wake a thread.
  * @threadfn: the function to run until signal_pending(current).
kernel/kthread.c +37 -0
index 83911c7..ea4e737 100644
@@ -15,6 +15,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <trace/events/sched.h>
+#include <linux/cgroup.h>
 
 static DEFINE_SPINLOCK(kthread_create_lock);
 static LIST_HEAD(kthread_create_list);
@@ -149,6 +150,42 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 }
 EXPORT_SYMBOL(kthread_create);
 
+struct task_struct *kthread_create_in_current_cg(int (*threadfn)(void *data),
+						 void *data, char *name)
+{
+	struct task_struct *worker;
+	cpumask_var_t mask;
+	int ret = -ENOMEM;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto out_free_mask;
+
+	worker = kthread_create(threadfn, data, "%s-%d", name, current->pid);
+	if (IS_ERR(worker))
+		goto out_free_mask;
+
+	ret = sched_getaffinity(current->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = sched_setaffinity(worker->pid, mask);
+	if (ret)
+		goto out_stop_worker;
+
+	ret = cgroup_attach_task_current_cg(worker);
+	if (ret)
+		goto out_stop_worker;
+
+	return worker;
+
+out_stop_worker:
+	kthread_stop(worker);
+out_free_mask:
+	free_cpumask_var(mask);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(kthread_create_in_current_cg);
+
 /**
  * kthread_bind - bind a just-created kthread to a cpu.
  * @p: thread created by kthread_create().

Thanks
Sridhar

#93 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 4 lines · Sridhar Samudrala

OK. So we want to create a thread that is a child of kthreadd, but inherits the cgroup/cpumask from the caller. How about an exported kthread function kthread_create_in_current_cg() that does this?

Well. I must admit, this looks a bit strange to me ;)

Instead of exporting sched_xxxaffinity() we export the new function which calls them. And I don't think this new helper is very useful in general. May be I am wrong...

Oleg.

#94 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 6 lines · Oleg Nesterov
Quoted 4 lines · Sridhar Samudrala

OK. So we want to create a thread that is a child of kthreadd, but inherits the cgroup/cpumask from the caller. How about an exported kthread function kthread_create_in_current_cg() that does this?

Quoted 6 lines

Well. I must admit, this looks a bit strange to me ;)

Instead of exporting sched_xxxaffinity() we export the new function which calls them. And I don't think this new helper is very useful in general. May be I am wrong...

If we agree on exporting sched_xxxaffinity() functions, we don't need this new kthread function and we can do the same in vhost as the original patch did.

Thanks
Sridhar

#95 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Oleg Nesterov
Quoted 21 lines · Oleg Nesterov
Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Quoted 4 lines

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Oleg.

With some machinery to stop it later, yes.
Oleg, how does the below look to you?

Here I explicitly drop the fds so we don't share them. CLONE_VM takes care of sharing the mm I think.
About signals - for the vhost-net use this is OK as we use uninterruptible sleep anyway (like the new kthread_worker does).

This code seems to work fine for me so far - any comments?

---

include/linux/kthread.h +5 -0
index aabc8a1..72c7b17 100644
@@ -9,6 +9,11 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 				   const char namefmt[], ...)
 	__attribute__((format(printf, 3, 4)));
 
+struct task_struct *kthread_create_inherit(int (*threadfn)(void *data),
+					   void *data,
+					   const char namefmt[], ...)
+	__attribute__((format(printf, 3, 4)));
+
 /**
  * kthread_run - create and wake a thread.
  * @threadfn: the function to run until signal_pending(current).
kernel/kthread.c +32 -0
index 83911c7..b81588c 100644
@@ -149,6 +149,38 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 }
 EXPORT_SYMBOL(kthread_create);
 
+/* Same as kthread_create, but inherit attributes (cgroups, priority, CPU mask)
+ * from current. */
+struct task_struct *kthread_create_inherit(int (*threadfn)(void *data),
+					   void *data,
+					   const char namefmt[],
+					   ...)
+{
+	struct kthread_create_info create;
+
+	create.threadfn = threadfn;
+	create.data = data;
+	init_completion(&create.done);
+
+	create_kthread(&create);
+	wait_for_completion(&create.done);
+
+	if (!IS_ERR(create.result)) {
+		va_list args;
+
+		/* Don't share files with parent as drivers use release for
+		 * close on exit, etc. */
+		exit_files(create.result);
+
+		va_start(args, namefmt);
+		vsnprintf(create.result->comm, sizeof(create.result->comm),
+			  namefmt, args);
+		va_end(args);
+	}
+	return create.result;
+}
+EXPORT_SYMBOL(kthread_create_inherit);
+
 /**
  * kthread_bind - bind a just-created kthread to a cpu.
  * @p: thread created by kthread_create().
#96 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 88 lines · Michael S. Tsirkin
Quoted 21 lines · Oleg Nesterov
Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Sridhar Samudrala

Does it (Tejun's kthread_clone() patch) also inherit the
cgroup of the caller?

Of course, its a simple do_fork() which inherits everything just as you would expect from a similar sys_clone()/sys_fork() call.

Yes. And I'm afraid it can inherit more than we want. IIUC, this is called from ioctl(), right?

Then the new thread becomes the natural child of the caller, and it shares ->mm with the parent. And files, dup_fd() without CLONE_FS.

Signals. Say, if you send SIGKILL to this new thread, it can't sleep in TASK_INTERRUPTIBLE or KILLABLE after that. And this SIGKILL can be sent just because the parent gets SIGQUIT or abother coredumpable signal. Or the new thread can recieve SIGSTOP via ^Z.

Quoted 4 lines

Perhaps this is OK, I do not know. Just to remind that kernel_thread() is merely clone(CLONE_VM).

Oleg.

With some machinery to stop it later, yes.
Oleg, how does the below look to you?

Here I explicitly drop the fds so we don't share them. CLONE_VM takes care of sharing the mm I think.
About signals - for the vhost-net use this is OK as we use uninterruptible sleep anyway (like the new kthread_worker does).

This code seems to work fine for me so far - any comments?

---

include/linux/kthread.h +5 -0
index aabc8a1..72c7b17 100644
@@ -9,6 +9,11 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 				   const char namefmt[], ...)
 	__attribute__((format(printf, 3, 4)));
 
+struct task_struct *kthread_create_inherit(int (*threadfn)(void *data),
+					   void *data,
+					   const char namefmt[], ...)
+	__attribute__((format(printf, 3, 4)));
+
 /**
  * kthread_run - create and wake a thread.
  * @threadfn: the function to run until signal_pending(current).
kernel/kthread.c +25 -0
index 83911c7..b81588c 100644
@@ -149,6 +149,38 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 }
 EXPORT_SYMBOL(kthread_create);
 
+/* Same as kthread_create, but inherit attributes (cgroups, priority, CPU mask)
+ * from current. */
+struct task_struct *kthread_create_inherit(int (*threadfn)(void *data),
+					   void *data,
+					   const char namefmt[],
+					   ...)
+{
+	struct kthread_create_info create;
+
+	create.threadfn = threadfn;
+	create.data = data;
+	init_completion(&create.done);
+
+	create_kthread(&create);
+	wait_for_completion(&create.done);
+
+	if (!IS_ERR(create.result)) {
+		va_list args;
+
+		/* Don't share files with parent as drivers use release for
+		 * close on exit, etc. */
+		exit_files(create.result);
+
+		va_start(args, namefmt);
+		vsnprintf(create.result->comm, sizeof(create.result->comm),

kernel/kthread.c struct task_struct *kthread_create(int (*threadfn)(void *data), · line 186

Quoted 10 lines
kernel/kthread.c +7 -0
+			  namefmt, args);
+		va_end(args);
+	}
+	return create.result;
+}
+EXPORT_SYMBOL(kthread_create_inherit);
+
 /**
  * kthread_bind - bind a just-created kthread to a cpu.
  * @p: thread created by kthread_create().

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.
Either version should be fine.

include/linux/kthread.h +5 -0
index aabc8a1..634eaf7 100644
@@ -9,6 +9,11 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 				   const char namefmt[], ...)
 	__attribute__((format(printf, 3, 4)));
 
+struct task_struct *kthread_clone(int (*threadfn)(void *data),
+				  void *data,
+				  const char namefmt[], ...)
+	__attribute__((format(printf, 3, 4)));
+
 /**
  * kthread_run - create and wake a thread.
  * @threadfn: the function to run until signal_pending(current).
kernel/kthread.c +32 -0
index 83911c7..806dae5 100644
@@ -149,6 +149,38 @@ struct task_struct *kthread_create(int (*threadfn)(void *data),
 }
 EXPORT_SYMBOL(kthread_create);
 
+struct task_struct *kthread_clone(int (*threadfn)(void *data),
+				  void *data,
+				  const char namefmt[],
+				  ...)
+{
+	struct kthread_create_info create;
+	int pid;
+
+	create.threadfn = threadfn;
+	create.data = data;
+	init_completion(&create.done);
+	INIT_LIST_HEAD(&create.list);
+
+	pid = kernel_thread(kthread, &create, CLONE_FS);
+	if (pid < 0) {
+		create.result = ERR_PTR(pid);
+		complete(&create.done);
+	}
+	wait_for_completion(&create.done);
+
+	if (!IS_ERR(create.result)) {
+		va_list args;
+		va_start(args, namefmt);
+		vsnprintf(create.result->comm, sizeof(create.result->comm),
+			  namefmt, args);
+		va_end(args);
+	}
+
+	return create.result;
+}
+EXPORT_SYMBOL(kthread_clone);
+
 /**
  * kthread_bind - bind a just-created kthread to a cpu.
  * @p: thread created by kthread_create().
#97 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

exit_files() is not enough too. How about the signals, reparenting?

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

#98 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 5 lines · Oleg Nesterov
Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

Quoted 8 lines

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

exit_files() is not enough too. How about the signals,

As I said, signals are unimportant as we are using this thread to base a worker on - it sleeps uninterruptibly.

Quoted 1 line

reparenting?

That's actually a feature: it lets us find out which process owns the device using the thread by looking at the parent.

Quoted 6 lines

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

#99 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Oleg Nesterov
Quoted 12 lines · Oleg Nesterov
Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

Quoted 8 lines

exit_files() is not enough too. How about the signals, reparenting?

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

Almost forgot, we need the same for priority.

Signature · 1 line
MST
#100 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Oleg Nesterov
Quoted 12 lines · Oleg Nesterov
Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

Quoted 8 lines

exit_files() is not enough too. How about the signals, reparenting?

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

Peter, could you please indicate whether you think this is the way to go, too?

Signature · 1 line
MST
#101 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 2 lines · Michael S. Tsirkin

Peter, could you please indicate whether you think this is the way to go, too?

I really dislike it, as you indicated, you now want priority too..

It seems the problem is that we normally don't consider work done by kernel threads for user processes part of that process.

I'm not sure what work you're doing, but I'm pretty sure there's similar things already in the kernel -- think about the work done by encryption threads for encrypted sockets and stuff.

If you want proper containment of work caused by a process, I'd suggest you start by looking at curing the general problem, instead of special casing this one case.

#102 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Oleg Nesterov
Quoted 12 lines · Oleg Nesterov
Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

Quoted 8 lines

exit_files() is not enough too. How about the signals, reparenting?

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

Oleg, so can I attach your Ack to the patch in question, and merge it all through net-next?

Signature · 1 line
MST
#103 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Sorry for the delay, I can't be responsive these days...

Quoted 17 lines · Michael S. Tsirkin
Quoted 15 lines · Oleg Nesterov
Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

exit_files() is not enough too. How about the signals, reparenting?

Quoted 8 lines
Quoted 5 lines

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

Oleg, so can I attach your Ack to the patch in question, and merge it all through net-next?

Well, I do not think you need my ack ;)

But I must admit, I personally dislike this idea. A kernel thread which is the child of the user-space process, and in fact it is not the "real" kernel thread. I think this is against the common case. If you do not care the signals/reparenting, why can't you fork the user-space process which does all the work via ioctl's ? OK, I do not understand the problem domain, probably this can't work.

Anyway, the patch looks buggy to me. Starting from

	create_kthread(&create);
	wait_for_completion(&create.done);

At least you should check create_kthread() suceeds, otherwise wait_for_completion() will hang forever. OTOH, if it suceeds then wait_for_completion() is not needed. But this is minor.

create_kthread()->kernel_thread() uses CLONE_VM, this means that the child will share ->mm. And this means that if the parent recieves the coredumping signal it will hang forever in kernel space waiting until this child exits.

This is just the immediate surprise I can see with this approach, I am afraid there is something else.

And once again. We are doing this hacks only because we lack a couples of exports (iiuk). This is, well, a bit strange ;)

Oleg.

maintainer
#104 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Hello,

Quoted 6 lines · Oleg Nesterov

But I must admit, I personally dislike this idea. A kernel thread which is the child of the user-space process, and in fact it is not the "real" kernel thread. I think this is against the common case. If you do not care the signals/reparenting, why can't you fork the user-space process which does all the work via ioctl's ? OK, I do not understand the problem domain, probably this can't work.

Having kernel threads which are children of user process is plain scary considering the many things parent/children relationship implies and various bugs and security vulnerabilities in the area. I can't pinpoint any problem but I think we really shouldn't be adding something like this for this specific use case. If we can get away with exporting a few symbols, let's go that way.

Thanks.

Signature · 1 line
tejun
#105 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Oleg Nesterov
Quoted 53 lines · Oleg Nesterov

Sorry for the delay, I can't be responsive these days...

Quoted 17 lines · Michael S. Tsirkin
Quoted 15 lines · Oleg Nesterov
Quoted 3 lines · Sridhar Samudrala

I have been testing out a similar patch that uses kernel_thread() without CLONE_FILES flag rather than create_kthread() and then closing the files.

!CLONE_FILES can't help. copy_files() does dup_fd() in this case. The child still inherits the files.

Quoted 1 line

Either version should be fine.

I think neither version is fine ;)

exit_files() is not enough too. How about the signals, reparenting?

Quoted 8 lines
Quoted 5 lines

I already forgot all details, probably I missed somethig. But it seems to me that it is better to just export get/set affinity and forget about all complications.

Oleg.

Oleg, so can I attach your Ack to the patch in question, and merge it all through net-next?

Well, I do not think you need my ack ;)

But I must admit, I personally dislike this idea. A kernel thread which is the child of the user-space process, and in fact it is not the "real" kernel thread. I think this is against the common case. If you do not care the signals/reparenting, why can't you fork the user-space process which does all the work via ioctl's ? OK, I do not understand the problem domain, probably this can't work.

Anyway, the patch looks buggy to me. Starting from

	create_kthread(&create);
	wait_for_completion(&create.done);

At least you should check create_kthread() suceeds, otherwise wait_for_completion() will hang forever. OTOH, if it suceeds then wait_for_completion() is not needed. But this is minor.

create_kthread()->kernel_thread() uses CLONE_VM, this means that the child will share ->mm. And this means that if the parent recieves the coredumping signal it will hang forever in kernel space waiting until this child exits.

Quoted 7 lines

This is just the immediate surprise I can see with this approach, I am afraid there is something else.

And once again. We are doing this hacks only because we lack a couples of exports (iiuk). This is, well, a bit strange ;)

Oleg.

Oleg, I mean Ack the exporting of get/set affinity.

Thanks!

Signature · 1 line
MST
#106 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 2 lines · Michael S. Tsirkin

Oleg, I mean Ack the exporting of get/set affinity.

Ah, I misunderstood.

Yes, I believe the exporting is the lesser evil. Please feel free to add my ack.

Oleg.

#107 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Peter Zijlstra
Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Michael S. Tsirkin

Maybe it makes sense to add kthread_clone (in addition to kthread_create) that would do what you suggest? If yes, any hints on an implementation?

I think that's called kernel_thread() see
kernel/kthread.c:create_kthread().

Well, strictly speaking kernel_thread() doesn't create the kernel thread. Unless the caller is the kernel thread. And daemonize() is deprecated. kernel_thread() just forks the CLONE_VM + flags child.

Oleg.

#108 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

In reply to Michael S. Tsirkin
Quoted 5 lines · Michael S. Tsirkin
Quoted 1 line

- why can't it set the kernel thread's affinity too?

It can. However: the threads are started internally by the driver when qemu does an ioctl. What we want to do is give it a sensible default affinity. management tool can later tweak it if it wants to.

So have that ioctl return the tid of that new fancy thread and then set its affinity, stuff it in cgroup, whatever you fancy.

Quoted 5 lines
Quoted 1 line

- what happens if someone changes the tasks' affinity?

We would normally create a cgroup including all internal tasks, making it easy to find and change affinity for them all if necessary.

And to stuff them in a cgroup you also need the tid, at which point it might as well set the affinity from userspace, right?

#109 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Michael S. Tsirkin
Quoted 1 line

- why can't it set the kernel thread's affinity too?

It can. However: the threads are started internally by the driver when qemu does an ioctl. What we want to do is give it a sensible default affinity. management tool can later tweak it if it wants to.

So have that ioctl return the tid of that new fancy thread and then set its affinity, stuff it in cgroup, whatever you fancy.

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

- what happens if someone changes the tasks' affinity?

We would normally create a cgroup including all internal tasks, making it easy to find and change affinity for them all if necessary.

And to stuff them in a cgroup you also need the tid, at which point it might as well set the affinity from userspace, right?

We also put it in a cgroup transparently. I think that it's actually important to do it on thread creation: if we don't, malicious userspace can create large amount of work exceeding the cgroup limits.

And the same applies so the affinity, right? If the qemu process is limited to a set of CPUs, isn't it important to make the kernel thread that does work our behalf limited to the same set of CPUs?

Signature · 1 line
MST
#110 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 21 lines · Michael S. Tsirkin
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Michael S. Tsirkin
Quoted 1 line

- why can't it set the kernel thread's affinity too?

It can. However: the threads are started internally by the driver when qemu does an ioctl. What we want to do is give it a sensible default affinity. management tool can later tweak it if it wants to.

So have that ioctl return the tid of that new fancy thread and then set its affinity, stuff it in cgroup, whatever you fancy.

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

- what happens if someone changes the tasks' affinity?

We would normally create a cgroup including all internal tasks, making it easy to find and change affinity for them all if necessary.

And to stuff them in a cgroup you also need the tid, at which point it might as well set the affinity from userspace, right?

Quoted 8 lines

We also put it in a cgroup transparently. I think that it's actually important to do it on thread creation: if we don't, malicious userspace can create large amount of work exceeding the cgroup limits.

And the same applies so the affinity, right? If the qemu process is limited to a set of CPUs, isn't it important to make the kernel thread that does work our behalf limited to the same set of CPUs?

I'm not sure we have anything like this, but I wouldn't think so, if a driver creates a kthread and manages to inject tons of work its not typically limited to whatever limits apply to the task that supplied the work.

Take the encryption threads for example, those don't run in the context of whoever provides the data to be encrypted (file,net whatever) and thus the task responsible could consume heaps more resources than when it would have to do the encryption itself.

That's how kthreads work.

#111 lore raw

Re: [PATCH repost] sched: export sched_set/getaffinity to modules

Quoted 36 lines · Peter Zijlstra
Quoted 21 lines · Michael S. Tsirkin
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Michael S. Tsirkin
Quoted 1 line

- why can't it set the kernel thread's affinity too?

It can. However: the threads are started internally by the driver when qemu does an ioctl. What we want to do is give it a sensible default affinity. management tool can later tweak it if it wants to.

So have that ioctl return the tid of that new fancy thread and then set its affinity, stuff it in cgroup, whatever you fancy.

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

- what happens if someone changes the tasks' affinity?

We would normally create a cgroup including all internal tasks, making it easy to find and change affinity for them all if necessary.

And to stuff them in a cgroup you also need the tid, at which point it might as well set the affinity from userspace, right?

Quoted 8 lines

We also put it in a cgroup transparently. I think that it's actually important to do it on thread creation: if we don't, malicious userspace can create large amount of work exceeding the cgroup limits.

And the same applies so the affinity, right? If the qemu process is limited to a set of CPUs, isn't it important to make the kernel thread that does work our behalf limited to the same set of CPUs?

I'm not sure we have anything like this, but I wouldn't think so, if a driver creates a kthread and manages to inject tons of work its not typically limited to whatever limits apply to the task that supplied the work.

Quoted 6 lines

Take the encryption threads for example, those don't run in the context of whoever provides the data to be encrypted (file,net whatever) and thus the task responsible could consume heaps more resources than when it would have to do the encryption itself.

That's how kthreads work.

Right. And IMHO ideally all such work would run on the appropriate CPU and be accounted to. It's just that with virt people seem to run untrusted applications and expect the damage to be contained. So we came up with a simple approach that seems to do the just just for us.

Signature · 1 line
MST
#112 lore raw
In reply to Oleg Nesterov
Quoted 63 lines · Oleg Nesterov
Quoted 7 lines · Michael S. Tsirkin
Quoted 4 lines · Sridhar Samudrala

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

Quoted 7 lines

Could someone familiar with workqueue code please comment on whether this patch is suitable for 2.6.35?

It is needed to fix the case where vhost user might cause a kernel thread to consume more CPU than allowed by the cgroup. Should I merge it through the vhost tree?
Ack for this?

I don't understand the reasons for this patch, but this doesn't matter.

I don't really see any need to change workqueue.c,

Quoted 4 lines

kernel/workqueue.c static const struct cpumask *cpu_singlethread_map __read_mostly; · line 200

Quoted 4 lines
kernel/workqueue.c +4 -0
+static struct task_struct *get_singlethread_wq_task(struct workqueue_struct *wq)
+{
+	return (per_cpu_ptr(wq->cpu_wq, singlethread_cpu))->thread;
+}

(Not sure this trivial static helper with the single caller makes sense, but
see below)

Quoted 24 lines
Quoted 24 lines
kernel/workqueue.c +24 -0
+/* Create a singlethread workqueue and attach it's task to the current task's
+ * cgroup and set it's cpumask to the current task's cpumask.
+ */
+struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
+{
+	struct workqueue_struct *wq;
+	struct task_struct *task;
+	cpumask_var_t mask;
+
+	wq = create_singlethread_workqueue(name);
+	if (!wq)
+		goto out;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto err;
+			
+	if (sched_getaffinity(current->pid, mask))
+		goto err;
+
+	task = get_singlethread_wq_task(wq);
+	if (sched_setaffinity(task->pid, mask))
+		goto err;
+
+	if (cgroup_attach_task_current_cg(task))
Quoted 8 lines

kernel/workqueue.c static const struct cpumask *cpu_singlethread_map __read_mostly; · line 233

Quoted 8 lines
kernel/workqueue.c +8 -0
+		goto err;
+out:	
+	return wq;
+err:
+	destroy_workqueue(wq);
+	wq = NULL;
+	goto out;
+}

Instead, cgroup.c (or whoever needs this) can do

Quoted 10 lines
	struct move_struct {
		struct work_struct work;
		int ret;
	};

	static void move_func(struct work_struct *work)
	{
		struct move_struct *move = container_of(...);

		if (cgroup_attach_task_current_cg(current))

We are trying to attach the task associated with workqueue to the current task's cgroup. So what we need is
cgroup_attach_task_current_cg(wq->task);

However there is no interface currently that exports the task associated with a workqueue. It is hidden in cpu_workqueue_struct and is only accessible within workqueue.c.

Quoted 25 lines
			ret = -EANY;
	}

	static struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
	{
		struct workqueue_struct *wq;
		struct move_struct move = {
			.work = __WORK_INITIALIZER(move_func);
		};

		wq = create_singlethread_workqueue(name);
		if (!wq)
			return NULL;

		queue_work(&move.work);
		flush_work(&move.work);

		if (move.ret) {
			destroy_workqueue(wq);
			wq = NULL;
		}

		return wq;
	}
Quoted 2 lines

Or. Just export wq_per_cpu() from workqueue.c (probably with a better name) and use it like the patch does.

This requires that struct cpu_workqueue_struct and struct workqueue_struct are made externally visible by moving them to kernel/workqueue.h.

Instead what about adding the simple helper get_singlethread_wq_task() in workqueue.c and exporting it.
I can add create_singlethread_workqueue_in_current_cg() to cgroup.c using this helper routine.

Thanks
Sridhar

#113 lore raw
Quoted 111 lines · Sridhar Samudrala
Quoted 63 lines · Oleg Nesterov
Quoted 7 lines · Michael S. Tsirkin
Quoted 4 lines · Sridhar Samudrala

Add a new kernel API to create a singlethread workqueue and attach it's task to current task's cgroup and cpumask.

Quoted 7 lines

Could someone familiar with workqueue code please comment on whether this patch is suitable for 2.6.35?

It is needed to fix the case where vhost user might cause a kernel thread to consume more CPU than allowed by the cgroup. Should I merge it through the vhost tree?
Ack for this?

I don't understand the reasons for this patch, but this doesn't matter.

I don't really see any need to change workqueue.c,

Quoted 4 lines

kernel/workqueue.c static const struct cpumask *cpu_singlethread_map __read_mostly; · line 200

Quoted 4 lines
kernel/workqueue.c +4 -0
+static struct task_struct *get_singlethread_wq_task(struct workqueue_struct *wq)
+{
+	return (per_cpu_ptr(wq->cpu_wq, singlethread_cpu))->thread;
+}

(Not sure this trivial static helper with the single caller makes sense, but
see below)

Quoted 24 lines
Quoted 24 lines
kernel/workqueue.c +24 -0
+/* Create a singlethread workqueue and attach it's task to the current task's
+ * cgroup and set it's cpumask to the current task's cpumask.
+ */
+struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
+{
+	struct workqueue_struct *wq;
+	struct task_struct *task;
+	cpumask_var_t mask;
+
+	wq = create_singlethread_workqueue(name);
+	if (!wq)
+		goto out;
+
+	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+		goto err;
+			
+	if (sched_getaffinity(current->pid, mask))
+		goto err;
+
+	task = get_singlethread_wq_task(wq);
+	if (sched_setaffinity(task->pid, mask))
+		goto err;
+
+	if (cgroup_attach_task_current_cg(task))
Quoted 8 lines

kernel/workqueue.c static const struct cpumask *cpu_singlethread_map __read_mostly; · line 233

Quoted 8 lines
kernel/workqueue.c +8 -0
+		goto err;
+out:	
+	return wq;
+err:
+	destroy_workqueue(wq);
+	wq = NULL;
+	goto out;
+}

Instead, cgroup.c (or whoever needs this) can do

Quoted 10 lines
	struct move_struct {
		struct work_struct work;
		int ret;
	};

	static void move_func(struct work_struct *work)
	{
		struct move_struct *move = container_of(...);

		if (cgroup_attach_task_current_cg(current))

We are trying to attach the task associated with workqueue to the current task's cgroup. So what we need is
cgroup_attach_task_current_cg(wq->task);

However there is no interface currently that exports the task associated with a workqueue. It is hidden in cpu_workqueue_struct and is only accessible within workqueue.c.

Quoted 25 lines
			ret = -EANY;
	}

	static struct workqueue_struct *create_singlethread_workqueue_in_current_cg(char *name)
	{
		struct workqueue_struct *wq;
		struct move_struct move = {
			.work = __WORK_INITIALIZER(move_func);
		};

		wq = create_singlethread_workqueue(name);
		if (!wq)
			return NULL;

		queue_work(&move.work);
		flush_work(&move.work);

		if (move.ret) {
			destroy_workqueue(wq);
			wq = NULL;
		}

		return wq;
	}
Quoted 2 lines

Or. Just export wq_per_cpu() from workqueue.c (probably with a better name) and use it like the patch does.

Quoted 8 lines

This requires that struct cpu_workqueue_struct and struct workqueue_struct are made externally visible by moving them to kernel/workqueue.h.

Instead what about adding the simple helper get_singlethread_wq_task() in workqueue.c and exporting it.
I can add create_singlethread_workqueue_in_current_cg() to cgroup.c using this helper routine.

Or to our driver, if that's more palatable.

Quoted 3 lines

Thanks
Sridhar

#114 lore raw
In reply to Sridhar Samudrala

What I am actually worried about is Tejun's rework, I am not sure cmwq has the "this thread services that wq" property...

Quoted 14 lines · Sridhar Samudrala
Quoted 12 lines · Oleg Nesterov

Instead, cgroup.c (or whoever needs this) can do

	struct move_struct {
		struct work_struct work;
		int ret;
	};

	static void move_func(struct work_struct *work)
	{
		struct move_struct *move = container_of(...);
Quoted 5 lines
Quoted 1 line

if (cgroup_attach_task_current_cg(current))

We are trying to attach the task associated with workqueue to the current task's cgroup. So what we need is
cgroup_attach_task_current_cg(wq->task);

I do not see cgroup_attach_task_current_cg() in Linus's tree and thus I do not now what exactly it does, and of course the code above is only template.

But I think this is easy. Just add "struct cgroup *cgrp" into move_struct and then move_func() can do cgroup_attach_task(move->cgrp, current) ?

Quoted 5 lines
Quoted 2 lines

Or. Just export wq_per_cpu() from workqueue.c (probably with a better name) and use it like the patch does.

This requires that struct cpu_workqueue_struct and struct workqueue_struct are made externally visible by moving them to kernel/workqueue.h.

no, no,

Quoted 2 lines

Instead what about adding the simple helper get_singlethread_wq_task() in workqueue.c and exporting it.

Indeed, this is what I meant.

But. I disagree with get_singlethread_wq_task(). If we add this helper, it should work with the multi-threaded wq's too, and needs the "int cpu" parameter, ignored when is_wq_single_threaded().

So. Either rename wq_per_cpu() and export it (once again, I do not mean we should move the body to workqueue.h!), or create the new helper which just calls wq_per_cpu().

Quoted 2 lines

I can add create_singlethread_workqueue_in_current_cg() to cgroup.c using this helper routine.

Imho, this is better.

But please note that it is possible to do without any changes in workqueue.[ch] afaics, see above.

Oleg.