Add not_critical_when_idle timer
Subsystems (3)
PATCH 1/2 Add not_critical_when_idle timer
Introduce a new kind of timers - not_critical_when_idle timers:
Timers that work normally when system is busy. But, will not cause CPU to come out of idle (just to service this timer), when CPU is idle. Instead, this timer will be serviced when CPU eventually wakes up with a subsequent critical_when_idle timer.
The main advantage of this is to avoid unnecessary timer interrupts when CPU is idle. If the routine currently called by a timer can wait until next event without any issues, this new timer can be used to setup timer event for that routine. This, with dynticks, allows CPUs to be lazy, allowing them to stay in idle for extended period of time by reducing unnecesary wakeup and thereby reducing the power consumption.
This patch:
Builds this new timer on top of existing timer infrastructure. It uses last bit in 'base' pointer of timer_list structure to store this extra information about timer. __next_timer_interrupt() function skips over these not_critical_when_idle timers when CPU looks for next timer event for which it has to wake up.
This is exported by a new interface add_timer_with_hint() and also a new parameter is added to existing add_timer_on() interface.
kernel/timer.c +24 -7
@@ -74,7 +74,7 @@tvec_t tv3;tvec_t tv4;tvec_t tv5; -}____cacheline_aligned_in_smp ; +}____cacheline_aligned ;typedef struct tvec_t_base_s tvec_base_t ;
@@ -325,7 +325,7 @@tvec_base_t *base;for (;;) { - base = timer->base; + base =TBASE_GET_BASE_PTR (timer->base);if (likely (base !=NULL )) {spin_lock_irqsave (&base->lock, *flags);if (likely (base == timer->base))
@@ -364,12 +364,15 @@* the timer is serialized wrt itself. */ if (likely (base->running_timer != timer)) { +unsigned long tflag; + tflag =TBASE_GET_DELAYED_ON_IDLE (timer->base);/* See the comment in lock_timer_base() */ timer->base =NULL ;spin_unlock (&base->lock); base = new_base;spin_lock (&base->lock); - timer->base = base; + timer->base = +TBASE_MERGE_DELAYED_ON_IDLE (new_base, tflag); } }
@@ -386,10 +389,12 @@* add_timer_on - start a timer on a particular CPU * @timer: the timer to be added * @cpu: the CPU to start it on +* @not_critical_when_idle: 1 to indicate timer is not critical and +* can be delayed when CPU is idle * * This is not very scalable on SMP. Double adds are not possible. */ -void add_timer_on (struct timer_list *timer,int cpu) +void add_timer_on (struct timer_list *timer,int cpu,int not_critical_when_idle) {tvec_base_t *base =per_cpu (tvec_bases, cpu);unsigned long flags;
@@ -397,7 +402,7 @@timer_stats_timer_set_start_info (timer);BUG_ON (timer_pending (timer) || !timer->function);spin_lock_irqsave (&base->lock, flags); - timer->base = base; + timer->base =TBASE_MERGE_DELAYED_ON_IDLE (base, not_critical_when_idle);internal_add_timer (base, timer);spin_unlock_irqrestore (&base->lock, flags); }
@@ -548,7 +553,7 @@* don't have to detach them individually. */ list_for_each_entry_safe (timer, tmp, &tv_list, entry) { -BUG_ON (timer->base != base); +BUG_ON (TBASE_GET_BASE_PTR (timer->base) != base);internal_add_timer (base, timer); }
@@ -634,6 +639,9 @@ index = slot = timer_jiffies &TVR_MASK ; do { list_for_each_entry(nte, base->tv1.vec + slot, entry) { + if (TBASE_GET_DELAYED_ON_IDLE(nte->base)) + continue; + found = 1; expires = nte->expires; /* Look at the cascade bucket(s)? */
@@ -1602,6 +1610,13 @@ cpu_to_node(cpu));if (!base)return -ENOMEM ; + +/* Make sure that tvec_base is 2 byte aligned */ +if (TBASE_GET_DELAYED_ON_IDLE (base)) { +WARN_ON (1 ); +kfree (base); +return -ENOMEM ; + }memset (base,0 ,sizeof (*base));per_cpu (tvec_bases, cpu) = base; }else {
@@ -1641,9 +1656,11 @@struct timer_list *timer;while (!list_empty (head)) { +unsigned long tflag; timer = list_entry(head->next, struct timer_list, entry); + tflag =TBASE_GET_DELAYED_ON_IDLE (timer->base); + timer->base =TBASE_MERGE_DELAYED_ON_IDLE (new_base, tflag);detach_timer (timer,0 ); - timer->base = new_base;internal_add_timer (new_base, timer); } }
include/linux/timer.h +39 -2
65 lines not on this page · raw message
arch/powerpc/platforms/chrp/setup.c +1 -1
@@ -565,7 +565,7 @@ timer = &per_cpu (heartbeat_timer, cpu);setup_timer (timer, chrp_event_scan,0 ); timer->expires = jiffies + offset; -add_timer_on (timer, cpu); +add_timer_on (timer, cpu,0 ); offset += interval; } printk("RTAS Event Scan Rate: %u (%lu jiffies)\n",
kernel/workqueue.c +1 -1
@@ -290,7 +290,7 @@ timer->expires = jiffies + delay; timer->data = (unsigned long )dwork; timer->function = delayed_work_timer_fn; -add_timer_on (timer, cpu); +add_timer_on (timer, cpu,0 ); ret =1 ; }return ret;
Quoted 14 lines · On Fri, 16 Mar 2007 15:07:35 -0700 Venkatesh Pallipadi…
Introduce a new kind of timers -
not_critical_when_idletimers:
Timers that work normally when system is busy. But, will not cause CPU to come out of idle (just to service this timer), when CPU is idle. Instead, this timer will be serviced when CPU eventually wakes up with a subsequentcritical_when_idletimer.The main advantage of this is to avoid unnecessary timer interrupts when CPU is idle. If the routine currently called by a timer can wait until next event without any issues, this new timer can be used to setup timer event for that routine. This, with dynticks, allows CPUs to be lazy, allowing them to stay in idle for extended period of time by reducing unnecesary wakeup and thereby reducing the power consumption.
Quoted 6 lines
This patch:
Builds this new timer on top of existing timer infrastructure. It uses last bit in 'base' pointer oftimer_liststructure to store this extra information about timer.__next_timer_interrupt()function skips over thesenot_critical_when_idletimers when CPU looks for next timer event for which it has to wake up.
Fair enough, I guess.
Quoted 3 lines
This is exported by a new interface
add_timer_with_hint()and also a new parameter is added to existingadd_timer_on()interface.
A few cosmetic and interface things:
kernel/timer.c line 77
Quoted 8 lines
kernel/timer.c +1
-1@@ -74,7 +74,7 @@tvec_t tv3;tvec_t tv4;tvec_t tv5; -}____cacheline_aligned_in_smp ; +}____cacheline_aligned ;
hm, that's an unrelated bugfix.
Quoted 18 lines
...
kernel/timer.c +16
-0+extern struct tvec_t_base_s boot_tvec_bases; +/* +* Note that all tvec_bases is 2 byte aligned and lower bit of +* base in timer_list is guaranteed to be zero. Use the LSB for +* the new flag to indicate whether it is OK to skip timer callback +* when CPU is idle. +*/ +#define TBASE_FLAG_DELAYED_ON_IDLE (0x1) + +#define TBASE_GET_BASE_PTR (x) \ + ((struct tvec_t_base_s *)((unsigned long)x & \ + (~TBASE_FLAG_DELAYED_ON_IDLE))) + +#define TBASE_GET_DELAYED_ON_IDLE (x) \ + ((unsigned long)x & TBASE_FLAG_DELAYED_ON_IDLE) +
kernel/timer.c
Quoted 10 lines
kernel/timer.c +10
-0+#define TBASE_SET_DELAYED_ON_IDLE (x) \ + ((struct tvec_t_base_s *)((unsigned long)x | \ + TBASE_FLAG_DELAYED_ON_IDLE)) + +#define TBASE_CLEAR_DELAYED_ON_IDLE (x) \ + ((struct tvec_t_base_s *)((unsigned long)x & \ + (~TBASE_FLAG_DELAYED_ON_IDLE))) + +#define TBASE_MERGE_DELAYED_ON_IDLE (x,f) \ + ((f) ? TBASE_SET_DELAYED_ON_IDLE(x) : TBASE_CLEAR_DELAYED_ON_IDLE(x))
Can we implement these as lower-case-named inline functions? I don't think there's any reason why they have to be macros?
Quoted 12 lines
struct
timer_list{kernel/timer.c +0
-1struct list_head entry;unsigned long expires;@@ -23,7 +50,6 @@#endif }; -extern struct tvec_t_base_s boot_tvec_bases;#define TIMER_INITIALIZER (_function, _expires, _data) { \ .function = (_function), \
kernel/timer.c line 92
Quoted 6 lines
kernel/timer.c +2
-1@@ -62,7 +88,8 @@return timer->entry.next !=NULL ; } -extern void add_timer_on (struct timer_list *timer,int cpu); +extern void add_timer_on (struct timer_list *timer,int cpu, +int not_critical_when_idle);
That `not_critical_when_idle' is a real mouthful. Could we just use "deferrable"? With a nice comment in a strategic spot which explains what it's all about?
Quoted 9 lines
kernel/timer.c +2
-0extern int del_timer (struct timer_list * timer);extern int __mod_timer (struct timer_list *timer,unsigned long expires);extern int mod_timer (struct timer_list *timer,unsigned long expires);@@ -144,6 +171,16 @@static inline void add_timer (struct timer_list *timer) {BUG_ON (timer_pending (timer)); + timer->base =TBASE_CLEAR_DELAYED_ON_IDLE (timer->base); +__mod_timer (timer, timer->expires);
kernel/timer.c line 185
Quoted 10 lines
kernel/timer.c +8
-0+} + +static inline void add_timer_with_hint (struct timer_list *timer, +int not_critical_when_idle) +{ +BUG_ON (timer_pending (timer)); + timer->base =TBASE_MERGE_DELAYED_ON_IDLE (timer->base, + not_critical_when_idle);__mod_timer (timer, timer->expires); }
I'm not sure I really like the idea of modifying a timer's state when installing it. Plus mod_timer() is a superset of add_timer(), and people might want to use mod_timer().
I think it would be better to specify the type of the timer when we're initialising it, not when we're installing it.
So would it not be nicer to do:
setup_deferrable_timer(timer, my_handler, my_data); add_timer(timer, whenever); or mod_timer(timer, whenever);
?
kernel/timer.c line 186
Quoted 2 lines
kernel/timer.c +1
-1-add_timer_on (timer, cpu); +add_timer_on (timer, cpu,0 );
OK.
Quoted 18 lines
-----Original Message-----
From: Andrew Morton [mailto:[email protected]]
Sent: Sunday, March 18, 2007 1:10 AM
To: Pallipadi, Venkatesh
Cc: Dave Jones; [email protected]; linux-kernel
Subject: Re: [PATCH 1/2] Addnot_critical_when_idletimerOn Fri, 16 Mar 2007 15:07:35 -0700 Venkatesh Pallipadi <[email protected]> wrote:
A few cosmetic and interface things:
Quoted 1 line
---
linux-2.6.20.orig/kernel/timer.c2007-03-1614:13:19.000000000 -0700
Quoted 1 line
+++
linux-2.6.20/kernel/timer.c2007-03-1614:51:15.000000000 -0700
Quoted 8 lines
kernel/timer.c line 77
Quoted 6 lines
kernel/timer.c +1
-1@@ -74,7 +74,7 @@tvec_t tv3;tvec_t tv4;tvec_t tv5; -}____cacheline_aligned_in_smp ; +}____cacheline_aligned ;hm, that's an unrelated bugfix.
This is not totally unrelated, as I want this base structure to be aligned in UP case as well, as the lower bits in the pointer to base is overloaded.
Quoted 35 lines
include/linux/timer.h
Quoted 12 lines
...
include/linux/timer.h +10
-0+extern struct tvec_t_base_s boot_tvec_bases; +/* +* Note that all tvec_bases is 2 byte aligned and lower bit of +* base in timer_list is guaranteed to be zero. Use the LSB for +* the new flag to indicate whether it is OK to skip timer callback +* when CPU is idle. +*/ +#define TBASE_FLAG_DELAYED_ON_IDLE (0x1) + +#define TBASE_GET_BASE_PTR (x)\Review
Quoted 1 line
(quoted diff) +1
-0+ ((struct tvec_t_base_s *)((unsigned long)x &\include/linux/timer.h
Quoted 3 lines
include/linux/timer.h +3
-0+ (~TBASE_FLAG_DELAYED_ON_IDLE))) + +#define TBASE_GET_DELAYED_ON_IDLE (x)\include/linux/timer.h
Quoted 3 lines
include/linux/timer.h +3
-0+ ((unsigned long)x & TBASE_FLAG_DELAYED_ON_IDLE) + +#define TBASE_SET_DELAYED_ON_IDLE (x)\Review
Quoted 1 line
(quoted diff) +1
-0+ ((struct tvec_t_base_s *)((unsigned long)x |\include/linux/timer.h
Quoted 3 lines
include/linux/timer.h +3
-0+ TBASE_FLAG_DELAYED_ON_IDLE)) + +#define TBASE_CLEAR_DELAYED_ON_IDLE (x)\Review
Quoted 1 line
(quoted diff) +1
-0+ ((struct tvec_t_base_s *)((unsigned long)x &\include/linux/timer.h
Quoted 3 lines
include/linux/timer.h +3
-0+ (~TBASE_FLAG_DELAYED_ON_IDLE))) + +#define TBASE_MERGE_DELAYED_ON_IDLE (x,f)\
Quoted 6 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ ((f) ? TBASE_SET_DELAYED_ON_IDLE(x) :
TBASE_CLEAR_DELAYED_ON_IDLE(x))Can we implement these as lower-case-named inline functions? I don't think
there's any reason why they have to be macros?
Yes. Will do.
Quoted 10 lines
Quoted 4 lines
-extern struct
tvec_t_base_sboot_tvec_bases;#define
TIMER_INITIALIZER(_function, _expires, _data){\Quoted 5 lines
include/linux/timer.h +0
-0.function = (_function), \@@ -62,7 +88,8 @@return timer->entry.next !=NULL ; }
Quoted 9 lines
include/linux/timer.h line 92
Quoted 3 lines
include/linux/timer.h +2
-1-extern void add_timer_on (struct timer_list *timer,int cpu); +extern void add_timer_on (struct timer_list *timer,int cpu, +int not_critical_when_idle);That `not_critical_when_idle' is a real mouthful. Could we just use "deferrable"? With a nice comment in a strategic spot which explains what
it's all about?
Yes. deferrable sounds better. Will change.
Quoted 31 lines
Review line 94
Quoted 2 lines
(quoted diff) +0
-0extern int del_timer(struct timer_list * timer); extern int __mod_timer(struct timer_list *timer, unsignedlong expires);
Review line 95
Quoted 1 line
(quoted diff) +0
-0extern int mod_timer(struct timer_list *timer, unsignedlong expires);
Quoted 4 lines
include/linux/timer.h +0
-0@@ -144,6 +171,16 @@static inline void add_timer (struct timer_list *timer) {BUG_ON (timer_pending (timer));include/linux/timer.h line 183
Quoted 10 lines
include/linux/timer.h +10
-0+ timer->base =TBASE_CLEAR_DELAYED_ON_IDLE (timer->base); +__mod_timer (timer, timer->expires); +} + +static inline void add_timer_with_hint (struct timer_list *timer, +int not_critical_when_idle) +{ +BUG_ON (timer_pending (timer)); + timer->base = TBASE_MERGE_DELAYED_ON_IDLE(timer->base, +
not_critical_when_idle);include/linux/timer.h line 185
Quoted 2 lines
include/linux/timer.h +0
-0__mod_timer (timer, timer->expires); }I'm not sure I really like the idea of modifying a timer's state when installing it. Plus
mod_timer()is a superset ofadd_timer(), and people
might want to usemod_timer().I think it would be better to specify the type of the timer when we're initialising it, not when we're installing it.
Quoted 7 lines
So would it not be nicer to do:
setup_deferrable_timer(timer, my_handler, my_data); add_timer(timer, whenever); or mod_timer(timer, whenever);
OK. I will look at having the setup functions this way. I guess this should be better as we will never want to use a single timer in both deferrrable and non-deferrable modes. Will refresh the patch with these changes soon.
Thanks,
Venki