* [PATCH 1/3] alarmtimer: Reset the expiry time in alarm_init()
2026-10-11 5:19 [PATCH 0/3] timers: Report disarmed alarm timerfds and SIGEV_NONE timers as disarmed Shashank Mohan Jain
@ 2026-10-11 5:19 ` Shashank Mohan Jain
2026-10-11 5:19 ` [PATCH 2/3] posix-timers: Report a disarmed SIGEV_NONE timer as disarmed Shashank Mohan Jain
2026-10-11 5:19 ` [PATCH 3/3] selftests/timers: Test timer_gettime() on a disarmed SIGEV_NONE timer Shashank Mohan Jain
2 siblings, 0 replies; 4+ messages in thread
From: Shashank Mohan Jain @ 2026-10-11 5:19 UTC (permalink / raw)
To: Thomas Gleixner, John Stultz, Anna-Maria Behnsen, Frederic Weisbecker
Cc: Stephen Boyd, Miroslav Lichvar, Shuah Khan, Todd Poynor,
linux-kernel, linux-kselftest
timerfd_gettime() on a CLOCK_REALTIME_ALARM or CLOCK_BOOTTIME_ALARM
timerfd keeps reporting the remaining time of the previous setting
after the timer has been disarmed:
struct itimerspec arm = { .it_value.tv_sec = 100 };
struct itimerspec disarm = { }, cur;
fd = timerfd_create(CLOCK_BOOTTIME_ALARM, 0);
timerfd_settime(fd, 0, &arm, NULL);
timerfd_settime(fd, 0, &disarm, NULL);
timerfd_gettime(fd, &cur);
leaves 99.99... seconds in cur.it_value, and the value counts down
until the old expiry time has passed. The same value is returned as
old_value by the next timerfd_settime() and shown as it_value in
/proc/<pid>/fdinfo/<fd>. A zero it_value is how timerfd_gettime()
tells user space that a timer is disarmed, and the timerfds on the
hrtimer based clocks report zero here.
timerfd_setup() initializes the timer again on every
timerfd_settime(). For the hrtimer based clocks hrtimer_setup() clears
the expiry time, so timerfd_get_remaining() finds an expiry time in
the past for a disarmed timer and returns 0. alarm_init() does not
touch alarm->node.expires, timerqueue_init() only clears the rbtree
node, and alarm_expires_remaining() computes the remaining time from
the expiry time of the previous setting.
Reset the expiry time in alarm_init(), like hrtimer_setup() does for
a hrtimer. The other callers of alarm_init() start the alarm, which
sets the expiry time, before anything reads it.
Tested in qemu with a program doing the above for both alarm clocks
(the VM has an RTC, which timerfd does not need for these clocks):
it_value, old_value and fdinfo are zero after the disarm with this
change.
Fixes: 11ffa9d6065f ("timerfd: Add alarm timers")
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
Prepared with Claude Code (Anthropic), model Claude Opus 5.5
(claude-opus-5-5).
kernel/time/alarmtimer.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/time/alarmtimer.c b/kernel/time/alarmtimer.c
index ea5be5870e..d6dc6e8272 100644
--- a/kernel/time/alarmtimer.c
+++ b/kernel/time/alarmtimer.c
@@ -316,6 +316,7 @@ __alarm_init(struct alarm *alarm, enum alarmtimer_type type,
void (*function)(struct alarm *, ktime_t))
{
timerqueue_init(&alarm->node);
+ alarm->node.expires = 0;
alarm->function = function;
alarm->type = type;
alarm->state = ALARMTIMER_STATE_INACTIVE;
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] posix-timers: Report a disarmed SIGEV_NONE timer as disarmed
2026-10-11 5:19 [PATCH 0/3] timers: Report disarmed alarm timerfds and SIGEV_NONE timers as disarmed Shashank Mohan Jain
2026-10-11 5:19 ` [PATCH 1/3] alarmtimer: Reset the expiry time in alarm_init() Shashank Mohan Jain
@ 2026-10-11 5:19 ` Shashank Mohan Jain
2026-10-11 5:19 ` [PATCH 3/3] selftests/timers: Test timer_gettime() on a disarmed SIGEV_NONE timer Shashank Mohan Jain
2 siblings, 0 replies; 4+ messages in thread
From: Shashank Mohan Jain @ 2026-10-11 5:19 UTC (permalink / raw)
To: Thomas Gleixner, John Stultz, Anna-Maria Behnsen, Frederic Weisbecker
Cc: Stephen Boyd, Miroslav Lichvar, Shuah Khan, Todd Poynor,
linux-kernel, linux-kselftest
A SIGEV_NONE timer on a hrtimer or alarmtimer based clock is never
queued. The expiry time stored in the timer is all that
common_timer_get() has to compute the remaining time, and
common_timer_set() leaves it alone when the timer is disarmed with a
zero it_value. timer_gettime() then reports the remaining time of the
previous setting until that time has passed:
struct sigevent sev = { .sigev_notify = SIGEV_NONE };
struct itimerspec arm = { .it_value.tv_sec = 100 };
struct itimerspec disarm = { }, cur;
timer_create(CLOCK_MONOTONIC, &sev, &t);
timer_settime(t, 0, &arm, NULL);
timer_settime(t, 0, &disarm, NULL);
timer_gettime(t, &cur);
leaves 99.99... seconds in cur.it_value. POSIX wants a zero it_value
for a disarmed timer. Timers which deliver a signal get that from the
POSIX_TIMER_DISARMED check in common_timer_get(), and the CPU time
clocks set the expiry time to 0 in posix_cpu_timer_set(), so they are
not affected. CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_REALTIME and the
two alarm clocks are.
When a SIGEV_NONE timer is disarmed, move its expiry time into the
past. The timer_arm() callbacks only store the expiry time for such a
timer, so call them with an absolute expiry time of 0, which is not
after the current time of any of these clocks. common_timer_get() then
sees a remaining time which is not positive and reports a zero
it_value.
This is old behaviour: the sequence above gives the same result on a
v6.8 based distribution kernel. The commit which introduced it was
not searched for, hence no Fixes tag.
Tested in qemu with the sequence above on the five clocks, and with
the posix_timers selftest including the test added in the next patch.
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
Prepared with Claude Code (Anthropic), model Claude Opus 5.5
(claude-opus-5-5).
kernel/time/posix-timers.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index 436ba794cc..f6dd8d34a8 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -904,15 +904,23 @@ int common_timer_set(struct k_itimer *timr, int flags,
timr->it_status = POSIX_TIMER_DISARMED;
posix_timer_set_common(timr, new_setting);
+ sigev_none = timr->it_sigev_notify == SIGEV_NONE;
/* Keep timer disarmed when it_value is zero */
- if (!new_setting->it_value.tv_sec && !new_setting->it_value.tv_nsec)
+ if (!new_setting->it_value.tv_sec && !new_setting->it_value.tv_nsec) {
+ /*
+ * SIGEV_NONE timers are never queued. The expiry time is
+ * the only information timer_gettime() has to tell whether
+ * such a timer is armed, so move it into the past.
+ */
+ if (sigev_none)
+ kc->timer_arm(timr, 0, true, true);
return 0;
+ }
expires = timespec64_to_ktime(new_setting->it_value);
if (flags & TIMER_ABSTIME)
expires = timens_ktime_to_host(timr->it_clock, expires);
- sigev_none = timr->it_sigev_notify == SIGEV_NONE;
if (kc->timer_arm(timr, expires, flags & TIMER_ABSTIME, sigev_none)) {
if (!sigev_none)
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] selftests/timers: Test timer_gettime() on a disarmed SIGEV_NONE timer
2026-10-11 5:19 [PATCH 0/3] timers: Report disarmed alarm timerfds and SIGEV_NONE timers as disarmed Shashank Mohan Jain
2026-10-11 5:19 ` [PATCH 1/3] alarmtimer: Reset the expiry time in alarm_init() Shashank Mohan Jain
2026-10-11 5:19 ` [PATCH 2/3] posix-timers: Report a disarmed SIGEV_NONE timer as disarmed Shashank Mohan Jain
@ 2026-10-11 5:19 ` Shashank Mohan Jain
2 siblings, 0 replies; 4+ messages in thread
From: Shashank Mohan Jain @ 2026-10-11 5:19 UTC (permalink / raw)
To: Thomas Gleixner, John Stultz, Anna-Maria Behnsen, Frederic Weisbecker
Cc: Stephen Boyd, Miroslav Lichvar, Shuah Khan, Todd Poynor,
linux-kernel, linux-kselftest
Arm a SIGEV_NONE timer for 100 seconds, disarm it and check that
timer_gettime() reports a zero it_value.
Run the test for CLOCK_MONOTONIC, where it fails without the previous
patch, and for CLOCK_PROCESS_CPUTIME_ID, where it passes with and
without it.
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
Prepared with Claude Code (Anthropic), model Claude Opus 5.5
(claude-opus-5-5).
tools/testing/selftests/timers/posix_timers.c | 38 ++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
index a92d4b9577..b6e56c08ba 100644
--- a/tools/testing/selftests/timers/posix_timers.c
+++ b/tools/testing/selftests/timers/posix_timers.c
@@ -495,6 +495,40 @@ static void check_sigev_none(int which)
"check_sigev_none %s\n", name);
}
+static void check_sigev_none_disarm(int which)
+{
+ const char *name = clock_name(which);
+ struct itimerspec its;
+ struct sigevent sev;
+ timer_t timerid;
+
+ memset(&sev, 0, sizeof(sev));
+ sev.sigev_notify = SIGEV_NONE;
+
+ if (timer_create(which, &sev, &timerid))
+ fatal_error(name, "timer_create()");
+
+ /* Arm the timer to expire in 100 seconds and disarm it again */
+ memset(&its, 0, sizeof(its));
+ its.it_value.tv_sec = 100;
+ if (timer_settime(timerid, 0, &its, NULL))
+ fatal_error(name, "timer_settime()");
+
+ memset(&its, 0, sizeof(its));
+ if (timer_settime(timerid, 0, &its, NULL))
+ fatal_error(name, "timer_settime()");
+
+ /* A disarmed timer must report a zero it_value */
+ if (timer_gettime(timerid, &its))
+ fatal_error(name, "timer_gettime()");
+
+ if (timer_delete(timerid))
+ fatal_error(name, "timer_delete()");
+
+ ksft_test_result(!its.it_value.tv_sec && !its.it_value.tv_nsec,
+ "check_sigev_none_disarm %s\n", name);
+}
+
static void check_gettime(int which)
{
const char *name = clock_name(which);
@@ -680,7 +714,7 @@ int main(int argc, char **argv)
ksft_print_header();
if (run_sig_ign_tests) {
- ksft_set_plan(19);
+ ksft_set_plan(21);
} else {
ksft_set_plan(10);
}
@@ -714,6 +748,8 @@ int main(int argc, char **argv)
check_delete();
check_sigev_none(CLOCK_MONOTONIC);
check_sigev_none(CLOCK_PROCESS_CPUTIME_ID);
+ check_sigev_none_disarm(CLOCK_MONOTONIC);
+ check_sigev_none_disarm(CLOCK_PROCESS_CPUTIME_ID);
check_gettime(CLOCK_MONOTONIC);
check_gettime(CLOCK_PROCESS_CPUTIME_ID);
check_gettime(CLOCK_THREAD_CPUTIME_ID);
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread