Skip to contentSkip to conversation

kernel BUG at sched.c:944! only with CONFIG_PREEMPT=y]

PATCH In review Robert Love 5 participants 9 messages Sep 12, 2002 – Sep 13, 2002 2 messages not in the archive

lore
Share & tools
lore

A message that is not in the archive (sent off-list or not yet received): [email protected]

A message that is not in the archive (sent off-list or not yet received): [email protected]

#2 lore raw

PATCH 1/1 kernel BUG at sched.c:944! only with CONFIG_PREEMPT=y]

Quoted 10 lines · Steven Cole

Yes. Sorry, didn't catch this reply until now.
I backed out Changeset 1.606 which Linus did to kernel/sched.c did this:

(quoted diff) +1 -1
-       if (unlikely(in_interrupt()))
+       if (unlikely(in_atomic()))

and 2.5.34-mm2 was able to boot with CONFIG_PREEMPT=y.

As I said in a response to myself on lkml, I know this isn't a fix, it just shows there is a problem somewhere with preempt.

No, there is not a problem in preempt... what this change does is BUG() out if schedule() is called while being in any way non-atomic.

While this sounds like a great debugging check, it is not useful in general since we surely have some bad code that calls schedule() with locks held. Further, since the atomic accounting only includes locks if CONFIG_PREEMPT is set, you only see this with kernel preemption enabled.

Linus, please back this out... attached patch is against current BK.

Yeah, I know we can change the BUG() to a show_stack() ... but I still think it will be too much and just deter people from using kernel preemption which is the opposite of what I want.

Robert Love

kernel/sched.c +1 -2
diff -urN linux-2.5.34/kernel/sched.c linux/kernel/sched.c
@@ -940,8 +940,7 @@
 	struct list_head *queue;
 	int idx;
 
-	if (unlikely(in_atomic()))
-		BUG();
+	BUG_ON(in_interrupt());
 
 #if CONFIG_DEBUG_HIGHMEM
 	check_highmem_ptes();
#3 lore raw
Quoted 4 lines · Robert Love

While this sounds like a great debugging check, it is not useful in general since we surely have some bad code that calls schedule() with locks held. Further, since the atomic accounting only includes locks if CONFIG_PREEMPT is set, you only see this with kernel preemption enabled.

it *is* a great debugging check, at zero added cost. Scheduling from an atomic region *is* a critical bug that can and will cause problems in 99% of the cases. Rather fix the asserts that got triggered instead of backing out useful debugging checks ...

Ingo

#4 lore raw
Quoted 4 lines · Ingo Molnar

it *is* a great debugging check, at zero added cost. Scheduling from an atomic region *is* a critical bug that can and will cause problems in 99% of the cases. Rather fix the asserts that got triggered instead of backing out useful debugging checks ...

There are a lot of shitty drivers that this is going to catch. Yes, that is great... but we cannot BUG(). There really are a LOT of them. In the least, we need to show_trace().

Anyhow, this currently will catch _all_ kernel preemptions because the PREEMPT_ACTIVE flag is set.

We need to do something like the attached (untested), at the very least...

I would prefer to make this a kernel debugging check, however. Or, make using kernel preemption the default. Using the "free" abilities of kernel preemption is great, but not at the expense of its users.

Robert Love

kernel/sched.c +5 -3
diff -urN linux-2.5.34/kernel/sched.c linux/kernel/sched.c
@@ -940,8 +940,10 @@
 	struct list_head *queue;
 	int idx;
 
-	if (unlikely(in_atomic()))
-		BUG();
+	if (unlikely(in_atomic() && preempt_count() != PREEMPT_ACTIVE)) {
+		printk(KERN_ERROR "schedule() called while non-atomic!\n");
+		show_stack(NULL);
+	}
 
 #if CONFIG_DEBUG_HIGHMEM
 	check_highmem_ptes();
@@ -959,7 +961,7 @@
 	 * if entering off of a kernel preemption go straight
 	 * to picking the next task.
 	 */
-	if (unlikely(preempt_count() & PREEMPT_ACTIVE))
+	if (unlikely(preempt_count() == PREEMPT_ACTIVE))
 		goto pick_next_task;
 
 	switch (prev->state) {
#5 lore raw
Quoted 41 lines · Robert Love
Quoted 4 lines · Ingo Molnar

it *is* a great debugging check, at zero added cost. Scheduling from an atomic region *is* a critical bug that can and will cause problems in 99% of the cases. Rather fix the asserts that got triggered instead of backing out useful debugging checks ...

There are a lot of shitty drivers that this is going to catch. Yes, that is great... but we cannot BUG(). There really are a LOT of them. In the least, we need to show_trace().

Anyhow, this currently will catch _all_ kernel preemptions because the PREEMPT_ACTIVE flag is set.

We need to do something like the attached (untested), at the very least...

I would prefer to make this a kernel debugging check, however. Or, make using kernel preemption the default. Using the "free" abilities of kernel preemption is great, but not at the expense of its users.

Robert Love

diff -urN linux-2.5.34/kernel/sched.c linux/kernel/sched.c

kernel/sched.c +4 -2
@@ -940,8 +940,10 @@
 	struct list_head *queue;
 	int idx;
 
-	if (unlikely(in_atomic()))
-		BUG();
+	if (unlikely(in_atomic() && preempt_count() != PREEMPT_ACTIVE)) {
+		printk(KERN_ERROR "schedule() called while non-atomic!\n");
+		show_stack(NULL);
+	}
 
 #if CONFIG_DEBUG_HIGHMEM
 	check_highmem_ptes();

kernel/sched.c line 967

Quoted 8 lines
kernel/sched.c +1 -1
@@ -959,7 +961,7 @@
 	 * if entering off of a kernel preemption go straight
 	 * to picking the next task.
 	 */
-	if (unlikely(preempt_count() & PREEMPT_ACTIVE))
+	if (unlikely(preempt_count() == PREEMPT_ACTIVE))
 		goto pick_next_task;
 
 	switch (prev->state) {

OK, that patch is tested now. I had to s/KERN_ERROR/KERN_ERR/, but I was able to get the system to spew out some "schedule() called while non-atomic!" messages along with the traces. I can type those into a file and run it through ksymoops if that would be helpful.

Steven

#6 lore raw
In reply to Robert Love
Quoted 6 lines · Robert Love
Quoted 4 lines

it *is* a great debugging check, at zero added cost. Scheduling from an atomic region *is* a critical bug that can and will cause problems in 99% of the cases. Rather fix the asserts that got triggered instead of backing out useful debugging checks ...

There are a lot of shitty drivers that this is going to catch. [...]

of course. And your point in making it in_interrupt() had what purpose - hiding that tons of code breaks preemption? [and tons of code breaks on SMP.] Your patch was removing precisely the tool that can be used to improve SMP quality on UP boxes as well.

Quoted 2 lines

[...] Yes, that is great... but we cannot BUG(). There really are a LOT of them. In the least, we need to show_trace().

yes. And we also need kallsyms and kksymoops in the kernel, so that people can send in meaningful traces.

Ingo

#7 lore raw
Quoted 4 lines · Ingo Molnar

of course. And your point in making it in_interrupt() had what purpose - hiding that tons of code breaks preemption? [and tons of code breaks on SMP.] Your patch was removing precisely the tool that can be used to improve SMP quality on UP boxes as well.

Ingo, Attached is the latest patch I sent Linus. I never doubted the usefulness, just the practicality of having endusers parse so much information.

At any rate, the approach in Linus's BK is wrong because (a) the debug is hit way too often to just BUG() and (b) we have to take into account PREEMPT_ACTIVE.

Quoted 2 lines

yes. And we also need kallsyms and kksymoops in the kernel, so that people can send in meaningful traces.

Agree 100% here.

Robert Love

iff -urN linux-2.5.34/kernel/sched.c linux/kernel/sched.c

kernel/sched.c +5 -3
@@ -940,8 +940,10 @@
 	struct list_head *queue;
 	int idx;
 
-	if (unlikely(in_atomic()))
-		BUG();
+	if (unlikely(in_atomic() && preempt_count() != PREEMPT_ACTIVE)) {
+		printk(KERN_ERROR "schedule() called while non-atomic!\n");
+		show_stack(NULL);
+	}
 
 #if CONFIG_DEBUG_HIGHMEM
 	check_highmem_ptes();
@@ -959,7 +961,7 @@
 	 * if entering off of a kernel preemption go straight
 	 * to picking the next task.
 	 */
-	if (unlikely(preempt_count() & PREEMPT_ACTIVE))
+	if (unlikely(preempt_count() == PREEMPT_ACTIVE))
 		goto pick_next_task;
 
 	switch (prev->state) {
#8 lore raw

kernel/sched.c line 946

Quoted 6 lines · Robert Love
kernel/sched.c +4 -2
-	if (unlikely(in_atomic()))
-		BUG();
+	if (unlikely(in_atomic() && preempt_count() != PREEMPT_ACTIVE)) {
+		printk(KERN_ERROR "schedule() called while non-atomic!\n");
+		show_stack(NULL);
+	}

Actually, looking at this again, we probably want to still BUG() if in_interrupt() but _not_ if in_atomic().

Robert Love

#9 lore raw

Hi,

Quoted 3 lines · Robert Love

On Fri, 2002-09-13 at 03:36, Robert Love wrote:
Actually, looking at this again, we probably want to still BUG() if in_interrupt() but _not_ if in_atomic().

- if (unlikely(in_atomic()))
- BUG();
+ if (unlikely((in_interrupt() || (!in_atomic())) && preempt_count() != PREEMPT_ACTIVE)) {
+ printk(KERN_ERROR "schedule() called while non-atomic, or in interrupt!\n");
+ show_stack(NULL);
+ }

?

Thunder

Signature · 4 lines
--./../...-/. -.--/---/..-/.-./..././.-../..-. .---/..-/.../- .-
--/../-./..-/-/./--..-- ../.----./.-../.-.. --./../...-/. -.--/---/..-
.- -/---/--/---/.-./.-./---/.--/.-.-.-
--./.-/-.../.-./.././.-../.-.-.-
#10 lore raw
In reply to Ingo Molnar
Quoted 8 lines · Ingo Molnar
Quoted 4 lines · Robert Love

While this sounds like a great debugging check, it is not useful in general since we surely have some bad code that calls schedule() with locks held. Further, since the atomic accounting only includes locks if CONFIG_PREEMPT is set, you only see this with kernel preemption enabled.

Quoted 5 lines

it *is* a great debugging check, at zero added cost. Scheduling from an atomic region *is* a critical bug that can and will cause problems in 99% of the cases. Rather fix the asserts that got triggered instead of backing out useful debugging checks ...

The problem here is that some random piece of code has bumped the preemption counter, and we've lost all trace of that at the site where the problem is detected.

So... In do_initcalls() we'd need:

        do {
                (*call)();
+		if (in_atomic())
+			printk("initcall at %p is buggy\n", call);
                call++;
        } while (call < &__initcall_end);

and to diagnose this particular problem I guess we need to add

	if (in_atomic())
		printk("goofed at %d\n", __LINE__);

to twenty or so places in start_kernel().