fork, vhost: Use CLONE_THREAD to fix freezer/ps regression
[linux-block.git] / kernel / vhost_task.c
CommitLineData
e297cd54
MC
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2021 Oracle Corporation
4 */
5#include <linux/slab.h>
6#include <linux/completion.h>
7#include <linux/sched/task.h>
8#include <linux/sched/vhost_task.h>
9#include <linux/sched/signal.h>
10
11enum vhost_task_flags {
12 VHOST_TASK_FLAGS_STOP,
13};
14
f9010dbd
MC
15struct vhost_task {
16 bool (*fn)(void *data);
17 void *data;
18 struct completion exited;
19 unsigned long flags;
20 struct task_struct *task;
21};
22
e297cd54
MC
23static int vhost_task_fn(void *data)
24{
25 struct vhost_task *vtsk = data;
f9010dbd
MC
26 bool dead = false;
27
28 for (;;) {
29 bool did_work;
30
31 /* mb paired w/ vhost_task_stop */
32 if (test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags))
33 break;
34
35 if (!dead && signal_pending(current)) {
36 struct ksignal ksig;
37 /*
38 * Calling get_signal will block in SIGSTOP,
39 * or clear fatal_signal_pending, but remember
40 * what was set.
41 *
42 * This thread won't actually exit until all
43 * of the file descriptors are closed, and
44 * the release function is called.
45 */
46 dead = get_signal(&ksig);
47 if (dead)
48 clear_thread_flag(TIF_SIGPENDING);
49 }
50
51 did_work = vtsk->fn(vtsk->data);
52 if (!did_work) {
53 set_current_state(TASK_INTERRUPTIBLE);
54 schedule();
55 }
56 }
e297cd54 57
e297cd54 58 complete(&vtsk->exited);
f9010dbd
MC
59 do_exit(0);
60}
61
62/**
63 * vhost_task_wake - wakeup the vhost_task
64 * @vtsk: vhost_task to wake
65 *
66 * wake up the vhost_task worker thread
67 */
68void vhost_task_wake(struct vhost_task *vtsk)
69{
70 wake_up_process(vtsk->task);
e297cd54 71}
f9010dbd 72EXPORT_SYMBOL_GPL(vhost_task_wake);
e297cd54
MC
73
74/**
75 * vhost_task_stop - stop a vhost_task
76 * @vtsk: vhost_task to stop
77 *
f9010dbd
MC
78 * vhost_task_fn ensures the worker thread exits after
79 * VHOST_TASK_FLAGS_SOP becomes true.
e297cd54
MC
80 */
81void vhost_task_stop(struct vhost_task *vtsk)
82{
e297cd54 83 set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
f9010dbd 84 vhost_task_wake(vtsk);
e297cd54
MC
85 /*
86 * Make sure vhost_task_fn is no longer accessing the vhost_task before
f9010dbd 87 * freeing it below.
e297cd54
MC
88 */
89 wait_for_completion(&vtsk->exited);
e297cd54
MC
90 kfree(vtsk);
91}
92EXPORT_SYMBOL_GPL(vhost_task_stop);
93
94/**
f9010dbd
MC
95 * vhost_task_create - create a copy of a task to be used by the kernel
96 * @fn: vhost worker function
e297cd54
MC
97 * @arg: data to be passed to fn
98 * @name: the thread's name
99 *
100 * This returns a specialized task for use by the vhost layer or NULL on
101 * failure. The returned task is inactive, and the caller must fire it up
102 * through vhost_task_start().
103 */
f9010dbd 104struct vhost_task *vhost_task_create(bool (*fn)(void *), void *arg,
e297cd54
MC
105 const char *name)
106{
107 struct kernel_clone_args args = {
f9010dbd
MC
108 .flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM |
109 CLONE_THREAD | CLONE_SIGHAND,
e297cd54
MC
110 .exit_signal = 0,
111 .fn = vhost_task_fn,
112 .name = name,
113 .user_worker = 1,
114 .no_files = 1,
e297cd54
MC
115 };
116 struct vhost_task *vtsk;
117 struct task_struct *tsk;
118
119 vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
120 if (!vtsk)
121 return NULL;
122 init_completion(&vtsk->exited);
123 vtsk->data = arg;
124 vtsk->fn = fn;
125
126 args.fn_arg = vtsk;
127
128 tsk = copy_process(NULL, 0, NUMA_NO_NODE, &args);
129 if (IS_ERR(tsk)) {
130 kfree(vtsk);
131 return NULL;
132 }
133
134 vtsk->task = tsk;
135 return vtsk;
136}
137EXPORT_SYMBOL_GPL(vhost_task_create);
138
139/**
140 * vhost_task_start - start a vhost_task created with vhost_task_create
141 * @vtsk: vhost_task to wake up
142 */
143void vhost_task_start(struct vhost_task *vtsk)
144{
145 wake_up_new_task(vtsk->task);
146}
147EXPORT_SYMBOL_GPL(vhost_task_start);