This patch allows the vhost and vhost_task code to use CLONE_THREAD, CLONE_SIGHAND and CLONE_FILES. It's a RFC because I didn't do all the normal testing, haven't coverted vsock and vdpa, and I know you guys will not like the first patch. However, I think it better shows what we need from the signal code and how we can support signals in the vhost_task layer. Note that I took the super simple route and kicked off some work to the system workqueue. We can do more invassive approaches: 1. Modify the vhost drivers so they can check for IO completions using a non-blocking interface. We then don't need to run from the system workqueue and can run from the vhost_task. 2. We could drop patch 1 and just say we are doing a polling type of approach. We then modify the vhost layer similar to #1 where we can check for completions using a non-blocking interface and use the vhost_task task.
Mike Christie
2023-May-18 00:09 UTC
[RFC PATCH 1/8] signal: Dequeue SIGKILL even if SIGNAL_GROUP_EXIT/group_exec_task is set
This has us deqeue SIGKILL even if SIGNAL_GROUP_EXIT/group_exec_task is set when we are dealing with PF_USER_WORKER tasks. When a vhost_task gets a SIGKILL, we could have outstanding IO in flight. We can easily stop new work/IO from being queued to the vhost_task, but for IO that's already been sent to something like the block layer we need to wait for the response then process it. These type of IO completions use the vhost_task to process the completion so we can't exit immediately. We need to handle wait for then handle those completions from the vhost_task, but when we have a SIGKLL pending, functions like schedule() return immediately so we can't wait like normal. Functions like vhost_worker() degrade to just a while(1); loop. This patch has get_signal drop down to the normal code path when SIGNAL_GROUP_EXIT/group_exec_task is set so the caller can still detect there is a SIGKILL but still perform some blocking cleanup. Note that in that chunk I'm now bypassing that does: sigdelset(¤t->pending.signal, SIGKILL); we look to be ok, because in the places we set SIGNAL_GROUP_EXIT/ group_exec_task we are already doing that on the threads in the group. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- kernel/signal.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index 8f6330f0e9ca..ae4972eea5db 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -2705,9 +2705,18 @@ bool get_signal(struct ksignal *ksig) struct k_sigaction *ka; enum pid_type type; - /* Has this task already been marked for death? */ - if ((signal->flags & SIGNAL_GROUP_EXIT) || - signal->group_exec_task) { + /* + * Has this task already been marked for death? + * + * If this is a PF_USER_WORKER then the task may need to do + * extra work that requires waiting on running work, so we want + * to dequeue the signal below and tell the caller its time to + * start its exit procedure. When the work has completed then + * the task will exit. + */ + if (!(current->flags & PF_USER_WORKER) && + ((signal->flags & SIGNAL_GROUP_EXIT) || + signal->group_exec_task)) { clear_siginfo(&ksig->info); ksig->info.si_signo = signr = SIGKILL; sigdelset(¤t->pending.signal, SIGKILL); @@ -2861,11 +2870,11 @@ bool get_signal(struct ksignal *ksig) } /* - * PF_IO_WORKER threads will catch and exit on fatal signals + * PF_USER_WORKER threads will catch and exit on fatal signals * themselves. They have cleanup that must be performed, so * we cannot call do_exit() on their behalf. */ - if (current->flags & PF_IO_WORKER) + if (current->flags & PF_USER_WORKER) goto out; /* -- 2.25.1
Mike Christie
2023-May-18 00:09 UTC
[RFC PATCH 2/8] vhost/vhost_task: Hook vhost layer into signal handler
This patch has vhost use get_signal to handle freezing and sort of handle signals. By the latter I mean that when we get SIGKILL, our parent will exit and call our file_operatons release function. That will then stop new work from breing queued and wait for the vhost_task to handle completions for running IO. We then exit when those are done. The next patches will then have us work more like io_uring where we handle the get_signal return value and key off that to cleanup. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 10 +++++++++- include/linux/sched/vhost_task.h | 1 + kernel/vhost_task.c | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index a92af08e7864..1ba9e068b2ab 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -349,8 +349,16 @@ static int vhost_worker(void *data) } node = llist_del_all(&worker->work_list); - if (!node) + if (!node) { schedule(); + /* + * When we get a SIGKILL our release function will + * be called. That will stop new IOs from being queued + * and check for outstanding cmd responses. It will then + * call vhost_task_stop to exit us. + */ + vhost_task_get_signal(); + } node = llist_reverse_order(node); /* make sure flag is seen after deletion */ diff --git a/include/linux/sched/vhost_task.h b/include/linux/sched/vhost_task.h index 6123c10b99cf..54b68115eb3b 100644 --- a/include/linux/sched/vhost_task.h +++ b/include/linux/sched/vhost_task.h @@ -19,5 +19,6 @@ struct vhost_task *vhost_task_create(int (*fn)(void *), void *arg, void vhost_task_start(struct vhost_task *vtsk); void vhost_task_stop(struct vhost_task *vtsk); bool vhost_task_should_stop(struct vhost_task *vtsk); +bool vhost_task_get_signal(void); #endif diff --git a/kernel/vhost_task.c b/kernel/vhost_task.c index b7cbd66f889e..a661cfa32ba3 100644 --- a/kernel/vhost_task.c +++ b/kernel/vhost_task.c @@ -61,6 +61,26 @@ bool vhost_task_should_stop(struct vhost_task *vtsk) } EXPORT_SYMBOL_GPL(vhost_task_should_stop); +/** + * vhost_task_get_signal - Check if there are pending signals + * + * Return true if we got SIGKILL. + */ +bool vhost_task_get_signal(void) +{ + struct ksignal ksig; + bool rc; + + if (!signal_pending(current)) + return false; + + __set_current_state(TASK_RUNNING); + rc = get_signal(&ksig); + set_current_state(TASK_INTERRUPTIBLE); + return rc; +} +EXPORT_SYMBOL_GPL(vhost_task_get_signal); + /** * vhost_task_create - create a copy of a process to be used by the kernel * @fn: thread stack -- 2.25.1
Mike Christie
2023-May-18 00:09 UTC
[RFC PATCH 3/8] fork/vhost_task: Switch to CLONE_THREAD and CLONE_SIGHAND
This is a modified version of Linus's patch which has vhost_task use CLONE_THREAD and CLONE_SIGHAND and allow SIGKILL and SIGSTOP. I renamed the ignore_signals to block_signals based on Linus's comment where it aligns with what we are doing with the siginitsetinv p->blocked use and no longer calling ignore_signals. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- include/linux/sched/task.h | 2 +- kernel/fork.c | 12 +++--------- kernel/vhost_task.c | 5 +++-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h index 537cbf9a2ade..249a5ece9def 100644 --- a/include/linux/sched/task.h +++ b/include/linux/sched/task.h @@ -29,7 +29,7 @@ struct kernel_clone_args { u32 io_thread:1; u32 user_worker:1; u32 no_files:1; - u32 ignore_signals:1; + u32 block_signals:1; unsigned long stack; unsigned long stack_size; unsigned long tls; diff --git a/kernel/fork.c b/kernel/fork.c index ed4e01daccaa..9e04ab5c3946 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2338,14 +2338,10 @@ __latent_entropy struct task_struct *copy_process( p->flags |= PF_KTHREAD; if (args->user_worker) p->flags |= PF_USER_WORKER; - if (args->io_thread) { - /* - * Mark us an IO worker, and block any signal that isn't - * fatal or STOP - */ + if (args->io_thread) p->flags |= PF_IO_WORKER; + if (args->block_signals) siginitsetinv(&p->blocked, sigmask(SIGKILL)|sigmask(SIGSTOP)); - } if (args->name) strscpy_pad(p->comm, args->name, sizeof(p->comm)); @@ -2517,9 +2513,6 @@ __latent_entropy struct task_struct *copy_process( if (retval) goto bad_fork_cleanup_io; - if (args->ignore_signals) - ignore_signals(p); - stackleak_task_init(p); if (pid != &init_struct_pid) { @@ -2861,6 +2854,7 @@ struct task_struct *create_io_thread(int (*fn)(void *), void *arg, int node) .fn_arg = arg, .io_thread = 1, .user_worker = 1, + .block_signals = 1, }; return copy_process(NULL, 0, node, &args); diff --git a/kernel/vhost_task.c b/kernel/vhost_task.c index a661cfa32ba3..a11f036290cc 100644 --- a/kernel/vhost_task.c +++ b/kernel/vhost_task.c @@ -95,13 +95,14 @@ struct vhost_task *vhost_task_create(int (*fn)(void *), void *arg, const char *name) { struct kernel_clone_args args = { - .flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM, + .flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM | + CLONE_THREAD | CLONE_SIGHAND, .exit_signal = 0, .fn = vhost_task_fn, .name = name, .user_worker = 1, .no_files = 1, - .ignore_signals = 1, + .block_signals = 1, }; struct vhost_task *vtsk; struct task_struct *tsk; -- 2.25.1
This moves vhost_net_open so in the next patches we can pass vhost_dev_init a new helper which will use the stop/flush functions. There is no functionality changes in this patch. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/net.c | 134 ++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 07181cd8d52e..8557072ff05e 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1285,73 +1285,6 @@ static void handle_rx_net(struct vhost_work *work) handle_rx(net); } -static int vhost_net_open(struct inode *inode, struct file *f) -{ - struct vhost_net *n; - struct vhost_dev *dev; - struct vhost_virtqueue **vqs; - void **queue; - struct xdp_buff *xdp; - int i; - - n = kvmalloc(sizeof *n, GFP_KERNEL | __GFP_RETRY_MAYFAIL); - if (!n) - return -ENOMEM; - vqs = kmalloc_array(VHOST_NET_VQ_MAX, sizeof(*vqs), GFP_KERNEL); - if (!vqs) { - kvfree(n); - return -ENOMEM; - } - - queue = kmalloc_array(VHOST_NET_BATCH, sizeof(void *), - GFP_KERNEL); - if (!queue) { - kfree(vqs); - kvfree(n); - return -ENOMEM; - } - n->vqs[VHOST_NET_VQ_RX].rxq.queue = queue; - - xdp = kmalloc_array(VHOST_NET_BATCH, sizeof(*xdp), GFP_KERNEL); - if (!xdp) { - kfree(vqs); - kvfree(n); - kfree(queue); - return -ENOMEM; - } - n->vqs[VHOST_NET_VQ_TX].xdp = xdp; - - dev = &n->dev; - vqs[VHOST_NET_VQ_TX] = &n->vqs[VHOST_NET_VQ_TX].vq; - vqs[VHOST_NET_VQ_RX] = &n->vqs[VHOST_NET_VQ_RX].vq; - n->vqs[VHOST_NET_VQ_TX].vq.handle_kick = handle_tx_kick; - n->vqs[VHOST_NET_VQ_RX].vq.handle_kick = handle_rx_kick; - for (i = 0; i < VHOST_NET_VQ_MAX; i++) { - n->vqs[i].ubufs = NULL; - n->vqs[i].ubuf_info = NULL; - n->vqs[i].upend_idx = 0; - n->vqs[i].done_idx = 0; - n->vqs[i].batched_xdp = 0; - n->vqs[i].vhost_hlen = 0; - n->vqs[i].sock_hlen = 0; - n->vqs[i].rx_ring = NULL; - vhost_net_buf_init(&n->vqs[i].rxq); - } - vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX, - UIO_MAXIOV + VHOST_NET_BATCH, - VHOST_NET_PKT_WEIGHT, VHOST_NET_WEIGHT, true, - NULL); - - vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev); - vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev); - - f->private_data = n; - n->page_frag.page = NULL; - n->refcnt_bias = 0; - - return 0; -} - static struct socket *vhost_net_stop_vq(struct vhost_net *n, struct vhost_virtqueue *vq) { @@ -1421,6 +1354,73 @@ static int vhost_net_release(struct inode *inode, struct file *f) return 0; } +static int vhost_net_open(struct inode *inode, struct file *f) +{ + struct vhost_net *n; + struct vhost_dev *dev; + struct vhost_virtqueue **vqs; + void **queue; + struct xdp_buff *xdp; + int i; + + n = kvmalloc(sizeof *n, GFP_KERNEL | __GFP_RETRY_MAYFAIL); + if (!n) + return -ENOMEM; + vqs = kmalloc_array(VHOST_NET_VQ_MAX, sizeof(*vqs), GFP_KERNEL); + if (!vqs) { + kvfree(n); + return -ENOMEM; + } + + queue = kmalloc_array(VHOST_NET_BATCH, sizeof(void *), + GFP_KERNEL); + if (!queue) { + kfree(vqs); + kvfree(n); + return -ENOMEM; + } + n->vqs[VHOST_NET_VQ_RX].rxq.queue = queue; + + xdp = kmalloc_array(VHOST_NET_BATCH, sizeof(*xdp), GFP_KERNEL); + if (!xdp) { + kfree(vqs); + kvfree(n); + kfree(queue); + return -ENOMEM; + } + n->vqs[VHOST_NET_VQ_TX].xdp = xdp; + + dev = &n->dev; + vqs[VHOST_NET_VQ_TX] = &n->vqs[VHOST_NET_VQ_TX].vq; + vqs[VHOST_NET_VQ_RX] = &n->vqs[VHOST_NET_VQ_RX].vq; + n->vqs[VHOST_NET_VQ_TX].vq.handle_kick = handle_tx_kick; + n->vqs[VHOST_NET_VQ_RX].vq.handle_kick = handle_rx_kick; + for (i = 0; i < VHOST_NET_VQ_MAX; i++) { + n->vqs[i].ubufs = NULL; + n->vqs[i].ubuf_info = NULL; + n->vqs[i].upend_idx = 0; + n->vqs[i].done_idx = 0; + n->vqs[i].batched_xdp = 0; + n->vqs[i].vhost_hlen = 0; + n->vqs[i].sock_hlen = 0; + n->vqs[i].rx_ring = NULL; + vhost_net_buf_init(&n->vqs[i].rxq); + } + vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX, + UIO_MAXIOV + VHOST_NET_BATCH, + VHOST_NET_PKT_WEIGHT, VHOST_NET_WEIGHT, true, + NULL); + + vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev); + vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev); + + f->private_data = n; + n->page_frag.page = NULL; + n->refcnt_bias = 0; + + return 0; +} + static struct socket *get_raw_socket(int fd) { int r; -- 2.25.1
Mike Christie
2023-May-18 00:09 UTC
[RFC PATCH 5/8] vhost: Add callback that stops new work and waits on running ones
When the vhost_task gets a SIGKILL we want to stop new work from being queued and also wait for and handle completions for running work. For the latter, we still need to use the vhost_task to handle the completing work so we can't just exit right away. But, this has us kick off the stopping and flushing/stopping of the device/vhost_task/worker to the system work queue while the vhost_task handles completions. When all completions are done we will then do vhost_task_stop and we will exit. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/net.c | 2 +- drivers/vhost/scsi.c | 4 ++-- drivers/vhost/test.c | 3 ++- drivers/vhost/vdpa.c | 2 +- drivers/vhost/vhost.c | 48 ++++++++++++++++++++++++++++++++++++------- drivers/vhost/vhost.h | 10 ++++++++- drivers/vhost/vsock.c | 4 ++-- 7 files changed, 58 insertions(+), 15 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 8557072ff05e..90c25127b3f8 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1409,7 +1409,7 @@ static int vhost_net_open(struct inode *inode, struct file *f) vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX, UIO_MAXIOV + VHOST_NET_BATCH, VHOST_NET_PKT_WEIGHT, VHOST_NET_WEIGHT, true, - NULL); + NULL, NULL); vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev); vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev); diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index bb10fa4bb4f6..40f9135e1a62 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -1820,8 +1820,8 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) vqs[i] = &vs->vqs[i].vq; vs->vqs[i].vq.handle_kick = vhost_scsi_handle_kick; } - vhost_dev_init(&vs->dev, vqs, nvqs, UIO_MAXIOV, - VHOST_SCSI_WEIGHT, 0, true, NULL); + vhost_dev_init(&vs->dev, vqs, nvqs, UIO_MAXIOV, VHOST_SCSI_WEIGHT, 0, + true, NULL, NULL); vhost_scsi_init_inflight(vs, NULL); diff --git a/drivers/vhost/test.c b/drivers/vhost/test.c index 42c955a5b211..11a2823d7532 100644 --- a/drivers/vhost/test.c +++ b/drivers/vhost/test.c @@ -120,7 +120,8 @@ static int vhost_test_open(struct inode *inode, struct file *f) vqs[VHOST_TEST_VQ] = &n->vqs[VHOST_TEST_VQ]; n->vqs[VHOST_TEST_VQ].handle_kick = handle_vq_kick; vhost_dev_init(dev, vqs, VHOST_TEST_VQ_MAX, UIO_MAXIOV, - VHOST_TEST_PKT_WEIGHT, VHOST_TEST_WEIGHT, true, NULL); + VHOST_TEST_PKT_WEIGHT, VHOST_TEST_WEIGHT, true, NULL, + NULL); f->private_data = n; diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 8c1aefc865f0..de9a83ecb70d 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -1279,7 +1279,7 @@ static int vhost_vdpa_open(struct inode *inode, struct file *filep) vqs[i]->handle_kick = handle_vq_kick; } vhost_dev_init(dev, vqs, nvqs, 0, 0, 0, false, - vhost_vdpa_process_iotlb_msg); + vhost_vdpa_process_iotlb_msg, NULL); r = vhost_vdpa_alloc_domain(v); if (r) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 1ba9e068b2ab..4163c86db50c 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -336,6 +336,7 @@ static void vhost_vq_reset(struct vhost_dev *dev, static int vhost_worker(void *data) { struct vhost_worker *worker = data; + struct vhost_dev *dev = worker->dev; struct vhost_work *work, *work_next; struct llist_node *node; @@ -352,12 +353,13 @@ static int vhost_worker(void *data) if (!node) { schedule(); /* - * When we get a SIGKILL our release function will - * be called. That will stop new IOs from being queued - * and check for outstanding cmd responses. It will then - * call vhost_task_stop to exit us. + * When we get a SIGKILL we kick off a work to + * run the driver's helper to stop new work and + * handle completions. When they are done they will + * call vhost_task_stop to tell us to exit. */ - vhost_task_get_signal(); + if (vhost_task_get_signal()) + schedule_work(&dev->destroy_worker); } node = llist_reverse_order(node); @@ -376,6 +378,33 @@ static int vhost_worker(void *data) return 0; } +static void __vhost_dev_stop_work(struct vhost_dev *dev) +{ + mutex_lock(&dev->stop_work_mutex); + if (dev->work_stopped) + goto done; + + if (dev->stop_dev_work) + dev->stop_dev_work(dev); + dev->work_stopped = true; +done: + mutex_unlock(&dev->stop_work_mutex); +} + +void vhost_dev_stop_work(struct vhost_dev *dev) +{ + __vhost_dev_stop_work(dev); + flush_work(&dev->destroy_worker); +} +EXPORT_SYMBOL_GPL(vhost_dev_stop_work); + +static void vhost_worker_destroy(struct work_struct *work) +{ + struct vhost_dev *dev = container_of(work, struct vhost_dev, + destroy_worker); + __vhost_dev_stop_work(dev); +} + static void vhost_vq_free_iovecs(struct vhost_virtqueue *vq) { kfree(vq->indirect); @@ -464,7 +493,8 @@ void vhost_dev_init(struct vhost_dev *dev, int iov_limit, int weight, int byte_weight, bool use_worker, int (*msg_handler)(struct vhost_dev *dev, u32 asid, - struct vhost_iotlb_msg *msg)) + struct vhost_iotlb_msg *msg), + void (*stop_dev_work)(struct vhost_dev *dev)) { struct vhost_virtqueue *vq; int i; @@ -472,6 +502,7 @@ void vhost_dev_init(struct vhost_dev *dev, dev->vqs = vqs; dev->nvqs = nvqs; mutex_init(&dev->mutex); + mutex_init(&dev->stop_work_mutex); dev->log_ctx = NULL; dev->umem = NULL; dev->iotlb = NULL; @@ -482,12 +513,14 @@ void vhost_dev_init(struct vhost_dev *dev, dev->byte_weight = byte_weight; dev->use_worker = use_worker; dev->msg_handler = msg_handler; + dev->work_stopped = false; + dev->stop_dev_work = stop_dev_work; + INIT_WORK(&dev->destroy_worker, vhost_worker_destroy); init_waitqueue_head(&dev->wait); INIT_LIST_HEAD(&dev->read_list); INIT_LIST_HEAD(&dev->pending_list); spin_lock_init(&dev->iotlb_lock); - for (i = 0; i < dev->nvqs; ++i) { vq = dev->vqs[i]; vq->log = NULL; @@ -572,6 +605,7 @@ static int vhost_worker_create(struct vhost_dev *dev) if (!worker) return -ENOMEM; + worker->dev = dev; dev->worker = worker; worker->kcov_handle = kcov_common_handle(); init_llist_head(&worker->work_list); diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 0308638cdeee..325e5e52c7ae 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -17,6 +17,7 @@ struct vhost_work; struct vhost_task; +struct vhost_dev; typedef void (*vhost_work_fn_t)(struct vhost_work *work); #define VHOST_WORK_QUEUED 1 @@ -28,6 +29,7 @@ struct vhost_work { struct vhost_worker { struct vhost_task *vtsk; + struct vhost_dev *dev; struct llist_head work_list; u64 kcov_handle; }; @@ -165,8 +167,12 @@ struct vhost_dev { int weight; int byte_weight; bool use_worker; + struct mutex stop_work_mutex; + bool work_stopped; + struct work_struct destroy_worker; int (*msg_handler)(struct vhost_dev *dev, u32 asid, struct vhost_iotlb_msg *msg); + void (*stop_dev_work)(struct vhost_dev *dev); }; bool vhost_exceeds_weight(struct vhost_virtqueue *vq, int pkts, int total_len); @@ -174,7 +180,8 @@ void vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue **vqs, int nvqs, int iov_limit, int weight, int byte_weight, bool use_worker, int (*msg_handler)(struct vhost_dev *dev, u32 asid, - struct vhost_iotlb_msg *msg)); + struct vhost_iotlb_msg *msg), + void (*stop_dev_work)(struct vhost_dev *dev)); long vhost_dev_set_owner(struct vhost_dev *dev); bool vhost_dev_has_owner(struct vhost_dev *dev); long vhost_dev_check_owner(struct vhost_dev *); @@ -182,6 +189,7 @@ struct vhost_iotlb *vhost_dev_reset_owner_prepare(void); void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *iotlb); void vhost_dev_cleanup(struct vhost_dev *); void vhost_dev_stop(struct vhost_dev *); +void vhost_dev_stop_work(struct vhost_dev *dev); long vhost_dev_ioctl(struct vhost_dev *, unsigned int ioctl, void __user *argp); long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp); bool vhost_vq_access_ok(struct vhost_virtqueue *vq); diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 6578db78f0ae..1ef53722d494 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -664,8 +664,8 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file) vsock->vqs[VSOCK_VQ_RX].handle_kick = vhost_vsock_handle_rx_kick; vhost_dev_init(&vsock->dev, vqs, ARRAY_SIZE(vsock->vqs), - UIO_MAXIOV, VHOST_VSOCK_PKT_WEIGHT, - VHOST_VSOCK_WEIGHT, true, NULL); + UIO_MAXIOV, VHOST_VSOCK_PKT_WEIGHT, VHOST_VSOCK_WEIGHT, + true, NULL, NULL); file->private_data = vsock; skb_queue_head_init(&vsock->send_pkt_queue); -- 2.25.1
Mike Christie
2023-May-18 00:09 UTC
[RFC PATCH 6/8] vhost-scsi: Add callback to stop and wait on works
This moves the scsi code we use to stop new works from being queued and wait on running works to a helper which is used by the vhost layer when the vhost_task is being killed by a SIGKILL. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/scsi.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 40f9135e1a62..a0f2588270f2 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -1768,6 +1768,19 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features) return 0; } +static void vhost_scsi_stop_dev_work(struct vhost_dev *dev) +{ + struct vhost_scsi *vs = container_of(dev, struct vhost_scsi, dev); + struct vhost_scsi_target t; + + mutex_lock(&vs->dev.mutex); + memcpy(t.vhost_wwpn, vs->vs_vhost_wwpn, sizeof(t.vhost_wwpn)); + mutex_unlock(&vs->dev.mutex); + vhost_scsi_clear_endpoint(vs, &t); + vhost_dev_stop(&vs->dev); + vhost_dev_cleanup(&vs->dev); +} + static int vhost_scsi_open(struct inode *inode, struct file *f) { struct vhost_scsi *vs; @@ -1821,7 +1834,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) vs->vqs[i].vq.handle_kick = vhost_scsi_handle_kick; } vhost_dev_init(&vs->dev, vqs, nvqs, UIO_MAXIOV, VHOST_SCSI_WEIGHT, 0, - true, NULL, NULL); + true, NULL, vhost_scsi_stop_dev_work); vhost_scsi_init_inflight(vs, NULL); @@ -1843,14 +1856,8 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) static int vhost_scsi_release(struct inode *inode, struct file *f) { struct vhost_scsi *vs = f->private_data; - struct vhost_scsi_target t; - mutex_lock(&vs->dev.mutex); - memcpy(t.vhost_wwpn, vs->vs_vhost_wwpn, sizeof(t.vhost_wwpn)); - mutex_unlock(&vs->dev.mutex); - vhost_scsi_clear_endpoint(vs, &t); - vhost_dev_stop(&vs->dev); - vhost_dev_cleanup(&vs->dev); + vhost_dev_stop_work(&vs->dev); kfree(vs->dev.vqs); kfree(vs->vqs); kfree(vs->old_inflight); -- 2.25.1
Mike Christie
2023-May-18 00:09 UTC
[RFC PATCH 7/8] vhost-net: Add callback to stop and wait on works
This moves the net code we use to stop new works from being queued and wait on running works to a helper which is used by the vhost layer when the vhost_task is being killed by a SIGKILL. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/net.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 90c25127b3f8..f8a5527b15ba 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1325,9 +1325,9 @@ static void vhost_net_flush(struct vhost_net *n) } } -static int vhost_net_release(struct inode *inode, struct file *f) +static void vhost_net_stop_dev_work(struct vhost_dev *dev) { - struct vhost_net *n = f->private_data; + struct vhost_net *n = container_of(dev, struct vhost_net, dev); struct socket *tx_sock; struct socket *rx_sock; @@ -1345,6 +1345,13 @@ static int vhost_net_release(struct inode *inode, struct file *f) /* We do an extra flush before freeing memory, * since jobs can re-queue themselves. */ vhost_net_flush(n); +} + +static int vhost_net_release(struct inode *inode, struct file *f) +{ + struct vhost_net *n = f->private_data; + + vhost_dev_stop_work(&n->dev); kfree(n->vqs[VHOST_NET_VQ_RX].rxq.queue); kfree(n->vqs[VHOST_NET_VQ_TX].xdp); kfree(n->dev.vqs); @@ -1409,7 +1416,7 @@ static int vhost_net_open(struct inode *inode, struct file *f) vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX, UIO_MAXIOV + VHOST_NET_BATCH, VHOST_NET_PKT_WEIGHT, VHOST_NET_WEIGHT, true, - NULL, NULL); + NULL, vhost_net_stop_dev_work); vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev); vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev); -- 2.25.1
The vhost_task can now support the worker being freed from under the device when we get a SIGKILL or the process exits without closing devices. We no longer need no_files so this removes it. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- include/linux/sched/task.h | 1 - kernel/fork.c | 10 ++-------- kernel/vhost_task.c | 3 +-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h index 249a5ece9def..342fe297ffd4 100644 --- a/include/linux/sched/task.h +++ b/include/linux/sched/task.h @@ -28,7 +28,6 @@ struct kernel_clone_args { u32 kthread:1; u32 io_thread:1; u32 user_worker:1; - u32 no_files:1; u32 block_signals:1; unsigned long stack; unsigned long stack_size; diff --git a/kernel/fork.c b/kernel/fork.c index 9e04ab5c3946..f2c081c15efb 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1769,8 +1769,7 @@ static int copy_fs(unsigned long clone_flags, struct task_struct *tsk) return 0; } -static int copy_files(unsigned long clone_flags, struct task_struct *tsk, - int no_files) +static int copy_files(unsigned long clone_flags, struct task_struct *tsk) { struct files_struct *oldf, *newf; int error = 0; @@ -1782,11 +1781,6 @@ static int copy_files(unsigned long clone_flags, struct task_struct *tsk, if (!oldf) goto out; - if (no_files) { - tsk->files = NULL; - goto out; - } - if (clone_flags & CLONE_FILES) { atomic_inc(&oldf->count); goto out; @@ -2488,7 +2482,7 @@ __latent_entropy struct task_struct *copy_process( retval = copy_semundo(clone_flags, p); if (retval) goto bad_fork_cleanup_security; - retval = copy_files(clone_flags, p, args->no_files); + retval = copy_files(clone_flags, p); if (retval) goto bad_fork_cleanup_semundo; retval = copy_fs(clone_flags, p); diff --git a/kernel/vhost_task.c b/kernel/vhost_task.c index a11f036290cc..642047765190 100644 --- a/kernel/vhost_task.c +++ b/kernel/vhost_task.c @@ -96,12 +96,11 @@ struct vhost_task *vhost_task_create(int (*fn)(void *), void *arg, { struct kernel_clone_args args = { .flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM | - CLONE_THREAD | CLONE_SIGHAND, + CLONE_THREAD | CLONE_FILES, CLONE_SIGHAND, .exit_signal = 0, .fn = vhost_task_fn, .name = name, .user_worker = 1, - .no_files = 1, .block_signals = 1, }; struct vhost_task *vtsk; -- 2.25.1