summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPorpoise <porpoise.chiang@gmail.com>2006-06-23 02:05:56 -0700
committerLinus Torvalds <torvalds@g5.osdl.org>2006-06-23 07:43:08 -0700
commit3439dd86e34580384d3b58cf8d54a9283cd7a342 (patch)
treeb421244c25d96e83a744576ad543587339e581fb
parent626ab0e69d376fa07599af669af8ba92d58e87c1 (diff)
downloadlwn-3439dd86e34580384d3b58cf8d54a9283cd7a342.tar.gz
lwn-3439dd86e34580384d3b58cf8d54a9283cd7a342.zip
[PATCH] When CONFIG_BASE_SMALL=1, cascade() may enter an infinite loop
When CONFIG_BASE_SAMLL=1, cascade() in may enter the infinite loop. Because of CONFIG_BASE_SMALL=1(TVR_BITS=6 and TVN_BITS=4), the list base->tv5 may cascade into base->tv5. So, the kernel enters the infinite loop in the function cascade(). I created a test module to verify this bug, and a patch to fix it. #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/timer.h> #if 0 #include <linux/kdb.h> #else #define kdb_printf printk #endif #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 TV_SIZE(N) (N*TVN_BITS + TVR_BITS) struct timer_list timer0; struct timer_list dummy_timer1; struct timer_list dummy_timer2; void dummy_timer_fun(unsigned long data) { } unsigned long j=0; void check_timer_base(unsigned long data) { kdb_printf("check_timer_base %08x\n",jiffies); mod_timer(&timer0,(jiffies & (~0xFFF)) + 0x1FFF); } int init_module(void) { init_timer(&timer0); timer0.data = (unsigned long)0; timer0.function = check_timer_base; mod_timer(&timer0,jiffies+1); init_timer(&dummy_timer1); dummy_timer1.data = (unsigned long)0; dummy_timer1.function = dummy_timer_fun; init_timer(&dummy_timer2); dummy_timer2.data = (unsigned long)0; dummy_timer2.function = dummy_timer_fun; j=jiffies; j&=(~((1<<TV_SIZE(3))-1)); j+=(1<<TV_SIZE(3)); j+=(1<<TV_SIZE(4)); kdb_printf("mod_timer %08x\n",j); mod_timer(&dummy_timer1, j ); mod_timer(&dummy_timer2, j ); return 0; } void cleanup_module() { del_timer_sync(&timer0); del_timer_sync(&dummy_timer1); del_timer_sync(&dummy_timer2); } (Cleanups from Oleg) [oleg@tv-sign.ru: use list_replace_init()] Cc: Oleg Nesterov <oleg@tv-sign.ru> Cc: Matt Mackall <mpm@selenic.com> Signed-off-by: Oleg Nesterov <oleg@tv-sign.ru> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--kernel/timer.c22
1 files changed, 9 insertions, 13 deletions
diff --git a/kernel/timer.c b/kernel/timer.c
index 3bf0e9ed2dbe..f35b3939e937 100644
--- a/kernel/timer.c
+++ b/kernel/timer.c
@@ -383,23 +383,19 @@ EXPORT_SYMBOL(del_timer_sync);
static int cascade(tvec_base_t *base, tvec_t *tv, int index)
{
/* cascade all the timers from tv up one level */
- struct list_head *head, *curr;
+ struct timer_list *timer, *tmp;
+ struct list_head tv_list;
+
+ list_replace_init(tv->vec + index, &tv_list);
- head = tv->vec + index;
- curr = head->next;
/*
- * We are removing _all_ timers from the list, so we don't have to
- * detach them individually, just clear the list afterwards.
+ * We are removing _all_ timers from the list, so we
+ * don't have to detach them individually.
*/
- while (curr != head) {
- struct timer_list *tmp;
-
- tmp = list_entry(curr, struct timer_list, entry);
- BUG_ON(tmp->base != base);
- curr = curr->next;
- internal_add_timer(base, tmp);
+ list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
+ BUG_ON(timer->base != base);
+ internal_add_timer(base, timer);
}
- INIT_LIST_HEAD(head);
return index;
}