diff options
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/task_work.c | 22 | 
1 files changed, 16 insertions, 6 deletions
diff --git a/kernel/task_work.c b/kernel/task_work.c index f13ec0bda1d..65bd3c92d6f 100644 --- a/kernel/task_work.c +++ b/kernel/task_work.c @@ -2,16 +2,17 @@  #include <linux/task_work.h>  #include <linux/tracehook.h> +static struct callback_head work_exited; /* all we need is ->next == NULL */ +  int  task_work_add(struct task_struct *task, struct callback_head *work, bool notify)  {  	struct callback_head *head; -	/* -	 * Not inserting the new work if the task has already passed -	 * exit_task_work() is the responisbility of callers. -	 */ +  	do {  		head = ACCESS_ONCE(task->task_works); +		if (unlikely(head == &work_exited)) +			return -ESRCH;  		work->next = head;  	} while (cmpxchg(&task->task_works, head, work) != head); @@ -30,7 +31,7 @@ task_work_cancel(struct task_struct *task, task_work_func_t func)  	 * If cmpxchg() fails we continue without updating pprev.  	 * Either we raced with task_work_add() which added the  	 * new entry before this work, we will find it again. Or -	 * we raced with task_work_run(), *pprev == NULL. +	 * we raced with task_work_run(), *pprev == NULL/exited.  	 */  	raw_spin_lock_irqsave(&task->pi_lock, flags);  	while ((work = ACCESS_ONCE(*pprev))) { @@ -51,7 +52,16 @@ void task_work_run(void)  	struct callback_head *work, *head, *next;  	for (;;) { -		work = xchg(&task->task_works, NULL); +		/* +		 * work->func() can do task_work_add(), do not set +		 * work_exited unless the list is empty. +		 */ +		do { +			work = ACCESS_ONCE(task->task_works); +			head = !work && (task->flags & PF_EXITING) ? +				&work_exited : NULL; +		} while (cmpxchg(&task->task_works, work, head) != work); +  		if (!work)  			break;  		/*  |