[tip: timers/urgent] posix-timers: Report zero remaining time for disarmed SIGEV_NONE timers

From: tip-bot2 for Hung-Jui Lin

Date: Sun Oct 25 2026 - 17:48:25 EST


The following commit has been merged into the timers/urgent branch of tip:

Commit-ID: 141a27750ec3d91ad9d1ad1764ebb5e51b963478
Gitweb: https://git.kernel.org/tip/141a27750ec3d91ad9d1ad1764ebb5e51b963478
Author: Hung-Jui Lin <alex.lin.programmer@xxxxxxxxx>
AuthorDate: Fri, 23 Oct 2026 23:14:10 +08:00
Committer: Thomas Gleixner <tglx@xxxxxxxxxx>
CommitterDate: Sun, 25 Oct 2026 22:43:19 +01:00

posix-timers: Report zero remaining time for disarmed SIGEV_NONE timers

SIGEV_NONE timers are never queued. common_timer_get() emulates their
expiry by computing the remaining time from the stored expiry value.
Because an armed SIGEV_NONE oneshot timer is not queued, its it_status
stays POSIX_TIMER_DISARMED. So common_timer_get() cannot use it_status to
skip a disarmed SIGEV_NONE timer and evaluates the stored expiry value for
all SIGEV_NONE timers.

Disarming a timer with timer_settime() and a zero it_value cancels the
timer and sets it_status to POSIX_TIMER_DISARMED, but leaves the expiry
value unchanged. As a consequence, timer_gettime() on a disarmed SIGEV_NONE
timer reports the remaining time of the previous arming until that time has
passed, instead of zero as POSIX requires for a disarmed timer. Example
with a CLOCK_MONOTONIC timer armed with it_value = 10ms and it_interval =
100ms, then disarmed with an all-zero itimerspec:

armed: it_value = 0.009999000 it_interval = 0.100000000
disarmed: it_value = 0.008700792 it_interval = 0.000000000

SIGEV_SIGNAL timers and SIGEV_NONE CPU timers report zero in the same
situation. All clocks which use common_timer_get() are affected, including
the alarm timer clocks.

Mark SIGEV_NONE timers as POSIX_TIMER_ARMED when they are armed, so that
common_timer_get() returns early for every disarmed timer. Forward
SIGEV_NONE interval timers in common_timer_get() regardless of it_status,
as they are never queued and therefore never forwarded by the expiry code.

After the change the disarmed timer above reports zero. Armed SIGEV_NONE
oneshot and interval timers on CLOCK_MONOTONIC, CLOCK_REALTIME,
CLOCK_BOOTTIME and CLOCK_BOOTTIME_ALARM still report their remaining time,
and the posix_timers selftest passes.

Fixes: becf8b5d00f4 ("[PATCH] hrtimer: convert posix timers completely")
Signed-off-by: Hung-Jui Lin <alex.lin.programmer@xxxxxxxxx>
Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Link: https://patch.msgid.link/20261023151411.28685-1-alex.lin.programmer@xxxxxxxxx
---
kernel/time/posix-timers.c | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index 436ba79..4c80c2d 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -694,16 +694,8 @@ void common_timer_get(struct k_itimer *timr, struct itimerspec64 *cur_setting)
if (iv) {
cur_setting->it_interval = ktime_to_timespec64(iv);
} else if (timr->it_status == POSIX_TIMER_DISARMED) {
- /*
- * SIGEV_NONE oneshot timers are never queued and therefore
- * timr->it_status is always DISARMED. The check below
- * vs. remaining time will handle this case.
- *
- * For all other timers there is nothing to update here, so
- * return.
- */
- if (!sig_none)
- return;
+ /* Nothing to update for a disarmed timer */
+ return;
}

now = kc->clock_get_ktime(timr->it_clock);
@@ -713,7 +705,7 @@ void common_timer_get(struct k_itimer *timr, struct itimerspec64 *cur_setting)
* is a SIGEV_NONE timer move the expiry time forward by intervals,
* so expiry is > now.
*/
- if (iv && timr->it_status != POSIX_TIMER_ARMED)
+ if (iv && (sig_none || timr->it_status != POSIX_TIMER_ARMED))
timr->it_overrun += kc->timer_forward(timr, now);

remaining = kc->timer_remaining(timr, now);
@@ -915,8 +907,15 @@ int common_timer_set(struct k_itimer *timr, int flags,
sigev_none = timr->it_sigev_notify == SIGEV_NONE;

if (kc->timer_arm(timr, expires, flags & TIMER_ABSTIME, sigev_none)) {
- if (!sigev_none)
- timr->it_status = POSIX_TIMER_ARMED;
+ /*
+ * SIGEV_NONE timers are not queued, but mark them armed
+ * so that common_timer_get() can tell them apart from
+ * disarmed timers. An expired SIGEV_NONE timer stays
+ * armed, which is harmless: common_timer_get() reports
+ * zero remaining time for it and it never reaches the
+ * signal delivery code.
+ */
+ timr->it_status = POSIX_TIMER_ARMED;
} else {
/* Timer was already expired, queue the signal */
posix_timer_queue_signal(timr);