[patch 07/44] posix-cpu-timers: Simplify sighand locking in run_posix_cpu_timers()
From: Thomas Gleixner
Date: Mon Aug 19 2019 - 11:45:44 EST
run_posix_cpu_timers() is called from the timer interrupt. The posix timer
expiry always affects the current task which got interrupted.
sighand locking is only racy when done on a foreign task, which must use
lock_task_sighand(). But in case of run_posix_cpu_timers() that's
sighand of a task can only be dropped or changed by the task itself. Drop
happens in do_exit(), changing sighand happens in execve().
So the timer interrupt can see one of the following states of the current
task which it interrupted:
- executing: sighand is set
- exiting: sighand is either set or NULL
- execing: sighand is either the original one or the new one
The 'check -> lock -> check again' logic in lock_task_sighand() is not
required here at all. The state cannot change as the interrupted task
context obviously is not executing instructions.
So the only interesting case here is to check whether current->sighand is
NULL or not.
Add the check for sighand == NULL right at the beginning and replace
lock_task_sighand() with a regular spin_lock() invocation along with proper
comments why this is safe.
Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
kernel/time/posix-cpu-timers.c | 26 ++++++++++++++++++++------
1 file changed, 20 insertions(+), 6 deletions(-)
@@ -1141,20 +1141,34 @@ void run_posix_cpu_timers(void)
struct task_struct *tsk = current;
struct k_itimer *timer, *next;
- unsigned long flags;
- * The fast path checks that there are no expired thread or thread
- * group timers. If that's so, just return.
+ * Verify that sighand exits. If not, the task is exiting and has
+ * dropped sighand already. Without sighand, timer expiry is
+ * pointless.
- if (!fastpath_timer_check(tsk))
+ if (!tsk->sighand)
- if (!lock_task_sighand(tsk, &flags))
+ * Now do a fast check for expired task and group timers. If
+ * no expired timers found, return.
+ * The timer interrupt hit current. It's verified that sighand
+ * exists, so it cannot go away before the timer interrupt returns
+ * as only current can remove or change its own sighand in exit()
+ * or exec() with sighand lock held and interrupts disabled. Ergo
+ * the interrupt can only observe a valid sighand or NULL.
* Here we take off tsk->signal->cpu_timers[N] and
* tsk->cpu_timers[N] all the timers that are firing, and
@@ -1172,7 +1186,7 @@ void run_posix_cpu_timers(void)
* that gets the timer lock before we do will give it up and
* spin until we've taken care of that timer below.
- unlock_task_sighand(tsk, &flags);
* Now that all the timers on our list have the firing flag,