mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	We can race where we have added work to the work_list, but vhost_task_fn has passed that check but not yet set us into TASK_INTERRUPTIBLE. wake_up_process will see us in TASK_RUNNING and just return. This bug was intoduced in commitf9010dbdce("fork, vhost: Use CLONE_THREAD to fix freezer/ps regression") when I moved the setting of TASK_INTERRUPTIBLE to simplfy the code and avoid get_signal from logging warnings about being in the wrong state. This moves the setting of TASK_INTERRUPTIBLE back to before we test if we need to stop the task to avoid a possible race there as well. We then have vhost_worker set TASK_RUNNING if it finds work similar to before. Fixes:f9010dbdce("fork, vhost: Use CLONE_THREAD to fix freezer/ps regression") Signed-off-by: Mike Christie <michael.christie@oracle.com> Message-Id: <20230607192338.6041-3-michael.christie@oracle.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
		
			
				
	
	
		
			149 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-only
 | 
						|
/*
 | 
						|
 * Copyright (C) 2021 Oracle Corporation
 | 
						|
 */
 | 
						|
#include <linux/slab.h>
 | 
						|
#include <linux/completion.h>
 | 
						|
#include <linux/sched/task.h>
 | 
						|
#include <linux/sched/vhost_task.h>
 | 
						|
#include <linux/sched/signal.h>
 | 
						|
 | 
						|
enum vhost_task_flags {
 | 
						|
	VHOST_TASK_FLAGS_STOP,
 | 
						|
};
 | 
						|
 | 
						|
struct vhost_task {
 | 
						|
	bool (*fn)(void *data);
 | 
						|
	void *data;
 | 
						|
	struct completion exited;
 | 
						|
	unsigned long flags;
 | 
						|
	struct task_struct *task;
 | 
						|
};
 | 
						|
 | 
						|
static int vhost_task_fn(void *data)
 | 
						|
{
 | 
						|
	struct vhost_task *vtsk = data;
 | 
						|
	bool dead = false;
 | 
						|
 | 
						|
	for (;;) {
 | 
						|
		bool did_work;
 | 
						|
 | 
						|
		if (!dead && signal_pending(current)) {
 | 
						|
			struct ksignal ksig;
 | 
						|
			/*
 | 
						|
			 * Calling get_signal will block in SIGSTOP,
 | 
						|
			 * or clear fatal_signal_pending, but remember
 | 
						|
			 * what was set.
 | 
						|
			 *
 | 
						|
			 * This thread won't actually exit until all
 | 
						|
			 * of the file descriptors are closed, and
 | 
						|
			 * the release function is called.
 | 
						|
			 */
 | 
						|
			dead = get_signal(&ksig);
 | 
						|
			if (dead)
 | 
						|
				clear_thread_flag(TIF_SIGPENDING);
 | 
						|
		}
 | 
						|
 | 
						|
		/* mb paired w/ vhost_task_stop */
 | 
						|
		set_current_state(TASK_INTERRUPTIBLE);
 | 
						|
 | 
						|
		if (test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags)) {
 | 
						|
			__set_current_state(TASK_RUNNING);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		did_work = vtsk->fn(vtsk->data);
 | 
						|
		if (!did_work)
 | 
						|
			schedule();
 | 
						|
	}
 | 
						|
 | 
						|
	complete(&vtsk->exited);
 | 
						|
	do_exit(0);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * vhost_task_wake - wakeup the vhost_task
 | 
						|
 * @vtsk: vhost_task to wake
 | 
						|
 *
 | 
						|
 * wake up the vhost_task worker thread
 | 
						|
 */
 | 
						|
void vhost_task_wake(struct vhost_task *vtsk)
 | 
						|
{
 | 
						|
	wake_up_process(vtsk->task);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(vhost_task_wake);
 | 
						|
 | 
						|
/**
 | 
						|
 * vhost_task_stop - stop a vhost_task
 | 
						|
 * @vtsk: vhost_task to stop
 | 
						|
 *
 | 
						|
 * vhost_task_fn ensures the worker thread exits after
 | 
						|
 * VHOST_TASK_FLAGS_SOP becomes true.
 | 
						|
 */
 | 
						|
void vhost_task_stop(struct vhost_task *vtsk)
 | 
						|
{
 | 
						|
	set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
 | 
						|
	vhost_task_wake(vtsk);
 | 
						|
	/*
 | 
						|
	 * Make sure vhost_task_fn is no longer accessing the vhost_task before
 | 
						|
	 * freeing it below.
 | 
						|
	 */
 | 
						|
	wait_for_completion(&vtsk->exited);
 | 
						|
	kfree(vtsk);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(vhost_task_stop);
 | 
						|
 | 
						|
/**
 | 
						|
 * vhost_task_create - create a copy of a task to be used by the kernel
 | 
						|
 * @fn: vhost worker function
 | 
						|
 * @arg: data to be passed to fn
 | 
						|
 * @name: the thread's name
 | 
						|
 *
 | 
						|
 * This returns a specialized task for use by the vhost layer or NULL on
 | 
						|
 * failure. The returned task is inactive, and the caller must fire it up
 | 
						|
 * through vhost_task_start().
 | 
						|
 */
 | 
						|
struct vhost_task *vhost_task_create(bool (*fn)(void *), void *arg,
 | 
						|
				     const char *name)
 | 
						|
{
 | 
						|
	struct kernel_clone_args args = {
 | 
						|
		.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,
 | 
						|
	};
 | 
						|
	struct vhost_task *vtsk;
 | 
						|
	struct task_struct *tsk;
 | 
						|
 | 
						|
	vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
 | 
						|
	if (!vtsk)
 | 
						|
		return NULL;
 | 
						|
	init_completion(&vtsk->exited);
 | 
						|
	vtsk->data = arg;
 | 
						|
	vtsk->fn = fn;
 | 
						|
 | 
						|
	args.fn_arg = vtsk;
 | 
						|
 | 
						|
	tsk = copy_process(NULL, 0, NUMA_NO_NODE, &args);
 | 
						|
	if (IS_ERR(tsk)) {
 | 
						|
		kfree(vtsk);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	vtsk->task = tsk;
 | 
						|
	return vtsk;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(vhost_task_create);
 | 
						|
 | 
						|
/**
 | 
						|
 * vhost_task_start - start a vhost_task created with vhost_task_create
 | 
						|
 * @vtsk: vhost_task to wake up
 | 
						|
 */
 | 
						|
void vhost_task_start(struct vhost_task *vtsk)
 | 
						|
{
 | 
						|
	wake_up_new_task(vtsk->task);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(vhost_task_start);
 |