diff options
author | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2014-08-04 09:10:23 -0400 |
---|---|---|
committer | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2014-09-07 19:27:22 -0400 |
commit | 3f95aa81d265223fdb13ea2b59883766a05adbdf (patch) | |
tree | 5b6d2c42aaf8b20397bd09c0ac31738618f57046 /kernel/exit.c | |
parent | 53c6d4edf874d3cbc031a53738c6cba9277faea5 (diff) |
rcu: Make TASKS_RCU handle tasks that are almost done exiting
Once a task has passed exit_notify() in the do_exit() code path, it
is no longer on the task lists, and is therefore no longer visible
to rcu_tasks_kthread(). This means that an almost-exited task might
be preempted while within a trampoline, and this task won't be waited
on by rcu_tasks_kthread(). This commit fixes this bug by adding an
srcu_struct. An exiting task does srcu_read_lock() just before calling
exit_notify(), and does the corresponding srcu_read_unlock() after
doing the final preempt_disable(). This means that rcu_tasks_kthread()
can do synchronize_srcu() to wait for all mostly-exited tasks to reach
their final preempt_disable() region, and then use synchronize_sched()
to wait for those tasks to finish exiting.
Reported-by: Oleg Nesterov <oleg@redhat.com>
Suggested-by: Lai Jiangshan <laijs@cn.fujitsu.com>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Diffstat (limited to 'kernel/exit.c')
-rw-r--r-- | kernel/exit.c | 3 |
1 files changed, 3 insertions, 0 deletions
diff --git a/kernel/exit.c b/kernel/exit.c index 32c58f7433a3..d13f2eec4bb8 100644 --- a/kernel/exit.c +++ b/kernel/exit.c | |||
@@ -667,6 +667,7 @@ void do_exit(long code) | |||
667 | { | 667 | { |
668 | struct task_struct *tsk = current; | 668 | struct task_struct *tsk = current; |
669 | int group_dead; | 669 | int group_dead; |
670 | TASKS_RCU(int tasks_rcu_i); | ||
670 | 671 | ||
671 | profile_task_exit(tsk); | 672 | profile_task_exit(tsk); |
672 | 673 | ||
@@ -775,6 +776,7 @@ void do_exit(long code) | |||
775 | */ | 776 | */ |
776 | flush_ptrace_hw_breakpoint(tsk); | 777 | flush_ptrace_hw_breakpoint(tsk); |
777 | 778 | ||
779 | TASKS_RCU(tasks_rcu_i = __srcu_read_lock(&tasks_rcu_exit_srcu)); | ||
778 | exit_notify(tsk, group_dead); | 780 | exit_notify(tsk, group_dead); |
779 | proc_exit_connector(tsk); | 781 | proc_exit_connector(tsk); |
780 | #ifdef CONFIG_NUMA | 782 | #ifdef CONFIG_NUMA |
@@ -814,6 +816,7 @@ void do_exit(long code) | |||
814 | if (tsk->nr_dirtied) | 816 | if (tsk->nr_dirtied) |
815 | __this_cpu_add(dirty_throttle_leaks, tsk->nr_dirtied); | 817 | __this_cpu_add(dirty_throttle_leaks, tsk->nr_dirtied); |
816 | exit_rcu(); | 818 | exit_rcu(); |
819 | TASKS_RCU(__srcu_read_unlock(&tasks_rcu_exit_srcu, tasks_rcu_i)); | ||
817 | 820 | ||
818 | /* | 821 | /* |
819 | * The setting of TASK_RUNNING by try_to_wake_up() may be delayed | 822 | * The setting of TASK_RUNNING by try_to_wake_up() may be delayed |