Use RCU to protect tasklist for unicast signals
Subsystems (4)
RFC PATCH 1/1 Use RCU to protect tasklist for unicast signals
Hello!
This patch is an experiment in use of RCU for individual code paths that read-acquire the tasklist lock, in this case, unicast signal delivery. It passes five kernbenches on 4-CPU x86, but obviously needs much more testing before it is considered for serious use, let alone inclusion.
My main question is whether I have the POSIX semantics covered. I believe that I do, but thought I should check with people who are more familiar with POSIX than am I.
For the record, some shortcomings of this patch:
o Needs lots more testing on more architectures.
o Needs performance and stress testing.
o Needs testing in Ingo's PREEMPT_RT environment.
o Uses cmpxchg(), which is currently architecture dependent.
This can be fixed, for example, by using the hashed locks proposed in an earlier patch from Dipankar:
http://marc.theaimsgroup.com/?l=linux-kernel&m=111875978502912&w=2
Thoughts?
Thanx, Paul
---
include/linux/sched.h | 27 +++++++++++++++++++++++++-- kernel/sched.c | 5 +++++ kernel/signal.c | 8 ++++++-- 3 files changed, 36 insertions(+), 4 deletions(-)
include/linux/sched.h +25 -2
@@ -34,6 +34,7 @@#include <linux/percpu.h> #include <linux/topology.h> #include <linux/seccomp.h> +#include <linux/rcupdate.h> struct exec_domain ;
@@ -770,6 +771,7 @@ struct task_struct {int cpuset_mems_generation;#endif atomic_t fs_excl;/* holding fs exclusive resources */ +struct rcu_head rcu; };static inline pid_t process_group (struct task_struct *tsk)
@@ -793,8 +795,29 @@ static inline int pid_alive(struct task_extern void free_task (struct task_struct *tsk);extern void __put_task_struct (struct task_struct *tsk);#define get_task_struct (tsk) do { atomic_inc(&(tsk)->usage); } while(0) -#define put_task_struct (tsk) \ -do { if (atomic_dec_and_test(&(tsk)->usage)) __put_task_struct(tsk); } while(0) + +static inline int get_task_struct_rcu (struct task_struct *t) +{ +int oldusage; + +do { + oldusage =atomic_read (&t->usage); +if (oldusage ==0 ) { +return 0 ; + } + }while (cmpxchg (&t->usage.counter, + oldusage, oldusage +1 ) != oldusage); +return 1 ; +} + +extern void __put_task_struct_cb (struct rcu_head *rhp); + +static inline void put_task_struct (struct task_struct *t) +{ +if (atomic_dec_and_test (&t->usage)) { +call_rcu (&t->rcu, __put_task_struct_cb); + } +}/* * Per process flags
kernel/sched.c +5 -0
@@ -176,6 +176,11 @@ static unsigned int task_timeslice(task_#define task_hot (p, now, sd) ((long long) ((now) - (p)->last_ran) \ < (long long) (sd)->cache_hot_time) +void __put_task_struct_cb (struct rcu_head *rhp) +{ +__put_task_struct (container_of(rhp ,struct task_struct ,rcu )); +} +/* * These are the runqueue data structures: */
kernel/signal.c +6 -2
@@ -1151,9 +1151,13 @@ int group_send_sig_info(int sig, struct ret =check_kill_permission (sig, info, p);if (!ret && sig && p->sighand) { +if (!get_task_struct_rcu (p)) { +return -ESRCH ; + }spin_lock_irqsave (&p->sighand->siglock, flags); ret =__group_send_sig_info (sig, info, p);spin_unlock_irqrestore (&p->sighand->siglock, flags); +put_task_struct (p); }return ret;
@@ -1200,12 +1204,12 @@ kill_proc_info(int sig, struct siginfo *int error;struct task_struct *p; -read_lock (&tasklist_lock); +rcu_read_lock (); p =find_task_by_pid (pid); error = -ESRCH ;if (p) error =group_send_sig_info (sig, info, p); -read_unlock (&tasklist_lock); +rcu_read_unlock ();return error; }
Quoted 12 lines · Paul E. McKenney
Hello!
This patch is an experiment in use of RCU for individual code paths that read-acquire the tasklist lock, in this case, unicast signal delivery. It passes five kernbenches on 4-CPU x86, but obviously needs much more testing before it is considered for serious use, let alone inclusion.
My main question is whether I have the POSIX semantics covered. I believe that I do, but thought I should check with people who are more familiar with POSIX than am I.
Quoted 7 lines
For the record, some shortcomings of this patch:
o Needs lots more testing on more architectures.
o Needs performance and stress testing.
o Needs testing in Ingo's
PREEMPT_RTenvironment.
cool patch! I have integrated it into my PREEMPT_RT tree, and all it needed to boot was the patch below (doesnt affect the upstream kernel). Using the raw IRQ flag isnt an issue in the RCU code, all the affected codepaths are small and deterministic.
(without this patch it locked up after detecting IRQ7 - not sure why.)
kernel still works fine after some (mostly light) testing.
Ingo
kernel/rcupdate.c +9 -9
@@ -134,11 +134,11 @@ void fastcall call_rcu(struct rcu_head * head->func = func; head->next =NULL ; -local_irq_save (flags); +raw_local_irq_save (flags); rdp = &__get_cpu_var (rcu_data); *rdp->nxttail = head; rdp->nxttail = &head->next; -local_irq_restore (flags); +raw_local_irq_restore (flags); }/**
@@ -165,11 +165,11 @@ void fastcall call_rcu_bh(struct rcu_hea head->func = func; head->next =NULL ; -local_irq_save (flags); +raw_local_irq_save (flags); rdp = &__get_cpu_var (rcu_bh_data); *rdp->nxttail = head; rdp->nxttail = &head->next; -local_irq_restore (flags); +raw_local_irq_restore (flags); }/*
@@ -305,11 +305,11 @@ static void rcu_check_quiescent_state(ststatic void rcu_move_batch (struct rcu_data *this_rdp,struct rcu_head *list,struct rcu_head **tail) { -local_irq_disable (); +raw_local_irq_disable (); *this_rdp->nxttail = list;if (list) this_rdp->nxttail = tail; -local_irq_enable (); +raw_local_irq_enable (); } static void __rcu_offline_cpu(struct rcu_data *this_rdp,
@@ -362,13 +362,13 @@ static void __rcu_process_callbacks(stru rdp->curtail = &rdp->curlist; } -local_irq_disable (); +raw_local_irq_disable ();if (rdp->nxtlist && !rdp->curlist) { rdp->curlist = rdp->nxtlist; rdp->curtail = rdp->nxttail; rdp->nxtlist =NULL ; rdp->nxttail = &rdp->nxtlist; -local_irq_enable (); +raw_local_irq_enable ();/* * start the next batch of callbacks
@@ -388,7 +388,7 @@ static void __rcu_process_callbacks(struspin_unlock (&rsp->lock); } }else { -local_irq_enable (); +raw_local_irq_enable (); }rcu_check_quiescent_state (rcp, rsp, rdp);if (rdp->donelist)
Quoted 21 lines · Ingo Molnar
Quoted 18 lines · Paul E. McKenney
Hello!
This patch is an experiment in use of RCU for individual code paths that read-acquire the tasklist lock, in this case, unicast signal delivery. It passes five kernbenches on 4-CPU x86, but obviously needs much more testing before it is considered for serious use, let alone inclusion.
My main question is whether I have the POSIX semantics covered. I believe that I do, but thought I should check with people who are more familiar with POSIX than am I.
For the record, some shortcomings of this patch:
o Needs lots more testing on more architectures.
o Needs performance and stress testing.
Quoted 8 lines
Quoted 1 line
o Needs testing in Ingo's
PREEMPT_RTenvironment.cool patch! I have integrated it into my
PREEMPT_RTtree, and all it needed to boot was the patch below (doesnt affect the upstream kernel). Using the raw IRQ flag isnt an issue in the RCU code, all the affected codepaths are small and deterministic.(without this patch it locked up after detecting IRQ7 - not sure why.)
Without this patch on an older version of PREEMPT_RT (V0.7.52-12), it would boot, pass kernbench, but fail LTP. Passed both on a stock kernel.
Will re-run with your patch. ;-)
Quoted 1 line
kernel still works fine after some (mostly light) testing.
Cool! Next step for me is to run some focussed stress tests.
Thanx, Paul
Quoted 74 lines
Ingo
Index:
linux/kernel/rcupdate.c
===================================================================kernel/rcupdate.c +9
-9@@ -134,11 +134,11 @@ void fastcall call_rcu(struct rcu_head * head->func = func; head->next =NULL ; -local_irq_save (flags); +raw_local_irq_save (flags); rdp = &__get_cpu_var (rcu_data); *rdp->nxttail = head; rdp->nxttail = &head->next; -local_irq_restore (flags); +raw_local_irq_restore (flags); }/** @@ -165,11 +165,11 @@ void fastcall call_rcu_bh(struct rcu_hea head->func = func; head->next =NULL ; -local_irq_save (flags); +raw_local_irq_save (flags); rdp = &__get_cpu_var (rcu_bh_data); *rdp->nxttail = head; rdp->nxttail = &head->next; -local_irq_restore (flags); +raw_local_irq_restore (flags); }/* @@ -305,11 +305,11 @@ static void rcu_check_quiescent_state(ststatic void rcu_move_batch (struct rcu_data *this_rdp,struct rcu_head *list,struct rcu_head **tail) { -local_irq_disable (); +raw_local_irq_disable (); *this_rdp->nxttail = list;if (list) this_rdp->nxttail = tail; -local_irq_enable (); +raw_local_irq_enable (); } static void __rcu_offline_cpu(struct rcu_data *this_rdp,@@ -362,13 +362,13 @@ static void __rcu_process_callbacks(stru rdp->curtail = &rdp->curlist; } -local_irq_disable (); +raw_local_irq_disable ();if (rdp->nxtlist && !rdp->curlist) { rdp->curlist = rdp->nxtlist; rdp->curtail = rdp->nxttail; rdp->nxtlist =NULL ; rdp->nxttail = &rdp->nxtlist; -local_irq_enable (); +raw_local_irq_enable ();/* * start the next batch of callbacks @@ -388,7 +388,7 @@ static void __rcu_process_callbacks(struspin_unlock (&rsp->lock); } }else { -local_irq_enable (); +raw_local_irq_enable (); }rcu_check_quiescent_state (rcp, rsp, rdp);if (rdp->donelist)
Quoted 4 lines · Ingo Molnar
Quoted 4 lines
For the record, some shortcomings of this patch:
o Needs lots more testing on more architectures.
Quoted 9 lines
Quoted 3 lines
o Needs performance and stress testing.
o Needs testing in Ingo's
PREEMPT_RTenvironment.cool patch! I have integrated it into my
PREEMPT_RTtree, and all it needed to boot was the patch below (doesnt affect the upstream kernel). Using the raw IRQ flag isnt an issue in the RCU code, all the affected codepaths are small and deterministic.
Ingo,
Doesn't this fix the longest latency we were seeing with PREEMPT_DESKTOP, I don't have a trace handy but the upshot was "signal delivery must remain atomic on !PREEMPT_RT"?
Lee
Quoted 3 lines · Lee Revell
Doesn't this fix the longest latency we were seeing with
PREEMPT_DESKTOP, I don't have a trace handy but the upshot was "signal delivery must remain atomic on !PREEMPT_RT"?
yes - although Paul's patch converts only a portion of the signal code to RCU-read-lock, so i'd expect there to be other latencies too. Might be worth a test (once we've sorted out the HRT build bugs).
Ingo
Quoted 10 lines · Ingo Molnar
Quoted 3 lines · Lee Revell
Doesn't this fix the longest latency we were seeing with
PREEMPT_DESKTOP, I don't have a trace handy but the upshot was "signal delivery must remain atomic on !PREEMPT_RT"?yes - although Paul's patch converts only a portion of the signal code to RCU-read-lock, so i'd expect there to be other latencies too. Might be worth a test (once we've sorted out the HRT build bugs).
And there remains the question of how much of the benefit remains after I handle the corner cases... :-/
Thanx, Paul
Quoted 6 lines · Paul E. McKenney
Hello!
This patch is an experiment in use of RCU for individual code paths that read-acquire the tasklist lock, in this case, unicast signal delivery. It passes five kernbenches on 4-CPU x86, but obviously needs much more testing before it is considered for serious use, let alone inclusion.
I think we should switch over tasklist_lock to RCU completely instead of adding suck hacks. I've started lots of preparation work to get rid of tasklist_lock users outside of kernel/, especialy getting rid of any use in modules.
Quoted 8 lines · Christoph Hellwig
Quoted 6 lines · Paul E. McKenney
Hello!
This patch is an experiment in use of RCU for individual code paths that read-acquire the tasklist lock, in this case, unicast signal delivery. It passes five kernbenches on 4-CPU x86, but obviously needs much more testing before it is considered for serious use, let alone inclusion.
Quoted 4 lines
I think we should switch over
tasklist_lockto RCU completely instead of adding suck hacks. I've started lots of preparation work to get rid oftasklist_lockusers outside ofkernel/, especialy getting rid of any use in modules.
I agree fully with your end goal -- I would certainly look funny disagreeing, right? ;-) And Oleg pointed out an area I missed in my patch, am fixing, but in the meantime it most certainly does qualify as a suck hack. :-/ Working on it...
But one of the cool things about RCU is that we can make this change incrementally, with a series of small patches. We can have some of the read paths protected by RCU and others continuing to be protected by read_lock(), so that we can avoid the need for a "flag day" that hits all 300+ uses of tasklist_lock.
FWIW, the approach that I am taking to fix the bug Oleg and Christoph spotted is roughly as follows:
o Add "struct rcu_head rcu", "struct sighand_struct *successor",
and "int deleted" to struct sighand_struct. This allows reliable signal delivery in face of sighand_struct replacements. If an RCU reader finds (deleted && !successor), that reader is trying to signal a dying process, so gives up. If an RCU reader instead finds (deleted && successor), the reader traverses the successor pointer to find the current version.
o Apply call_rcu() to deletion of struct sighand_struct, with
the exception of a couple of failure paths in exec.c, where the sighand_struct was never exposed to RCU readers.
Thoughts?
Thanx, Paul
Quoted 8 lines · Christoph Hellwig
Quoted 6 lines · Paul E. McKenney
Hello!
This patch is an experiment in use of RCU for individual code paths that read-acquire the tasklist lock, in this case, unicast signal delivery. It passes five kernbenches on 4-CPU x86, but obviously needs much more testing before it is considered for serious use, let alone inclusion.
Quoted 4 lines
I think we should switch over
tasklist_lockto RCU completely instead of adding suck hacks. I've started lots of preparation work to get rid oftasklist_lockusers outside ofkernel/, especialy getting rid of any use in modules.
That would be really helpful, specially the ones in drivers/char/tty*.c :)
When I worked on this last (a year or so ago), it seemed that I would need to put a number of additional structures under RCU control. It would be better to gradually move it towards RCU rather than trying make all the readers lock-free.
Thanks
Dipankar
Quoted 4 lines · Dipankar Sarma
When I worked on this last (a year or so ago), it seemed that I would need to put a number of additional structures under RCU control. It would be better to gradually move it towards RCU rather than trying make all the readers lock-free.
Just for reference, this was my tasks-rcu patch. 2.6.0-test2, no less :) I was interested in get_pid_list() at that time. IIRC, I tested it with lots of top running along with other tests.
Thanks
Dipankar
Incremental patch to do lockfree traversal of the task list using RCU. For now it just does one of the costlies ones in /proc.
fs/exec.c | 1 + fs/proc/base.c | 5 +++-- include/linux/sched.h | 21 ++++++++++++++++----- 3 files changed, 20 insertions(+), 7 deletions(-)
fs/exec.c renamed from fs/exec.c~tasks-rcu +1 -0
@@ -676,6 +676,7 @@ static inline int de_thread(struct task_list_del (¤t->tasks);list_add_tail (¤t->tasks, &init_task.tasks); +list_add_tail_rcu (¤t->tasks, &init_task.tasks); current->exit_signal =SIGCHLD ; state = leader->state;
fs/proc/base.c renamed from fs/proc/base.c~tasks-rcu +3 -2
@@ -32,6 +32,7 @@#include <linux/mount.h> #include <linux/security.h> #include <linux/ptrace.h> +#include <linux/rcupdate.h> /* * For hysterical raisins we keep the same inumbers as in the old procfs.
@@ -1403,7 +1404,7 @@ static int get_pid_list(int index, unsigint nr_pids =0 ; index--; -read_lock (&tasklist_lock); +rcu_read_lock ();for_each_process (p) {int pid = p->pid;if (!pid_alive (p))
@@ -1415,7 +1416,7 @@ static int get_pid_list(int index, unsigif (nr_pids >=PROC_MAXPIDS )break ; } -read_unlock (&tasklist_lock); +rcu_read_unlock ();return nr_pids; }
include/linux/sched.h renamed from include/linux/sched.h~tasks-rcu +16 -5
@@ -28,6 +28,7 @@#include <linux/completion.h> #include <linux/pid.h> #include <linux/percpu.h> +#include <linux/rcupdate.h> struct exec_domain ;
@@ -456,13 +457,23 @@ struct task_struct {struct io_context *io_context;unsigned long ptrace_message; +struct rcu_head rcu;siginfo_t *last_siginfo;/* For ptrace use. */ };extern void __put_task_struct (struct task_struct *tsk);#define get_task_struct (tsk) do { atomic_inc(&(tsk)->usage); } while(0) -#define put_task_struct (tsk) \ -do { if (atomic_dec_and_test(&(tsk)->usage)) __put_task_struct(tsk); } while(0) +static void put_task_struct_rcu (struct rcu_head *rcu) +{ + struct task_struct *tsk = container_of(rcu, struct task_struct, rcu); +__put_task_struct (tsk); +} +static inline void put_task_struct (struct task_struct *tsk) +{ +if (atomic_dec_and_test (&tsk->usage)) +call_rcu (&tsk->rcu, put_task_struct_rcu); +} +/* * Per process flags
@@ -675,13 +686,13 @@ extern void wait_task_inactive(task_t *#define REMOVE_LINKS (p) do { \ if (thread_group_leader(p)) \ - list_del_init(&(p)->tasks); \ + list_del_rcu(&(p)->tasks); \ remove_parent(p); \ } while (0)#define SET_LINKS (p) do { \ if (thread_group_leader(p)) \ - list_add_tail(&(p)->tasks,&init_task.tasks); \ + list_add_tail_rcu(&(p)->tasks,&init_task.tasks); \ add_parent(p, (p)->parent); \ } while (0)
@@ -689,7 +700,7 @@ extern void wait_task_inactive(task_t *#define prev_task (p) list_entry((p)->tasks.prev, struct task_struct, tasks)#define for_each_process (p) \ - for (p = &init_task ; (p = next_task(p)) != &init_task ; ) + for (p = &init_task ; (p = next_task(p)),({ read_barrier_depends(); 0;}),p != &init_task ; )/* * Careful: do_each_thread/while_each_thread is a double loop so
_