[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
pointless.

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(-)

--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -1141,20 +1141,34 @@ void run_posix_cpu_timers(void)
{
struct task_struct *tsk = current;
struct k_itimer *timer, *next;
- unsigned long flags;
LIST_HEAD(firing);

lockdep_assert_irqs_disabled();

/*
- * 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)
return;

- if (!lock_task_sighand(tsk, &flags))
+ /*
+ * Now do a fast check for expired task and group timers. If
+ * no expired timers found, return.
+ */
+ if(!fastpath_timer_check(tsk))
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.
+ */
+ spin_lock(&tsk->sighand->siglock);
+
/*
* 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);
+ spin_unlock(&tsk->sighand->siglock);

/*
* Now that all the timers on our list have the firing flag,