diff options
author | Thomas Gleixner <tglx@linutronix.de> | 2023-04-25 20:49:03 +0200 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2023-06-18 22:41:49 +0200 |
commit | ae88967d71f1b4ffb6e48043993d37a106da8109 (patch) | |
tree | fb3b4fcae5a77ccda5557f0205594d878eca20f5 /kernel/time | |
parent | posix-timers: Cleanup comments about timer ID tracking (diff) | |
download | linux-ae88967d71f1b4ffb6e48043993d37a106da8109.tar.xz linux-ae88967d71f1b4ffb6e48043993d37a106da8109.zip |
posix-timers: Add comments about timer lookup
Document how the timer ID validation in the hash table works.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Frederic Weisbecker <frederic@kernel.org>
Link: https://lore.kernel.org/r/20230425183313.091081515@linutronix.de
Diffstat (limited to 'kernel/time')
-rw-r--r-- | kernel/time/posix-timers.c | 39 |
1 files changed, 32 insertions, 7 deletions
diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 79909a2ac3e4..d7890ac703ae 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -506,6 +506,12 @@ static int do_timer_create(clockid_t which_clock, struct sigevent *event, return -EAGAIN; spin_lock_init(&new_timer->it_lock); + + /* + * Add the timer to the hash table. The timer is not yet valid + * because new_timer::it_signal is still NULL. The timer id is also + * not yet visible to user space. + */ new_timer_id = posix_timer_add(new_timer); if (new_timer_id < 0) { error = new_timer_id; @@ -551,6 +557,7 @@ static int do_timer_create(clockid_t which_clock, struct sigevent *event, goto out; spin_lock_irq(¤t->sighand->siglock); + /* This makes the timer valid in the hash table */ new_timer->it_signal = current->signal; list_add(&new_timer->list, ¤t->signal->posix_timers); spin_unlock_irq(¤t->sighand->siglock); @@ -597,13 +604,6 @@ COMPAT_SYSCALL_DEFINE3(timer_create, clockid_t, which_clock, } #endif -/* - * Locking issues: We need to protect the result of the id look up until - * we get the timer locked down so it is not deleted under us. The - * removal is done under the idr spinlock so we use that here to bridge - * the find to the timer lock. To avoid a dead lock, the timer id MUST - * be release with out holding the timer lock. - */ static struct k_itimer *__lock_timer(timer_t timer_id, unsigned long *flags) { struct k_itimer *timr; @@ -615,10 +615,35 @@ static struct k_itimer *__lock_timer(timer_t timer_id, unsigned long *flags) if ((unsigned long long)timer_id > INT_MAX) return NULL; + /* + * The hash lookup and the timers are RCU protected. + * + * Timers are added to the hash in invalid state where + * timr::it_signal == NULL. timer::it_signal is only set after the + * rest of the initialization succeeded. + * + * Timer destruction happens in steps: + * 1) Set timr::it_signal to NULL with timr::it_lock held + * 2) Release timr::it_lock + * 3) Remove from the hash under hash_lock + * 4) Call RCU for removal after the grace period + * + * Holding rcu_read_lock() accross the lookup ensures that + * the timer cannot be freed. + * + * The lookup validates locklessly that timr::it_signal == + * current::it_signal and timr::it_id == @timer_id. timr::it_id + * can't change, but timr::it_signal becomes NULL during + * destruction. + */ rcu_read_lock(); timr = posix_timer_by_id(timer_id); if (timr) { spin_lock_irqsave(&timr->it_lock, *flags); + /* + * Validate under timr::it_lock that timr::it_signal is + * still valid. Pairs with #1 above. + */ if (timr->it_signal == current->signal) { rcu_read_unlock(); return timr; |