The kernel has a divide by zero crash when trying to run the system
timer less than 100Hz. The problem is x/(HZ/USER_HZ) and related.
Now x*(USER_HZ/HZ) will be used if HZ<USER_HZ.
I'm running the Linux kernel under qemu and went to run a slower
system timer to take less CPU (and battery) on the host. I found that
the kernel paniced under emulation because of a divide by zero in
three places. Here is the patch. The base git was updated today
01-05-2008. I went for a 20Hz system time by adding config HZ_20 etc
to kernel/Kconfig.hz. With this patch I verified the system timer by
looking at /proc/interrupts.
Signed-off-by: David Fries <[email protected]>
--
David Fries <[email protected]>
http://fries.net/~david/ (PGP encryption key available)
diff --git a/include/linux/acct.h b/include/linux/acct.h
index 302eb72..86b848d 100644
--- a/include/linux/acct.h
+++ b/include/linux/acct.h
@@ -173,7 +173,11 @@ typedef struct acct acct_t;
static inline u32 jiffies_to_AHZ(unsigned long x)
{
#if (TICK_NSEC % (NSEC_PER_SEC / AHZ)) == 0
- return x / (HZ / AHZ);
+ #if HZ < AHZ
+ return x * (AHZ / HZ);
+ #else
+ return x / (HZ / AHZ);
+ #endif
#else
u64 tmp = (u64)x * TICK_NSEC;
do_div(tmp, (NSEC_PER_SEC / AHZ));
diff --git a/kernel/time.c b/kernel/time.c
index 09d3c45..23af26f 100644
--- a/kernel/time.c
+++ b/kernel/time.c
@@ -565,7 +565,11 @@ EXPORT_SYMBOL(jiffies_to_timeval);
clock_t jiffies_to_clock_t(long x)
{
#if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
+ #if HZ < USER_HZ
+ return x * (USER_HZ / HZ);
+ #else
return x / (HZ / USER_HZ);
+ #endif
#else
u64 tmp = (u64)x * TICK_NSEC;
do_div(tmp, (NSEC_PER_SEC / USER_HZ));
@@ -598,7 +602,12 @@ EXPORT_SYMBOL(clock_t_to_jiffies);
u64 jiffies_64_to_clock_t(u64 x)
{
#if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
- do_div(x, HZ / USER_HZ);
+ #if HZ < USER_HZ
+ x *= USER_HZ;
+ do_div(x, HZ);
+ #else
+ do_div(x, HZ / USER_HZ);
+ #endif
#else
/*
* There are better ways that don't overflow early,
On Sat, 5 Jan 2008 16:16:55 -0600
David Fries <[email protected]> wrote:
> The kernel has a divide by zero crash when trying to run the system
> timer less than 100Hz. The problem is x/(HZ/USER_HZ) and related.
> Now x*(USER_HZ/HZ) will be used if HZ<USER_HZ.
>
> I'm running the Linux kernel under qemu and went to run a slower
> system timer to take less CPU (and battery) on the host. I found that
> the kernel paniced under emulation because of a divide by zero in
> three places. Here is the patch. The base git was updated today
> 01-05-2008. I went for a 20Hz system time by adding config HZ_20 etc
> to kernel/Kconfig.hz. With this patch I verified the system timer by
> looking at /proc/interrupts.
>
> Signed-off-by: David Fries <[email protected]>
>
> --
> David Fries <[email protected]>
> http://fries.net/~david/ (PGP encryption key available)
>
>
> diff --git a/include/linux/acct.h b/include/linux/acct.h
> index 302eb72..86b848d 100644
> --- a/include/linux/acct.h
> +++ b/include/linux/acct.h
> @@ -173,7 +173,11 @@ typedef struct acct acct_t;
> static inline u32 jiffies_to_AHZ(unsigned long x)
> {
> #if (TICK_NSEC % (NSEC_PER_SEC / AHZ)) == 0
> - return x / (HZ / AHZ);
> + #if HZ < AHZ
> + return x * (AHZ / HZ);
> + #else
> + return x / (HZ / AHZ);
> + #endif
> #else
> u64 tmp = (u64)x * TICK_NSEC;
> do_div(tmp, (NSEC_PER_SEC / AHZ));
> diff --git a/kernel/time.c b/kernel/time.c
> index 09d3c45..23af26f 100644
> --- a/kernel/time.c
> +++ b/kernel/time.c
> @@ -565,7 +565,11 @@ EXPORT_SYMBOL(jiffies_to_timeval);
> clock_t jiffies_to_clock_t(long x)
> {
> #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
> + #if HZ < USER_HZ
> + return x * (USER_HZ / HZ);
> + #else
> return x / (HZ / USER_HZ);
> + #endif
> #else
> u64 tmp = (u64)x * TICK_NSEC;
> do_div(tmp, (NSEC_PER_SEC / USER_HZ));
> @@ -598,7 +602,12 @@ EXPORT_SYMBOL(clock_t_to_jiffies);
> u64 jiffies_64_to_clock_t(u64 x)
> {
> #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
> - do_div(x, HZ / USER_HZ);
> + #if HZ < USER_HZ
> + x *= USER_HZ;
> + do_div(x, HZ);
> + #else
> + do_div(x, HZ / USER_HZ);
> + #endif
> #else
> /*
> * There are better ways that don't overflow early,
Alas, I get 100% rejects due to conflicting changes from Peter's
avoid-overflows-in-kernel-timec.patch.
Peter, did that patch propagate this failure, or might it have happily
fixed it?
Andrew Morton wrote:
>>
>> diff --git a/include/linux/acct.h b/include/linux/acct.h
>> index 302eb72..86b848d 100644
>> --- a/include/linux/acct.h
>> +++ b/include/linux/acct.h
>> @@ -173,7 +173,11 @@ typedef struct acct acct_t;
>> static inline u32 jiffies_to_AHZ(unsigned long x)
>> {
>> #if (TICK_NSEC % (NSEC_PER_SEC / AHZ)) == 0
>> - return x / (HZ / AHZ);
>> + #if HZ < AHZ
>> + return x * (AHZ / HZ);
>> + #else
>> + return x / (HZ / AHZ);
>> + #endif
>> #else
>> u64 tmp = (u64)x * TICK_NSEC;
>> do_div(tmp, (NSEC_PER_SEC / AHZ));
>> diff --git a/kernel/time.c b/kernel/time.c
>> index 09d3c45..23af26f 100644
>> --- a/kernel/time.c
>> +++ b/kernel/time.c
>> @@ -565,7 +565,11 @@ EXPORT_SYMBOL(jiffies_to_timeval);
>> clock_t jiffies_to_clock_t(long x)
>> {
>> #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
>> + #if HZ < USER_HZ
>> + return x * (USER_HZ / HZ);
>> + #else
>> return x / (HZ / USER_HZ);
>> + #endif
>> #else
>> u64 tmp = (u64)x * TICK_NSEC;
>> do_div(tmp, (NSEC_PER_SEC / USER_HZ));
>> @@ -598,7 +602,12 @@ EXPORT_SYMBOL(clock_t_to_jiffies);
>> u64 jiffies_64_to_clock_t(u64 x)
>> {
>> #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
>> - do_div(x, HZ / USER_HZ);
>> + #if HZ < USER_HZ
>> + x *= USER_HZ;
>> + do_div(x, HZ);
>> + #else
>> + do_div(x, HZ / USER_HZ);
>> + #endif
>> #else
>> /*
>> * There are better ways that don't overflow early,
>
> Alas, I get 100% rejects due to conflicting changes from Peter's
> avoid-overflows-in-kernel-timec.patch.
>
> Peter, did that patch propagate this failure, or might it have happily
> fixed it?
>
My patch doesn't touch any of these functions, nor touches any code
within 70 lines of this patch -- the last line touched is line 478 --
and doesn't touch linux/acct.h at all, so how could it cause a conflict?
But no, it doesn't fix this particular problem, even if using a similar
technique might very well be a better way to do this kind of conversion.
-hpa
On Mon, 07 Jan 2008 15:51:09 -0800
"H. Peter Anvin" <[email protected]> wrote:
> >> + do_div(x, HZ / USER_HZ);
> >> + #endif
> >> #else
> >> /*
> >> * There are better ways that don't overflow early,
> >
> > Alas, I get 100% rejects due to conflicting changes from Peter's
> > avoid-overflows-in-kernel-timec.patch.
> >
> > Peter, did that patch propagate this failure, or might it have happily
> > fixed it?
> >
>
> My patch doesn't touch any of these functions, nor touches any code
> within 70 lines of this patch -- the last line touched is line 478 --
> and doesn't touch linux/acct.h at all, so how could it cause a conflict?
oop, sorry, it was the mime-mess-mangles-everything problem and I failed to
spot it.
> But no, it doesn't fix this particular problem, even if using a similar
> technique might very well be a better way to do this kind of conversion.
OK.
On Sat, 5 Jan 2008 16:16:55 -0600 David Fries <[email protected]> wrote:
> --- a/kernel/time.c
> +++ b/kernel/time.c
> @@ -565,7 +565,11 @@ EXPORT_SYMBOL(jiffies_to_timeval);
> clock_t jiffies_to_clock_t(long x)
> {
> #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
> + #if HZ < USER_HZ
> + return x * (USER_HZ / HZ);
> + #else
> return x / (HZ / USER_HZ);
> + #endif
> #else
> u64 tmp = (u64)x * TICK_NSEC;
> do_div(tmp, (NSEC_PER_SEC / USER_HZ));
> @@ -598,7 +602,12 @@ EXPORT_SYMBOL(clock_t_to_jiffies);
> u64 jiffies_64_to_clock_t(u64 x)
> {
> #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
> - do_div(x, HZ / USER_HZ);
> + #if HZ < USER_HZ
> + x *= USER_HZ;
> + do_div(x, HZ);
> + #else
> + do_div(x, HZ / USER_HZ);
> + #endif
> #else
Somwhat off-topic:
I guess HZ=USER_HZ is a not-uncommon case, and it's pretty silly calling
do_div(x, 1) all the time. How about we optimise that case?
Perhaps there are other places...
--- a/kernel/time.c~speed-up-jiffies-conversion-functions-if-hz==user_hz
+++ a/kernel/time.c
@@ -618,8 +618,10 @@ u64 jiffies_64_to_clock_t(u64 x)
# if HZ < USER_HZ
x *= USER_HZ;
do_div(x, HZ);
-# else
+# elif HZ > USER_HZ
do_div(x, HZ / USER_HZ);
+# else
+ /* Nothing to do */
# endif
#else
/*
_