[PATCH 2/3] time/jiffies: Use the exact HZ ratio in jiffies_to_clock_t()

From: Shashank Mohan Jain

Date: Mon Sep 28 2026 - 05:42:13 EST


When TICK_NSEC is not a multiple of NSEC_PER_SEC / USER_HZ,
jiffies_to_clock_t() and jiffies_64_to_clock_t() convert with

div_u64((u64)x * TICK_NSEC, NSEC_PER_SEC / USER_HZ)

TICK_NSEC is NSEC_PER_SEC / HZ rounded to the nearest nanosecond. When
HZ does not divide NSEC_PER_SEC it is slightly short (3333333 ns at
HZ=300), so a number of jiffies that is exactly a whole number of clock
ticks converts to just below it and is truncated to one tick less. With
HZ=300 and USER_HZ=100, jiffies_to_clock_t(300) is 99, and every
multiple of 3 jiffies is one tick short.

The tick does advance jiffies every TICK_NSEC, so as a measure of
elapsed time the old result is accurate to about 1e-7. But intervals
that userspace sets in clock ticks are stored with clock_t_to_jiffies(),
which uses the nominal HZ ratio, as do jiffies_to_msecs() and
jiffies_to_usecs(), and they no longer read back. With HZ=300 the bridge
defaults read as forward_delay 1499, hello_time 199, max_age 1999 and
ageing_time 29999 through sysfs and netlink, and each read-modify-write
through sysfs loses one more tick (1500 -> 1499 -> 1498). The neighbour
sysctls that use proc_dointvec_userhz_jiffies() behave the same way:
net.ipv4.neigh.*.retrans_time, anycast_delay and locktime read 99
instead of 100, proxy_delay reads 79 instead of 80, and
net.ipv6.neigh.*.anycast_delay reads 99. Inside the kernel, IPv6 route
lifetimes from router advertisements and netlink go through
jiffies_to_clock_t() and back through clock_t_to_jiffies(), so at
HZ=300 those routes expire 3 jiffies early.

Convert with the nominal ratio, x * USER_HZ / HZ, like
clock_t_to_jiffies() does. With USER_HZ=100 the result does not change
where TICK_NSEC is exact (e.g. HZ=250). On alpha, where USER_HZ is 1024,
NSEC_PER_SEC / USER_HZ is itself truncated and the old code returned one
tick too many for large values (jiffies_to_clock_t(976562) == 976563
with HZ=1024); that is fixed too. The value returned by times() now
advances by exactly USER_HZ ticks per HZ jiffies. The product also
overflows much later than x * TICK_NSEC did in jiffies_64_to_clock_t().

The TICK_NSEC based conversion predates git. It made sense while
TICK_NSEC was the tick length measured from CLOCK_TICK_RATE, when x86
returned 99 for 300 jiffies at HZ=300 and for 1000 jiffies at HZ=1000.
Commit b3c869d35b9b ("jiffies: Remove compile time assumptions about
CLOCK_TICK_RATE") made TICK_NSEC exact for HZ values that divide
NSEC_PER_SEC, but left the others rounded.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@xxxxxxxxx>
---
The KUnit test in patch 3 fails without this patch on qemu x86_64 and
i386 with HZ=300 and passes with it; UML (HZ=100) is unaffected. It
also passes on UML x86_64 with HZ/USER_HZ temporarily edited to
1200/100 and 1024/1024, both of which fail without the series. In
userspace, with the code extracted verbatim, both functions equal
floor(x * USER_HZ / HZ) for every x below 5e7 and invert
clock_t_to_jiffies() for every whole-jiffy clock_t value below 2e7, for
every Kconfig HZ value with USER_HZ 100 and 1024, 32- and 64-bit. The
bridge and neighbour values above were read back on an x86_64 HZ=300
kernel in qemu, before and after this series.

Possible follow-up, not part of this series:
tools/testing/selftests/bpf/progs/bpf_iter_tcp4.c and bpf_iter_tcp6.c
carry a copy of the old TICK_NSEC formula so that they print the same
values as /proc/net/tcp. The test only does a dummy read, so nothing
breaks, but at HZ=300 the copies no longer match the kernel.

Prepared with Claude Code (Anthropic), model Claude Opus 5.5
(claude-opus-5-5).

kernel/time/time.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/kernel/time/time.c b/kernel/time/time.c
index 470195e88ca0..f19e742c9562 100644
--- a/kernel/time/time.c
+++ b/kernel/time/time.c
@@ -684,7 +684,11 @@ clock_t jiffies_to_clock_t(unsigned long x)
return x / (HZ / USER_HZ);
# endif
#else
- return div_u64((u64)x * TICK_NSEC, NSEC_PER_SEC / USER_HZ);
+ /*
+ * Use the nominal HZ ratio like clock_t_to_jiffies(); TICK_NSEC is
+ * rounded when HZ does not divide NSEC_PER_SEC.
+ */
+ return div_u64((u64)x * USER_HZ, HZ);
#endif
}
EXPORT_SYMBOL(jiffies_to_clock_t);
@@ -729,12 +733,8 @@ notrace u64 jiffies_64_to_clock_t(u64 x)
/* Nothing to do */
# endif
#else
- /*
- * There are better ways that don't overflow early,
- * but even this doesn't overflow in hundreds of years
- * in 64 bits, so..
- */
- x = div_u64(x * TICK_NSEC, (NSEC_PER_SEC / USER_HZ));
+ /* Nominal HZ ratio, see jiffies_to_clock_t() */
+ x = div_u64(x * USER_HZ, HZ);
#endif
return x;
}
--
2.43.0