Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1420610 > unrolled thread
| Started by | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| First post | 2016-06-13 10:50 +0200 |
| Last post | 2016-06-15 22:30 +0200 |
| Articles | 20 on this page of 50 — 8 participants |
Back to article view | Back to linux.kernel
[patch 00/20] timer: Refactor the timer wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 11/20] hlist: Add hlist_is_last_node() helper Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
Re: [patch 11/20] hlist: Add hlist_is_last_node() helper Paolo Bonzini <pbonzini@redhat.com> - 2016-06-13 12:30 +0200
[patch 07/20] drivers/tty/mips_ejtag: Initialize timer as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 01/20] timer: Make pinned a timer property Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 17/20] tick/sched: Remove pointless empty function Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 05/20] driver/net/ethernet/tile: Initialize timer as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 15/20] timer: Move __run_timers() function Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 12/20] timer: Give a few structs and members proper names Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 04/20] cpufreq/powernv: Initialize timer as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
Re: [patch 04/20] cpufreq/powernv: Initialize timer as pinned Arjan van de Ven <arjanvandeven@gmail.com> - 2016-06-13 15:20 +0200
[patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Peter Zijlstra <peterz@infradead.org> - 2016-06-13 13:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 14:40 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Eric Dumazet <edumazet@google.com> - 2016-06-13 14:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 16:40 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Ingo Molnar <mingo@kernel.org> - 2016-06-14 12:20 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-14 18:40 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Arjan van de Ven <arjanvandeven@gmail.com> - 2016-06-14 19:20 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-14 20:10 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Peter Zijlstra <peterz@infradead.org> - 2016-06-14 22:40 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Peter Zijlstra <peterz@infradead.org> - 2016-06-14 22:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Eric Dumazet <edumazet@google.com> - 2016-06-14 23:20 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-15 17:00 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Arjan van de Ven <arjanvandeven@gmail.com> - 2016-06-15 17:00 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-15 18:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-16 17:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2016-06-16 18:10 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Peter Zijlstra <peterz@infradead.org> - 2016-06-16 20:20 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2016-06-17 02:50 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2016-06-17 06:10 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Arjan van de Ven <arjanvandeven@gmail.com> - 2016-06-16 18:10 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-16 18:20 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Eric Dumazet <edumazet@google.com> - 2016-06-15 17:10 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Richard Cochran <richardcochran@gmail.com> - 2016-06-13 16:40 +0200
Re: [patch 13/20] timer: Switch to a non cascading wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 16:50 +0200
[patch 14/20] timer: Remove slack leftovers Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
[patch 18/20] timer: Forward wheel clock whenever possible Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 10:50 +0200
Re: [patch 18/20] timer: Forward wheel clock whenever possible Richard Cochran <richardcochran@gmail.com> - 2016-06-13 17:20 +0200
Re: [patch 18/20] timer: Forward wheel clock whenever possible Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 17:30 +0200
[patch 06/20] drivers/tty/metag_da: Initialize timer as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 11:00 +0200
Re: [patch 06/20] drivers/tty/metag_da: Initialize timer as pinned Arjan van de Ven <arjanvandeven@gmail.com> - 2016-06-13 15:20 +0200
[patch 03/20] x86/mce: Initialize timer as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 11:00 +0200
[patch 08/20] net/ipv4/inet: Initialize timers as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 11:00 +0200
[patch 02/20] x86/apic/uv: Initialize timer as pinned Thomas Gleixner <tglx@linutronix.de> - 2016-06-13 11:00 +0200
Re: [patch 00/20] timer: Refactor the timer wheel Eric Dumazet <edumazet@google.com> - 2016-06-13 16:20 +0200
Re: [patch 00/20] timer: Refactor the timer wheel "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2016-06-13 18:20 +0200
Re: [patch 00/20] timer: Refactor the timer wheel "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2016-06-15 17:20 +0200
Re: [patch 00/20] timer: Refactor the timer wheel Thomas Gleixner <tglx@linutronix.de> - 2016-06-15 19:10 +0200
Re: [patch 00/20] timer: Refactor the timer wheel "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2016-06-15 22:30 +0200
Page 1 of 3 [1] 2 3 Next page →
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 00/20] timer: Refactor the timer wheel |
| Message-ID | <rJvJf-3OU-5@gated-at.bofh.it> |
The current timer wheel has some drawbacks:
1) Cascading
Cascading can be an unbound operation and is completely pointless in most
cases because the vast majority of the timer wheel timers are canceled or
rearmed before expiration.
2) No fast lookup of the next expiring timer
In NOHZ scenarios the first timer soft interrupt after a long NOHZ period
must fast forward the base time to current jiffies. As we have no way to
find the next expiring timer fast, the code loops and increments the base
time by one and checks for expired timers in each step. I've observed loops
lasting 1 ms!
There are some other issues caused by the above, but they are minor compare to
those.
After a thorough analysis of real world data gathered on laptops,
workstations, webservers and other machines (thanks Chris!) I came to the
conclusion that the current 'classic' timer wheel implementation can be
modified to address the above issues.
The vast majority of timer wheel timers is canceled or rearmed before
expiry. Most of them are timeouts for networking and other I/O tasks. The
nature of timeouts is to catch the exception from normal operation (TCP ack
timed out, disk does not respond, etc.). For these kind of timeouts the
accuracy is not really a concern. In case the timeout fires, performance is
down the drain already.
The few timers which actually expire can be split into two categories:
1) Short expiry times which expect halfways accurate expiry
2) Long term expiry times are inaccurate today already due to the batching
which is done for NOHZ.
So for long term expiry timers we can avoid the cascading property and just
leave them in the less granular outer wheels until expiry or cancelation.
Contrary to the classic wheel the granularities of the next wheel is not the
capacity of the first wheel. The granularities of the wheels are in the
currently chosen setting 8 times the granularity of the previous wheel. So for
HZ=250 we end up with the following granularity levels:
Level Offset Granularity Range
0 0 4 ms 0 ms - 252 ms
1 64 32 ms 256 ms - 2044 ms (256ms - ~2s)
2 128 256 ms 2048 ms - 16380 ms (~2s - ~16s)
3 192 2048 ms (~2s) 16384 ms - 131068 ms (~16s - ~2m)
4 256 16384 ms (~16s) 131072 ms - 1048572 ms (~2m - ~17m)
5 320 131072 ms (~2m) 1048576 ms - 8388604 ms (~17m - ~2h)
That's a worst case inaccuracy of 12.5% for the timers which are queued at the
beginning of a level.
So the new wheel concept addresses the old issues:
1) Cascading is avoided (except for extreme long time timers)
2) By keeping the timers in the bucket until expiry/cancelation we can track
the buckets which have timers enqueued in a bucket bitmap and therefor can
lookup the next expiring timer fast and time bound.
A further benefit of the concept is, that the slack calculation which is done
on every timer start is not longer necessary because the granularity levels
provide natural batching already.
Our extensive testing with various loads did not show any performance
degradation vs. the current wheel implementation.
This series has also preparatory patches for changing the NOHZ timer handling
from the current push to a pull model. Currently we decide at timer enqueue
time on which cpu we queue the timer. This is exceptionally silly because
there is no way to predict at enqueue time which cpu will be idle when the
timer expires. Given the fact that most timers are canceled or rearmed before
expiry this is even more silly. We trade a expensive decision and cross cpu
access for a very doubtful benefit.
The solution to this is to store the migrateable timers in a seperate storage
space on the local cpu and when the cpu goes idle tell the others about its
next expiring timer and let the other cpu pull the timer in case of expiry. We
have a proof of concept implementation for this, but it's not yet ready for
posting.
Thanks,
tglx
---
arch/x86/kernel/apic/x2apic_uv_x.c | 4
arch/x86/kernel/cpu/mcheck/mce.c | 4
block/genhd.c | 5
drivers/cpufreq/powernv-cpufreq.c | 5
drivers/mmc/host/jz4740_mmc.c | 2
drivers/net/ethernet/tile/tilepro.c | 4
drivers/power/bq27xxx_battery.c | 5
drivers/tty/metag_da.c | 4
drivers/tty/mips_ejtag_fdc.c | 4
drivers/usb/host/ohci-hcd.c | 1
drivers/usb/host/xhci.c | 2
include/linux/list.h | 10
include/linux/timer.h | 30
include/trace/events/timer.h | 11
kernel/time/tick-internal.h | 1
kernel/time/tick-sched.c | 46 -
kernel/time/timer.c | 1090 +++++++++++++++++++++---------------
lib/random32.c | 1
net/ipv4/inet_connection_sock.c | 7
net/ipv4/inet_timewait_sock.c | 5
20 files changed, 731 insertions(+), 510 deletions(-)
[toc] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 11/20] hlist: Add hlist_is_last_node() helper |
| Message-ID | <rJvJg-3OU-55@gated-at.bofh.it> |
| In reply to | #1420610 |
Required to figure out whether the entry is the last one in the hlist.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
include/linux/list.h | 10 ++++++++++
1 file changed, 10 insertions(+)
Index: b/include/linux/list.h
===================================================================
--- a/include/linux/list.h
+++ b/include/linux/list.h
@@ -679,6 +679,16 @@ static inline bool hlist_fake(struct hli
}
/*
+ * Check whether the node is the last node of the head without
+ * accessing head.
+ */
+static inline bool hlist_is_last_node(struct hlist_node *n,
+ struct hlist_head *h)
+{
+ return !n->next && n->pprev == &h->first;
+}
+
+/*
* Move a list from one list head to another. Fixup the pprev
* reference of the first entry if it exists.
*/
[toc] | [prev] | [next] | [standalone]
| From | Paolo Bonzini <pbonzini@redhat.com> |
|---|---|
| Date | 2016-06-13 12:30 +0200 |
| Subject | Re: [patch 11/20] hlist: Add hlist_is_last_node() helper |
| Message-ID | <rJxi2-4Ym-23@gated-at.bofh.it> |
| In reply to | #1420611 |
On 13/06/2016 10:40, Thomas Gleixner wrote:
> /*
> + * Check whether the node is the last node of the head without
> + * accessing head.
> + */
> +static inline bool hlist_is_last_node(struct hlist_node *n,
> + struct hlist_head *h)
> +{
> + return !n->next && n->pprev == &h->first;
> +}
Perhaps hlist_is_only_node, or hlist_is_singular_node (which is ugly but
matches list_is_singular)? last_node seems to check only for !n->next.
Thanks,
Paolo
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 07/20] drivers/tty/mips_ejtag: Initialize timer as pinned |
| Message-ID | <rJvJg-3OU-39@gated-at.bofh.it> |
| In reply to | #1420610 |
Pinned timers must carry that attribute in the timer itself. No functional
change.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
drivers/tty/mips_ejtag_fdc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
Index: b/drivers/tty/mips_ejtag_fdc.c
===================================================================
--- a/drivers/tty/mips_ejtag_fdc.c
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -689,7 +689,7 @@ static void mips_ejtag_fdc_tty_timer(uns
mips_ejtag_fdc_handle(priv);
if (!priv->removing)
- mod_timer_pinned(&priv->poll_timer, jiffies + FDC_TTY_POLL);
+ mod_timer(&priv->poll_timer, jiffies + FDC_TTY_POLL);
}
/* TTY Port operations */
@@ -1002,7 +1002,7 @@ static int mips_ejtag_fdc_tty_probe(stru
raw_spin_unlock_irq(&priv->lock);
} else {
/* If we didn't get an usable IRQ, poll instead */
- setup_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer,
+ setup_pinned_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer,
(unsigned long)priv);
priv->poll_timer.expires = jiffies + FDC_TTY_POLL;
/*
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 01/20] timer: Make pinned a timer property |
| Message-ID | <rJvJg-3OU-37@gated-at.bofh.it> |
| In reply to | #1420610 |
We want to move the timer migration from a push to a pull model. This requires
to store the pinned attribute of a timer in the timer itself. This must happen
at initialization time.
Add the helper macros for this.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
include/linux/timer.h | 25 ++++++++++++++++++++++---
kernel/time/timer.c | 10 +++++-----
2 files changed, 27 insertions(+), 8 deletions(-)
Index: b/include/linux/timer.h
===================================================================
--- a/include/linux/timer.h
+++ b/include/linux/timer.h
@@ -62,7 +62,8 @@ struct timer_list {
#define TIMER_MIGRATING 0x00080000
#define TIMER_BASEMASK (TIMER_CPUMASK | TIMER_MIGRATING)
#define TIMER_DEFERRABLE 0x00100000
-#define TIMER_IRQSAFE 0x00200000
+#define TIMER_PINNED 0x00200000
+#define TIMER_IRQSAFE 0x00400000
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .next = TIMER_ENTRY_STATIC }, \
@@ -78,9 +79,15 @@ struct timer_list {
#define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
+#define TIMER_PINNED_INITIALIZER(_function, _expires, _data) \
+ __TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_PINNED)
+
#define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
+#define TIMER_PINNED_DEFERRED_INITIALIZER(_function, _expires, _data) \
+ __TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE | TIMER_PINNED)
+
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
@@ -124,8 +131,12 @@ static inline void init_timer_on_stack_k
#define init_timer(timer) \
__init_timer((timer), 0)
+#define init_timer_pinned(timer) \
+ __init_timer((timer), TIMER_PINNED)
#define init_timer_deferrable(timer) \
__init_timer((timer), TIMER_DEFERRABLE)
+#define init_timer_pinned_deferrable(timer) \
+ __init_timer((timer), TIMER_DEFERRABLE | TIMER_PINNED)
#define init_timer_on_stack(timer) \
__init_timer_on_stack((timer), 0)
@@ -145,12 +156,20 @@ static inline void init_timer_on_stack_k
#define setup_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), 0)
+#define setup_pinned_timer(timer, fn, data) \
+ __setup_timer((timer), (fn), (data), TIMER_PINNED)
#define setup_deferrable_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), TIMER_DEFERRABLE)
+#define setup_pinned_deferrable_timer(timer, fn, data) \
+ __setup_timer((timer), (fn), (data), TIMER_DEFERRABLE | TIMER_PINNED)
#define setup_timer_on_stack(timer, fn, data) \
__setup_timer_on_stack((timer), (fn), (data), 0)
+#define setup_pinned_timer_on_stack(timer, fn, data) \
+ __setup_timer_on_stack((timer), (fn), (data), TIMER_PINNED)
#define setup_deferrable_timer_on_stack(timer, fn, data) \
__setup_timer_on_stack((timer), (fn), (data), TIMER_DEFERRABLE)
+#define setup_pinned_deferrable_timer_on_stack(timer, fn, data) \
+ __setup_timer_on_stack((timer), (fn), (data), TIMER_DEFERRABLE | TIMER_PINNED)
/**
* timer_pending - is a timer pending?
@@ -175,8 +194,8 @@ extern int mod_timer_pinned(struct timer
extern void set_timer_slack(struct timer_list *time, int slack_hz);
-#define TIMER_NOT_PINNED 0
-#define TIMER_PINNED 1
+#define MOD_TIMER_NOT_PINNED 0
+#define MOD_TIMER_PINNED 1
/*
* The jiffies value which is added to now, when there is no timer
* in the timer wheel:
Index: b/kernel/time/timer.c
===================================================================
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -782,7 +782,7 @@ static inline int
debug_activate(timer, expires);
- new_base = get_target_base(base, pinned);
+ new_base = get_target_base(base, pinned || timer->flags & TIMER_PINNED);
if (base != new_base) {
/*
@@ -825,7 +825,7 @@ static inline int
*/
int mod_timer_pending(struct timer_list *timer, unsigned long expires)
{
- return __mod_timer(timer, expires, true, TIMER_NOT_PINNED);
+ return __mod_timer(timer, expires, true, MOD_TIMER_NOT_PINNED);
}
EXPORT_SYMBOL(mod_timer_pending);
@@ -900,7 +900,7 @@ int mod_timer(struct timer_list *timer,
if (timer_pending(timer) && timer->expires == expires)
return 1;
- return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
+ return __mod_timer(timer, expires, false, MOD_TIMER_NOT_PINNED);
}
EXPORT_SYMBOL(mod_timer);
@@ -928,7 +928,7 @@ int mod_timer_pinned(struct timer_list *
if (timer->expires == expires && timer_pending(timer))
return 1;
- return __mod_timer(timer, expires, false, TIMER_PINNED);
+ return __mod_timer(timer, expires, false, MOD_TIMER_PINNED);
}
EXPORT_SYMBOL(mod_timer_pinned);
@@ -1512,7 +1512,7 @@ signed long __sched schedule_timeout(sig
expire = timeout + jiffies;
setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
- __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
+ __mod_timer(&timer, expire, false, MOD_TIMER_NOT_PINNED);
schedule();
del_singleshot_timer_sync(&timer);
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 17/20] tick/sched: Remove pointless empty function |
| Message-ID | <rJvJg-3OU-45@gated-at.bofh.it> |
| In reply to | #1420610 |
This was a failed attempt to optimize the timer expiry in idle, which was
disabled and never revisited. Remove the cruft.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
kernel/time/tick-sched.c | 33 +--------------------------------
1 file changed, 1 insertion(+), 32 deletions(-)
Index: b/kernel/time/tick-sched.c
===================================================================
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -1092,35 +1092,6 @@ static void tick_nohz_switch_to_nohz(voi
tick_nohz_activate(ts, NOHZ_MODE_LOWRES);
}
-/*
- * When NOHZ is enabled and the tick is stopped, we need to kick the
- * tick timer from irq_enter() so that the jiffies update is kept
- * alive during long running softirqs. That's ugly as hell, but
- * correctness is key even if we need to fix the offending softirq in
- * the first place.
- *
- * Note, this is different to tick_nohz_restart. We just kick the
- * timer and do not touch the other magic bits which need to be done
- * when idle is left.
- */
-static void tick_nohz_kick_tick(struct tick_sched *ts, ktime_t now)
-{
-#if 0
- /* Switch back to 2.6.27 behaviour */
- ktime_t delta;
-
- /*
- * Do not touch the tick device, when the next expiry is either
- * already reached or less/equal than the tick period.
- */
- delta = ktime_sub(hrtimer_get_expires(&ts->sched_timer), now);
- if (delta.tv64 <= tick_period.tv64)
- return;
-
- tick_nohz_restart(ts, now);
-#endif
-}
-
static inline void tick_nohz_irq_enter(void)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
@@ -1131,10 +1102,8 @@ static inline void tick_nohz_irq_enter(v
now = ktime_get();
if (ts->idle_active)
tick_nohz_stop_idle(ts, now);
- if (ts->tick_stopped) {
+ if (ts->tick_stopped)
tick_nohz_update_jiffies(now);
- tick_nohz_kick_tick(ts, now);
- }
}
#else
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 05/20] driver/net/ethernet/tile: Initialize timer as pinned |
| Message-ID | <rJvJg-3OU-59@gated-at.bofh.it> |
| In reply to | #1420610 |
Pinned timers must carry that attribute in the timer itself. No functional
change.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
drivers/net/ethernet/tile/tilepro.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
--- a/drivers/net/ethernet/tile/tilepro.c
+++ b/drivers/net/ethernet/tile/tilepro.c
@@ -588,7 +588,7 @@ static bool tile_net_lepp_free_comps(str
static void tile_net_schedule_egress_timer(struct tile_net_cpu *info)
{
if (!info->egress_timer_scheduled) {
- mod_timer_pinned(&info->egress_timer, jiffies + 1);
+ mod_timer(&info->egress_timer, jiffies + 1);
info->egress_timer_scheduled = true;
}
}
@@ -1004,7 +1004,7 @@ static void tile_net_register(void *dev_
BUG();
/* Initialize the egress timer. */
- init_timer(&info->egress_timer);
+ init_pinned_timer(&info->egress_timer);
info->egress_timer.data = (long)info;
info->egress_timer.function = tile_net_handle_egress_timer;
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 15/20] timer: Move __run_timers() function |
| Message-ID | <rJvJg-3OU-43@gated-at.bofh.it> |
| In reply to | #1420610 |
From: Anna-Maria Gleixner <anna-maria@linutronix.de>
Move __run_timers() below __next_timer_interrupt() and next_pending_bucket()
in preparation for __run_timers() NOHZ optimization.
No functional change.
Signed-off-by: Anna-Maria Gleixner <anna-maria@linutronix.de>
Cc: rt@linutronix.de
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
kernel/time/timer.c | 46 +++++++++++++++++++++++-----------------------
1 file changed, 23 insertions(+), 23 deletions(-)
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -1280,29 +1280,6 @@ static int collect_expired_timers(struct
return levels;
}
-/**
- * __run_timers - run all expired timers (if any) on this CPU.
- * @base: the timer vector to be processed.
- */
-static inline void __run_timers(struct timer_base *base)
-{
- struct hlist_head heads[LVL_DEPTH];
- int levels;
-
- spin_lock_irq(&base->lock);
-
- while (time_after_eq(jiffies, base->clk)) {
-
- levels = collect_expired_timers(base, heads);
- base->clk++;
-
- while (levels--)
- expire_timers(base, heads + levels);
- }
- base->running_timer = NULL;
- spin_unlock_irq(&base->lock);
-}
-
#ifdef CONFIG_NO_HZ_COMMON
/*
* Find the next pending bucket of a level. Search from @offset + @clk upwards
@@ -1478,6 +1455,29 @@ void update_process_times(int user_tick)
run_posix_cpu_timers(p);
}
+/**
+ * __run_timers - run all expired timers (if any) on this CPU.
+ * @base: the timer vector to be processed.
+ */
+static inline void __run_timers(struct timer_base *base)
+{
+ struct hlist_head heads[LVL_DEPTH];
+ int levels;
+
+ spin_lock_irq(&base->lock);
+
+ while (time_after_eq(jiffies, base->clk)) {
+
+ levels = collect_expired_timers(base, heads);
+ base->clk++;
+
+ while (levels--)
+ expire_timers(base, heads + levels);
+ }
+ base->running_timer = NULL;
+ spin_unlock_irq(&base->lock);
+}
+
/*
* This function runs timers and the timer-tq in bottom half context.
*/
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 12/20] timer: Give a few structs and members proper names |
| Message-ID | <rJvJg-3OU-47@gated-at.bofh.it> |
| In reply to | #1420610 |
Some of the names are not longer correct and others are simply too long to
type. Clean it up before we switch the wheel implementation over to the new
scheme.
No functional change.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
kernel/time/timer.c | 118 ++++++++++++++++++++++++++--------------------------
1 file changed, 59 insertions(+), 59 deletions(-)
Index: b/kernel/time/timer.c
===================================================================
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -77,10 +77,10 @@ struct tvec_root {
struct hlist_head vec[TVR_SIZE];
};
-struct tvec_base {
+struct timer_base {
spinlock_t lock;
struct timer_list *running_timer;
- unsigned long timer_jiffies;
+ unsigned long clk;
unsigned long next_timer;
unsigned long active_timers;
unsigned long all_timers;
@@ -95,7 +95,7 @@ struct tvec_base {
} ____cacheline_aligned;
-static DEFINE_PER_CPU(struct tvec_base, tvec_bases);
+static DEFINE_PER_CPU(struct timer_base, timer_bases);
#if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON)
unsigned int sysctl_timer_migration = 1;
@@ -106,15 +106,15 @@ void timers_update_migration(bool update
unsigned int cpu;
/* Avoid the loop, if nothing to update */
- if (this_cpu_read(tvec_bases.migration_enabled) == on)
+ if (this_cpu_read(timer_bases.migration_enabled) == on)
return;
for_each_possible_cpu(cpu) {
- per_cpu(tvec_bases.migration_enabled, cpu) = on;
+ per_cpu(timer_bases.migration_enabled, cpu) = on;
per_cpu(hrtimer_bases.migration_enabled, cpu) = on;
if (!update_nohz)
continue;
- per_cpu(tvec_bases.nohz_active, cpu) = true;
+ per_cpu(timer_bases.nohz_active, cpu) = true;
per_cpu(hrtimer_bases.nohz_active, cpu) = true;
}
}
@@ -134,18 +134,18 @@ int timer_migration_handler(struct ctl_t
return ret;
}
-static inline struct tvec_base *get_target_base(struct tvec_base *base,
+static inline struct timer_base *get_target_base(struct timer_base *base,
int pinned)
{
if (pinned || !base->migration_enabled)
- return this_cpu_ptr(&tvec_bases);
- return per_cpu_ptr(&tvec_bases, get_nohz_timer_target());
+ return this_cpu_ptr(&timer_bases);
+ return per_cpu_ptr(&timer_bases, get_nohz_timer_target());
}
#else
-static inline struct tvec_base *get_target_base(struct tvec_base *base,
+static inline struct timer_base *get_target_base(struct timer_base *base,
int pinned)
{
- return this_cpu_ptr(&tvec_bases);
+ return this_cpu_ptr(&timer_bases);
}
#endif
@@ -371,10 +371,10 @@ void set_timer_slack(struct timer_list *
EXPORT_SYMBOL_GPL(set_timer_slack);
static void
-__internal_add_timer(struct tvec_base *base, struct timer_list *timer)
+__internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
- unsigned long idx = expires - base->timer_jiffies;
+ unsigned long idx = expires - base->clk;
struct hlist_head *vec;
if (idx < TVR_SIZE) {
@@ -394,7 +394,7 @@ static void
* Can happen if you add a timer with expires == jiffies,
* or you set a timer to go off in the past
*/
- vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
+ vec = base->tv1.vec + (base->clk & TVR_MASK);
} else {
int i;
/* If the timeout is larger than MAX_TVAL (on 64-bit
@@ -403,7 +403,7 @@ static void
*/
if (idx > MAX_TVAL) {
idx = MAX_TVAL;
- expires = idx + base->timer_jiffies;
+ expires = idx + base->clk;
}
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->tv5.vec + i;
@@ -412,11 +412,11 @@ static void
hlist_add_head(&timer->entry, vec);
}
-static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
+static void internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
/* Advance base->jiffies, if the base is empty */
if (!base->all_timers++)
- base->timer_jiffies = jiffies;
+ base->clk = jiffies;
__internal_add_timer(base, timer);
/*
@@ -707,7 +707,7 @@ static inline void detach_timer(struct t
}
static inline void
-detach_expired_timer(struct timer_list *timer, struct tvec_base *base)
+detach_expired_timer(struct timer_list *timer, struct timer_base *base)
{
detach_timer(timer, true);
if (!(timer->flags & TIMER_DEFERRABLE))
@@ -715,7 +715,7 @@ detach_expired_timer(struct timer_list *
base->all_timers--;
}
-static int detach_if_pending(struct timer_list *timer, struct tvec_base *base,
+static int detach_if_pending(struct timer_list *timer, struct timer_base *base,
bool clear_pending)
{
if (!timer_pending(timer))
@@ -725,16 +725,16 @@ static int detach_if_pending(struct time
if (!(timer->flags & TIMER_DEFERRABLE)) {
base->active_timers--;
if (timer->expires == base->next_timer)
- base->next_timer = base->timer_jiffies;
+ base->next_timer = base->clk;
}
/* If this was the last timer, advance base->jiffies */
if (!--base->all_timers)
- base->timer_jiffies = jiffies;
+ base->clk = jiffies;
return 1;
}
/*
- * We are using hashed locking: holding per_cpu(tvec_bases).lock
+ * We are using hashed locking: holding per_cpu(timer_bases).lock
* means that all timers which are tied to this base via timer->base are
* locked, and the base itself is locked too.
*
@@ -744,16 +744,16 @@ static int detach_if_pending(struct time
* When the timer's base is locked and removed from the list, the
* TIMER_MIGRATING flag is set, FIXME
*/
-static struct tvec_base *lock_timer_base(struct timer_list *timer,
+static struct timer_base *lock_timer_base(struct timer_list *timer,
unsigned long *flags)
__acquires(timer->base->lock)
{
for (;;) {
u32 tf = timer->flags;
- struct tvec_base *base;
+ struct timer_base *base;
if (!(tf & TIMER_MIGRATING)) {
- base = per_cpu_ptr(&tvec_bases, tf & TIMER_CPUMASK);
+ base = per_cpu_ptr(&timer_bases, tf & TIMER_CPUMASK);
spin_lock_irqsave(&base->lock, *flags);
if (timer->flags == tf)
return base;
@@ -766,7 +766,7 @@ static struct tvec_base *lock_timer_base
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
{
- struct tvec_base *base, *new_base;
+ struct timer_base *base, *new_base;
unsigned long flags;
int ret = 0;
@@ -933,8 +933,8 @@ EXPORT_SYMBOL(add_timer);
*/
void add_timer_on(struct timer_list *timer, int cpu)
{
- struct tvec_base *new_base = per_cpu_ptr(&tvec_bases, cpu);
- struct tvec_base *base;
+ struct timer_base *new_base = per_cpu_ptr(&timer_bases, cpu);
+ struct timer_base *base;
unsigned long flags;
timer_stats_timer_set_start_info(timer);
@@ -975,7 +975,7 @@ EXPORT_SYMBOL_GPL(add_timer_on);
*/
int del_timer(struct timer_list *timer)
{
- struct tvec_base *base;
+ struct timer_base *base;
unsigned long flags;
int ret = 0;
@@ -1001,7 +1001,7 @@ EXPORT_SYMBOL(del_timer);
*/
int try_to_del_timer_sync(struct timer_list *timer)
{
- struct tvec_base *base;
+ struct timer_base *base;
unsigned long flags;
int ret = -1;
@@ -1085,7 +1085,7 @@ int del_timer_sync(struct timer_list *ti
EXPORT_SYMBOL(del_timer_sync);
#endif
-static int cascade(struct tvec_base *base, struct tvec *tv, int index)
+static int cascade(struct timer_base *base, struct tvec *tv, int index)
{
/* cascade all the timers from tv up one level */
struct timer_list *timer;
@@ -1150,7 +1150,7 @@ static void call_timer_fn(struct timer_l
}
}
-#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)
+#define INDEX(N) ((base->clk >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)
/**
* __run_timers - run all expired timers (if any) on this CPU.
@@ -1159,23 +1159,23 @@ static void call_timer_fn(struct timer_l
* This function cascades all vectors and executes all expired timer
* vectors.
*/
-static inline void __run_timers(struct tvec_base *base)
+static inline void __run_timers(struct timer_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
- while (time_after_eq(jiffies, base->timer_jiffies)) {
+ while (time_after_eq(jiffies, base->clk)) {
struct hlist_head work_list;
struct hlist_head *head = &work_list;
int index;
if (!base->all_timers) {
- base->timer_jiffies = jiffies;
+ base->clk = jiffies;
break;
}
- index = base->timer_jiffies & TVR_MASK;
+ index = base->clk & TVR_MASK;
/*
* Cascade timers:
@@ -1185,7 +1185,7 @@ static inline void __run_timers(struct t
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
- ++base->timer_jiffies;
+ ++base->clk;
hlist_move_list(base->tv1.vec + index, head);
while (!hlist_empty(head)) {
void (*fn)(unsigned long);
@@ -1223,16 +1223,16 @@ static inline void __run_timers(struct t
* is used on S/390 to stop all activity when a CPU is idle.
* This function needs to be called with interrupts disabled.
*/
-static unsigned long __next_timer_interrupt(struct tvec_base *base)
+static unsigned long __next_timer_interrupt(struct timer_base *base)
{
- unsigned long timer_jiffies = base->timer_jiffies;
- unsigned long expires = timer_jiffies + NEXT_TIMER_MAX_DELTA;
+ unsigned long clk = base->clk;
+ unsigned long expires = clk + NEXT_TIMER_MAX_DELTA;
int index, slot, array, found = 0;
struct timer_list *nte;
struct tvec *varray[4];
/* Look for timer events in tv1. */
- index = slot = timer_jiffies & TVR_MASK;
+ index = slot = clk & TVR_MASK;
do {
hlist_for_each_entry(nte, base->tv1.vec + slot, entry) {
if (nte->flags & TIMER_DEFERRABLE)
@@ -1251,8 +1251,8 @@ static unsigned long __next_timer_interr
cascade:
/* Calculate the next cascade event */
if (index)
- timer_jiffies += TVR_SIZE - index;
- timer_jiffies >>= TVR_BITS;
+ clk += TVR_SIZE - index;
+ clk >>= TVR_BITS;
/* Check tv2-tv5. */
varray[0] = &base->tv2;
@@ -1263,7 +1263,7 @@ static unsigned long __next_timer_interr
for (array = 0; array < 4; array++) {
struct tvec *varp = varray[array];
- index = slot = timer_jiffies & TVN_MASK;
+ index = slot = clk & TVN_MASK;
do {
hlist_for_each_entry(nte, varp->vec + slot, entry) {
if (nte->flags & TIMER_DEFERRABLE)
@@ -1287,8 +1287,8 @@ static unsigned long __next_timer_interr
} while (slot != index);
if (index)
- timer_jiffies += TVN_SIZE - index;
- timer_jiffies >>= TVN_BITS;
+ clk += TVN_SIZE - index;
+ clk >>= TVN_BITS;
}
return expires;
}
@@ -1336,7 +1336,7 @@ static u64 cmp_next_hrtimer_event(u64 ba
*/
u64 get_next_timer_interrupt(unsigned long basej, u64 basem)
{
- struct tvec_base *base = this_cpu_ptr(&tvec_bases);
+ struct timer_base *base = this_cpu_ptr(&timer_bases);
u64 expires = KTIME_MAX;
unsigned long nextevt;
@@ -1349,7 +1349,7 @@ u64 get_next_timer_interrupt(unsigned lo
spin_lock(&base->lock);
if (base->active_timers) {
- if (time_before_eq(base->next_timer, base->timer_jiffies))
+ if (time_before_eq(base->next_timer, base->clk))
base->next_timer = __next_timer_interrupt(base);
nextevt = base->next_timer;
if (time_before_eq(nextevt, basej))
@@ -1388,9 +1388,9 @@ void update_process_times(int user_tick)
*/
static void run_timer_softirq(struct softirq_action *h)
{
- struct tvec_base *base = this_cpu_ptr(&tvec_bases);
+ struct timer_base *base = this_cpu_ptr(&timer_bases);
- if (time_after_eq(jiffies, base->timer_jiffies))
+ if (time_after_eq(jiffies, base->clk))
__run_timers(base);
}
@@ -1535,7 +1535,7 @@ signed long __sched schedule_timeout_idl
EXPORT_SYMBOL(schedule_timeout_idle);
#ifdef CONFIG_HOTPLUG_CPU
-static void migrate_timer_list(struct tvec_base *new_base, struct hlist_head *head)
+static void migrate_timer_list(struct timer_base *new_base, struct hlist_head *head)
{
struct timer_list *timer;
int cpu = new_base->cpu;
@@ -1551,13 +1551,13 @@ static void migrate_timer_list(struct tv
static void migrate_timers(int cpu)
{
- struct tvec_base *old_base;
- struct tvec_base *new_base;
+ struct timer_base *old_base;
+ struct timer_base *new_base;
int i;
BUG_ON(cpu_online(cpu));
- old_base = per_cpu_ptr(&tvec_bases, cpu);
- new_base = get_cpu_ptr(&tvec_bases);
+ old_base = per_cpu_ptr(&timer_bases, cpu);
+ new_base = get_cpu_ptr(&timer_bases);
/*
* The caller is globally serialized and nobody else
* takes two locks at once, deadlock is not possible.
@@ -1581,7 +1581,7 @@ static void migrate_timers(int cpu)
spin_unlock(&old_base->lock);
spin_unlock_irq(&new_base->lock);
- put_cpu_ptr(&tvec_bases);
+ put_cpu_ptr(&timer_bases);
}
static int timer_cpu_notify(struct notifier_block *self,
@@ -1609,13 +1609,13 @@ static inline void timer_register_cpu_no
static void __init init_timer_cpu(int cpu)
{
- struct tvec_base *base = per_cpu_ptr(&tvec_bases, cpu);
+ struct timer_base *base = per_cpu_ptr(&timer_bases, cpu);
base->cpu = cpu;
spin_lock_init(&base->lock);
- base->timer_jiffies = jiffies;
- base->next_timer = base->timer_jiffies;
+ base->clk = jiffies;
+ base->next_timer = base->clk;
}
static void __init init_timer_cpus(void)
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 04/20] cpufreq/powernv: Initialize timer as pinned |
| Message-ID | <rJvJg-3OU-51@gated-at.bofh.it> |
| In reply to | #1420610 |
Pinned timers must carry that attribute in the timer itself. No functional change. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> --- drivers/cpufreq/powernv-cpufreq.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) Index: b/drivers/cpufreq/powernv-cpufreq.c =================================================================== --- a/drivers/cpufreq/powernv-cpufreq.c +++ b/drivers/cpufreq/powernv-cpufreq.c @@ -530,8 +530,7 @@ static inline void queue_gpstate_timer( else timer_interval = GPSTATE_TIMER_INTERVAL; - mod_timer_pinned(&gpstates->timer, jiffies + - msecs_to_jiffies(timer_interval)); + mod_timer(&gpstates->timer, jiffies + msecs_to_jiffies(timer_interval)); } /** @@ -699,7 +698,7 @@ static int powernv_cpufreq_cpu_init(stru policy->driver_data = gpstates; /* initialize timer */ - init_timer_deferrable(&gpstates->timer); + init_timer_pinned_deferrable(&gpstates->timer); gpstates->timer.data = (unsigned long)policy; gpstates->timer.function = gpstate_timer_handler; gpstates->timer.expires = jiffies +
[toc] | [prev] | [next] | [standalone]
| From | Arjan van de Ven <arjanvandeven@gmail.com> |
|---|---|
| Date | 2016-06-13 15:20 +0200 |
| Subject | Re: [patch 04/20] cpufreq/powernv: Initialize timer as pinned |
| Message-ID | <rJzWx-6Md-19@gated-at.bofh.it> |
| In reply to | #1420620 |
On Mon, Jun 13, 2016 at 1:40 AM, Thomas Gleixner <tglx@linutronix.de> wrote: > mod_timer(&gpstates->timer, jiffies + msecs_to_jiffies(timer_interval)); are you sure this is right? the others did not get replaced by mod_timer().. (and this is more evidence that a relative API in msecs is what drivers really want)
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 10:50 +0200 |
| Subject | [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJvJg-3OU-41@gated-at.bofh.it> |
| In reply to | #1420610 |
The current timer wheel has some drawbacks:
1) Cascading
Cascading can be an unbound operation and is completely pointless in most
cases because the vast majority of the timer wheel timers are canceled or
rearmed before expiration.
2) No fast lookup of the next expiring timer
In NOHZ scenarios the first timer soft interrupt after a long NOHZ period
must fast forward the base time to current jiffies. As we have no way to
find the next expiring timer fast, the code loops and increments the base
time by one and checks for expired timers in each step.
After a thorough analysis of real world data gathered on laptops,
workstations, webservers and other machines (thanks Chris!) I came to the
conclusion that the current 'classic' timer wheel implementation can be
modified to address the above issues.
The vast majority of timer wheel timers is canceled or rearmed before
expiry. Most of them are timeouts for networking and other I/O tasks. The
nature of timeouts is to catch the exception from normal operation (TCP ack
timed out, disk does not respond, etc.). For these kind of timeouts the
accuracy is not really a concern. In case the timeout fires, performance is
down the drain already.
The few timers which actually expire can be split into two categories:
1) Short expiry times which expect halfways accurate expiry
2) Long term expiry times are inaccurate today already due to the batching
which is done for NOHZ.
So for long term expiry timers we can avoid the cascading property and just
leave them in the less granular outer wheels until expiry or cancelation.
Contrary to the classic wheel the granularities of the next wheel is not the
capacity of the first wheel. The granularities of the wheels are in the
currently chosen setting 8 times the granularity of the previous wheel. So for
HZ=250 we end up with the following granularity levels:
Level Offset Granularity Range
0 0 4 ms 0 ms - 252 ms
1 64 32 ms 256 ms - 2044 ms (256ms - ~2s)
2 128 256 ms 2048 ms - 16380 ms (~2s - ~16s)
3 192 2048 ms (~2s) 16384 ms - 131068 ms (~16s - ~2m)
4 256 16384 ms (~16s) 131072 ms - 1048572 ms (~2m - ~17m)
5 320 131072 ms (~2m) 1048576 ms - 8388604 ms (~17m - ~2h)
That's a worst case inaccuracy of 12.5% for the timers which are queued at the
beginning of a level.
So the new wheel concept addresses the old issues:
1) Cascading is avoided (except for extreme long time timers)
2) By keeping the timers in the bucket until expiry/cancelation we can track
the buckets which have timers enqueued in a bucket bitmap and therefor can
lookup the next expiring timer fast and time bound.
A further benefit of the concept is, that the slack calculation which is done
on every timer start is not longer necessary because the granularity levels
provide natural batching already.
Our extensive testing with various loads did not show any performance
degradation vs. the current wheel implementation.
This patch does not address the 'fast lookup' issue as we wanted to make sure
that there is no regression introduced by the wheel redesign. The
optimizations are in follow up patches.
[ Contains fixes from Anna-Maria Gleixner and Richard Cochran ]
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: rt@linutronix.de
---
v3: fix return value of __next_timer_interrupt()
v2: change HASH_SIZE to TOT_HASH_SIZE (as Richard mentioned)
include/linux/timer.h | 2
kernel/time/timer.c | 825 ++++++++++++++++++++++++++++----------------------
2 files changed, 471 insertions(+), 356 deletions(-)
--- a/include/linux/timer.h
+++ b/include/linux/timer.h
@@ -64,6 +64,8 @@ struct timer_list {
#define TIMER_DEFERRABLE 0x00100000
#define TIMER_PINNED 0x00200000
#define TIMER_IRQSAFE 0x00400000
+#define TIMER_ARRAYSHIFT 23
+#define TIMER_ARRAYMASK 0xFF800000
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .next = TIMER_ENTRY_STATIC }, \
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -59,43 +59,136 @@
EXPORT_SYMBOL(jiffies_64);
/*
- * per-CPU timer vector definitions:
+ * The timer wheel has LVL_DEPTH array levels. Each level provides an array of
+ * LVL_SIZE buckets. Each level is driven by its own clock and therefor each
+ * level has a different granularity.
+ *
+ * The level granularity is: LVL_CLK_DIV ^ lvl
+ * The level clock frequency is: HZ / (LVL_CLK_DIV ^ level)
+ *
+ * The array level of a newly armed timer depends on the relative expiry
+ * time. The farther the expiry time is away the higher the array level and
+ * therefor the granularity becomes.
+ *
+ * Contrary to the original timer wheel implementation, which aims for 'exact'
+ * expiry of the timers, this implementation mostly removes the need for
+ * recascading the timers into the lower array levels. The previous 'classic'
+ * timer wheel implementation of the kernel already violated the 'exact'
+ * expiry by adding slack to the expiry time to provide batched
+ * expiration. The granularity levels provide implicit batching.
+ *
+ * This is an optimization of the original timer wheel implementation for the
+ * majority of the timer wheel use cases: timeouts. The vast majority of
+ * timeout timers (networking, disk I/O ...) are canceled before expiry. If
+ * the timeout expires it indicates that normal operation is disturbed, so it
+ * does not matter much whether the timeout comes with a slight delay.
+ *
+ * The currently chosen array constants values are a good compromise between
+ * array size and granularity. This results in the following granularity and
+ * range levels:
+ *
+ * HZ 1000
+ * Level Offset Granularity Range
+ * 0 0 1 ms 0 ms - 63 ms
+ * 1 64 8 ms 64 ms - 511 ms
+ * 2 128 64 ms 512 ms - 4095 ms (512ms - ~4s)
+ * 3 192 512 ms 4096 ms - 32767 ms (~4s - ~32s)
+ * 4 256 4096 ms (~4s) 32768 ms - 262143 ms (~32s - ~4m)
+ * 5 320 32768 ms (~32s) 262144 ms - 2097151 ms (~4m - ~34m)
+ *
+ * HZ 250
+ * Level Offset Granularity Range
+ * 0 0 4 ms 0 ms - 252 ms
+ * 1 64 32 ms 256 ms - 2044 ms (256ms - ~2s)
+ * 2 128 256 ms 2048 ms - 16380 ms (~2s - ~16s)
+ * 3 192 2048 ms (~2s) 16384 ms - 131068 ms (~16s - ~2m)
+ * 4 256 16384 ms (~16s) 131072 ms - 1048572 ms (~2m - ~17m)
+ * 5 320 131072 ms (~2m) 1048576 ms - 8388604 ms (~17m - ~2h)
+ *
+ * HZ 100
+ * Level Offset Granularity Range
+ * 0 0 10 ms 0 ms - 630 ms
+ * 1 64 80 ms 640 ms - 5110 ms (640ms - ~5s)
+ * 2 128 640 ms 5120 ms - 40950 ms (~5s - ~40s)
+ * 3 192 5120 ms (~5s) 40960 ms - 327670 ms (~40s - ~5m)
+ * 4 256 40960 ms (~40s) 327680 ms - 2621430 ms (~5m - ~43m)
+ * 5 320 327680 ms (~5m) 2621440 ms - 20971510 ms (~43m - ~5h)
+ */
+
+/* Size of each clock level */
+#define LVL_SHIFT 6
+#define LVL_SIZE (1 << LVL_SHIFT)
+#define LVL_MASK (LVL_SIZE - 1)
+
+/* Level depth */
+#define LVL_DEPTH 6
+
+/*
+ * The resulting wheel size. If NOHZ is configured we allocate two
+ * wheels so we have a separate storage for the deferrable timers.
*/
-#define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)
-#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
-#define TVN_SIZE (1 << TVN_BITS)
-#define TVR_SIZE (1 << TVR_BITS)
-#define TVN_MASK (TVN_SIZE - 1)
-#define TVR_MASK (TVR_SIZE - 1)
-#define MAX_TVAL ((unsigned long)((1ULL << (TVR_BITS + 4*TVN_BITS)) - 1))
+#define WHEEL_SIZE (LVL_SIZE * LVL_DEPTH)
-struct tvec {
- struct hlist_head vec[TVN_SIZE];
-};
+/* Level offsets in the wheel */
+#define LVL0_OFFS (0)
+#define LVL1_OFFS (LVL_SIZE)
+#define LVL2_OFFS (LVL1_OFFS + LVL_SIZE)
+#define LVL3_OFFS (LVL2_OFFS + LVL_SIZE)
+#define LVL4_OFFS (LVL3_OFFS + LVL_SIZE)
+#define LVL5_OFFS (LVL4_OFFS + LVL_SIZE)
+
+/* Clock divisor for the next level */
+#define LVL_CLK_SHIFT 3
+#define LVL_CLK_DIV (1 << LVL_CLK_SHIFT)
+#define LVL_CLK_MASK (LVL_CLK_DIV - 1)
+
+/* The shift constants for selecting the bucket at the levels */
+#define LVL1_SHIFT (1 * LVL_CLK_SHIFT)
+#define LVL2_SHIFT (2 * LVL_CLK_SHIFT)
+#define LVL3_SHIFT (3 * LVL_CLK_SHIFT)
+#define LVL4_SHIFT (4 * LVL_CLK_SHIFT)
+#define LVL5_SHIFT (5 * LVL_CLK_SHIFT)
+
+/* The granularity of each level */
+#define LVL0_GRAN 0x00000001
+#define LVL1_GRAN (LVL0_GRAN << LVL_CLK_SHIFT)
+#define LVL2_GRAN (LVL1_GRAN << LVL_CLK_SHIFT)
+#define LVL3_GRAN (LVL2_GRAN << LVL_CLK_SHIFT)
+#define LVL4_GRAN (LVL3_GRAN << LVL_CLK_SHIFT)
+#define LVL5_GRAN (LVL4_GRAN << LVL_CLK_SHIFT)
-struct tvec_root {
- struct hlist_head vec[TVR_SIZE];
-};
+/*
+ * The time start value for each level to select the bucket at enqueue
+ * time.
+ */
+#define LVL1_TSTART (LVL_SIZE - 1)
+#define LVL2_TSTART (LVL1_TSTART << LVL_CLK_SHIFT)
+#define LVL3_TSTART (LVL2_TSTART << LVL_CLK_SHIFT)
+#define LVL4_TSTART (LVL3_TSTART << LVL_CLK_SHIFT)
+#define LVL5_TSTART (LVL4_TSTART << LVL_CLK_SHIFT)
+
+#ifdef CONFIG_NO_HZ_COMMON
+# define NR_BASES 2
+# define BASE_STD 0
+# define BASE_DEF 1
+#else
+# define NR_BASES 1
+# define BASE_STD 0
+# define BASE_DEF 0
+#endif
struct timer_base {
- spinlock_t lock;
- struct timer_list *running_timer;
- unsigned long clk;
- unsigned long next_timer;
- unsigned long active_timers;
- unsigned long all_timers;
- int cpu;
- bool migration_enabled;
- bool nohz_active;
- struct tvec_root tv1;
- struct tvec tv2;
- struct tvec tv3;
- struct tvec tv4;
- struct tvec tv5;
+ spinlock_t lock;
+ struct timer_list *running_timer;
+ unsigned long clk;
+ unsigned int cpu;
+ bool migration_enabled;
+ bool nohz_active;
+ DECLARE_BITMAP(pending_map, WHEEL_SIZE);
+ struct hlist_head vectors[WHEEL_SIZE];
} ____cacheline_aligned;
-
-static DEFINE_PER_CPU(struct timer_base, timer_bases);
+static DEFINE_PER_CPU(struct timer_base, timer_bases[NR_BASES]);
#if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON)
unsigned int sysctl_timer_migration = 1;
@@ -106,15 +199,17 @@ void timers_update_migration(bool update
unsigned int cpu;
/* Avoid the loop, if nothing to update */
- if (this_cpu_read(timer_bases.migration_enabled) == on)
+ if (this_cpu_read(timer_bases[BASE_STD].migration_enabled) == on)
return;
for_each_possible_cpu(cpu) {
- per_cpu(timer_bases.migration_enabled, cpu) = on;
+ per_cpu(timer_bases[BASE_STD].migration_enabled, cpu) = on;
+ per_cpu(timer_bases[BASE_DEF].migration_enabled, cpu) = on;
per_cpu(hrtimer_bases.migration_enabled, cpu) = on;
if (!update_nohz)
continue;
- per_cpu(timer_bases.nohz_active, cpu) = true;
+ per_cpu(timer_bases[BASE_STD].nohz_active, cpu) = true;
+ per_cpu(timer_bases[BASE_DEF].nohz_active, cpu) = true;
per_cpu(hrtimer_bases.nohz_active, cpu) = true;
}
}
@@ -133,20 +228,6 @@ int timer_migration_handler(struct ctl_t
mutex_unlock(&mutex);
return ret;
}
-
-static inline struct timer_base *get_target_base(struct timer_base *base,
- int pinned)
-{
- if (pinned || !base->migration_enabled)
- return this_cpu_ptr(&timer_bases);
- return per_cpu_ptr(&timer_bases, get_nohz_timer_target());
-}
-#else
-static inline struct timer_base *get_target_base(struct timer_base *base,
- int pinned)
-{
- return this_cpu_ptr(&timer_bases);
-}
#endif
static unsigned long round_jiffies_common(unsigned long j, int cpu,
@@ -370,78 +451,84 @@ void set_timer_slack(struct timer_list *
}
EXPORT_SYMBOL_GPL(set_timer_slack);
+static inline unsigned int timer_get_idx(struct timer_list *timer)
+{
+ return (timer->flags & TIMER_ARRAYMASK) >> TIMER_ARRAYSHIFT;
+}
+
+static inline void timer_set_idx(struct timer_list *timer, unsigned int idx)
+{
+ timer->flags = (timer->flags & ~TIMER_ARRAYMASK) |
+ idx << TIMER_ARRAYSHIFT;
+}
+
+/*
+ * Helper function to calculate the array index for a given expiry
+ * time.
+ */
+static inline unsigned calc_index(unsigned expires, unsigned gran,
+ unsigned sft, unsigned base)
+{
+ return base + (((expires + gran) >> sft) & LVL_MASK);
+}
+
static void
__internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
- unsigned long idx = expires - base->clk;
+ unsigned long delta = expires - base->clk;
struct hlist_head *vec;
+ unsigned int idx;
- if (idx < TVR_SIZE) {
- int i = expires & TVR_MASK;
- vec = base->tv1.vec + i;
- } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
- int i = (expires >> TVR_BITS) & TVN_MASK;
- vec = base->tv2.vec + i;
- } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
- int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
- vec = base->tv3.vec + i;
- } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
- int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
- vec = base->tv4.vec + i;
- } else if ((signed long) idx < 0) {
- /*
- * Can happen if you add a timer with expires == jiffies,
- * or you set a timer to go off in the past
- */
- vec = base->tv1.vec + (base->clk & TVR_MASK);
+ if (delta < LVL1_TSTART) {
+ idx = (expires + LVL0_GRAN) & LVL_MASK;
+ } else if (delta < LVL2_TSTART) {
+ idx = calc_index(expires, LVL1_GRAN, LVL1_SHIFT, LVL1_OFFS);
+ } else if (delta < LVL3_TSTART) {
+ idx = calc_index(expires, LVL2_GRAN, LVL2_SHIFT, LVL2_OFFS);
+ } else if (delta < LVL4_TSTART) {
+ idx = calc_index(expires, LVL3_GRAN, LVL3_SHIFT, LVL3_OFFS);
+ } else if (delta < LVL5_TSTART) {
+ idx = calc_index(expires, LVL4_GRAN, LVL4_SHIFT, LVL4_OFFS);
+ } else if ((long) delta < 0) {
+ idx = base->clk & LVL_MASK;
} else {
- int i;
- /* If the timeout is larger than MAX_TVAL (on 64-bit
- * architectures or with CONFIG_BASE_SMALL=1) then we
- * use the maximum timeout.
+ /*
+ * The long timeouts go into the last array level. They
+ * have to be cascaded eventually, but most of them
+ * are removed way before cascading takes place.
*/
- if (idx > MAX_TVAL) {
- idx = MAX_TVAL;
- expires = idx + base->clk;
- }
- i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
- vec = base->tv5.vec + i;
+ idx = calc_index(expires, LVL5_GRAN, LVL5_SHIFT, LVL5_OFFS);
}
+ /*
+ * Enqueue the timer into the array bucket, mark it pending in
+ * the bitmap and store the index in the timer flags.
+ */
+ vec = base->vectors + idx;
hlist_add_head(&timer->entry, vec);
+ __set_bit(idx, base->pending_map);
+ timer_set_idx(timer, idx);
}
static void internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
- /* Advance base->jiffies, if the base is empty */
- if (!base->all_timers++)
- base->clk = jiffies;
-
__internal_add_timer(base, timer);
- /*
- * Update base->active_timers and base->next_timer
- */
- if (!(timer->flags & TIMER_DEFERRABLE)) {
- if (!base->active_timers++ ||
- time_before(timer->expires, base->next_timer))
- base->next_timer = timer->expires;
- }
/*
* Check whether the other CPU is in dynticks mode and needs
- * to be triggered to reevaluate the timer wheel.
- * We are protected against the other CPU fiddling
- * with the timer by holding the timer base lock. This also
- * makes sure that a CPU on the way to stop its tick can not
- * evaluate the timer wheel.
+ * to be triggered to reevaluate the timer wheel. We are
+ * protected against the other CPU fiddling with the timer by
+ * holding the timer base lock. This also makes sure that a
+ * CPU on the way to stop its tick can not evaluate the timer
+ * wheel.
*
* Spare the IPI for deferrable timers on idle targets though.
* The next busy ticks will take care of it. Except full dynticks
* require special care against races with idle_cpu(), lets deal
* with that later.
*/
- if (base->nohz_active) {
+ if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && base->nohz_active) {
if (!(timer->flags & TIMER_DEFERRABLE) ||
tick_nohz_full_cpu(base->cpu))
wake_up_nohz_cpu(base->cpu);
@@ -706,54 +793,92 @@ static inline void detach_timer(struct t
entry->next = LIST_POISON2;
}
-static inline void
-detach_expired_timer(struct timer_list *timer, struct timer_base *base)
+static inline void detach_expired_timer(struct timer_list *timer)
{
detach_timer(timer, true);
- if (!(timer->flags & TIMER_DEFERRABLE))
- base->active_timers--;
- base->all_timers--;
}
static int detach_if_pending(struct timer_list *timer, struct timer_base *base,
bool clear_pending)
{
+ unsigned idx = timer_get_idx(timer);
+
if (!timer_pending(timer))
return 0;
+ if (hlist_is_last_node(&timer->entry, base->vectors + idx))
+ __clear_bit(idx, base->pending_map);
+
detach_timer(timer, clear_pending);
- if (!(timer->flags & TIMER_DEFERRABLE)) {
- base->active_timers--;
- if (timer->expires == base->next_timer)
- base->next_timer = base->clk;
- }
- /* If this was the last timer, advance base->jiffies */
- if (!--base->all_timers)
- base->clk = jiffies;
return 1;
}
+static inline struct timer_base *get_timer_cpu_base(u32 tflags, u32 cpu)
+{
+ struct timer_base *base = per_cpu_ptr(&timer_bases[BASE_STD], cpu);
+
+ /*
+ * If the timer is deferrable and nohz is active then we need to use
+ * the deferrable base.
+ */
+ if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && base->nohz_active &&
+ (tflags & TIMER_DEFERRABLE))
+ base = per_cpu_ptr(&timer_bases[BASE_DEF], cpu);
+ return base;
+}
+
+static inline struct timer_base *get_timer_this_cpu_base(u32 tflags)
+{
+ struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
+
+ /*
+ * If the timer is deferrable and nohz is active then we need to use
+ * the deferrable base.
+ */
+ if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && base->nohz_active &&
+ (tflags & TIMER_DEFERRABLE))
+ base = this_cpu_ptr(&timer_bases[BASE_DEF]);
+ return base;
+}
+
+static inline struct timer_base *get_timer_base(u32 tflags)
+{
+ return get_timer_cpu_base(tflags, tflags & TIMER_BASEMASK);
+}
+
+static inline struct timer_base *get_target_base(struct timer_base *base,
+ unsigned tflags)
+{
+#ifdef CONFIG_NO_HZ_COMMON
+ if ((tflags & TIMER_PINNED) || !base->migration_enabled)
+ return get_timer_this_cpu_base(tflags);
+ return get_timer_cpu_base(tflags, get_nohz_timer_target());
+#else
+ return get_timer_this_cpu_base(tflags);
+#endif
+}
+
/*
- * We are using hashed locking: holding per_cpu(timer_bases).lock
- * means that all timers which are tied to this base via timer->base are
- * locked, and the base itself is locked too.
+ * We are using hashed locking: Holding per_cpu(timer_bases[x]).lock means
+ * that all timers which are tied to this base are locked, and the base itself
+ * is locked too.
*
* So __run_timers/migrate_timers can safely modify all timers which could
- * be found on ->tvX lists.
+ * be found in the base->vectors array.
*
- * When the timer's base is locked and removed from the list, the
- * TIMER_MIGRATING flag is set, FIXME
+ * When a timer is migrating then the TIMER_MIGRATING flag is set and we need
+ * to wait until the migration is done.
*/
static struct timer_base *lock_timer_base(struct timer_list *timer,
- unsigned long *flags)
+ unsigned long *flags)
__acquires(timer->base->lock)
{
for (;;) {
- u32 tf = timer->flags;
struct timer_base *base;
+ u32 tf = timer->flags;
if (!(tf & TIMER_MIGRATING)) {
- base = per_cpu_ptr(&timer_bases, tf & TIMER_CPUMASK);
+ base = get_timer_base(tf);
spin_lock_irqsave(&base->lock, *flags);
if (timer->flags == tf)
return base;
@@ -770,6 +895,27 @@ static inline int
unsigned long flags;
int ret = 0;
+ /*
+ * TODO: Calculate the array bucket of the timer right here w/o
+ * holding the base lock. This allows to check not only
+ * timer->expires == expires below, but also whether the timer
+ * ends up in the same bucket. If we really need to requeue
+ * the timer then we check whether base->clk have
+ * advanced between here and locking the timer base. If
+ * jiffies advanced we have to recalc the array bucket with the
+ * lock held.
+ */
+
+ /*
+ * This is a common optimization triggered by the
+ * networking code - if the timer is re-modified
+ * to be the same thing then just return:
+ */
+ if (timer_pending(timer)) {
+ if (timer->expires == expires)
+ return 1;
+ }
+
timer_stats_timer_set_start_info(timer);
BUG_ON(!timer->function);
@@ -781,15 +927,15 @@ static inline int
debug_activate(timer, expires);
- new_base = get_target_base(base, timer->flags & TIMER_PINNED);
+ new_base = get_target_base(base, timer->flags);
if (base != new_base) {
/*
- * We are trying to schedule the timer on the local CPU.
+ * We are trying to schedule the timer on the new base.
* However we can't change timer's base while it is running,
* otherwise del_timer_sync() can't detect that the timer's
- * handler yet has not finished. This also guarantees that
- * the timer is serialized wrt itself.
+ * handler yet has not finished. This also guarantees that the
+ * timer is serialized wrt itself.
*/
if (likely(base->running_timer != timer)) {
/* See the comment in lock_timer_base() */
@@ -828,45 +974,6 @@ int mod_timer_pending(struct timer_list
}
EXPORT_SYMBOL(mod_timer_pending);
-/*
- * Decide where to put the timer while taking the slack into account
- *
- * Algorithm:
- * 1) calculate the maximum (absolute) time
- * 2) calculate the highest bit where the expires and new max are different
- * 3) use this bit to make a mask
- * 4) use the bitmask to round down the maximum time, so that all last
- * bits are zeros
- */
-static inline
-unsigned long apply_slack(struct timer_list *timer, unsigned long expires)
-{
- unsigned long expires_limit, mask;
- int bit;
-
- if (timer->slack >= 0) {
- expires_limit = expires + timer->slack;
- } else {
- long delta = expires - jiffies;
-
- if (delta < 256)
- return expires;
-
- expires_limit = expires + delta / 256;
- }
- mask = expires ^ expires_limit;
- if (mask == 0)
- return expires;
-
- bit = __fls(mask);
-
- mask = (1UL << bit) - 1;
-
- expires_limit = expires_limit & ~(mask);
-
- return expires_limit;
-}
-
/**
* mod_timer - modify a timer's timeout
* @timer: the timer to be modified
@@ -889,16 +996,6 @@ unsigned long apply_slack(struct timer_l
*/
int mod_timer(struct timer_list *timer, unsigned long expires)
{
- expires = apply_slack(timer, expires);
-
- /*
- * This is a common optimization triggered by the
- * networking code - if the timer is re-modified
- * to be the same thing then just return:
- */
- if (timer_pending(timer) && timer->expires == expires)
- return 1;
-
return __mod_timer(timer, expires, false);
}
EXPORT_SYMBOL(mod_timer);
@@ -933,13 +1030,14 @@ EXPORT_SYMBOL(add_timer);
*/
void add_timer_on(struct timer_list *timer, int cpu)
{
- struct timer_base *new_base = per_cpu_ptr(&timer_bases, cpu);
- struct timer_base *base;
+ struct timer_base *new_base, *base;
unsigned long flags;
timer_stats_timer_set_start_info(timer);
BUG_ON(timer_pending(timer) || !timer->function);
+ new_base = get_timer_cpu_base(timer->flags, cpu);
+
/*
* If @timer was on a different CPU, it should be migrated with the
* old base locked to prevent other operations proceeding with the
@@ -1085,28 +1183,6 @@ int del_timer_sync(struct timer_list *ti
EXPORT_SYMBOL(del_timer_sync);
#endif
-static int cascade(struct timer_base *base, struct tvec *tv, int index)
-{
- /* cascade all the timers from tv up one level */
- struct timer_list *timer;
- struct hlist_node *tmp;
- struct hlist_head tv_list;
-
- hlist_move_list(tv->vec + index, &tv_list);
-
- /*
- * We are removing _all_ timers from the list, so we
- * don't have to detach them individually.
- */
- hlist_for_each_entry_safe(timer, tmp, &tv_list, entry) {
- trace_timer_cascade(timer);
- /* No accounting, while moving them */
- __internal_add_timer(base, timer);
- }
-
- return index;
-}
-
static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long),
unsigned long data)
{
@@ -1150,68 +1226,97 @@ static void call_timer_fn(struct timer_l
}
}
-#define INDEX(N) ((base->clk >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)
+static void expire_timers(struct timer_base *base, struct hlist_head *head)
+{
+ while (!hlist_empty(head)) {
+ struct timer_list *timer;
+ void (*fn)(unsigned long);
+ unsigned long data;
+
+ timer = hlist_entry(head->first, struct timer_list, entry);
+ fn = timer->function;
+ data = timer->data;
+
+ timer_stats_account_timer(timer);
+
+ base->running_timer = timer;
+ detach_expired_timer(timer);
+
+ if (timer->flags & TIMER_IRQSAFE) {
+ spin_unlock(&base->lock);
+ call_timer_fn(timer, fn, data);
+ spin_lock(&base->lock);
+ } else {
+ spin_unlock_irq(&base->lock);
+ call_timer_fn(timer, fn, data);
+ spin_lock_irq(&base->lock);
+ }
+ }
+}
+
+static int collect_expired_timers(struct timer_base *base,
+ struct hlist_head *heads)
+{
+ unsigned long clock = base->clk;
+ struct hlist_head tmp, *vec;
+ int i, levels = 0;
+ unsigned int idx;
+
+ /* Expire the regular buckets */
+ for (i = 0; i < LVL_DEPTH - 1; i++) {
+ idx = (clock & LVL_MASK) + i * LVL_SIZE;
+
+ if (__test_and_clear_bit(idx, base->pending_map)) {
+ vec = base->vectors + idx;
+ hlist_move_list(vec, heads++);
+ levels++;
+ }
+ /* Is it time to look at the next level? */
+ if (clock & LVL_CLK_MASK)
+ return levels;
+ /* Shift clock for the next level granularity */
+ clock >>= LVL_CLK_SHIFT;
+ }
+
+ /* Cascading, sigh... */
+ idx = (clock & LVL_MASK) + i * LVL_SIZE;
+
+ if (__test_and_clear_bit(idx, base->pending_map)) {
+ vec = base->vectors + idx;
+ hlist_move_list(vec, &tmp);
+ /* Make sure we queue them in the future */
+ base->clk++;
+ while (!hlist_empty(&tmp)) {
+ struct timer_list *timer;
+
+ timer = hlist_entry(tmp.first, struct timer_list, entry);
+ trace_timer_cascade(timer);
+ __hlist_del(&timer->entry);
+ __internal_add_timer(base, timer);
+ }
+ base->clk--;
+ }
+ return levels;
+}
/**
* __run_timers - run all expired timers (if any) on this CPU.
* @base: the timer vector to be processed.
- *
- * This function cascades all vectors and executes all expired timer
- * vectors.
*/
static inline void __run_timers(struct timer_base *base)
{
- struct timer_list *timer;
+ struct hlist_head heads[LVL_DEPTH];
+ int levels;
spin_lock_irq(&base->lock);
while (time_after_eq(jiffies, base->clk)) {
- struct hlist_head work_list;
- struct hlist_head *head = &work_list;
- int index;
-
- if (!base->all_timers) {
- base->clk = jiffies;
- break;
- }
- index = base->clk & TVR_MASK;
+ levels = collect_expired_timers(base, heads);
+ base->clk++;
- /*
- * Cascade timers:
- */
- if (!index &&
- (!cascade(base, &base->tv2, INDEX(0))) &&
- (!cascade(base, &base->tv3, INDEX(1))) &&
- !cascade(base, &base->tv4, INDEX(2)))
- cascade(base, &base->tv5, INDEX(3));
- ++base->clk;
- hlist_move_list(base->tv1.vec + index, head);
- while (!hlist_empty(head)) {
- void (*fn)(unsigned long);
- unsigned long data;
- bool irqsafe;
-
- timer = hlist_entry(head->first, struct timer_list, entry);
- fn = timer->function;
- data = timer->data;
- irqsafe = timer->flags & TIMER_IRQSAFE;
-
- timer_stats_account_timer(timer);
-
- base->running_timer = timer;
- detach_expired_timer(timer, base);
-
- if (irqsafe) {
- spin_unlock(&base->lock);
- call_timer_fn(timer, fn, data);
- spin_lock(&base->lock);
- } else {
- spin_unlock_irq(&base->lock);
- call_timer_fn(timer, fn, data);
- spin_lock_irq(&base->lock);
- }
- }
+ while (levels--)
+ expire_timers(base, heads + levels);
}
base->running_timer = NULL;
spin_unlock_irq(&base->lock);
@@ -1219,78 +1324,93 @@ static inline void __run_timers(struct t
#ifdef CONFIG_NO_HZ_COMMON
/*
- * Find out when the next timer event is due to happen. This
- * is used on S/390 to stop all activity when a CPU is idle.
- * This function needs to be called with interrupts disabled.
+ * Find the next pending bucket of a level. Search from @offset + @clk upwards
+ * and if nothing there, search from start of the level (@offset) up to
+ * @offset + clk.
+ */
+static int next_pending_bucket(struct timer_base *base, unsigned offset,
+ unsigned clk)
+{
+ unsigned pos, start = offset + clk;
+ unsigned end = offset + LVL_SIZE;
+
+ pos = find_next_bit(base->pending_map, end, start);
+ if (pos < end)
+ return pos - start;
+
+ pos = find_next_bit(base->pending_map, start, offset);
+ return pos < start ? pos + LVL_SIZE - start : -1;
+}
+
+/*
+ * Search the first expiring timer in the various clock levels.
+ *
+ * Note: This implementation might be suboptimal vs. timers enqueued in the
+ * cascade level because we do not look at the timers to figure out when
+ * they really expire. So for now, we just treat the cascading timers
+ * like any other timer. If each cascading bucket has a timer, we wake
+ * up with the granularity of the last level.
*/
static unsigned long __next_timer_interrupt(struct timer_base *base)
{
- unsigned long clk = base->clk;
- unsigned long expires = clk + NEXT_TIMER_MAX_DELTA;
- int index, slot, array, found = 0;
- struct timer_list *nte;
- struct tvec *varray[4];
-
- /* Look for timer events in tv1. */
- index = slot = clk & TVR_MASK;
- do {
- hlist_for_each_entry(nte, base->tv1.vec + slot, entry) {
- if (nte->flags & TIMER_DEFERRABLE)
- continue;
-
- found = 1;
- expires = nte->expires;
- /* Look at the cascade bucket(s)? */
- if (!index || slot < index)
- goto cascade;
- return expires;
- }
- slot = (slot + 1) & TVR_MASK;
- } while (slot != index);
+ unsigned long clk, next, adj;
+ unsigned lvl, offset = 0;
-cascade:
- /* Calculate the next cascade event */
- if (index)
- clk += TVR_SIZE - index;
- clk >>= TVR_BITS;
-
- /* Check tv2-tv5. */
- varray[0] = &base->tv2;
- varray[1] = &base->tv3;
- varray[2] = &base->tv4;
- varray[3] = &base->tv5;
-
- for (array = 0; array < 4; array++) {
- struct tvec *varp = varray[array];
-
- index = slot = clk & TVN_MASK;
- do {
- hlist_for_each_entry(nte, varp->vec + slot, entry) {
- if (nte->flags & TIMER_DEFERRABLE)
- continue;
-
- found = 1;
- if (time_before(nte->expires, expires))
- expires = nte->expires;
- }
- /*
- * Do we still search for the first timer or are
- * we looking up the cascade buckets ?
- */
- if (found) {
- /* Look at the cascade bucket(s)? */
- if (!index || slot < index)
- break;
- return expires;
- }
- slot = (slot + 1) & TVN_MASK;
- } while (slot != index);
-
- if (index)
- clk += TVN_SIZE - index;
- clk >>= TVN_BITS;
+ spin_lock(&base->lock);
+ clk = base->clk;
+ next = clk + NEXT_TIMER_MAX_DELTA;
+ for (lvl = 0; lvl < LVL_DEPTH; lvl++, offset += LVL_SIZE) {
+ int pos = next_pending_bucket(base, offset, clk & LVL_MASK);
+
+ if (pos >= 0) {
+ unsigned long tmp = clk + (unsigned long) pos;
+
+ tmp <<= lvl * LVL_CLK_SHIFT;
+ if (time_before(tmp, next))
+ next = tmp;
+ }
+ /*
+ * Clock for the next level. If the current level clock lower
+ * bits are zero, we look at the next level as is. If not we
+ * need to advance it by one because that's going to be the
+ * next expiring bucket in that level. base->clk is the next
+ * expiring jiffie. So in case of:
+ *
+ * LVL5 LVL4 LVL3 LVL2 LVL1 LVL0
+ * 0 0 0 0 0 0
+ *
+ * we have to look at all levels @index 0. With
+ *
+ * LVL5 LVL4 LVL3 LVL2 LVL1 LVL0
+ * 0 0 0 0 0 2
+ *
+ * LVL0 has the next expiring bucket @index 2. The upper
+ * levels have the next expiring bucket @index 1.
+ *
+ * In case that the propagation wraps the next level the same
+ * rules apply:
+ *
+ * LVL5 LVL4 LVL3 LVL2 LVL1 LVL0
+ * 0 0 0 0 F 2
+ *
+ * So after looking at LVL0 we get:
+ *
+ * LVL5 LVL4 LVL3 LVL2 LVL1
+ * 0 0 0 1 0
+ *
+ * So no propagation from LVL1 to LVL2 because that happened
+ * with the add already, but then we need to propagate further
+ * from LVL2 to LVL3.
+ *
+ * So the simple check whether the lower bits of the current
+ * level are 0 or not is sufficient for all cases.
+ */
+ adj = clk & LVL_CLK_MASK ? 1 : 0;
+ clk >>= LVL_CLK_SHIFT;
+ clk += adj;
}
- return expires;
+ spin_unlock(&base->lock);
+ return next;
}
/*
@@ -1336,7 +1456,7 @@ static u64 cmp_next_hrtimer_event(u64 ba
*/
u64 get_next_timer_interrupt(unsigned long basej, u64 basem)
{
- struct timer_base *base = this_cpu_ptr(&timer_bases);
+ struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
u64 expires = KTIME_MAX;
unsigned long nextevt;
@@ -1347,17 +1467,11 @@ u64 get_next_timer_interrupt(unsigned lo
if (cpu_is_offline(smp_processor_id()))
return expires;
- spin_lock(&base->lock);
- if (base->active_timers) {
- if (time_before_eq(base->next_timer, base->clk))
- base->next_timer = __next_timer_interrupt(base);
- nextevt = base->next_timer;
- if (time_before_eq(nextevt, basej))
- expires = basem;
- else
- expires = basem + (nextevt - basej) * TICK_NSEC;
- }
- spin_unlock(&base->lock);
+ nextevt = __next_timer_interrupt(base);
+ if (time_before_eq(nextevt, basej))
+ expires = basem;
+ else
+ expires = basem + (nextevt - basej) * TICK_NSEC;
return cmp_next_hrtimer_event(basem, expires);
}
@@ -1388,10 +1502,14 @@ void update_process_times(int user_tick)
*/
static void run_timer_softirq(struct softirq_action *h)
{
- struct timer_base *base = this_cpu_ptr(&timer_bases);
+ struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
- if (time_after_eq(jiffies, base->clk))
- __run_timers(base);
+ if (!time_after_eq(jiffies, base->clk))
+ return;
+
+ __run_timers(base);
+ if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && base->nohz_active)
+ __run_timers(this_cpu_ptr(&timer_bases[BASE_DEF]));
}
/*
@@ -1542,7 +1660,6 @@ static void migrate_timer_list(struct ti
while (!hlist_empty(head)) {
timer = hlist_entry(head->first, struct timer_list, entry);
- /* We ignore the accounting on the dying cpu */
detach_timer(timer, false);
timer->flags = (timer->flags & ~TIMER_BASEMASK) | cpu;
internal_add_timer(new_base, timer);
@@ -1553,35 +1670,29 @@ static void migrate_timers(int cpu)
{
struct timer_base *old_base;
struct timer_base *new_base;
- int i;
+ int b, i;
BUG_ON(cpu_online(cpu));
- old_base = per_cpu_ptr(&timer_bases, cpu);
- new_base = get_cpu_ptr(&timer_bases);
- /*
- * The caller is globally serialized and nobody else
- * takes two locks at once, deadlock is not possible.
- */
- spin_lock_irq(&new_base->lock);
- spin_lock_nested(&old_base->lock, SINGLE_DEPTH_NESTING);
- BUG_ON(old_base->running_timer);
+ for (b = 0; b < NR_BASES; b++) {
+ old_base = per_cpu_ptr(&timer_bases[b], cpu);
+ new_base = get_cpu_ptr(&timer_bases[b]);
+ /*
+ * The caller is globally serialized and nobody else
+ * takes two locks at once, deadlock is not possible.
+ */
+ spin_lock_irq(&new_base->lock);
+ spin_lock_nested(&old_base->lock, SINGLE_DEPTH_NESTING);
+
+ BUG_ON(old_base->running_timer);
- for (i = 0; i < TVR_SIZE; i++)
- migrate_timer_list(new_base, old_base->tv1.vec + i);
- for (i = 0; i < TVN_SIZE; i++) {
- migrate_timer_list(new_base, old_base->tv2.vec + i);
- migrate_timer_list(new_base, old_base->tv3.vec + i);
- migrate_timer_list(new_base, old_base->tv4.vec + i);
- migrate_timer_list(new_base, old_base->tv5.vec + i);
- }
-
- old_base->active_timers = 0;
- old_base->all_timers = 0;
-
- spin_unlock(&old_base->lock);
- spin_unlock_irq(&new_base->lock);
- put_cpu_ptr(&timer_bases);
+ for (i = 0; i < WHEEL_SIZE; i++)
+ migrate_timer_list(new_base, old_base->vectors + i);
+
+ spin_unlock(&old_base->lock);
+ spin_unlock_irq(&new_base->lock);
+ put_cpu_ptr(&timer_bases);
+ }
}
static int timer_cpu_notify(struct notifier_block *self,
@@ -1609,13 +1720,15 @@ static inline void timer_register_cpu_no
static void __init init_timer_cpu(int cpu)
{
- struct timer_base *base = per_cpu_ptr(&timer_bases, cpu);
-
- base->cpu = cpu;
- spin_lock_init(&base->lock);
+ struct timer_base *base;
+ int i;
- base->clk = jiffies;
- base->next_timer = base->clk;
+ for (i = 0; i < NR_BASES; i++) {
+ base = per_cpu_ptr(&timer_bases[i], cpu);
+ base->cpu = cpu;
+ spin_lock_init(&base->lock);
+ base->clk = jiffies;
+ }
}
static void __init init_timer_cpus(void)
[toc] | [prev] | [next] | [standalone]
| From | Peter Zijlstra <peterz@infradead.org> |
|---|---|
| Date | 2016-06-13 13:50 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJyxr-5JN-27@gated-at.bofh.it> |
| In reply to | #1420621 |
On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote:
> + * HZ 1000
> + * Level Offset Granularity Range
> + * 0 0 1 ms 0 ms - 63 ms
> + * 1 64 8 ms 64 ms - 511 ms
> + * 2 128 64 ms 512 ms - 4095 ms (512ms - ~4s)
> + * 3 192 512 ms 4096 ms - 32767 ms (~4s - ~32s)
> + * 4 256 4096 ms (~4s) 32768 ms - 262143 ms (~32s - ~4m)
> + * 5 320 32768 ms (~32s) 262144 ms - 2097151 ms (~4m - ~34m)
> +static int collect_expired_timers(struct timer_base *base,
> + struct hlist_head *heads)
> +{
> + unsigned long clock = base->clk;
> + struct hlist_head tmp, *vec;
> + int i, levels = 0;
> + unsigned int idx;
> +
> + /* Expire the regular buckets */
> + for (i = 0; i < LVL_DEPTH - 1; i++) {
> + idx = (clock & LVL_MASK) + i * LVL_SIZE;
> +
> + if (__test_and_clear_bit(idx, base->pending_map)) {
> + vec = base->vectors + idx;
> + hlist_move_list(vec, heads++);
> + levels++;
> + }
> + /* Is it time to look at the next level? */
> + if (clock & LVL_CLK_MASK)
> + return levels;
> + /* Shift clock for the next level granularity */
> + clock >>= LVL_CLK_SHIFT;
> + }
> +
> + /* Cascading, sigh... */
So given that userspace has no influence on timer period; can't we
simply fail to support timers longer than 30 minutes?
In anything really arming timers _that_ long?
> + idx = (clock & LVL_MASK) + i * LVL_SIZE;
> +
> + if (__test_and_clear_bit(idx, base->pending_map)) {
> + vec = base->vectors + idx;
> + hlist_move_list(vec, &tmp);
> + /* Make sure we queue them in the future */
> + base->clk++;
> + while (!hlist_empty(&tmp)) {
> + struct timer_list *timer;
> +
> + timer = hlist_entry(tmp.first, struct timer_list, entry);
> + trace_timer_cascade(timer);
> + __hlist_del(&timer->entry);
> + __internal_add_timer(base, timer);
> + }
> + base->clk--;
> + }
> + return levels;
> +}
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 14:40 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJzjQ-6gW-33@gated-at.bofh.it> |
| In reply to | #1420747 |
On Mon, 13 Jun 2016, Peter Zijlstra wrote: > On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote: > > + > > + /* Cascading, sigh... */ > > So given that userspace has no influence on timer period; can't we > simply fail to support timers longer than 30 minutes? > > In anything really arming timers _that_ long? Unfortunately yes. Networking being one of those. Real cascading happens once in a blue moon, but it happens. Thanks, tglx
[toc] | [prev] | [next] | [standalone]
| From | Eric Dumazet <edumazet@google.com> |
|---|---|
| Date | 2016-06-13 14:50 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJztv-6l4-9@gated-at.bofh.it> |
| In reply to | #1420778 |
On Mon, Jun 13, 2016 at 5:30 AM, Thomas Gleixner <tglx@linutronix.de> wrote: > On Mon, 13 Jun 2016, Peter Zijlstra wrote: >> On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote: >> > + >> > + /* Cascading, sigh... */ >> >> So given that userspace has no influence on timer period; can't we >> simply fail to support timers longer than 30 minutes? >> >> In anything really arming timers _that_ long? > > Unfortunately yes. Networking being one of those. Real cascading happens once > in a blue moon, but it happens. > Anyway, for the more common HZ=1000 case, what would the 'limit' be ?
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-13 16:40 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJBbY-7vM-15@gated-at.bofh.it> |
| In reply to | #1420787 |
On Mon, 13 Jun 2016, Eric Dumazet wrote: > On Mon, Jun 13, 2016 at 5:30 AM, Thomas Gleixner <tglx@linutronix.de> wrote: > > On Mon, 13 Jun 2016, Peter Zijlstra wrote: > >> On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote: > >> > + > >> > + /* Cascading, sigh... */ > >> > >> So given that userspace has no influence on timer period; can't we > >> simply fail to support timers longer than 30 minutes? > >> > >> In anything really arming timers _that_ long? > > > > Unfortunately yes. Networking being one of those. Real cascading happens once > > in a blue moon, but it happens. > > > Anyway, for the more common HZ=1000 case, what would the 'limit' be ? 34 minutes with the currently chosen parameters.
[toc] | [prev] | [next] | [standalone]
| From | Ingo Molnar <mingo@kernel.org> |
|---|---|
| Date | 2016-06-14 12:20 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJTBT-3uB-9@gated-at.bofh.it> |
| In reply to | #1420778 |
* Thomas Gleixner <tglx@linutronix.de> wrote: > On Mon, 13 Jun 2016, Peter Zijlstra wrote: > > On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote: > > > + > > > + /* Cascading, sigh... */ > > > > So given that userspace has no influence on timer period; can't we > > simply fail to support timers longer than 30 minutes? > > > > In anything really arming timers _that_ long? > > Unfortunately yes. Networking being one of those. Real cascading happens once > in a blue moon, but it happens. So I'd really prefer it if we added a few more levels, a hard limit and got rid of the cascading once and for all! IMHO 'once in a blue moon' code is much worse than a bit more data overhead. Thanks, Ingo
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-14 18:40 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rJZxD-7jp-23@gated-at.bofh.it> |
| In reply to | #1421741 |
On Tue, 14 Jun 2016, Ingo Molnar wrote:
> * Thomas Gleixner <tglx@linutronix.de> wrote:
> > On Mon, 13 Jun 2016, Peter Zijlstra wrote:
> > > On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote:
> > > > +
> > > > + /* Cascading, sigh... */
> > >
> > > So given that userspace has no influence on timer period; can't we
> > > simply fail to support timers longer than 30 minutes?
> > >
> > > In anything really arming timers _that_ long?
> >
> > Unfortunately yes. Networking being one of those. Real cascading happens once
> > in a blue moon, but it happens.
>
> So I'd really prefer it if we added a few more levels, a hard limit and got rid of
> the cascading once and for all!
>
> IMHO 'once in a blue moon' code is much worse than a bit more data overhead.
I agree. If we add two wheel levels then we end up with:
HZ 1000: 134217727 ms ~= 37 hours
HZ 250: 536870908 ms ~= 149 hours
HZ 100: 1342177270 ms ~= 372 hours
Looking through all my data I found exactly one timeout which is insanely
large: 120 hours!
That's net/netfilter/nf_conntrack_core.c:
setup_timer(&ct->timeout, death_by_timeout, (unsigned long)ct);
Anything else is way below 37 hours.
Thanks,
tglx
[toc] | [prev] | [next] | [standalone]
| From | Arjan van de Ven <arjanvandeven@gmail.com> |
|---|---|
| Date | 2016-06-14 19:20 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rK0am-7OM-23@gated-at.bofh.it> |
| In reply to | #1422072 |
evaluating a 120 hours timer ever 37 hours to see if it should fire... not too horrid. On Tue, Jun 14, 2016 at 9:28 AM, Thomas Gleixner <tglx@linutronix.de> wrote: > On Tue, 14 Jun 2016, Ingo Molnar wrote: >> * Thomas Gleixner <tglx@linutronix.de> wrote: >> > On Mon, 13 Jun 2016, Peter Zijlstra wrote: >> > > On Mon, Jun 13, 2016 at 08:41:00AM -0000, Thomas Gleixner wrote: >> > > > + >> > > > + /* Cascading, sigh... */ >> > > >> > > So given that userspace has no influence on timer period; can't we >> > > simply fail to support timers longer than 30 minutes? >> > > >> > > In anything really arming timers _that_ long? >> > >> > Unfortunately yes. Networking being one of those. Real cascading happens once >> > in a blue moon, but it happens. >> >> So I'd really prefer it if we added a few more levels, a hard limit and got rid of >> the cascading once and for all! >> >> IMHO 'once in a blue moon' code is much worse than a bit more data overhead. > > I agree. If we add two wheel levels then we end up with: > > HZ 1000: 134217727 ms ~= 37 hours > HZ 250: 536870908 ms ~= 149 hours > HZ 100: 1342177270 ms ~= 372 hours > > Looking through all my data I found exactly one timeout which is insanely > large: 120 hours! > > That's net/netfilter/nf_conntrack_core.c: > setup_timer(&ct->timeout, death_by_timeout, (unsigned long)ct); > > Anything else is way below 37 hours. > > Thanks, > > tglx
[toc] | [prev] | [next] | [standalone]
| From | Thomas Gleixner <tglx@linutronix.de> |
|---|---|
| Date | 2016-06-14 20:10 +0200 |
| Subject | Re: [patch 13/20] timer: Switch to a non cascading wheel |
| Message-ID | <rK0WJ-8p2-5@gated-at.bofh.it> |
| In reply to | #1422119 |
On Tue, 14 Jun 2016, Arjan van de Ven wrote:
> evaluating a 120 hours timer ever 37 hours to see if it should fire...
> not too horrid.
Well that thing is doing weird stuff anyway:
swapper 0 [001] 1789995.305532: timer:timer_start: timer=0xffff8800c8346920 function=death_by_timeout expires=4850639994 [timeout=108000000]
ssh 3870 [001] 1790025.284704: timer:timer_cancel: timer=0xffff8800c8346920
ssh 3870 [001] 1790025.284707: timer:timer_start: timer=0xffff8800c8346920 function=death_by_timeout expires=4742722493 [timeout=75000]
swapper 0 [001] 1790025.330514: timer:timer_cancel: timer=0xffff8800c8346920
swapper 0 [001] 1790025.330515: timer:timer_start: timer=0xffff8800c8346920 function=death_by_timeout expires=4850647504 [timeout=108000000]
ssh 3870 [001] 1790055.307058: timer:timer_cancel: timer=0xffff8800c8346920
ssh 3870 [001] 1790055.307060: timer:timer_start: timer=0xffff8800c8346920 function=death_by_timeout expires=4742730003 [timeout=75000]
swapper 0 [001] 1790055.352146: timer:timer_cancel: timer=0xffff8800c8346920
And that goes on forever. 2834 such sequences for this particular timer
instance in 4.5 hours. 90000 sequences total for all timers related to
death_by_timeout in 4.5 hours
No idea what this is doing and why the heck it nees a 120 hour timeout ....
Thanks,
tglx
[toc] | [prev] | [next] | [standalone]
Page 1 of 3 [1] 2 3 Next page →
Back to top | Article view | linux.kernel
csiph-web