task_work.c 2.2 KB
Newer Older
1 2 3 4
#include <linux/spinlock.h>
#include <linux/task_work.h>
#include <linux/tracehook.h>

5 6
static struct callback_head work_exited; /* all we need is ->next == NULL */

7
int
8
task_work_add(struct task_struct *task, struct callback_head *work, bool notify)
9
{
10
	struct callback_head *head;
11

12 13
	do {
		head = ACCESS_ONCE(task->task_works);
14 15
		if (unlikely(head == &work_exited))
			return -ESRCH;
16 17
		work->next = head;
	} while (cmpxchg(&task->task_works, head, work) != head);
18

19
	if (notify)
20
		set_notify_resume(task);
21
	return 0;
22 23
}

24
struct callback_head *
25 26
task_work_cancel(struct task_struct *task, task_work_func_t func)
{
27
	struct callback_head **pprev = &task->task_works;
O
Oleg Nesterov 已提交
28
	struct callback_head *work;
29
	unsigned long flags;
30 31 32 33
	/*
	 * 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
34
	 * we raced with task_work_run(), *pprev == NULL/exited.
35
	 */
36
	raw_spin_lock_irqsave(&task->pi_lock, flags);
37
	while ((work = ACCESS_ONCE(*pprev))) {
O
Oleg Nesterov 已提交
38
		smp_read_barrier_depends();
39 40 41 42
		if (work->func != func)
			pprev = &work->next;
		else if (cmpxchg(pprev, work, work->next) == work)
			break;
43 44
	}
	raw_spin_unlock_irqrestore(&task->pi_lock, flags);
45 46

	return work;
47 48 49 50 51
}

void task_work_run(void)
{
	struct task_struct *task = current;
52
	struct callback_head *work, *head, *next;
53

54
	for (;;) {
55 56 57 58 59 60 61 62 63 64
		/*
		 * 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);

65 66 67 68 69 70 71 72 73
		if (!work)
			break;
		/*
		 * Synchronize with task_work_cancel(). It can't remove
		 * the first entry == work, cmpxchg(task_works) should
		 * fail, but it can play with *work and other entries.
		 */
		raw_spin_unlock_wait(&task->pi_lock);
		smp_mb();
74

75 76 77 78 79 80 81 82
		/* Reverse the list to run the works in fifo order */
		head = NULL;
		do {
			next = work->next;
			work->next = head;
			head = work;
			work = next;
		} while (work);
83

84 85 86 87 88
		work = head;
		do {
			next = work->next;
			work->func(work);
			work = next;
89
			cond_resched();
90
		} while (work);
91 92
	}
}